diff --git a/geo-types/CHANGES.md b/geo-types/CHANGES.md index 87ff7f00e..4f762ccc9 100644 --- a/geo-types/CHANGES.md +++ b/geo-types/CHANGES.md @@ -7,6 +7,9 @@ * `Geometry` and `GeometryCollection` now support serde. * +* add `Coordinate` iterators to LineString, regularise its iterator methods, and refactor its docs + * https://github.com/georust/geo/pull/705 + ## 0.7.2 * Implement `RelativeEq` and `AbsDiffEq` for fuzzy comparison of remaining Geometry Types diff --git a/geo-types/src/line_string.rs b/geo-types/src/line_string.rs index d3f6d0429..46f385010 100644 --- a/geo-types/src/line_string.rs +++ b/geo-types/src/line_string.rs @@ -10,28 +10,27 @@ use std::ops::{Index, IndexMut}; /// /// # Semantics /// -/// A `LineString` is _closed_ if it is empty, or if the -/// first and last coordinates are the same. The _boundary_ -/// of a `LineString` is empty if closed, and otherwise the -/// end points. The _interior_ is the (infinite) set of all -/// points along the linestring _not including_ the -/// boundary. A `LineString` is _simple_ if it does not -/// intersect except possibly at the first and last -/// coordinates. A simple and closed `LineString` is a -/// `LinearRing` as defined in the OGC-SFA (but is not a -/// separate type here). +/// 1. A [`LineString`] is _closed_ if it is empty, **or** if the first and last coordinates are the same. +/// 2. The _boundary_ of a [`LineString`] is either: +/// - **empty** if it is _closed_ (see **1**) **or** +/// - contains the **start** and **end** coordinates. +/// 3. The _interior_ is the (infinite) set of all coordinates along the [`LineString`], _not including_ the boundary. +/// 4. A [`LineString`] is _simple_ if it does not intersect except **optionally** at the first and last coordinates (in which case it is also _closed_, see **1**). +/// 5. A _simple_ **and** _closed_ [`LineString`] is a `LinearRing` as defined in the OGC-SFA (but is not defined as a separate type in this crate). /// /// # Validity /// -/// A `LineString` is valid if it is either empty or -/// contains 2 or more coordinates. Further, a closed -/// `LineString` must not self intersect. Note that the -/// validity is not enforced, and the operations and -/// predicates are undefined on invalid linestrings. +/// A [`LineString`] is valid if it is either empty or +/// contains 2 or more coordinates. +/// +/// Further, a closed [`LineString`] **must not** self-intersect. Note that its +/// validity is **not** enforced, and operations and +/// predicates are **undefined** on invalid `LineString`s. /// /// # Examples +/// ## Creation /// -/// Create a `LineString` by calling it directly: +/// Create a [`LineString`] by calling it directly: /// /// ``` /// use geo_types::{Coordinate, LineString}; @@ -42,7 +41,7 @@ use std::ops::{Index, IndexMut}; /// ]); /// ``` /// -/// Create a `LineString` with the [`line_string!`] macro: +/// Create a [`LineString`] with the [`line_string!`] macro: /// /// ``` /// use geo_types::line_string; @@ -53,7 +52,7 @@ use std::ops::{Index, IndexMut}; /// ]; /// ``` /// -/// Converting a `Vec` of `Coordinate`-like things: +/// By converting from a [`Vec`] of coordinate-like things: /// /// ``` /// use geo_types::LineString; @@ -67,7 +66,7 @@ use std::ops::{Index, IndexMut}; /// let line_string: LineString = vec![[0., 0.], [10., 0.]].into(); /// ``` // -/// Or `collect`ing from a `Coordinate` iterator +/// Or by `collect`ing from a [`Coordinate`] iterator /// /// ``` /// use geo_types::{Coordinate, LineString}; @@ -78,7 +77,8 @@ use std::ops::{Index, IndexMut}; /// let line_string: LineString = coords_iter.collect(); /// ``` /// -/// You can iterate over the coordinates in the `LineString`: +/// ## Iteration +/// [`LineString`] provides five iterators: [`coords`](LineString::coords), [`coords_mut`](LineString::coords_mut), [`points`](LineString::points), [`lines`](LineString::lines), and [`triangles`](LineString::triangles): /// /// ``` /// use geo_types::{Coordinate, LineString}; @@ -88,12 +88,14 @@ use std::ops::{Index, IndexMut}; /// Coordinate { x: 10., y: 0. }, /// ]); /// -/// for coord in line_string { -/// println!("Coordinate x = {}, y = {}", coord.x, coord.y); +/// line_string.coords().for_each(|coord| println!("{:?}", coord)); +/// +/// for point in line_string.points() { +/// println!("Point x = {}, y = {}", point.x(), point.y()); /// } /// ``` /// -/// You can also iterate over the coordinates in the `LineString` as `Point`s: +/// Note that its [`IntoIterator`] impl yields [`Coordinate`]s when looping: /// /// ``` /// use geo_types::{Coordinate, LineString}; @@ -103,9 +105,29 @@ use std::ops::{Index, IndexMut}; /// Coordinate { x: 10., y: 0. }, /// ]); /// -/// for point in line_string.points_iter() { -/// println!("Point x = {}, y = {}", point.x(), point.y()); +/// for coord in &line_string { +/// println!("Coordinate x = {}, y = {}", coord.x, coord.y); /// } +/// +/// for coord in line_string { +/// println!("Coordinate x = {}, y = {}", coord.x, coord.y); +/// } +/// +/// ``` +/// ## Decomposition +/// +/// You can decompose a [`LineString`] into a [`Vec`] of [`Coordinate`]s or [`Point`]s: +/// ``` +/// use geo_types::{Coordinate, LineString, Point}; +/// +/// let line_string = LineString(vec![ +/// Coordinate { x: 0., y: 0. }, +/// Coordinate { x: 10., y: 0. }, +/// ]); +/// +/// let coordinate_vec = line_string.clone().into_inner(); +/// let point_vec = line_string.clone().into_points(); +/// /// ``` #[derive(Eq, PartialEq, Clone, Debug, Hash)] @@ -114,7 +136,7 @@ pub struct LineString(pub Vec>) where T: CoordNum; -/// A `Point` iterator returned by the `points_iter` method +/// A [`Point`] iterator returned by the `points` method #[derive(Debug)] pub struct PointsIter<'a, T: CoordNum + 'a>(::std::slice::Iter<'a, Coordinate>); @@ -132,19 +154,64 @@ impl<'a, T: CoordNum> DoubleEndedIterator for PointsIter<'a, T> { } } +/// A [`Coordinate`] iterator used by the `into_iter` method on a [`LineString`] +#[derive(Debug)] +pub struct CoordinatesIter<'a, T: CoordNum + 'a>(::std::slice::Iter<'a, Coordinate>); + +impl<'a, T: CoordNum> Iterator for CoordinatesIter<'a, T> { + type Item = &'a Coordinate; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl<'a, T: CoordNum> ExactSizeIterator for CoordinatesIter<'a, T> { + fn len(&self) -> usize { + self.0.len() + } +} + +impl<'a, T: CoordNum> DoubleEndedIterator for CoordinatesIter<'a, T> { + fn next_back(&mut self) -> Option { + self.0.next_back() + } +} + impl LineString { - /// Return an iterator yielding the coordinates of a `LineString` as `Point`s + /// Return an iterator yielding the coordinates of a [`LineString`] as [`Point`]s + #[deprecated(note = "Use points() instead")] pub fn points_iter(&self) -> PointsIter { PointsIter(self.0.iter()) } - /// Return the coordinates of a `LineString` as a `Vec` of `Point`s + /// Return an iterator yielding the coordinates of a [`LineString`] as [`Point`]s + pub fn points(&self) -> PointsIter { + PointsIter(self.0.iter()) + } + + /// Return an iterator yielding the members of a [`LineString`] as [`Coordinate`]s + pub fn coords(&self) -> impl Iterator> { + self.0.iter() + } + + /// Return an iterator yielding the coordinates of a [`LineString`] as mutable [`Coordinate`]s + pub fn coords_mut(&mut self) -> impl Iterator> { + self.0.iter_mut() + } + + /// Return the coordinates of a [`LineString`] as a [`Vec`] of [`Point`]s pub fn into_points(self) -> Vec> { self.0.into_iter().map(Point).collect() } - /// Return an iterator yielding one `Line` for each line segment - /// in the `LineString`. + /// Return the coordinates of a [`LineString`] as a [`Vec`] of [`Coordinate`]s + pub fn into_inner(self) -> Vec> { + self.0 + } + + /// Return an iterator yielding one [Line] for each line segment + /// in the [`LineString`]. /// /// # Examples /// @@ -178,7 +245,7 @@ impl LineString { }) } - /// An iterator which yields the coordinates of a `LineString` as `Triangle`s + /// An iterator which yields the coordinates of a [`LineString`] as [Triangle]s pub fn triangles(&'_ self) -> impl ExactSizeIterator + Iterator> + '_ { self.0.windows(3).map(|w| { // slice::windows(N) is guaranteed to yield a slice with exactly N elements @@ -192,9 +259,9 @@ impl LineString { }) } - /// Close the `LineString`. Specifically, if the `LineString` has at least one coordinate, and - /// the value of the first coordinate does not equal the value of the last coordinate, then a - /// new coordinate is added to the end with the value of the first coordinate. + /// Close the [`LineString`]. Specifically, if the [`LineString`] has at least one [`Coordinate`], and + /// the value of the first [`Coordinate`] **does not** equal the value of the last [`Coordinate`], then a + /// new [`Coordinate`] is added to the end with the value of the first [`Coordinate`]. pub fn close(&mut self) { if !self.is_closed() { // by definition, we treat empty LineString's as closed. @@ -203,7 +270,7 @@ impl LineString { } } - /// Return the number of coordinates in the `LineString`. + /// Return the number of coordinates in the [`LineString`]. /// /// # Examples /// @@ -233,21 +300,21 @@ impl LineString { /// assert!(line_string.is_closed()); /// ``` /// - /// Note that we diverge from some libraries (JTS et al), which have a LinearRing type, - /// separate from LineString. Those libraries treat an empty LinearRing as closed, by - /// definition, while treating an empty LineString as open. Since we don't have a separate - /// LinearRing type, and use a LineString in its place, we adopt the JTS LinearRing `is_closed` - /// behavior in all places, that is, we consider an empty LineString as closed. + /// Note that we diverge from some libraries ([JTS](https://locationtech.github.io/jts/javadoc/org/locationtech/jts/geom/LinearRing.html) et al), which have a `LinearRing` type, + /// separate from [`LineString`]. Those libraries treat an empty `LinearRing` as **closed** by + /// definition, while treating an empty `LineString` as **open**. Since we don't have a separate + /// `LinearRing` type, and use a [`LineString`] in its place, we adopt the JTS `LinearRing` `is_closed` + /// behavior in all places: that is, **we consider an empty [`LineString`] as closed**. /// - /// This is expected when used in the context of a Polygon.exterior and elswhere; And there - /// seems to be no reason to maintain the separate behavior for LineStrings used in - /// non-LinearRing contexts. + /// This is expected when used in the context of a [`Polygon.exterior`](crate::Polygon::exterior) and elsewhere; And there + /// seems to be no reason to maintain the separate behavior for [`LineString`]s used in + /// non-`LinearRing` contexts. pub fn is_closed(&self) -> bool { self.0.first() == self.0.last() } } -/// Turn a `Vec` of `Point`-like objects into a `LineString`. +/// Turn a [`Vec`] of [`Point`]-like objects into a [`LineString`]. impl>> From> for LineString { fn from(v: Vec) -> Self { LineString(v.into_iter().map(|c| c.into()).collect()) @@ -260,14 +327,14 @@ impl From> for LineString { } } -/// Turn an iterator of `Point`-like objects into a `LineString`. +/// Turn an iterator of [`Point`]-like objects into a [`LineString`]. impl>> FromIterator for LineString { fn from_iter>(iter: I) -> Self { LineString(iter.into_iter().map(|c| c.into()).collect()) } } -/// Iterate over all the [Coordinate](struct.Coordinates.html)s in this `LineString`. +/// Iterate over all the [`Coordinate`]s in this [`LineString`]. impl IntoIterator for LineString { type Item = Coordinate; type IntoIter = ::std::vec::IntoIter>; @@ -277,7 +344,16 @@ impl IntoIterator for LineString { } } -/// Mutably iterate over all the [Coordinate](struct.Coordinates.html)s in this `LineString`. +impl<'a, T: CoordNum> IntoIterator for &'a LineString { + type Item = &'a Coordinate; + type IntoIter = CoordinatesIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + CoordinatesIter(self.0.iter()) + } +} + +/// Mutably iterate over all the [`Coordinate`]s in this [`LineString`] impl<'a, T: CoordNum> IntoIterator for &'a mut LineString { type Item = &'a mut Coordinate; type IntoIter = ::std::slice::IterMut<'a, Coordinate>; @@ -337,7 +413,7 @@ where return false; } - let points_zipper = self.points_iter().zip(other.points_iter()); + let points_zipper = self.points().zip(other.points()); for (lhs, rhs) in points_zipper { if lhs.relative_ne(&rhs, epsilon, max_relative) { return false; @@ -376,7 +452,7 @@ impl + CoordNum> AbsDiffEq for LineString { if self.0.len() != other.0.len() { return false; } - let mut points_zipper = self.points_iter().zip(other.points_iter()); + let mut points_zipper = self.points().zip(other.points()); points_zipper.all(|(lhs, rhs)| lhs.abs_diff_eq(&rhs, epsilon)) } } diff --git a/geo-types/src/polygon.rs b/geo-types/src/polygon.rs index 60c7a21bf..c1bed2bd9 100644 --- a/geo-types/src/polygon.rs +++ b/geo-types/src/polygon.rs @@ -433,8 +433,8 @@ where .map(|(idx, _)| { let prev_1 = self.previous_vertex(idx); let prev_2 = self.previous_vertex(prev_1); - Point(self.exterior.0[prev_2]) - .cross_prod(Point(self.exterior.0[prev_1]), Point(self.exterior.0[idx])) + Point(self.exterior[prev_2]) + .cross_prod(Point(self.exterior[prev_1]), Point(self.exterior[idx])) }) // accumulate and check cross-product result signs in a single pass // positive implies ccw convexity, negative implies cw convexity diff --git a/geo-types/src/private_utils.rs b/geo-types/src/private_utils.rs index cff4ae392..a20ec101d 100644 --- a/geo-types/src/private_utils.rs +++ b/geo-types/src/private_utils.rs @@ -9,7 +9,7 @@ pub fn line_string_bounding_rect(line_string: &LineString) -> Option(line: Line) -> Rect @@ -132,7 +132,7 @@ where } // LineString with one point equal p if line_string.0.len() == 1 { - return point_contains_point(Point(line_string.0[0]), point); + return point_contains_point(Point(line_string[0]), point); } // check if point is a vertex if line_string.0.contains(&point.0) { diff --git a/geo/src/algorithm/euclidean_distance.rs b/geo/src/algorithm/euclidean_distance.rs index 5123fb044..e11572646 100644 --- a/geo/src/algorithm/euclidean_distance.rs +++ b/geo/src/algorithm/euclidean_distance.rs @@ -559,19 +559,15 @@ where let tree_b: RTree> = RTree::bulk_load(geom2.lines().collect::>()); // Return minimum distance between all geom a points and all geom b points geom2 - .points_iter() + .points() .fold(::max_value(), |acc, point| { let nearest = tree_a.nearest_neighbor(&point).unwrap(); acc.min(nearest.euclidean_distance(&point)) }) - .min( - geom1 - .points_iter() - .fold(Bounded::max_value(), |acc, point| { - let nearest = tree_b.nearest_neighbor(&point).unwrap(); - acc.min(nearest.euclidean_distance(&point)) - }), - ) + .min(geom1.points().fold(Bounded::max_value(), |acc, point| { + let nearest = tree_b.nearest_neighbor(&point).unwrap(); + acc.min(nearest.euclidean_distance(&point)) + })) } #[cfg(test)] diff --git a/geo/src/algorithm/line_interpolate_point.rs b/geo/src/algorithm/line_interpolate_point.rs index 9d8108ee0..65536fc37 100644 --- a/geo/src/algorithm/line_interpolate_point.rs +++ b/geo/src/algorithm/line_interpolate_point.rs @@ -240,11 +240,11 @@ mod test { // fraction is nan or inf assert_eq!( linestring.line_interpolate_point(Float::infinity()), - linestring.points_iter().last() + linestring.points().last() ); assert_eq!( linestring.line_interpolate_point(Float::neg_infinity()), - linestring.points_iter().next() + linestring.points().next() ); assert_eq!(linestring.line_interpolate_point(Float::nan()), None); diff --git a/geo/src/algorithm/map_coords.rs b/geo/src/algorithm/map_coords.rs index 5cbbfca9e..2cfb686ce 100644 --- a/geo/src/algorithm/map_coords.rs +++ b/geo/src/algorithm/map_coords.rs @@ -236,7 +236,7 @@ impl MapCoords for LineString { fn map_coords(&self, func: impl Fn(&(T, T)) -> (NT, NT) + Copy) -> Self::Output { LineString::from( - self.points_iter() + self.points() .map(|p| p.map_coords(func)) .collect::>(), ) @@ -251,7 +251,7 @@ impl TryMapCoords for LineString { func: impl Fn(&(T, T)) -> Result<(NT, NT), Box> + Copy, ) -> Result> { Ok(LineString::from( - self.points_iter() + self.points() .map(|p| p.try_map_coords(func)) .collect::, Box>>()?, )) diff --git a/geo/src/algorithm/rotate.rs b/geo/src/algorithm/rotate.rs index 6df531888..9c0b92288 100644 --- a/geo/src/algorithm/rotate.rs +++ b/geo/src/algorithm/rotate.rs @@ -222,10 +222,10 @@ where // return a rotated polygon, or a clone if no centroid is computable if let Some(centroid) = centroid { Polygon::new( - rotate_many(angle, centroid, self.exterior().points_iter()).collect(), + rotate_many(angle, centroid, self.exterior().points()).collect(), self.interiors() .iter() - .map(|ring| rotate_many(angle, centroid, ring.points_iter()).collect()) + .map(|ring| rotate_many(angle, centroid, ring.points()).collect()) .collect(), ) } else { diff --git a/geo/src/algorithm/winding_order.rs b/geo/src/algorithm/winding_order.rs index 8c1f0e137..662803419 100644 --- a/geo/src/algorithm/winding_order.rs +++ b/geo/src/algorithm/winding_order.rs @@ -157,8 +157,8 @@ where /// order, so that the resultant order makes it appear clockwise fn points_cw(&self) -> Points { match self.winding_order() { - Some(WindingOrder::CounterClockwise) => Points(EitherIter::B(self.points_iter().rev())), - _ => Points(EitherIter::A(self.points_iter())), + Some(WindingOrder::CounterClockwise) => Points(EitherIter::B(self.points().rev())), + _ => Points(EitherIter::A(self.points())), } } @@ -168,8 +168,8 @@ where /// order, so that the resultant order makes it appear counter-clockwise fn points_ccw(&self) -> Points { match self.winding_order() { - Some(WindingOrder::Clockwise) => Points(EitherIter::B(self.points_iter().rev())), - _ => Points(EitherIter::A(self.points_iter())), + Some(WindingOrder::Clockwise) => Points(EitherIter::B(self.points().rev())), + _ => Points(EitherIter::A(self.points())), } }