diff --git a/geozero/src/chaining.rs b/geozero/src/chaining.rs new file mode 100644 index 00000000..dc7a0bed --- /dev/null +++ b/geozero/src/chaining.rs @@ -0,0 +1,176 @@ +//! Chaining and duplexing processors. + +use crate::error::Result; +use crate::events::GeomEventProcessor; +use crate::events::*; + +// ------- Chain events ------- + +/// Processing geometry events and passing events to a chained visitor +pub trait ChainedGeomEventProcessor { + /// Geometry processing event with geometry type information + fn chain_event( + &mut self, + event: &Event, + geom_type: GeometryType, + collection: bool, + visitor: &mut GeomVisitor, + ) -> Result<()>; +} + +/// Chaining [GeomEventProcessor] +pub struct ChainedProcessor<'a> { + processor1: &'a mut dyn ChainedGeomEventProcessor, + visitor: GeomVisitor<'a>, +} + +impl<'a> ChainedProcessor<'a> { + pub fn new( + processor1: &'a mut dyn ChainedGeomEventProcessor, + processor2: &'a mut dyn GeomEventProcessor, + ) -> Self { + ChainedProcessor { + processor1, + visitor: GeomVisitor::new(processor2), + } + } +} + +impl<'a> GeomEventProcessor for ChainedProcessor<'a> { + fn event( + &mut self, + event: &Event, + geom_type: GeometryType, + collection: bool, + ) -> crate::error::Result<()> { + self.processor1 + .chain_event(event, geom_type, collection, &mut self.visitor) + } +} + +// ------- Duplex --------- + +/// Duplexing [GeomEventProcessor] +pub struct DuplexProcessor<'a> { + processor1: &'a mut dyn GeomEventProcessor, + processor2: &'a mut dyn GeomEventProcessor, +} + +impl<'a> DuplexProcessor<'a> { + pub fn new( + processor1: &'a mut dyn GeomEventProcessor, + processor2: &'a mut dyn GeomEventProcessor, + ) -> Self { + DuplexProcessor { + processor1, + processor2, + } + } +} + +impl GeomEventProcessor for DuplexProcessor<'_> { + fn event( + &mut self, + event: &Event, + geom_type: GeometryType, + collection: bool, + ) -> crate::error::Result<()> { + self.processor1.event(event, geom_type, collection)?; + self.processor2.event(event, geom_type, collection)?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::error::Result; + use crate::events::test::{GeomEventBuffer, NullIsland}; + use crate::events::Event::*; + use crate::processor::GeomEventSink; + + pub struct PromoteToMulti; + impl ChainedGeomEventProcessor for PromoteToMulti { + fn chain_event( + &mut self, + event: &Event, + _geom_type: GeometryType, + _collection: bool, + visitor: &mut GeomVisitor, + ) -> Result<()> { + match *event { + PointBegin(idx) => { + visitor.multipoint_begin(1, idx)?; + } + PointEnd(idx) => { + visitor.multipoint_end(idx)?; + } + _ => visitor.emit_event(event)?, + } + Ok(()) + } + } + + #[test] + fn chained_processor() -> Result<()> { + let mut buffer_processor = GeomEventBuffer::new(); + let mut multi = PromoteToMulti; + let mut processor = ChainedProcessor::new(&mut multi, &mut buffer_processor); + let mut visitor = GeomVisitor::new(&mut processor); + + let mut geom = NullIsland; + geom.process_geom(&mut visitor)?; + + assert_eq!( + buffer_processor.buffer, + [MultiPointBegin(1, 0), Xy(0.0, 0.0, 0), MultiPointEnd(0)] + ); + + Ok(()) + } + + #[test] + fn duplex_processor() -> Result<()> { + let mut buffer_processor = GeomEventBuffer::new(); + let mut sink = GeomEventSink; + let mut processor = DuplexProcessor::new(&mut sink, &mut buffer_processor); + let mut visitor = GeomVisitor::new(&mut processor); + + let mut geom = NullIsland; + geom.process_geom(&mut visitor)?; + + assert_eq!( + buffer_processor.buffer, + [PointBegin(0), Xy(0.0, 0.0, 0), PointEnd(0),] + ); + + Ok(()) + } + + #[test] + fn chain_and_duplex() -> Result<()> { + // geom ------+----> PromoteToMulti (1a) ----> GeomEventBuffer (2a) + // | + // +----> GeomEventBuffer (1b) + let mut processor1a = PromoteToMulti; + let mut processor2a = GeomEventBuffer::new(); + let mut processor1b = GeomEventBuffer::new(); + let mut processor_a = ChainedProcessor::new(&mut processor1a, &mut processor2a); + let mut processor = DuplexProcessor::new(&mut processor_a, &mut processor1b); + let mut visitor = GeomVisitor::new(&mut processor); + + let mut geom = NullIsland; + geom.process_geom(&mut visitor)?; + + assert_eq!( + processor2a.buffer, + [MultiPointBegin(1, 0), Xy(0.0, 0.0, 0), MultiPointEnd(0)] + ); + assert_eq!( + processor1b.buffer, + [PointBegin(0), Xy(0.0, 0.0, 0), PointEnd(0),] + ); + + Ok(()) + } +} diff --git a/geozero/src/events.rs b/geozero/src/events.rs new file mode 100644 index 00000000..e8b13f6f --- /dev/null +++ b/geozero/src/events.rs @@ -0,0 +1,1139 @@ +//! Traits for reading and creating geomeries. +//! +//! Main traits: +//! * [GeometryReader]: Reading geometries by passing events to a visitor object +//! * [GeomEventProcessor]: Processing geometry events, e.g. for producing an output geometry +//! +//! Main structs: +//! * [GeomVisitor]: Geometry visitor emitting events to a [GeomEventProcessor] +//! +//! ```md +//! Geometry ---------------> GeomVisitor +//! GeometryReader -------------------> Geometry +//! GeomEventProcessor +//! ``` +// +// GeomProcessor API: +// ```md +// Geometry ----------------> -------------------> Geometry +// GeozeroGeometry GeomProcessor +// ``` + +use crate::error::{GeozeroError, Result}; + +/// Geometry processing events +/// +/// State machine: +/// ```md +/// +-----------------+ +/// | | +/// | Initial <-------------+ +/// | | | +/// +--------^--------+ +--------v---------+ +/// | | | +/// +-+-+---+-+ |GeometryCollection| +/// | | | | | | | | +/// v v v v v v +--------^---------+ +/// +-----------------+ | +/// | | +-+-+---+-+ +/// | MultiPolygon | | | | | | | +/// | | v v v v v v +/// +--------^--------+ +-----------------+ +/// | | | +/// | | MultiLineString | +/// +--------v--------+ | | +/// | | +--------^--------+ +/// | Polygon | | +/// | | | +/// +--------^--------+ +--------v--------+ +-------------+ +--------------+ +/// | | | | | | | +/// +----------->+ LineString | | Point | | MultiPoint | +/// | | | | | | +/// +--------+--------+ +-----------------+ +------+------+ +-------+------+ +/// | | | | | +/// +------------> Coordinate <----------+------------------+ +/// | | +/// +-----------------+ +/// ``` +/* Type size: +print-type-size type: `geozero::events::Event`: 96 bytes, alignment: 8 bytes +print-type-size discriminant: 8 bytes +print-type-size variant `Coordinate`: 88 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size field `.2`: 16 bytes +print-type-size field `.3`: 16 bytes +print-type-size field `.4`: 16 bytes +print-type-size field `.5`: 16 bytes +print-type-size field `.6`: 8 bytes +print-type-size variant `Xy`: 24 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size field `.2`: 8 bytes +print-type-size variant `XySlice`: 24 bytes +print-type-size field `.0`: 16 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `MultiPointBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `LineStringBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `MultiLineStringBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `PolygonBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `MultiPolygonBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `GeometryCollectionBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `CircularStringBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `CompoundCurveBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `CurvePolygonBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `MultiCurveBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `MultiSurfaceBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `TriangleBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `PolyhedralSurfaceBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `TinBegin`: 16 bytes +print-type-size field `.0`: 8 bytes +print-type-size field `.1`: 8 bytes +print-type-size variant `PointBegin`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `PointEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `MultiPointEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `LineStringEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `MultiLineStringEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `PolygonEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `MultiPolygonEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `GeometryCollectionEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `CircularStringEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `CompoundCurveEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `CurvePolygonEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `MultiCurveEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `MultiSurfaceEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `TriangleEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `PolyhedralSurfaceEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `TinEnd`: 8 bytes +print-type-size field `.0`: 8 bytes +print-type-size variant `Empty`: 0 bytes + +With Coordinate(Box<(..)>): +print-type-size type: `events::Event`: 32 bytes, alignment: 8 bytes +print-type-size discriminant: 8 bytes +print-type-size variant `Coordinate`: 8 bytes +print-type-size field `.0`: 8 bytes +*/ +#[derive(Clone, PartialEq, Debug)] +pub enum Event { + /// Coordinate with x,y dimensions (x, y, idx) + Xy(f64, f64, usize), + /// Coordinate with all requested dimensions (x, y, z, m, t, tm, idx) + Coordinate( + f64, + f64, + Option, + Option, + Option, + Option, + usize, + ), + /// Empty geometry + Empty, + /// Begin of Point (idx) + PointBegin(usize), + /// End of Point (idx) + PointEnd(usize), + /// Begin of MultiPoint (size, idx) + MultiPointBegin(usize, usize), + /// End of MultiPoint (idx) + MultiPointEnd(usize), + /// Begin of LineString (size, idx) + /// + /// Can be also a Polygon ring or part of a MultiLineString + LineStringBegin(usize, usize), + /// End of LineString (idx) + LineStringEnd(usize), + /// Begin of MultiLineString (size, idx) + MultiLineStringBegin(usize, usize), + /// End of MultiLineString (idx) + MultiLineStringEnd(usize), + /// Begin of Polygon (size, idx) + PolygonBegin(usize, usize), + /// End of Polygon (idx) + PolygonEnd(usize), + /// Begin of MultiPolygon (size, idx) + MultiPolygonBegin(usize, usize), + /// End of MultiPolygon (idx) + MultiPolygonEnd(usize), + /// Begin of GeometryCollection (size, idx) + GeometryCollectionBegin(usize, usize), + /// End of GeometryCollection (idx) + GeometryCollectionEnd(usize), + /// Begin of CircularString (size, idx) + /// + /// The CircularString is the basic curve type, similar to a LineString in the linear world. A single segment required three points, the start and end points (first and third) and any other point on the arc. The exception to this is for a closed circle, where the start and end points are the same. In this case the second point MUST be the center of the arc, ie the opposite side of the circle. To chain arcs together, the last point of the previous arc becomes the first point of the next arc, just like in LineString. This means that a valid circular string must have an odd number of points greated than 1. + CircularStringBegin(usize, usize), + /// End of CircularString (idx) + CircularStringEnd(usize), + /// Begin of CompoundCurve (size, idx) + /// + /// A compound curve is a single, continuous curve that has both curved (circular) segments and linear segments. That means that in addition to having well-formed components, the end point of every component (except the last) must be coincident with the start point of the following component. + CompoundCurveBegin(usize, usize), + /// End of CompoundCurve (idx) + CompoundCurveEnd(usize), + /// Begin of CurvePolygon (size, idx) + /// + /// A CurvePolygon is just like a polygon, with an outer ring and zero or more inner rings. The difference is that a ring can take the form of a circular string, linear string or compound string. + CurvePolygonBegin(usize, usize), + /// End of CurvePolygon (idx) + CurvePolygonEnd(usize), + /// Begin of MultiCurve (size, idx) + /// + /// The MultiCurve is a collection of curves, which can include linear strings, circular strings or compound strings. + MultiCurveBegin(usize, usize), + /// End of MultiCurve (idx) + MultiCurveEnd(usize), + /// Begin of MultiSurface (size, idx) + /// + /// The MultiSurface is a collection of surfaces, which can be (linear) polygons or curve polygons. + MultiSurfaceBegin(usize, usize), + /// End of MultiSurface (idx) + MultiSurfaceEnd(usize), + /// Begin of Triangle (size, idx) + /// + /// An untagged Triangle is part of a Tin + TriangleBegin(usize, usize), + /// End of Triangle (idx) + TriangleEnd(usize), + /// Begin of PolyhedralSurface (size, idx) + PolyhedralSurfaceBegin(usize, usize), + /// End of PolyhedralSurface (idx) + PolyhedralSurfaceEnd(usize), + /// Begin of Tin (size, idx) + TinBegin(usize, usize), + /// End of Tin (idx) + TinEnd(usize), +} + +/// Main Geometry type +/// +/// This is the first state after `Initial` or `GeometryCollection` +/// according to the state diagram of [Event] +// WKB Types according to OGC 06-103r4 () +#[derive(PartialEq, Copy, Clone, Debug)] +pub enum GeometryType { + Unknown, + Point, + LineString, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, + //GeometryCollection, + CircularString, + CompoundCurve, + CurvePolygon, + MultiCurve, + MultiSurface, + Curve, + Surface, + PolyhedralSurface, + Tin, + Triangle, +} + +#[derive(PartialEq, Debug)] +pub(crate) enum Vstate { + Initial, + Point, + LineString, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, + GeometryCollection, + CircularString, + CompoundCurve, + CurvePolygon, + MultiCurve, + MultiSurface, + #[allow(dead_code)] + Curve, + #[allow(dead_code)] + Surface, + PolyhedralSurface, + Tin, + Triangle, +} + +/// Geometry visitor emitting events to a processor +pub struct GeomVisitor<'a> { + // pub dims: CoordDimensions, + pub check_states: bool, + /// Main geometry type + geom_type: GeometryType, + /// Geometry is part of collection + collection: bool, + state_stack: Vec, + /// Event processor + pub processor: &'a mut dyn GeomEventProcessor, +} + +/// Processing geometry events, e.g. for producing an output geometry +pub trait GeomEventProcessor { + /// Geometry processing event with geometry type information + fn event(&mut self, event: &Event, geom_type: GeometryType, collection: bool) -> Result<()>; +} + +/// Reading geometries by passing events to a visitor object +pub trait GeometryReader { + /// Process geometry. + fn process_geom(&mut self, visitor: &mut GeomVisitor) -> Result<()>; +} + +impl<'a> GeomVisitor<'a> { + pub fn new(processor: &'a mut dyn GeomEventProcessor) -> Self { + GeomVisitor { + check_states: true, // Should maybe set from env var? + geom_type: GeometryType::Unknown, + collection: false, + state_stack: Vec::new(), + processor, + } + } + fn emit(&mut self, event: &Event) -> Result<()> { + self.processor.event(event, self.geom_type, self.collection) + } + /// Pass event to chained processor with original state + #[inline(always)] + pub fn chain_event( + &mut self, + event: &Event, + geom_type: GeometryType, + collection: bool, + ) -> Result<()> { + self.processor.event(event, geom_type, collection) + } + /// Pass event to visitor with state recalculation + #[inline] + pub fn emit_event(&mut self, event: &Event) -> Result<()> { + match *event { + Event::Xy(_x, _y, _idx) => self.emit(event), + Event::Coordinate(_x, _y, _z, _m, _t, _tm, _idx) => self.emit(event), + Event::Empty => self.empty(), + Event::PointBegin(idx) => self.point_begin(idx), + Event::PointEnd(idx) => self.point_end(idx), + Event::MultiPointBegin(size, idx) => self.multipoint_begin(size, idx), + Event::MultiPointEnd(idx) => self.multipoint_end(idx), + Event::LineStringBegin(size, idx) => self.linestring_begin(size, idx), + Event::LineStringEnd(idx) => self.linestring_end(idx), + Event::MultiLineStringBegin(size, idx) => self.multilinestring_begin(size, idx), + Event::MultiLineStringEnd(idx) => self.multilinestring_end(idx), + Event::PolygonBegin(size, idx) => self.polygon_begin(size, idx), + Event::PolygonEnd(idx) => self.polygon_end(idx), + Event::MultiPolygonBegin(size, idx) => self.multipolygon_begin(size, idx), + Event::MultiPolygonEnd(idx) => self.multipolygon_end(idx), + Event::GeometryCollectionBegin(size, idx) => self.geometrycollection_begin(size, idx), + Event::GeometryCollectionEnd(idx) => self.geometrycollection_end(idx), + Event::CircularStringBegin(size, idx) => self.circularstring_begin(size, idx), + Event::CircularStringEnd(idx) => self.circularstring_end(idx), + Event::CompoundCurveBegin(size, idx) => self.compoundcurve_begin(size, idx), + Event::CompoundCurveEnd(idx) => self.compoundcurve_end(idx), + Event::CurvePolygonBegin(size, idx) => self.curvepolygon_begin(size, idx), + Event::CurvePolygonEnd(idx) => self.curvepolygon_end(idx), + Event::MultiCurveBegin(size, idx) => self.multicurve_begin(size, idx), + Event::MultiCurveEnd(idx) => self.multicurve_end(idx), + Event::MultiSurfaceBegin(size, idx) => self.multisurface_begin(size, idx), + Event::MultiSurfaceEnd(idx) => self.multisurface_end(idx), + Event::TriangleBegin(size, idx) => self.triangle_begin(size, idx), + Event::TriangleEnd(idx) => self.triangle_end(idx), + Event::PolyhedralSurfaceBegin(size, idx) => self.polyhedralsurface_begin(size, idx), + Event::PolyhedralSurfaceEnd(idx) => self.polyhedralsurface_end(idx), + Event::TinBegin(size, idx) => self.tin_begin(size, idx), + Event::TinEnd(idx) => self.tin_end(idx), + } + } + fn state(&self) -> &Vstate { + let len = self.state_stack.len(); + if len > 0 { + &self.state_stack[len - 1] + } else { + &Vstate::Initial + } + } + fn enter_state(&mut self, state: Vstate) -> Result<()> { + match (self.state(), &state) { + (Vstate::Initial, Vstate::GeometryCollection) + | (Vstate::Initial, Vstate::Point) + | (Vstate::Initial, Vstate::LineString) + | (Vstate::Initial, Vstate::Polygon) + | (Vstate::Initial, Vstate::MultiPoint) + | (Vstate::Initial, Vstate::MultiLineString) + | (Vstate::Initial, Vstate::MultiPolygon) + | (Vstate::Initial, Vstate::CircularString) + | (Vstate::Initial, Vstate::CompoundCurve) + | (Vstate::Initial, Vstate::CurvePolygon) + | (Vstate::Initial, Vstate::MultiCurve) + | (Vstate::Initial, Vstate::MultiSurface) + | (Vstate::Initial, Vstate::Curve) + | (Vstate::Initial, Vstate::Surface) + | (Vstate::Initial, Vstate::PolyhedralSurface) + | (Vstate::Initial, Vstate::Tin) + | (Vstate::Initial, Vstate::Triangle) + | (Vstate::Polygon, Vstate::LineString) + | (Vstate::MultiLineString, Vstate::LineString) + | (Vstate::MultiPolygon, Vstate::Polygon) + | (Vstate::GeometryCollection, Vstate::Point) + | (Vstate::GeometryCollection, Vstate::LineString) + | (Vstate::GeometryCollection, Vstate::Polygon) + | (Vstate::GeometryCollection, Vstate::MultiPoint) + | (Vstate::GeometryCollection, Vstate::MultiLineString) + | (Vstate::GeometryCollection, Vstate::MultiPolygon) + | (Vstate::GeometryCollection, Vstate::GeometryCollection) + | (Vstate::GeometryCollection, Vstate::CircularString) + | (Vstate::GeometryCollection, Vstate::CompoundCurve) + | (Vstate::GeometryCollection, Vstate::CurvePolygon) + | (Vstate::GeometryCollection, Vstate::MultiCurve) + | (Vstate::GeometryCollection, Vstate::MultiSurface) + | (Vstate::GeometryCollection, Vstate::Curve) + | (Vstate::GeometryCollection, Vstate::Surface) + | (Vstate::GeometryCollection, Vstate::PolyhedralSurface) + | (Vstate::GeometryCollection, Vstate::Tin) + | (Vstate::GeometryCollection, Vstate::Triangle) + | (Vstate::CompoundCurve, Vstate::CircularString) + | (Vstate::CompoundCurve, Vstate::LineString) + | (Vstate::CurvePolygon, Vstate::CircularString) + | (Vstate::CurvePolygon, Vstate::LineString) + | (Vstate::CurvePolygon, Vstate::CompoundCurve) + | (Vstate::MultiCurve, Vstate::CircularString) + | (Vstate::MultiCurve, Vstate::LineString) + | (Vstate::MultiCurve, Vstate::CompoundCurve) + | (Vstate::MultiSurface, Vstate::CurvePolygon) + | (Vstate::MultiSurface, Vstate::Polygon) + | (Vstate::Triangle, Vstate::LineString) + | (Vstate::PolyhedralSurface, Vstate::Polygon) + | (Vstate::Tin, Vstate::Polygon) => { + // println!("Enter state {:?}=>{:?}", self.state(), state); + self.state_stack.push(state); + Ok(()) + } + _ => Err(GeozeroError::Geometry(format!( + "Invalid state transition from {:?} to {:?}", + self.state(), + state + ))), + } + } + fn exit_state(&mut self, state: Vstate) -> Result<()> { + let valid = if let Some(prev_state) = self.state_stack.pop() { + state == prev_state + } else { + false + }; + if valid { + // println!( + // "Exit state {:?} (GeometryType::{:?})=>{:?}", + // &self.state_stack, + // &self.geom_type, + // self.state() + // ); + } else { + return Err(GeozeroError::Geometry(format!( + "Invalid state transition from {:?} to {:?}", + self.state_stack, state + ))); + } + Ok(()) + } + fn set_type(&mut self, inner_type: GeometryType) -> Result<()> { + if self.geom_type == GeometryType::Unknown { + // println!("Set GeometryType {:?} => {:?}", &self.geom_type, inner_type); + self.geom_type = inner_type; + } + Ok(()) + } + fn reset_type(&mut self, inner_type: GeometryType) { + // Reset geometry type within collections + if self.collection && self.geom_type == inner_type { + self.geom_type = GeometryType::Unknown; + } + } + /// Process coordinate with x,y dimensions + pub fn xy(&mut self, x: f64, y: f64, idx: usize) -> Result<()> { + self.emit(&Event::Xy(x, y, idx)) + } + + /// Process coordinate with all requested dimensions + pub fn coordinate( + &mut self, + x: f64, + y: f64, + z: Option, + m: Option, + t: Option, + tm: Option, + idx: usize, + ) -> Result<()> { + self.emit(&Event::Coordinate(x, y, z, m, t, tm, idx)) + } + /// Process empty coordinates, like an empty point + pub fn empty(&mut self) -> Result<()> { + self.emit(&Event::Empty)?; + Ok(()) + } + + /// Begin of Point processing + pub fn point_begin(&mut self, idx: usize) -> Result<()> { + self.set_type(GeometryType::Point)?; + if self.check_states { + self.enter_state(Vstate::Point)?; + } + self.emit(&Event::PointBegin(idx))?; + Ok(()) + } + + /// End of Point processing + pub fn point_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::PointEnd(idx))?; + self.reset_type(GeometryType::Point); + if self.check_states { + self.exit_state(Vstate::Point)?; + } + Ok(()) + } + + /// Begin of MultiPoint processing + /// + /// Next: size * xy/coordinate + pub fn multipoint_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::MultiPoint)?; + if self.check_states { + self.enter_state(Vstate::MultiPoint)?; + } + self.emit(&Event::MultiPointBegin(size, idx))?; + Ok(()) + } + + /// End of MultiPoint processing + pub fn multipoint_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::MultiPointEnd(idx))?; + self.reset_type(GeometryType::MultiPoint); + if self.check_states { + self.exit_state(Vstate::MultiPoint)?; + } + Ok(()) + } + + /// Begin of LineString processing + /// + /// Can also be a Polygon ring or part of a MultiLineString + /// + /// Next: size * xy/coordinate + pub fn linestring_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::LineString)?; + if self.check_states { + self.enter_state(Vstate::LineString)?; + } + self.emit(&Event::LineStringBegin(size, idx))?; + Ok(()) + } + + /// End of LineString processing + pub fn linestring_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::LineStringEnd(idx))?; + self.reset_type(GeometryType::LineString); + if self.check_states { + self.exit_state(Vstate::LineString)?; + } + Ok(()) + } + + /// Begin of MultiLineString processing + /// + /// Next: size * LineString + pub fn multilinestring_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::MultiLineString)?; + if self.check_states { + self.enter_state(Vstate::MultiLineString)?; + } + self.emit(&Event::MultiLineStringBegin(size, idx))?; + Ok(()) + } + + /// End of MultiLineString processing + pub fn multilinestring_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::MultiLineStringEnd(idx))?; + self.reset_type(GeometryType::MultiLineString); + if self.check_states { + self.exit_state(Vstate::MultiLineString)?; + } + Ok(()) + } + + /// Begin of Polygon processing + /// + /// Can also be part of a MultiPolygon + /// + /// Next: size * LineString = rings + pub fn polygon_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::Polygon)?; + if self.check_states { + self.enter_state(Vstate::Polygon)?; + } + self.emit(&Event::PolygonBegin(size, idx))?; + Ok(()) + } + + /// End of Polygon processing + pub fn polygon_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::PolygonEnd(idx))?; + self.reset_type(GeometryType::Polygon); + if self.check_states { + self.exit_state(Vstate::Polygon)?; + } + Ok(()) + } + + /// Begin of MultiPolygon processing + /// + /// Next: size * Polygon + pub fn multipolygon_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::MultiPolygon)?; + if self.check_states { + self.enter_state(Vstate::MultiPolygon)?; + } + self.emit(&Event::MultiPolygonBegin(size, idx))?; + Ok(()) + } + + /// End of MultiPolygon processing + pub fn multipolygon_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::MultiPolygonEnd(idx))?; + self.reset_type(GeometryType::MultiPolygon); + if self.check_states { + self.exit_state(Vstate::MultiPolygon)?; + } + Ok(()) + } + + /// Begin of GeometryCollection processing + pub fn geometrycollection_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.collection = true; + self.geom_type = GeometryType::Unknown; + if self.check_states { + self.enter_state(Vstate::GeometryCollection)?; + } + self.emit(&Event::GeometryCollectionBegin(size, idx))?; + Ok(()) + } + + /// End of GeometryCollection processing + pub fn geometrycollection_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::GeometryCollectionEnd(idx))?; + self.geom_type = GeometryType::Unknown; + if self.check_states { + self.exit_state(Vstate::GeometryCollection)?; + } + self.collection = false; + Ok(()) + } + + /// Begin of CircularString processing + /// + /// The CircularString is the basic curve type, similar to a LineString in the linear world. A single segment required three points, the start and end points (first and third) and any other point on the arc. The exception to this is for a closed circle, where the start and end points are the same. In this case the second point MUST be the center of the arc, ie the opposite side of the circle. To chain arcs together, the last point of the previous arc becomes the first point of the next arc, just like in LineString. This means that a valid circular string must have an odd number of points greated than 1. + /// + /// Next: size * xy/coordinate + pub fn circularstring_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::CircularString)?; + if self.check_states { + self.enter_state(Vstate::CircularString)?; + } + self.emit(&Event::CircularStringBegin(size, idx))?; + Ok(()) + } + + /// End of CircularString processing + pub fn circularstring_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::CircularStringEnd(idx))?; + self.reset_type(GeometryType::CircularString); + if self.check_states { + self.exit_state(Vstate::CircularString)?; + } + Ok(()) + } + + /// Begin of CompoundCurve processing + /// + /// A compound curve is a single, continuous curve that has both curved (circular) segments and linear segments. That means that in addition to having well-formed components, the end point of every component (except the last) must be coincident with the start point of the following component. + /// + /// Next: size * (CircularString | LineString) + pub fn compoundcurve_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::CompoundCurve)?; + if self.check_states { + self.enter_state(Vstate::CompoundCurve)?; + } + self.emit(&Event::CompoundCurveBegin(size, idx))?; + Ok(()) + } + + /// End of CompoundCurve processing + pub fn compoundcurve_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::CompoundCurveEnd(idx))?; + self.reset_type(GeometryType::CompoundCurve); + if self.check_states { + self.exit_state(Vstate::CompoundCurve)?; + } + Ok(()) + } + + /// Begin of CurvePolygon processing + /// + /// A CurvePolygon is just like a polygon, with an outer ring and zero or more inner rings. The difference is that a ring can take the form of a circular string, linear string or compound string. + /// + /// Next: size * (CircularString | LineString | CompoundCurve) + pub fn curvepolygon_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::CurvePolygon)?; + if self.check_states { + self.enter_state(Vstate::CurvePolygon)?; + } + self.emit(&Event::CurvePolygonBegin(size, idx))?; + Ok(()) + } + + /// End of CurvePolygon processing + pub fn curvepolygon_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::CurvePolygonEnd(idx))?; + self.reset_type(GeometryType::CurvePolygon); + if self.check_states { + self.exit_state(Vstate::CurvePolygon)?; + } + Ok(()) + } + + /// Begin of MultiCurve processing + /// + /// The MultiCurve is a collection of curves, which can include linear strings, circular strings or compound strings. + /// + /// Next: size * (CircularString | LineString | CompoundCurve) + pub fn multicurve_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::MultiCurve)?; + if self.check_states { + self.enter_state(Vstate::MultiCurve)?; + } + self.emit(&Event::MultiCurveBegin(size, idx))?; + Ok(()) + } + + /// End of MultiCurve processing + pub fn multicurve_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::MultiCurveEnd(idx))?; + self.reset_type(GeometryType::MultiCurve); + if self.check_states { + self.exit_state(Vstate::MultiCurve)?; + } + Ok(()) + } + + /// Begin of MultiSurface processing + /// + /// The MultiSurface is a collection of surfaces, which can be (linear) polygons or curve polygons. + /// + /// Next: size * (CurvePolygon | Polygon) + pub fn multisurface_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::MultiSurface)?; + if self.check_states { + self.enter_state(Vstate::MultiSurface)?; + } + self.emit(&Event::MultiSurfaceBegin(size, idx))?; + Ok(()) + } + + /// End of MultiSurface processing + pub fn multisurface_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::MultiSurfaceEnd(idx))?; + self.reset_type(GeometryType::MultiSurface); + if self.check_states { + self.exit_state(Vstate::MultiSurface)?; + } + Ok(()) + } + /// Begin of Triangle processing + /// + /// Can also be part of a Tin + /// + /// Next: size * LineString = rings + pub fn triangle_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::Triangle)?; + if self.check_states { + self.enter_state(Vstate::Triangle)?; + } + self.emit(&Event::TriangleBegin(size, idx))?; + Ok(()) + } + + /// End of Triangle processing + pub fn triangle_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::TriangleEnd(idx))?; + self.reset_type(GeometryType::Triangle); + if self.check_states { + self.exit_state(Vstate::Triangle)?; + } + Ok(()) + } + + /// Begin of PolyhedralSurface processing + /// + /// Next: size * Polygon + pub fn polyhedralsurface_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::PolyhedralSurface)?; + if self.check_states { + self.enter_state(Vstate::PolyhedralSurface)?; + } + self.emit(&Event::PolyhedralSurfaceBegin(size, idx))?; + Ok(()) + } + + /// End of PolyhedralSurface processing + pub fn polyhedralsurface_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::PolyhedralSurfaceEnd(idx))?; + self.reset_type(GeometryType::PolyhedralSurface); + if self.check_states { + self.exit_state(Vstate::PolyhedralSurface)?; + } + Ok(()) + } + + /// Begin of Tin processing + /// + /// Next: size * Polygon + pub fn tin_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.set_type(GeometryType::Tin)?; + if self.check_states { + self.enter_state(Vstate::Tin)?; + } + self.emit(&Event::TinBegin(size, idx))?; + Ok(()) + } + + /// End of Tin processing + pub fn tin_end(&mut self, idx: usize) -> Result<()> { + self.emit(&Event::TinEnd(idx))?; + self.reset_type(GeometryType::Tin); + if self.check_states { + self.exit_state(Vstate::Tin)?; + } + Ok(()) + } +} + +#[cfg(test)] +pub(crate) mod test { + use super::*; + use crate::events::Event::*; + use crate::processor::GeomEventSink; + + // -- Event emitter (geometry input) -- + + pub struct NullIsland; + + impl GeometryReader for NullIsland { + fn process_geom(&mut self, visitor: &mut GeomVisitor) -> Result<()> { + visitor.point_begin(0)?; + visitor.xy(0.0, 0.0, 0)?; + visitor.point_end(0)?; + Ok(()) + } + } + + // -- Event processor (geometry output) -- + + pub struct GeomEventBuffer { + pub buffer: Vec, + } + + impl GeomEventBuffer { + pub fn new() -> Self { + GeomEventBuffer { buffer: Vec::new() } + } + } + + impl GeomEventProcessor for GeomEventBuffer { + fn event( + &mut self, + event: &Event, + _geom_type: GeometryType, + _collection: bool, + ) -> Result<()> { + self.buffer.push(event.clone()); + Ok(()) + } + } + + #[test] + fn processing() -> Result<()> { + let mut processor = GeomEventBuffer::new(); + let mut visitor = GeomVisitor::new(&mut processor); + let mut geom = NullIsland; + geom.process_geom(&mut visitor)?; + + assert_eq!( + processor.buffer, + [PointBegin(0), Xy(0.0, 0.0, 0), PointEnd(0)] + ); + + Ok(()) + } + + #[test] + fn runtime_definition() -> Result<()> { + let do_buffer = true; + let mut processor = GeomEventBuffer::new(); + let mut sink = GeomEventSink; + let mut visitor = if do_buffer { + GeomVisitor::new(&mut processor) + } else { + GeomVisitor::new(&mut sink) + }; + let mut geom = NullIsland; + geom.process_geom(&mut visitor)?; + + if do_buffer { + assert_eq!( + processor.buffer, + [PointBegin(0), Xy(0.0, 0.0, 0), PointEnd(0)] + ); + } + Ok(()) + } + + #[test] + fn owned_visitor() -> Result<()> { + struct App { + processor: GeomEventBuffer, + } + struct AppVisitor<'a> { + visitor: GeomVisitor<'a>, + } + impl App { + fn new() -> Self { + App { + processor: GeomEventBuffer::new(), + } + } + fn visitor(&mut self) -> AppVisitor { + AppVisitor { + visitor: GeomVisitor::new(&mut self.processor), + } + } + fn buf_size(&mut self) -> usize { + self.processor.buffer.len() + } + } + let mut app = App::new(); + let mut app_visitor = app.visitor(); + let mut geom = NullIsland; + geom.process_geom(&mut app_visitor.visitor)?; + let size = app.buf_size(); + assert_eq!(size, 3); + Ok(()) + } + + struct Point2D { + pub x: f64, + pub y: f64, + } + + impl GeomEventProcessor for Point2D { + fn event( + &mut self, + event: &Event, + _geom_type: GeometryType, + _collection: bool, + ) -> Result<()> { + match *event { + PointBegin(_) | PointEnd(_) => {} // OK + Xy(x, y, _idx) => (self.x, self.y) = (x, y), + _ => return Err(GeozeroError::GeometryFormat), + } + Ok(()) + } + } + + #[test] + fn process_point() -> Result<()> { + let mut geom_out = Point2D { + x: f64::NAN, + y: f64::NAN, + }; + let mut visitor = GeomVisitor::new(&mut geom_out); + + let mut geom_in = NullIsland; + geom_in.process_geom(&mut visitor)?; + + assert_eq!((geom_out.x, geom_out.y), (0.0, 0.0)); + + Ok(()) + } + + #[test] + fn polygon() -> Result<()> { + let mut processor = GeomEventBuffer::new(); + let mut visitor = GeomVisitor::new(&mut processor); + visitor.polygon_begin(2, 0)?; + visitor.linestring_begin(2, 0)?; + visitor.xy(0.0, 0.0, 0)?; + visitor.xy(1.0, 1.0, 1)?; + visitor.linestring_end(0)?; + visitor.linestring_begin(2, 1)?; + visitor.xy(0.0, 0.0, 0)?; + visitor.xy(1.0, 1.0, 1)?; + visitor.linestring_end(1)?; + visitor.polygon_end(0)?; + + dbg!(&processor.buffer); + assert_eq!( + processor.buffer, + [ + PolygonBegin(2, 0), + LineStringBegin(2, 0), + Xy(0.0, 0.0, 0), + Xy(1.0, 1.0, 1), + LineStringEnd(0), + LineStringBegin(2, 1), + Xy(0.0, 0.0, 0), + Xy(1.0, 1.0, 1), + LineStringEnd(1), + PolygonEnd(0) + ] + ); + + Ok(()) + } + + #[test] + fn collection() -> Result<()> { + let mut processor = GeomEventBuffer::new(); + let mut visitor = GeomVisitor::new(&mut processor); + visitor.geometrycollection_begin(2, 0)?; + visitor.point_begin(0)?; + visitor.xy(0.0, 0.0, 0)?; + visitor.point_end(0)?; + visitor.linestring_begin(2, 1)?; + visitor.xy(0.0, 0.0, 0)?; + visitor.xy(1.0, 1.0, 1)?; + visitor.linestring_end(1)?; + visitor.geometrycollection_end(0)?; + + assert_eq!( + processor.buffer, + [ + GeometryCollectionBegin(2, 0), + PointBegin(0), + Xy(0.0, 0.0, 0), + PointEnd(0), + LineStringBegin(2, 1), + Xy(0.0, 0.0, 0), + Xy(1.0, 1.0, 1), + LineStringEnd(1), + GeometryCollectionEnd(0) + ] + ); + + Ok(()) + } + + #[test] + fn invalid_transitions() -> Result<()> { + let mut processor = GeomEventSink; + let mut visitor = GeomVisitor::new(&mut processor); + visitor.point_begin(0)?; + visitor.xy(0.0, 0.0, 0)?; + let result = visitor.polygon_end(0); + assert!(result.is_err()); + + visitor.check_states = false; + visitor.point_begin(0)?; + visitor.xy(0.0, 0.0, 0)?; + visitor.polygon_end(0)?; + + Ok(()) + } + + #[test] + #[cfg(feature = "with-geojson")] + fn geozero_geometry_api() -> Result<()> { + use crate::api::GeozeroGeometry; + use crate::geojson::GeoJson; + + let geojson = GeoJson( + r#"{"type": "Polygon", "coordinates": [[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]}"#, + ); + let mut processor = GeomEventBuffer::new(); + let mut visitor = GeomVisitor::new(&mut processor); + geojson.process_geom(&mut visitor)?; + assert_eq!( + processor.buffer, + [ + PolygonBegin(1, 0), + LineStringBegin(22, 0), + Xy(20.590247, 41.855404, 0), + Xy(20.463175, 41.515089, 1), + Xy(20.605182, 41.086226, 2), + Xy(21.02004, 40.842727, 3), + Xy(20.99999, 40.580004, 4), + Xy(20.674997, 40.435, 5), + Xy(20.615, 40.110007, 6), + Xy(20.150016, 39.624998, 7), + Xy(19.98, 39.694993, 8), + Xy(19.960002, 39.915006, 9), + Xy(19.406082, 40.250773, 10), + Xy(19.319059, 40.72723, 11), + Xy(19.40355, 41.409566, 12), + Xy(19.540027, 41.719986, 13), + Xy(19.371769, 41.877548, 14), + Xy(19.304486, 42.195745, 15), + Xy(19.738051, 42.688247, 16), + Xy(19.801613, 42.500093, 17), + Xy(20.0707, 42.58863, 18), + Xy(20.283755, 42.32026, 19), + Xy(20.52295, 42.21787, 20), + Xy(20.590247, 41.855404, 21), + LineStringEnd(0), + PolygonEnd(0) + ] + ); + Ok(()) + } +} diff --git a/geozero/src/geo_types/geo_types_writer.rs b/geozero/src/geo_types/geo_types_writer.rs index ea39fd6a..04c9ca72 100644 --- a/geozero/src/geo_types/geo_types_writer.rs +++ b/geozero/src/geo_types/geo_types_writer.rs @@ -1,5 +1,6 @@ use crate::error::{GeozeroError, Result}; -use crate::{FeatureProcessor, GeomProcessor, PropertyProcessor}; +use crate::events::{self, Event, GeomEventProcessor}; +use crate::{FeatureProcessor, PropertyProcessor}; use geo_types::*; use std::mem; @@ -14,12 +15,15 @@ pub struct GeoWriter { line_strings: Option>>, // In-progress point or line_string coords: Option>>, + // Empty geometry flag + empty: bool, } impl GeoWriter { pub fn new() -> GeoWriter { GeoWriter { geoms: Vec::new(), + empty: false, coords: None, line_strings: None, polygons: None, @@ -50,155 +54,172 @@ impl GeoWriter { } } -impl GeomProcessor for GeoWriter { - fn xy(&mut self, x: f64, y: f64, _idx: usize) -> Result<()> { - let coords = self - .coords - .as_mut() - .ok_or(GeozeroError::Geometry("Not ready for coords".to_string()))?; - coords.push(coord!(x: x, y: y)); - Ok(()) - } +impl GeomEventProcessor for GeoWriter { + fn event( + &mut self, + event: &Event, + geom_type: events::GeometryType, + _collection: bool, + ) -> Result<()> { + match *event { + Event::Xy(x, y, _idx) => { + self.empty = false; + let coords = self + .coords + .as_mut() + .ok_or(GeozeroError::Geometry("Not ready for coords".to_string()))?; + coords.push(coord!(x: x, y: y)); + } - fn point_begin(&mut self, _idx: usize) -> Result<()> { - debug_assert!(self.coords.is_none()); - self.coords = Some(Vec::with_capacity(1)); - Ok(()) - } + Event::Empty => { + self.empty = true; + } - fn point_end(&mut self, _idx: usize) -> Result<()> { - let coords = self - .coords - .take() - .ok_or(GeozeroError::Geometry("No coords for Point".to_string()))?; - debug_assert!(coords.len() == 1); - self.finish_geometry(Point(coords[0]).into()) - } + Event::PointBegin(_idx) => { + debug_assert!(self.coords.is_none()); + self.coords = Some(Vec::with_capacity(1)); + } - fn multipoint_begin(&mut self, size: usize, _idx: usize) -> Result<()> { - debug_assert!(self.coords.is_none()); - self.coords = Some(Vec::with_capacity(size)); - Ok(()) - } + Event::PointEnd(_idx) => { + if self.empty { + return Ok(()); + } + let coords = self + .coords + .take() + .ok_or(GeozeroError::Geometry("No coords for Point".to_string()))?; + debug_assert!(coords.len() == 1); + self.finish_geometry(Point(coords[0]).into())?; + } - fn multipoint_end(&mut self, _idx: usize) -> Result<()> { - let coords = self.coords.take().ok_or(GeozeroError::Geometry( - "No coords for MultiPoint".to_string(), - ))?; - let points: Vec> = coords.into_iter().map(From::from).collect(); - self.finish_geometry(MultiPoint(points).into()) - } + Event::MultiPointBegin(size, _idx) => { + debug_assert!(self.coords.is_none()); + self.coords = Some(Vec::with_capacity(size)); + } - fn linestring_begin(&mut self, _tagged: bool, size: usize, _idx: usize) -> Result<()> { - debug_assert!(self.coords.is_none()); - self.coords = Some(Vec::with_capacity(size)); - Ok(()) - } + Event::MultiPointEnd(_idx) => { + let coords = self.coords.take().ok_or(GeozeroError::Geometry( + "No coords for MultiPoint".to_string(), + ))?; + let points: Vec> = coords.into_iter().map(From::from).collect(); + self.finish_geometry(MultiPoint(points).into())?; + } - fn linestring_end(&mut self, tagged: bool, _idx: usize) -> Result<()> { - let coords = self.coords.take().ok_or(GeozeroError::Geometry( - "No coords for LineString".to_string(), - ))?; - let line_string = LineString(coords); - if tagged { - self.finish_geometry(line_string.into())?; - } else { - let line_strings = self.line_strings.as_mut().ok_or(GeozeroError::Geometry( - "Missing container for LineString".to_string(), - ))?; - line_strings.push(line_string); - } - Ok(()) - } + Event::LineStringBegin(size, _idx) => { + debug_assert!(self.coords.is_none()); + self.coords = Some(Vec::with_capacity(size)); + } - fn multilinestring_begin(&mut self, size: usize, _idx: usize) -> Result<()> { - debug_assert!(self.line_strings.is_none()); - self.line_strings = Some(Vec::with_capacity(size)); - Ok(()) - } + Event::LineStringEnd(_idx) => { + let coords = self.coords.take().ok_or(GeozeroError::Geometry( + "No coords for LineString".to_string(), + ))?; + let line_string = LineString(coords); + if geom_type == events::GeometryType::LineString { + self.finish_geometry(line_string.into())?; + } else { + let line_strings = self.line_strings.as_mut().ok_or(GeozeroError::Geometry( + "Missing container for LineString".to_string(), + ))?; + line_strings.push(line_string); + } + } - fn multilinestring_end(&mut self, _idx: usize) -> Result<()> { - let line_strings = self.line_strings.take().ok_or(GeozeroError::Geometry( - "No LineStrings for MultiLineString".to_string(), - ))?; - self.finish_geometry(MultiLineString(line_strings).into()) - } + Event::MultiLineStringBegin(size, _idx) => { + debug_assert!(self.line_strings.is_none()); + self.line_strings = Some(Vec::with_capacity(size)); + } - fn polygon_begin(&mut self, _tagged: bool, size: usize, _idx: usize) -> Result<()> { - debug_assert!(self.line_strings.is_none()); - self.line_strings = Some(Vec::with_capacity(size)); - Ok(()) - } + Event::MultiLineStringEnd(_idx) => { + let line_strings = self.line_strings.take().ok_or(GeozeroError::Geometry( + "No LineStrings for MultiLineString".to_string(), + ))?; + self.finish_geometry(MultiLineString(line_strings).into())?; + } - fn polygon_end(&mut self, tagged: bool, _idx: usize) -> Result<()> { - let mut line_strings = self.line_strings.take().ok_or(GeozeroError::Geometry( - "Missing LineStrings for Polygon".to_string(), - ))?; + Event::PolygonBegin(size, _idx) => { + debug_assert!(self.line_strings.is_none()); + self.line_strings = Some(Vec::with_capacity(size)); + } - let polygon = if line_strings.len() == 0 { - Polygon::new(LineString(vec![]), vec![]) - } else { - let exterior = line_strings.remove(0); - Polygon::new(exterior, mem::take(&mut line_strings)) - }; + Event::PolygonEnd(_idx) => { + let mut line_strings = self.line_strings.take().ok_or(GeozeroError::Geometry( + "Missing LineStrings for Polygon".to_string(), + ))?; + + let polygon = if line_strings.len() == 0 { + Polygon::new(LineString(vec![]), vec![]) + } else { + let exterior = line_strings.remove(0); + Polygon::new(exterior, mem::take(&mut line_strings)) + }; + + if geom_type == events::GeometryType::Polygon { + self.finish_geometry(polygon.into())?; + } else { + let polygons = self.polygons.as_mut().ok_or(GeozeroError::Geometry( + "Missing container for Polygon".to_string(), + ))?; + polygons.push(polygon); + } + } - if tagged { - self.finish_geometry(polygon.into())?; - } else { - let polygons = self.polygons.as_mut().ok_or(GeozeroError::Geometry( - "Missing container for Polygon".to_string(), - ))?; - polygons.push(polygon); - } - Ok(()) - } + Event::MultiPolygonBegin(size, _idx) => { + debug_assert!(self.polygons.is_none()); + self.polygons = Some(Vec::with_capacity(size)); + } - fn multipolygon_begin(&mut self, size: usize, _idx: usize) -> Result<()> { - debug_assert!(self.polygons.is_none()); - self.polygons = Some(Vec::with_capacity(size)); - Ok(()) - } + Event::MultiPolygonEnd(_idx) => { + let polygons = self.polygons.take().ok_or(GeozeroError::Geometry( + "Missing polygons for MultiPolygon".to_string(), + ))?; + self.finish_geometry(MultiPolygon(polygons).into())?; + } - fn multipolygon_end(&mut self, _idx: usize) -> Result<()> { - let polygons = self.polygons.take().ok_or(GeozeroError::Geometry( - "Missing polygons for MultiPolygon".to_string(), - ))?; - self.finish_geometry(MultiPolygon(polygons).into()) - } + Event::GeometryCollectionBegin(size, _idx) => { + self.collections.push(Vec::with_capacity(size)); + } - fn geometrycollection_begin(&mut self, size: usize, _idx: usize) -> Result<()> { - self.collections.push(Vec::with_capacity(size)); - Ok(()) - } + Event::GeometryCollectionEnd(_idx) => { + let geometries = self.collections.pop().ok_or(GeozeroError::Geometry( + "Unexpected geometry type".to_string(), + ))?; - fn geometrycollection_end(&mut self, _idx: usize) -> Result<()> { - let geometries = self.collections.pop().ok_or(GeozeroError::Geometry( - "Unexpected geometry type".to_string(), - ))?; + self.finish_geometry(Geometry::GeometryCollection(GeometryCollection(geometries)))?; + } - self.finish_geometry(Geometry::GeometryCollection(GeometryCollection(geometries))) + _ => { + return Err(GeozeroError::Geometry(format!( + "Unexpected geometry event {:?}", + event + ))) + } // ?? With GeomProcessor we ignore all other states + } + Ok(()) } } -impl PropertyProcessor for GeoWriter {} +impl PropertyProcessor for events::GeomVisitor<'_> {} -impl FeatureProcessor for GeoWriter {} +impl FeatureProcessor for events::GeomVisitor<'_> {} #[cfg(test)] #[cfg(feature = "with-geojson")] mod test { use super::*; + use crate::events::GeomVisitor; use crate::geojson::{read_geojson, GeoJson}; - use crate::ToGeo; + use crate::{ToGeo, ToJson}; use geo::algorithm::coords_iter::CoordsIter; #[test] fn line_string() -> Result<()> { - let geojson = r#"{"type": "LineString", "coordinates": [[1875038.447610231,-3269648.6879248763],[1874359.641504197,-3270196.812984864],[1874141.0428635243,-3270953.7840121365],[1874440.1778162003,-3271619.4315206874],[1876396.0598222911,-3274138.747656357],[1876442.0805243007,-3275052.60551469],[1874739.312657555,-3275457.333765534]]}"#; - let mut geo = GeoWriter::new(); - assert!(read_geojson(geojson.as_bytes(), &mut geo).is_ok()); - let geom = geo.take_geometry().unwrap(); - match geom { + let geojson = GeoJson( + r#"{"type": "LineString", "coordinates": [[1875038.447610231,-3269648.6879248763],[1874359.641504197,-3270196.812984864],[1874141.0428635243,-3270953.7840121365],[1874440.1778162003,-3271619.4315206874],[1876396.0598222911,-3274138.747656357],[1876442.0805243007,-3275052.60551469],[1874739.312657555,-3275457.333765534]]}"#, + ); + let geo = geojson.to_geo().unwrap(); + println!("{:?}", geo); + match geo { Geometry::LineString(line) => { assert_eq!(line.coords_count(), 7); assert_eq!( @@ -245,6 +266,16 @@ mod test { ])); assert_eq!(expected, actual); + + let wkt = WktStr("GEOMETRYCOLLECTION ( + LINESTRING (6308869.40378 356821.22669, 6308867.893 356822.41744, 6308852.75314 356830.22159, 6308869.92754 356844.26638), + LINESTRING (6308755.07971 356674.51686, 6308784.81355 356719.16757, 6308815.20022 356765.46178, 6308829.63774 356763.22832, 6308852.87023 356759.82402, 6308867.19982 356771.06823, 6308875.40631 356796.20162, 6308872.51907 356815.17242), + LINESTRING (6308874.12086 356813.73392, 6308876.83028 356795.77697, 6308868.23871 356770.06254, 6308853.09618 356758.29456, 6308815.86529 356763.89689, 6308799.76731 356739.37835, 6308747.77971 356662.11613, 6308746.55411 356661.61702, 6308744.06545 356657.72563, 6308731.77184 356668.45076, 6308699.45221 356683.15463, 6308682.44689 356684.63193, 6308654.96629 356683.66846, 6308636.13879 356680.0482, 6308618.19888 356671.76352, 6308608.41685 356661.79428, 6308578.7973 356592.35062, 6308545.33908 356542.14886, 6308517.52088 356509.38474, 6308505.40266 356506.84141, 6308493.59689 356506.98067, 6308375.07918 356520.46209), + POLYGON ((6309096.87876754 357058.96992573235, 6309100.9240038069 357067.89795246266, 6309103.1497403858 357077.44361610821, 6309103.4704434676 357087.24008216924, 6309101.8737886148 357096.91087794991, 6309098.421134375 357106.08436019259, 6309093.2451643161 357114.40799712023, 6309086.5447880644 357121.56191603432, 6309078.5774973193 357127.27119584358, 6309013.594489282 357164.78915304772, 6309004.6664625369 357168.8343893392, 6308995.1207988719 357171.06012593472, 6308985.3243327877 357171.38082902494, 6308975.6535369828 357169.78417417, 6308966.4800547194 357166.33151992067, 6308958.156417775 357161.1555498438, 6308951.0024988521 357154.45517356717, 6308945.293219042 357146.48788279406, 6308795.0043396624 356886.1799069175, 6308790.959103398 356877.251880196, 6308788.7333668182 356867.70621655986, 6308788.4126637317 356857.90975050797, 6308790.0093185771 356848.2389547351, 6308793.4619728047 356839.0654724979, 6308798.6379428506 356830.74183557258, 6308805.3383190883 356823.587916657, 6308813.3056098176 356817.87863684172, 6308878.2886178084 356780.36067953659, 6308887.2166445563 356776.315443229, 6308896.7623082288 356774.08970661991, 6308906.5587743223 356773.76900351944, 6308916.2295701364 356775.36565836804, 6308925.403052411 356778.81831261492, 6308933.7266893657 356783.99428269314, 6308940.8806082979 356790.69465897442, 6308946.5898881136 356798.66194975481, 6309096.87876754 357058.96992573235)), + LINESTRING (6308877.92941 356819.50984, 6309072.26249 356514.14689, 6309073.44938 356513.3739, 6309076.25423 356511.31751, 6309096.05004 356528.52014, 6309103.33938 356535.32615, 6309107.49584 356539.20699, 6309107.78601 356539.47793, 6309119.09139 356550.03322, 6309137.04465 356567.13752, 6309137.6323 356567.69515, 6309138.92096 356568.91355, 6309138.46355 356569.69798, 6309150.68532 356566.34027, 6309151.94333 356567.03108, 6309157.81557 356565.41779, 6309161.54152 356564.33408, 6309174.6464 356579.77423, 6309175.71622 356581.0361, 6309177.25892 356582.84545, 6309225.37695 356611.76515, 6309226.90588 356612.65173, 6309229.72021 356614.34101, 6309232.64678 356598.75445, 6309244.10246 356528.49893, 6309251.20809 356487.90256, 6309252.35489 356481.34967, 6309258.41778 356442.34047, 6309258.56036 356441.19511, 6309258.76115 356440.13123, 6309260.99127 356426.22389, 6309258.49745 356425.57244, 6309240.94882 356422.48836, 6309240.53276 356422.37171, 6309240.10958 356422.29068), + LINESTRING (6308870.96141 356823.05522, 6308881.43519 356846.04558, 6308859.94336 356857.75024, 6308859.6305 356857.95378, 6308893.96675 356932.14467, 6308921.19517 356993.60222, 6308942.68768 357040.82051, 6308961.42173 357079.52481, 6308976.48471 357108.08898, 6308992.14194 357136.52543, 6309018.60922 357184.68892, 6309024.87557 357193.57884, 6309025.31785 357194.20629, 6309028.73486 357199.05392, 6309045.86114 357220.97586, 6309078.85225 357261.01696, 6309131.17986 357323.22098, 6309184.03434 357388.33409, 6309212.61182 357423.54026, 6309252.80543 357467.20429, 6309288.51836 357504.59499, 6309318.98068 357536.37443, 6309366.01084 357588.07961, 6309383.32941 357609.89089, 6309383.33718 357609.92579, 6309383.36584 357611.49516) + )"); + assert!(wkt.to_geo().is_ok()); } #[test] @@ -273,11 +304,21 @@ mod test { } #[test] - fn complex() { - use crate::wkt::WktStr; - let wkt = WktStr("GEOMETRYCOLLECTION (LINESTRING (6308869.40378 356821.22669, 6308867.893 356822.41744, 6308852.75314 356830.22159, 6308869.92754 356844.26638), LINESTRING (6308755.07971 356674.51686, 6308784.81355 356719.16757, 6308815.20022 356765.46178, 6308829.63774 356763.22832, 6308852.87023 356759.82402, 6308867.19982 356771.06823, 6308875.40631 356796.20162, 6308872.51907 356815.17242), LINESTRING (6308874.12086 356813.73392, 6308876.83028 356795.77697, 6308868.23871 356770.06254, 6308853.09618 356758.29456, 6308815.86529 356763.89689, 6308799.76731 356739.37835, 6308747.77971 356662.11613, 6308746.55411 356661.61702, 6308744.06545 356657.72563, 6308731.77184 356668.45076, 6308699.45221 356683.15463, 6308682.44689 356684.63193, 6308654.96629 356683.66846, 6308636.13879 356680.0482, 6308618.19888 356671.76352, 6308608.41685 356661.79428, 6308578.7973 356592.35062, 6308545.33908 356542.14886, 6308517.52088 356509.38474, 6308505.40266 356506.84141, 6308493.59689 356506.98067, 6308375.07918 356520.46209), LINESTRING (6308877.92941 356819.50984, 6309072.26249 356514.14689, 6309073.44938 356513.3739, 6309076.25423 356511.31751, 6309096.05004 356528.52014, 6309103.33938 356535.32615, 6309107.49584 356539.20699, 6309107.78601 356539.47793, 6309119.09139 356550.03322, 6309137.04465 356567.13752, 6309137.6323 356567.69515, 6309138.92096 356568.91355, 6309138.46355 356569.69798, 6309150.68532 356566.34027, 6309151.94333 356567.03108, 6309157.81557 356565.41779, 6309161.54152 356564.33408, 6309174.6464 356579.77423, 6309175.71622 356581.0361, 6309177.25892 356582.84545, 6309225.37695 356611.76515, 6309226.90588 356612.65173, 6309229.72021 356614.34101, 6309232.64678 356598.75445, 6309244.10246 356528.49893, 6309251.20809 356487.90256, 6309252.35489 356481.34967, 6309258.41778 356442.34047, 6309258.56036 356441.19511, 6309258.76115 356440.13123, 6309260.99127 356426.22389, 6309258.49745 356425.57244, 6309240.94882 356422.48836, 6309240.53276 356422.37171, 6309240.10958 356422.29068), LINESTRING (6308870.96141 356823.05522, 6308881.43519 356846.04558, 6308859.94336 356857.75024, 6308859.6305 356857.95378, 6308893.96675 356932.14467, 6308921.19517 356993.60222, 6308942.68768 357040.82051, 6308961.42173 357079.52481, 6308976.48471 357108.08898, 6308992.14194 357136.52543, 6309018.60922 357184.68892, 6309024.87557 357193.57884, 6309025.31785 357194.20629, 6309028.73486 357199.05392, 6309045.86114 357220.97586, 6309078.85225 357261.01696, 6309131.17986 357323.22098, 6309184.03434 357388.33409, 6309212.61182 357423.54026, 6309252.80543 357467.20429, 6309288.51836 357504.59499, 6309318.98068 357536.37443, 6309366.01084 357588.07961, 6309383.32941 357609.89089, 6309383.33718 357609.92579, 6309383.36584 357611.49516), POLYGON ((6309096.87876754 357058.96992573235, 6309100.9240038069 357067.89795246266, 6309103.1497403858 357077.44361610821, 6309103.4704434676 357087.24008216924, 6309101.8737886148 357096.91087794991, 6309098.421134375 357106.08436019259, 6309093.2451643161 357114.40799712023, 6309086.5447880644 357121.56191603432, 6309078.5774973193 357127.27119584358, 6309013.594489282 357164.78915304772, 6309004.6664625369 357168.8343893392, 6308995.1207988719 357171.06012593472, 6308985.3243327877 357171.38082902494, 6308975.6535369828 357169.78417417, 6308966.4800547194 357166.33151992067, 6308958.156417775 357161.1555498438, 6308951.0024988521 357154.45517356717, 6308945.293219042 357146.48788279406, 6308795.0043396624 356886.1799069175, 6308790.959103398 356877.251880196, 6308788.7333668182 356867.70621655986, 6308788.4126637317 356857.90975050797, 6308790.0093185771 356848.2389547351, 6308793.4619728047 356839.0654724979, 6308798.6379428506 356830.74183557258, 6308805.3383190883 356823.587916657, 6308813.3056098176 356817.87863684172, 6308878.2886178084 356780.36067953659, 6308887.2166445563 356776.315443229, 6308896.7623082288 356774.08970661991, 6308906.5587743223 356773.76900351944, 6308916.2295701364 356775.36565836804, 6308925.403052411 356778.81831261492, 6308933.7266893657 356783.99428269314, 6308940.8806082979 356790.69465897442, 6308946.5898881136 356798.66194975481, 6309096.87876754 357058.96992573235)))"); - - assert!(wkt.to_geo().is_ok()); + fn feature_collection() -> Result<()> { + let geojson = r#"{"type": "FeatureCollection","name": "countries","features": [ + {"type": "Feature", "properties": {"id": "ALB", "name": "Albania"}, "geometry": {"type": "Polygon", "coordinates": [[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]}}, + {"type": "Feature", "properties": {"id": "TLS", "name": "East Timor"}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[124.968682, -8.89279], [125.086246, -8.656887], [125.947072, -8.432095], [126.644704, -8.398247], [126.957243, -8.273345], [127.335928, -8.397317], [126.967992, -8.668256], [125.925885, -9.106007], [125.08852, -9.393173], [125.07002, -9.089987], [124.968682, -8.89279]]]]}} + ]}"#; + let mut geo = GeoWriter::new(); + let mut visitor = GeomVisitor::new(&mut geo); + assert!(read_geojson(geojson.as_bytes(), &mut visitor).is_ok()); + let geom = geo.take_geometry().unwrap(); + dbg!(&geom); + println!("{}", geom.to_json()?); + // TODO: we get a broken GeometryCollection + let expected = r#"{"type": "GeometryCollection", "geometries": [{"type": "Polygon", "coordinates": [[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]},{"type": "Polygon", "coordinates": [[[124.968682,-8.89279],[125.086246,-8.656887],[125.947072,-8.432095],[126.644704,-8.398247],[126.957243,-8.273345],[127.335928,-8.397317],[126.967992,-8.668256],[125.925885,-9.106007],[125.08852,-9.393173],[125.07002,-9.089987],[124.968682,-8.89279]]]},{"type": "MultiPolygon", "coordinates": []}]}"#; + assert_eq!(expected, geom.to_json()?); + Ok(()) } #[test] diff --git a/geozero/src/geo_types/mod.rs b/geozero/src/geo_types/mod.rs index 9ea1bd9b..d3efae9c 100644 --- a/geozero/src/geo_types/mod.rs +++ b/geozero/src/geo_types/mod.rs @@ -8,6 +8,7 @@ pub use geo_types_writer::*; pub(crate) mod conversion { use super::geo_types_writer::*; use crate::error::{GeozeroError, Result}; + use crate::events::GeomVisitor; use crate::GeozeroGeometry; /// Convert to geo-types Geometry. @@ -18,9 +19,11 @@ pub(crate) mod conversion { impl ToGeo for T { fn to_geo(&self) -> Result> { - let mut geo = GeoWriter::new(); - self.process_geom(&mut geo)?; - geo.take_geometry() + let mut writer = GeoWriter::new(); + let mut visitor = GeomVisitor::new(&mut writer); + self.process_geom(&mut visitor)?; + writer + .take_geometry() .ok_or(GeozeroError::Geometry("Missing Geometry".to_string())) } } @@ -30,14 +33,17 @@ pub(crate) mod conversion { mod wkb { use super::geo_types_writer::*; use crate::error::{GeozeroError, Result}; + use crate::events::GeomVisitor; use crate::wkb::{FromWkb, WkbDialect}; use std::io::Read; impl FromWkb for geo_types::Geometry { fn from_wkb(rdr: &mut R, dialect: WkbDialect) -> Result { - let mut geo = GeoWriter::new(); - crate::wkb::process_wkb_type_geom(rdr, &mut geo, dialect)?; - geo.take_geometry() + let mut writer = GeoWriter::new(); + let mut visitor = GeomVisitor::new(&mut writer); + crate::wkb::process_wkb_type_geom(rdr, &mut visitor, dialect)?; + writer + .take_geometry() .ok_or(GeozeroError::Geometry("Missing Geometry".to_string())) } } diff --git a/geozero/src/geometry_processor.rs b/geozero/src/geometry_processor.rs index 5b933df1..baa4a84d 100644 --- a/geozero/src/geometry_processor.rs +++ b/geozero/src/geometry_processor.rs @@ -1,7 +1,8 @@ use crate::error::{GeozeroError, Result}; +use crate::events::GeomVisitor; /// Dimensions requested for processing -#[derive(Default, Clone, Copy)] +#[derive(Default, Clone, Copy, Debug)] pub struct CoordDimensions { /// height pub z: bool, @@ -303,6 +304,120 @@ pub trait GeomProcessor { } } +impl GeomProcessor for GeomVisitor<'_> { + fn xy(&mut self, x: f64, y: f64, idx: usize) -> Result<()> { + self.xy(x, y, idx) + } + fn coordinate( + &mut self, + x: f64, + y: f64, + z: Option, + m: Option, + t: Option, + tm: Option, + idx: usize, + ) -> Result<()> { + self.coordinate(x, y, z, m, t, tm, idx) + } + fn empty_point(&mut self, idx: usize) -> Result<()> { + self.point_begin(idx)?; + self.empty()?; + self.point_end(idx)?; + Ok(()) + } + fn point_begin(&mut self, idx: usize) -> Result<()> { + self.point_begin(idx) + } + fn point_end(&mut self, idx: usize) -> Result<()> { + self.point_end(idx) + } + fn multipoint_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.multipoint_begin(size, idx) + } + fn multipoint_end(&mut self, idx: usize) -> Result<()> { + self.multipoint_end(idx) + } + fn linestring_begin(&mut self, _tagged: bool, size: usize, idx: usize) -> Result<()> { + self.linestring_begin(size, idx) + } + fn linestring_end(&mut self, _tagged: bool, idx: usize) -> Result<()> { + self.linestring_end(idx) + } + fn multilinestring_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.multilinestring_begin(size, idx) + } + fn multilinestring_end(&mut self, idx: usize) -> Result<()> { + self.multilinestring_end(idx) + } + fn polygon_begin(&mut self, _tagged: bool, size: usize, idx: usize) -> Result<()> { + self.polygon_begin(size, idx) + } + fn polygon_end(&mut self, _tagged: bool, idx: usize) -> Result<()> { + self.polygon_end(idx) + } + fn multipolygon_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.multipolygon_begin(size, idx) + } + fn multipolygon_end(&mut self, idx: usize) -> Result<()> { + self.multipolygon_end(idx) + } + fn geometrycollection_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.geometrycollection_begin(size, idx) + } + fn geometrycollection_end(&mut self, idx: usize) -> Result<()> { + self.geometrycollection_end(idx) + } + fn circularstring_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.circularstring_begin(size, idx) + } + fn circularstring_end(&mut self, idx: usize) -> Result<()> { + self.circularstring_end(idx) + } + fn compoundcurve_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.compoundcurve_begin(size, idx) + } + fn compoundcurve_end(&mut self, idx: usize) -> Result<()> { + self.compoundcurve_end(idx) + } + fn curvepolygon_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.curvepolygon_begin(size, idx) + } + fn curvepolygon_end(&mut self, idx: usize) -> Result<()> { + self.curvepolygon_end(idx) + } + fn multicurve_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.multicurve_begin(size, idx) + } + fn multicurve_end(&mut self, idx: usize) -> Result<()> { + self.multicurve_end(idx) + } + fn multisurface_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.multisurface_begin(size, idx) + } + fn multisurface_end(&mut self, idx: usize) -> Result<()> { + self.multisurface_end(idx) + } + fn triangle_begin(&mut self, _tagged: bool, size: usize, idx: usize) -> Result<()> { + self.triangle_begin(size, idx) + } + fn triangle_end(&mut self, _tagged: bool, idx: usize) -> Result<()> { + self.triangle_end(idx) + } + fn polyhedralsurface_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.polyhedralsurface_begin(size, idx) + } + fn polyhedralsurface_end(&mut self, idx: usize) -> Result<()> { + self.polyhedralsurface_end(idx) + } + fn tin_begin(&mut self, size: usize, idx: usize) -> Result<()> { + self.tin_begin(size, idx) + } + fn tin_end(&mut self, idx: usize) -> Result<()> { + self.tin_end(idx) + } +} + #[test] fn error_message() { use crate::error::GeozeroError; diff --git a/geozero/src/iterator.rs b/geozero/src/iterator.rs new file mode 100644 index 00000000..a3baceaa --- /dev/null +++ b/geozero/src/iterator.rs @@ -0,0 +1,225 @@ +//! Event emitting geometry iterator. +// Inspiration: https://docs.rs/lyon_path/latest/lyon_path/enum.Event.html# + +use crate::error::{GeozeroError, Result}; +use crate::events::{GeometryType, Vstate}; +use crate::CoordDimensions; + +pub struct EventIter { + visitor: T, + state: IterState, +} + +struct IterState { + /// Main geometry type + pub geom_type: GeometryType, + /// Geometry is part of collection + pub collection: bool, + // Iterator settings + iter_dims: CoordDimensions, + check_states: bool, + state_stack: Vec, + // Convert single to multi geometries, if declared as multi type or Unknown + promote_to_multi: bool, +} + +impl IterState { + fn state(&self) -> &Vstate { + let len = self.state_stack.len(); + if len > 0 { + &self.state_stack[len - 1] + } else { + &Vstate::Initial + } + } + fn enter_state(&mut self, state: Vstate) -> Result<()> { + match (self.state(), &state) { + (Vstate::Initial, Vstate::GeometryCollection) + | (Vstate::Initial, Vstate::Point) + | (Vstate::Initial, Vstate::LineString) + | (Vstate::Initial, Vstate::Polygon) + | (Vstate::Initial, Vstate::MultiPoint) + | (Vstate::Initial, Vstate::MultiLineString) + | (Vstate::Initial, Vstate::MultiPolygon) + | (Vstate::Initial, Vstate::CircularString) + | (Vstate::Initial, Vstate::CompoundCurve) + | (Vstate::Initial, Vstate::CurvePolygon) + | (Vstate::Initial, Vstate::MultiCurve) + | (Vstate::Initial, Vstate::MultiSurface) + | (Vstate::Initial, Vstate::Curve) + | (Vstate::Initial, Vstate::Surface) + | (Vstate::Initial, Vstate::PolyhedralSurface) + | (Vstate::Initial, Vstate::Tin) + | (Vstate::Initial, Vstate::Triangle) + | (Vstate::Polygon, Vstate::LineString) + | (Vstate::MultiLineString, Vstate::LineString) + | (Vstate::MultiPolygon, Vstate::Polygon) + | (Vstate::GeometryCollection, Vstate::Point) + | (Vstate::GeometryCollection, Vstate::LineString) + | (Vstate::GeometryCollection, Vstate::Polygon) + | (Vstate::GeometryCollection, Vstate::MultiPoint) + | (Vstate::GeometryCollection, Vstate::MultiLineString) + | (Vstate::GeometryCollection, Vstate::MultiPolygon) + | (Vstate::GeometryCollection, Vstate::GeometryCollection) + | (Vstate::GeometryCollection, Vstate::CircularString) + | (Vstate::GeometryCollection, Vstate::CompoundCurve) + | (Vstate::GeometryCollection, Vstate::CurvePolygon) + | (Vstate::GeometryCollection, Vstate::MultiCurve) + | (Vstate::GeometryCollection, Vstate::MultiSurface) + | (Vstate::GeometryCollection, Vstate::Curve) + | (Vstate::GeometryCollection, Vstate::Surface) + | (Vstate::GeometryCollection, Vstate::PolyhedralSurface) + | (Vstate::GeometryCollection, Vstate::Tin) + | (Vstate::GeometryCollection, Vstate::Triangle) + | (Vstate::CompoundCurve, Vstate::CircularString) + | (Vstate::CompoundCurve, Vstate::LineString) + | (Vstate::CurvePolygon, Vstate::CircularString) + | (Vstate::CurvePolygon, Vstate::LineString) + | (Vstate::CurvePolygon, Vstate::CompoundCurve) + | (Vstate::MultiCurve, Vstate::CircularString) + | (Vstate::MultiCurve, Vstate::LineString) + | (Vstate::MultiCurve, Vstate::CompoundCurve) + | (Vstate::MultiSurface, Vstate::CurvePolygon) + | (Vstate::MultiSurface, Vstate::Polygon) + | (Vstate::Triangle, Vstate::LineString) + | (Vstate::PolyhedralSurface, Vstate::Polygon) + | (Vstate::Tin, Vstate::Polygon) => { + // println!("Enter state {:?}=>{:?}", self.state(), state); + self.state_stack.push(state); + Ok(()) + } + _ => Err(GeozeroError::Geometry(format!( + "Invalid state transition from {:?} to {:?}", + self.state(), + state + ))), + } + } + fn exit_state(&mut self, state: Vstate) -> Result<()> { + let valid = if let Some(prev_state) = self.state_stack.pop() { + state == prev_state + } else { + false + }; + if valid { + // println!( + // "Exit state {:?} (GeometryType::{:?})=>{:?}", + // &self.state_stack, + // &self.geom_type, + // self.state() + // ); + } else { + return Err(GeozeroError::Geometry(format!( + "Invalid state transition from {:?} to {:?}", + self.state_stack, state + ))); + } + Ok(()) + } + fn set_type(&mut self, inner_type: GeometryType) -> Result<()> { + if self.geom_type == GeometryType::Unknown { + // println!("Set GeometryType {:?} => {:?}", &self.geom_type, inner_type); + self.geom_type = inner_type; + } + Ok(()) + } + fn reset_type(&mut self, inner_type: GeometryType) { + // Reset geometry type within collections + if self.collection && self.geom_type == inner_type { + self.geom_type = GeometryType::Unknown; + } + } +} + +impl EventIter { + // Default iterator (XY) + pub fn new(visitor: T) -> EventIter { + Self::new_with_config(visitor, CoordDimensions::xy(), true, false) + } + pub fn new_with_config( + visitor: T, + dims: CoordDimensions, + check_states: bool, + promote_to_multi: bool, + ) -> EventIter { + EventIter:: { + visitor, + state: IterState { + geom_type: GeometryType::Unknown, + collection: false, + iter_dims: dims, + check_states, + state_stack: Vec::new(), + promote_to_multi, + }, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::events::Event; + use std::io::{BufWriter, Write}; + + pub struct NullIsland { + events: Vec, + } + + impl NullIsland { + pub fn new() -> Self { + Self { + events: vec![ + Event::PointBegin(1), + Event::Xy(0.0, 0.0, 0), + Event::PointEnd(1), + ], + } + } + } + + pub struct NullIslandVisitor<'a> { + events: std::slice::Iter<'a, Event>, + } + + impl<'a> NullIslandVisitor<'a> { + pub fn new(geom: &'a NullIsland) -> Self { + NullIslandVisitor { + events: geom.events.iter(), + } + } + } + + // Implement reader for NullIsland type + impl<'a> Iterator for EventIter> { + type Item = Result<&'a Event>; + fn next(&mut self) -> Option { + // We have access to self.visitor and self.state + if self.state.geom_type == GeometryType::Unknown { + self.state.set_type(GeometryType::Point).unwrap(); //TODO: propagate errors + } + // we should also call enter_state/exit_state to ensure + // valid state transitions. + self.visitor.events.next().map(|ev| Ok(ev)) + } + } + + #[test] + fn null_island_to_wkt() -> std::io::Result<()> { + let mut out = BufWriter::new(Vec::new()); + let geom = NullIsland::new(); + let visitor = NullIslandVisitor::new(&geom); + for (i, event) in EventIter::new(visitor).enumerate() { + let event = event.unwrap(); + assert_eq!(*event, geom.events[i]); + let _ = match event { + Event::PointBegin(_) => out.write(b"POINT(")?, + Event::Xy(x, y, _) => out.write(&format!("{x} {y}").as_bytes())?, + Event::PointEnd(_) => out.write(b")")?, + _ => 0, + }; + } + assert_eq!(std::str::from_utf8(&out.into_inner()?), Ok("POINT(0 0)")); + Ok(()) + } +} diff --git a/geozero/src/lib.rs b/geozero/src/lib.rs index 4a8c35f9..fdd44d27 100644 --- a/geozero/src/lib.rs +++ b/geozero/src/lib.rs @@ -29,10 +29,14 @@ //! | WKT | [wkt::WktStr], [wkt::WktString] | XYZM | [wkt::WktReader], [wkt::WktStr], [wkt::WktString] | [ToWkt] | [WktWriter](wkt::WktWriter) | mod api; +pub mod chaining; pub mod error; +pub mod events; mod feature_processor; mod geometry_processor; +pub mod iterator; mod multiplex; +pub mod processor; mod property_processor; pub use api::*; diff --git a/geozero/src/processor/bbox.rs b/geozero/src/processor/bbox.rs new file mode 100644 index 00000000..0978fd98 --- /dev/null +++ b/geozero/src/processor/bbox.rs @@ -0,0 +1,187 @@ +use crate::error::Result; +use crate::events::Event::*; +use crate::events::{Event, GeomEventProcessor, GeometryType}; + +#[derive(Clone, PartialEq, Debug)] +/// Bounding Box +pub struct Bbox { + pub min_x: f64, + pub min_y: f64, + pub max_x: f64, + pub max_y: f64, +} + +impl Default for Bbox { + fn default() -> Self { + Bbox { + min_x: f64::INFINITY, + min_y: f64::INFINITY, + max_x: f64::NEG_INFINITY, + max_y: f64::NEG_INFINITY, + } + } +} + +impl Bbox { + pub fn new(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Bbox { + Bbox { + min_x, + min_y, + max_x, + max_y, + } + } + + #[inline] + pub fn reset(&mut self) { + self.min_x = f64::INFINITY; + self.min_y = f64::INFINITY; + self.max_x = f64::NEG_INFINITY; + self.max_y = f64::NEG_INFINITY; + } + + pub fn width(&self) -> f64 { + self.max_x - self.min_x + } + + pub fn height(&self) -> f64 { + self.max_y - self.min_y + } + + pub fn sum(mut a: Bbox, b: &Bbox) -> Bbox { + a.expand(b); + a + } + + #[inline] + pub fn expand(&mut self, r: &Bbox) { + if r.min_x < self.min_x { + self.min_x = r.min_x; + } + if r.min_y < self.min_y { + self.min_y = r.min_y; + } + if r.max_x > self.max_x { + self.max_x = r.max_x; + } + if r.max_y > self.max_y { + self.max_y = r.max_y; + } + } + + #[inline] + pub fn expand_xy(&mut self, x: f64, y: f64) { + if x < self.min_x { + self.min_x = x; + } + if y < self.min_y { + self.min_y = y; + } + if x > self.max_x { + self.max_x = x; + } + if y > self.max_y { + self.max_y = y; + } + } + + pub fn intersects(&self, r: &Bbox) -> bool { + if self.max_x < r.min_x { + return false; + } + if self.max_y < r.min_y { + return false; + } + if self.min_x > r.max_x { + return false; + } + if self.min_y > r.max_y { + return false; + } + true + } +} + +impl GeomEventProcessor for Bbox { + fn event(&mut self, event: &Event, geom_type: GeometryType, collection: bool) -> Result<()> { + match *event { + Xy(x, y, _idx) => { + self.expand_xy(x, y); + } + Coordinate(x, y, _z, _m, _t, _tm, _idx) => { + self.expand_xy(x, y); + } + MultiPointBegin(_, _) + | MultiLineStringBegin(_, _) + | MultiPolygonBegin(_, _) + | GeometryCollectionBegin(_, _) + | CircularStringBegin(_, _) + | CompoundCurveBegin(_, _) + | CurvePolygonBegin(_, _) + | MultiCurveBegin(_, _) + | MultiSurfaceBegin(_, _) + | PolyhedralSurfaceBegin(_, _) + | TinBegin(_, _) + if !collection => + { + self.reset(); + } + PointBegin(_) if !collection => { + self.reset(); + } + LineStringBegin(_, _) if !collection => { + if geom_type == GeometryType::LineString { + self.reset(); + } + } + PolygonBegin(_, _) if !collection => { + if geom_type == GeometryType::Polygon { + self.reset(); + } + } + TriangleBegin(_, _) if !collection => { + if geom_type == GeometryType::Triangle { + self.reset(); + } + } + _ => {} + } + Ok(()) + } +} + +#[cfg(test)] +#[cfg(feature = "with-geojson")] +mod test { + use super::*; + use crate::api::GeozeroGeometry; + use crate::events::GeomVisitor; + use crate::geojson::GeoJson; + + #[test] + fn polygon() -> Result<()> { + let geojson = GeoJson( + r#"{"type": "Polygon", "coordinates": [[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]}"#, + ); + let mut processor = Bbox::default(); + let mut visitor = GeomVisitor::new(&mut processor); + geojson.process_geom(&mut visitor)?; + assert_eq!( + processor, + Bbox::new(19.304486, 39.624998, 21.02004, 42.688247) + ); + Ok(()) + } + + #[test] + fn geomcollection() -> Result<()> { + let geojson = GeoJson( + r#"{"type": "GeometryCollection", "geometries": [{"type": "Point", "coordinates": [100.1,0.1]},{"type": "LineString", "coordinates": [[101.1,0.1],[102.1,1.1]]}]}"#, + ); + let mut processor = Bbox::default(); + let mut visitor = GeomVisitor::new(&mut processor); + geojson.process_geom(&mut visitor)?; + assert_eq!(processor, Bbox::new(100.1, 0.1, 102.1, 1.1)); + Ok(()) + } +} diff --git a/geozero/src/processor/mod.rs b/geozero/src/processor/mod.rs new file mode 100644 index 00000000..0666cd6b --- /dev/null +++ b/geozero/src/processor/mod.rs @@ -0,0 +1,9 @@ +mod bbox; +mod promote_to_multi; +mod reprojection; +mod sink; + +pub use bbox::*; +pub use promote_to_multi::*; +pub use reprojection::*; +pub use sink::*; diff --git a/geozero/src/processor/promote_to_multi.rs b/geozero/src/processor/promote_to_multi.rs new file mode 100644 index 00000000..295a39f0 --- /dev/null +++ b/geozero/src/processor/promote_to_multi.rs @@ -0,0 +1,88 @@ +use crate::chaining::ChainedGeomEventProcessor; +use crate::error::Result; +use crate::events::Event::*; +use crate::events::{Event, GeomVisitor, GeometryType}; + +/// Convert single geometry types to multi geometry types +pub struct PromoteToMulti; + +impl ChainedGeomEventProcessor for PromoteToMulti { + fn chain_event( + &mut self, + event: &Event, + geom_type: GeometryType, + _collection: bool, + visitor: &mut GeomVisitor, + ) -> Result<()> { + match *event { + PointBegin(idx) => { + visitor.multipoint_begin(1, idx)?; + } + PointEnd(idx) => { + visitor.multipoint_end(idx)?; + } + LineStringBegin(size, idx) if geom_type == GeometryType::LineString => { + visitor.multilinestring_begin(1, idx)?; + visitor.linestring_begin(size, 0)?; + } + LineStringEnd(idx) if geom_type == GeometryType::LineString => { + visitor.linestring_end(0)?; + visitor.multilinestring_end(idx)?; + } + PolygonBegin(size, idx) if geom_type == GeometryType::Polygon => { + visitor.multipolygon_begin(1, idx)?; + visitor.polygon_begin(size, 0)?; + } + PolygonEnd(idx) if geom_type == GeometryType::Polygon => { + visitor.polygon_end(0)?; + visitor.multipolygon_end(idx)?; + } + _ => visitor.emit_event(event)?, + } + Ok(()) + } +} + +#[cfg(test)] +#[cfg(all(feature = "with-geojson", feature = "with-geo"))] +mod test { + use super::*; + use crate::api::GeozeroGeometry; + use crate::chaining::ChainedProcessor; + use crate::events::GeomVisitor; + use crate::geo_types::GeoWriter; + use crate::geojson::conversion::ToJson; + use crate::geojson::GeoJson; + + #[test] + fn polygon() -> Result<()> { + let geojson = GeoJson( + r#"{"type": "Polygon", "coordinates": [[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]}"#, + ); + let mut processor1 = PromoteToMulti; + let mut processor2 = GeoWriter::new(); // TODO: Json writer + let mut processor = ChainedProcessor::new(&mut processor1, &mut processor2); + let mut visitor = GeomVisitor::new(&mut processor); + geojson.process_geom(&mut visitor)?; + let geom = processor2.take_geometry().unwrap(); + let expected = r#"{"type": "MultiPolygon", "coordinates": [[[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]]}"#; + assert_eq!(expected, geom.to_json()?); + Ok(()) + } + + #[test] + fn geomcollection() -> Result<()> { + let geojson = GeoJson( + r#"{"type": "GeometryCollection", "geometries": [{"type": "Point", "coordinates": [100.1,0.1]},{"type": "LineString", "coordinates": [[101.1,0.1],[102.1,1.1]]}]}"#, + ); + let mut processor1 = PromoteToMulti; + let mut processor2 = GeoWriter::new(); // TODO: Json writer + let mut processor = ChainedProcessor::new(&mut processor1, &mut processor2); + let mut visitor = GeomVisitor::new(&mut processor); + geojson.process_geom(&mut visitor)?; + let geom = processor2.take_geometry().unwrap(); + let expected = r#"{"type": "GeometryCollection", "geometries": [{"type": "MultiPoint", "coordinates": [[100.1,0.1]]},{"type": "MultiLineString", "coordinates": [[[101.1,0.1],[102.1,1.1]]]}]}"#; + assert_eq!(expected, geom.to_json()?); + Ok(()) + } +} diff --git a/geozero/src/processor/reprojection.rs b/geozero/src/processor/reprojection.rs new file mode 100644 index 00000000..387d0e18 --- /dev/null +++ b/geozero/src/processor/reprojection.rs @@ -0,0 +1,65 @@ +use crate::chaining::ChainedGeomEventProcessor; +use crate::error::Result; +use crate::events::Event::*; +use crate::events::{Event, GeomVisitor, GeometryType}; +use std::f64::consts; + +/// Convert lon/lat to Spherical Mercator in meters +pub struct LonLatToMercator; + +fn lonlat_to_merc(lon: f64, lat: f64) -> (f64, f64) { + let x = 6378137.0 * lon.to_radians(); + let y = 6378137.0 * ((consts::PI * 0.25) + (0.5 * lat.to_radians())).tan().ln(); + (x, y) +} + +impl ChainedGeomEventProcessor for LonLatToMercator { + fn chain_event( + &mut self, + event: &Event, + geom_type: GeometryType, + collection: bool, + visitor: &mut GeomVisitor, + ) -> Result<()> { + match *event { + Xy(x, y, idx) => { + let (x, y) = lonlat_to_merc(x, y); + visitor.chain_event(&Xy(x, y, idx), geom_type, collection)?; + } + Coordinate(x, y, z, m, t, tm, idx) => { + let (x, y) = lonlat_to_merc(x, y); + visitor.chain_event(&Coordinate(x, y, z, m, t, tm, idx), geom_type, collection)?; + } + _ => visitor.chain_event(event, geom_type, collection)?, + } + Ok(()) + } +} + +#[cfg(test)] +#[cfg(all(feature = "with-geojson", feature = "with-geo"))] +mod test { + use super::*; + use crate::api::GeozeroGeometry; + use crate::chaining::ChainedProcessor; + use crate::events::GeomVisitor; + use crate::geo_types::GeoWriter; + use crate::geojson::conversion::ToJson; + use crate::geojson::GeoJson; + + #[test] + fn polygon() -> Result<()> { + let geojson = GeoJson( + r#"{"type": "Polygon", "coordinates": [[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]}"#, + ); + let mut processor1 = LonLatToMercator; + let mut processor2 = GeoWriter::new(); // TODO: Json writer + let mut processor = ChainedProcessor::new(&mut processor1, &mut processor2); + let mut visitor = GeomVisitor::new(&mut processor); + geojson.process_geom(&mut visitor)?; + let geom = processor2.take_geometry().unwrap(); + let expected = r#"{"type": "Polygon", "coordinates": [[[2292095.811347729,5139344.213425913],[2277950.2210136456,5088616.6330585545],[2293758.3679427262,5025068.310371113],[2339940.1492542424,4989171.535691388],[2337708.193463837,4950588.339806374],[2301530.138192459,4929358.117761691],[2294851.3027033345,4881941.097754033],[2243089.5205963156,4811596.717986056],[2224163.426049606,4821717.98297803],[2221937.2588727223,4853598.859120461],[2160275.1665325123,4902451.127115252],[2150587.810485209,4972191.022999316],[2159993.3055818235,5072941.548683907],[2175185.8557268167,5119126.5562673],[2156455.4608449223,5142654.340537157],[2148965.551545879,5190346.344385106],[2197229.7865716643,5264639.607013478],[2204305.476045466,5236187.825747299],[2234260.1038645557,5249565.284016839],[2257977.2779755164,5209074.219280535],[2284604.3435758143,5193671.390117393],[2292095.811347729,5139344.213425913]]]}"#; + assert_eq!(expected, geom.to_json()?); + Ok(()) + } +} diff --git a/geozero/src/processor/sink.rs b/geozero/src/processor/sink.rs new file mode 100644 index 00000000..55300cc2 --- /dev/null +++ b/geozero/src/processor/sink.rs @@ -0,0 +1,11 @@ +use crate::error::Result; +use crate::events::{Event, GeomEventProcessor, GeometryType}; + +/// Geometry processor without any actions +pub struct GeomEventSink; + +impl GeomEventProcessor for GeomEventSink { + fn event(&mut self, _event: &Event, _geom_type: GeometryType, _collection: bool) -> Result<()> { + Ok(()) + } +}