diff --git a/geo-types/CHANGES.md b/geo-types/CHANGES.md index 064eb9f737..84e192f59e 100644 --- a/geo-types/CHANGES.md +++ b/geo-types/CHANGES.md @@ -2,10 +2,12 @@ ## Unreleased +* BREAKING: Make `Rect::to_lines` return lines in winding order for `Rect::to_polygon`. + * * Note: All crates have been migrated to Rust 2021 edition. The MSRV when installing the latest dependencies has increased to 1.56. * * Macros `coord!`, `point!`, `line_string!`, and `polygon!` now support trailing commas such as `coord! { x: 181.2, y: 51.79, }` - * + * ## 0.7.3 diff --git a/geo-types/src/rect.rs b/geo-types/src/rect.rs index 6101c1f564..568c1d9ddf 100644 --- a/geo-types/src/rect.rs +++ b/geo-types/src/rect.rs @@ -248,8 +248,8 @@ impl Rect { ), Line::new( coord! { - x: self.min.x, - y: self.min.y, + x: self.max.x, + y: self.max.y, }, coord! { x: self.max.x, @@ -262,8 +262,8 @@ impl Rect { y: self.min.y, }, coord! { - x: self.max.x, - y: self.max.y, + x: self.min.x, + y: self.min.y, }, ), ] diff --git a/geo/CHANGES.md b/geo/CHANGES.md index cbcf541388..aeb173c494 100644 --- a/geo/CHANGES.md +++ b/geo/CHANGES.md @@ -2,6 +2,9 @@ ## Unreleased +* Add `LinesIter` algorithm to iterate over the lines in geometries. + * Very similar to `CoordsIter`, but only implemented where it makes sense (e.g., for `Polygon`, `Rect`, but not `Point`). + * * Add `Default` constraint to all `CoordNum`/`CoordFloat` values. ## 0.19.0 diff --git a/geo/src/algorithm/lines_iter.rs b/geo/src/algorithm/lines_iter.rs new file mode 100644 index 0000000000..8f3d2374fa --- /dev/null +++ b/geo/src/algorithm/lines_iter.rs @@ -0,0 +1,303 @@ +use crate::{ + CoordNum, Coordinate, Line, LineString, MultiLineString, MultiPolygon, Polygon, Rect, Triangle, +}; +use core::slice; +use std::fmt::Debug; +use std::iter; + +/// Iterate over lines of a geometry. +pub trait LinesIter<'a> { + type Scalar: CoordNum; + type Iter: Iterator>; + + /// Iterate over all exterior and (if any) interior lines of a geometry. + /// + /// # Examples + /// + /// ``` + /// use geo::line_string; + /// use geo::lines_iter::LinesIter; + /// use geo::{Coordinate, Line}; + /// + /// let ls = line_string![ + /// (x: 1., y: 2.), + /// (x: 23., y: 82.), + /// (x: -1., y: 0.), + /// ]; + /// + /// let mut iter = ls.lines_iter(); + /// assert_eq!( + /// Some(Line::new( + /// Coordinate { x: 1., y: 2. }, + /// Coordinate { x: 23., y: 82. } + /// )), + /// iter.next() + /// ); + /// assert_eq!( + /// Some(Line::new( + /// Coordinate { x: 23., y: 82. }, + /// Coordinate { x: -1., y: 0. } + /// )), + /// iter.next() + /// ); + /// assert_eq!(None, iter.next()); + /// ``` + fn lines_iter(&'a self) -> Self::Iter; +} + +impl<'a, T: CoordNum + 'a> LinesIter<'a> for Line { + type Scalar = T; + type Iter = iter::Copied>>; + + fn lines_iter(&'a self) -> Self::Iter { + iter::once(self).copied() + } +} + +impl<'a, T: CoordNum + 'a> LinesIter<'a> for LineString { + type Scalar = T; + type Iter = LineStringIter<'a, Self::Scalar>; + + fn lines_iter(&'a self) -> Self::Iter { + LineStringIter::new(self) + } +} + +/// Iterator over lines in a [LineString]. +#[derive(Debug)] +pub struct LineStringIter<'a, T: CoordNum>(slice::Windows<'a, Coordinate>); + +impl<'a, T: CoordNum> LineStringIter<'a, T> { + fn new(line_string: &'a LineString) -> Self { + Self(line_string.0.windows(2)) + } +} + +impl<'a, T: CoordNum> Iterator for LineStringIter<'a, T> { + type Item = Line; + + fn next(&mut self) -> Option { + // Can't use LineString::lines() because it returns an `impl Trait` + // and there is no way to name that type in `LinesIter::Iter` until [RFC 2071] is stabilized. + // + // [RFC 2071]: https://rust-lang.github.io/rfcs/2071-impl-trait-existential-types.html + self.0.next().map(|w| { + // SAFETY: slice::windows(2) is guaranteed to yield a slice with exactly 2 elements + unsafe { Line::new(*w.get_unchecked(0), *w.get_unchecked(1)) } + }) + } +} + +type MultiLineStringIter<'a, T> = + iter::Flatten>, LineString>>; + +impl<'a, T: CoordNum + 'a> LinesIter<'a> for MultiLineString { + type Scalar = T; + type Iter = MultiLineStringIter<'a, Self::Scalar>; + + fn lines_iter(&'a self) -> Self::Iter { + MapLinesIter(self.0.iter()).flatten() + } +} + +type PolygonIter<'a, T> = iter::Chain< + LineStringIter<'a, T>, + iter::Flatten>, LineString>>, +>; + +impl<'a, T: CoordNum + 'a> LinesIter<'a> for Polygon { + type Scalar = T; + type Iter = PolygonIter<'a, Self::Scalar>; + + fn lines_iter(&'a self) -> Self::Iter { + self.exterior() + .lines_iter() + .chain(MapLinesIter(self.interiors().iter()).flatten()) + } +} + +type MultiPolygonIter<'a, T> = + iter::Flatten>, Polygon>>; + +impl<'a, T: CoordNum + 'a> LinesIter<'a> for MultiPolygon { + type Scalar = T; + type Iter = MultiPolygonIter<'a, Self::Scalar>; + + fn lines_iter(&'a self) -> Self::Iter { + MapLinesIter(self.0.iter()).flatten() + } +} + +impl<'a, T: CoordNum + 'a> LinesIter<'a> for Rect { + type Scalar = T; + type Iter = <[Line; 4] as IntoIterator>::IntoIter; + + fn lines_iter(&'a self) -> Self::Iter { + self.to_lines().into_iter() + } +} + +impl<'a, T: CoordNum + 'a> LinesIter<'a> for Triangle { + type Scalar = T; + type Iter = <[Line; 3] as IntoIterator>::IntoIter; + + fn lines_iter(&'a self) -> Self::Iter { + self.to_lines().into_iter() + } +} + +/// Utility to transform Iterator into Iterator>. +#[derive(Debug)] +pub struct MapLinesIter<'a, Iter1: Iterator, Iter2: 'a + LinesIter<'a>>(Iter1); + +impl<'a, Iter1: Iterator, Iter2: LinesIter<'a>> Iterator + for MapLinesIter<'a, Iter1, Iter2> +{ + type Item = Iter2::Iter; + + fn next(&mut self) -> Option { + self.0.next().map(|g| g.lines_iter()) + } +} + +#[cfg(test)] +mod test { + + use super::LinesIter; + use crate::{ + line_string, polygon, Coordinate, Line, LineString, MultiLineString, MultiPolygon, Rect, + Triangle, + }; + + #[test] + fn test_line() { + let line = Line::new(Coordinate { x: 0., y: 0. }, Coordinate { x: 5., y: 10. }); + let want = vec![Line::new( + Coordinate { x: 0., y: 0. }, + Coordinate { x: 5., y: 10. }, + )]; + assert_eq!(want, line.lines_iter().collect::>()); + } + + #[test] + fn test_empty_line_string() { + let ls: LineString = line_string![]; + assert_eq!(Vec::>::new(), ls.lines_iter().collect::>()); + } + + #[test] + fn test_open_line_string() { + let ls = line_string![(x: 0., y: 0.), (x: 1., y: 1.), (x:2., y: 2.)]; + let want = vec![ + Line::new(Coordinate { x: 0., y: 0. }, Coordinate { x: 1., y: 1. }), + Line::new(Coordinate { x: 1., y: 1. }, Coordinate { x: 2., y: 2. }), + ]; + assert_eq!(want, ls.lines_iter().collect::>()); + } + + #[test] + fn test_closed_line_string() { + let mut ls = line_string![(x: 0., y: 0.), (x: 1., y: 1.), (x:2., y: 2.)]; + ls.close(); + let want = vec![ + Line::new(Coordinate { x: 0., y: 0. }, Coordinate { x: 1., y: 1. }), + Line::new(Coordinate { x: 1., y: 1. }, Coordinate { x: 2., y: 2. }), + Line::new(Coordinate { x: 2., y: 2. }, Coordinate { x: 0., y: 0. }), + ]; + assert_eq!(want, ls.lines_iter().collect::>()); + } + + #[test] + fn test_multi_line_string() { + let mls = MultiLineString(vec![ + line_string![], + line_string![(x: 0., y: 0.), (x: 1., y: 1.)], + line_string![(x: 0., y: 0.), (x: 1., y: 1.), (x:2., y: 2.)], + ]); + let want = vec![ + Line::new(Coordinate { x: 0., y: 0. }, Coordinate { x: 1., y: 1. }), + Line::new(Coordinate { x: 0., y: 0. }, Coordinate { x: 1., y: 1. }), + Line::new(Coordinate { x: 1., y: 1. }, Coordinate { x: 2., y: 2. }), + ]; + assert_eq!(want, mls.lines_iter().collect::>()); + } + + #[test] + fn test_polygon() { + let p = polygon!( + exterior: [(x: 0., y: 0.), (x: 0., y: 10.), (x: 10., y: 10.), (x: 10., y: 0.)], + interiors: [ + [(x: 1., y: 1.), (x: 1., y: 2.), (x: 2., y: 2.), (x: 2., y: 1.)], + [(x: 3., y: 3.), (x: 5., y: 3.), (x: 5., y: 5.), (x: 3., y: 5.)], + ], + ); + let want = vec![ + // exterior ring + Line::new(Coordinate { x: 0., y: 0. }, Coordinate { x: 0., y: 10. }), + Line::new(Coordinate { x: 0., y: 10. }, Coordinate { x: 10., y: 10. }), + Line::new(Coordinate { x: 10., y: 10. }, Coordinate { x: 10., y: 0. }), + Line::new(Coordinate { x: 10., y: 0. }, Coordinate { x: 0., y: 0. }), + // first interior ring + Line::new(Coordinate { x: 1., y: 1. }, Coordinate { x: 1., y: 2. }), + Line::new(Coordinate { x: 1., y: 2. }, Coordinate { x: 2., y: 2. }), + Line::new(Coordinate { x: 2., y: 2. }, Coordinate { x: 2., y: 1. }), + Line::new(Coordinate { x: 2., y: 1. }, Coordinate { x: 1., y: 1. }), + // second interior ring + Line::new(Coordinate { x: 3., y: 3. }, Coordinate { x: 5., y: 3. }), + Line::new(Coordinate { x: 5., y: 3. }, Coordinate { x: 5., y: 5. }), + Line::new(Coordinate { x: 5., y: 5. }, Coordinate { x: 3., y: 5. }), + Line::new(Coordinate { x: 3., y: 5. }, Coordinate { x: 3., y: 3. }), + ]; + assert_eq!(want, p.lines_iter().collect::>()); + } + + #[test] + fn test_multi_polygon() { + let mp = MultiPolygon(vec![ + polygon!( + exterior: [(x: 0., y: 0.), (x: 0., y: 10.), (x: 10., y: 10.), (x: 10., y: 0.)], + interiors: [[(x: 1., y: 1.), (x: 1., y: 2.), (x: 2., y: 2.), (x: 2., y: 1.)]], + ), + polygon!( + exterior: [(x: 3., y: 3.), (x: 5., y: 3.), (x: 5., y: 5.), (x: 3., y: 5.)], + interiors: [], + ), + ]); + let want = vec![ + // first polygon - exterior ring + Line::new(Coordinate { x: 0., y: 0. }, Coordinate { x: 0., y: 10. }), + Line::new(Coordinate { x: 0., y: 10. }, Coordinate { x: 10., y: 10. }), + Line::new(Coordinate { x: 10., y: 10. }, Coordinate { x: 10., y: 0. }), + Line::new(Coordinate { x: 10., y: 0. }, Coordinate { x: 0., y: 0. }), + // first polygon - interior ring + Line::new(Coordinate { x: 1., y: 1. }, Coordinate { x: 1., y: 2. }), + Line::new(Coordinate { x: 1., y: 2. }, Coordinate { x: 2., y: 2. }), + Line::new(Coordinate { x: 2., y: 2. }, Coordinate { x: 2., y: 1. }), + Line::new(Coordinate { x: 2., y: 1. }, Coordinate { x: 1., y: 1. }), + // second polygon - exterior ring + Line::new(Coordinate { x: 3., y: 3. }, Coordinate { x: 5., y: 3. }), + Line::new(Coordinate { x: 5., y: 3. }, Coordinate { x: 5., y: 5. }), + Line::new(Coordinate { x: 5., y: 5. }, Coordinate { x: 3., y: 5. }), + Line::new(Coordinate { x: 3., y: 5. }, Coordinate { x: 3., y: 3. }), + ]; + assert_eq!(want, mp.lines_iter().collect::>()); + } + + #[test] + fn test_rect() { + let rect = Rect::new(Coordinate { x: 0., y: 0. }, Coordinate { x: 1., y: 2. }); + let want = rect.to_polygon().lines_iter().collect::>(); + assert_eq!(want, rect.lines_iter().collect::>()); + } + + #[test] + fn test_triangle() { + let triangle = Triangle( + Coordinate { x: 0., y: 0. }, + Coordinate { x: 1., y: 2. }, + Coordinate { x: 2., y: 3. }, + ); + let want = triangle.to_polygon().lines_iter().collect::>(); + assert_eq!(want, triangle.lines_iter().collect::>()); + } +} diff --git a/geo/src/algorithm/mod.rs b/geo/src/algorithm/mod.rs index e7eb41c5ab..ce93bbcbc7 100644 --- a/geo/src/algorithm/mod.rs +++ b/geo/src/algorithm/mod.rs @@ -61,6 +61,8 @@ pub mod line_interpolate_point; pub mod line_intersection; /// Locate a point along a `Line` or `LineString`. pub mod line_locate_point; +/// Iterate over the lines in a geometry. +pub mod lines_iter; /// Apply a function to all `Coordinates` of a `Geometry`. pub mod map_coords; /// Orient a `Polygon`'s exterior and interior rings. diff --git a/geo/src/lib.rs b/geo/src/lib.rs index 11e5defb53..431b0796e3 100644 --- a/geo/src/lib.rs +++ b/geo/src/lib.rs @@ -103,6 +103,7 @@ //! coordinates in a geometry in-place //! - **[`TryMapCoords`](algorithm::map_coords::TryMapCoords)**: Map a fallible function over all //! the coordinates in a geometry, returning a new geometry wrapped in a `Result` +//! - **[`LinesIter`](algorithm::lines_iter::LinesIter)**: Iterate over lines of a geometry //! //! ## Boundary //!