galileo_types/
contour.rs

1//! Contour is a sequence of points.
2//!
3//! Contours can be:
4//! * **open** - meaning that the first and the last points of the contour are not connected. For example, a road on
5//!   the map can be represented as an open contour.
6//! * **closed** - when the first and the last points of the contour are connected. For example, a shoreline can be
7//!   represented as a closed contour.
8//!
9//! Both open and closed contours are represented by the [`Contour`] trait, but there is also a separate [`ClosedContour`]
10//! traits for situations when only closed contour makes sense. For example, a [`Polygon`](super::Polygon) can
11//! consist only of closed contours. All closed contours also implement the `Contour` trait automatically.
12//!
13//! # Contour vs OGC LineString
14//!
15//! In the OGC Simple Feature Access standard, the corresponding geometry type is called a `LineString`. There is
16//! a important difference between them thought.
17//!
18//! `LineString` is considered to be closed when the first and the last points in the sequence are exactly same. `Contour`
19//! does not have that requirement. Even more, it should not duplicate the first and the last points. `Contour` trait
20//! deals with the last segment of closed contours with [`Contour::iter_points_closing`] and
21//! [`Contour::iter_segments`] methods instead.
22
23use crate::cartesian::{CartesianPoint2d, Rect};
24use crate::geo::Projection;
25use crate::geometry::{CartesianGeometry2dSpecialization, Geom, Geometry, GeometrySpecialization};
26use crate::geometry_type::{CartesianSpace2d, ContourGeometryType, GeometryType};
27use crate::segment::Segment;
28
29/// Sequence of points. See module level documentation for details.
30pub trait Contour {
31    /// Type of the points the contour is consisted of.
32    type Point;
33
34    /// Whether the contour is closed.
35    ///
36    /// A closed contour has a segment connecting the last and the first points.
37    fn is_closed(&self) -> bool;
38
39    /// Iterate over the points of the contour.
40    ///
41    /// Note, that the last point shall not be the same as the first one even for the closed contours. If you want to
42    /// include the first point at the end of iterator for closed contours, use [`Contour::iter_points_closing`]
43    /// instead.
44    fn iter_points(&self) -> impl Iterator<Item = Self::Point>;
45
46    /// Same as [`Contour::iter_points`] but for closed contours repeats the first point again at the end of the iterator.
47    fn iter_points_closing(&self) -> impl Iterator<Item = Self::Point>
48    where
49        Self::Point: Copy,
50    {
51        Box::new(ContourPointsIterator::new(
52            self.iter_points(),
53            self.is_closed(),
54        ))
55    }
56
57    /// Iterates over segments of the contour. For closed contours this includes the segment between the last and the
58    /// first points of the contour.
59    fn iter_segments(&self) -> impl Iterator<Item = Segment<Self::Point>>
60    where
61        Self::Point: Copy,
62    {
63        ContourSegmentIterator::new(ContourPointsIterator::new(
64            self.iter_points(),
65            self.is_closed(),
66        ))
67    }
68
69    /// Project all the points of the contour with the given `projection`.
70    fn project_points<Proj>(
71        &self,
72        projection: &Proj,
73    ) -> Option<crate::impls::Contour<Proj::OutPoint>>
74    where
75        Proj: Projection<InPoint = Self::Point> + ?Sized,
76    {
77        Some(crate::impls::Contour::new(
78            self.iter_points()
79                .map(|p| projection.project(&p))
80                .collect::<Option<Vec<Proj::OutPoint>>>()?,
81            self.is_closed(),
82        ))
83    }
84}
85
86/// A closed contour. See module documentation for details.
87pub trait ClosedContour {
88    /// Type of the points the contour is consisted of.
89    type Point;
90
91    /// Iterate over the points of the contour.
92    ///
93    /// Note, that the last point shall not be the same as the first one even for the closed contours. If you want to
94    /// include the first point at the end of iterator for closed contours, use [`Contour::iter_points_closing`]
95    /// instead.
96    fn iter_points(&self) -> impl Iterator<Item = Self::Point>;
97}
98
99impl<P, T: ClosedContour<Point = P>> Contour for T {
100    type Point = P;
101
102    fn is_closed(&self) -> bool {
103        true
104    }
105
106    fn iter_points(&self) -> impl Iterator<Item = Self::Point> {
107        self.iter_points()
108    }
109}
110
111/// Iterator of contour points.
112#[derive(Debug, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Hash)]
113pub struct ContourPointsIterator<P, Iter>
114where
115    Iter: Iterator<Item = P>,
116{
117    points_iter: Iter,
118    is_closed: bool,
119    first_point: Option<P>,
120}
121
122impl<P, Iter> ContourPointsIterator<P, Iter>
123where
124    Iter: Iterator<Item = P>,
125{
126    fn new(points_iter: Iter, is_closed: bool) -> Self {
127        Self {
128            points_iter,
129            is_closed,
130            first_point: None,
131        }
132    }
133}
134
135impl<P, Iter> Iterator for ContourPointsIterator<P, Iter>
136where
137    P: Copy,
138    Iter: Iterator<Item = P>,
139{
140    type Item = P;
141
142    fn next(&mut self) -> Option<Self::Item> {
143        let next = self.points_iter.next();
144        if self.is_closed && self.first_point.is_none() {
145            self.first_point = next;
146        }
147
148        if next.is_none() {
149            self.first_point.take()
150        } else {
151            next
152        }
153    }
154}
155
156/// Iterator of contour segments.
157#[derive(Debug, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Hash)]
158pub struct ContourSegmentIterator<P, Iter>
159where
160    Iter: Iterator<Item = P>,
161{
162    points_iter: ContourPointsIterator<P, Iter>,
163    prev_point: Option<P>,
164}
165
166impl<P, Iter> ContourSegmentIterator<P, Iter>
167where
168    Iter: Iterator<Item = P>,
169{
170    fn new(points_iter: ContourPointsIterator<P, Iter>) -> Self {
171        Self {
172            points_iter,
173            prev_point: None,
174        }
175    }
176}
177
178impl<P, Iter> Iterator for ContourSegmentIterator<P, Iter>
179where
180    P: Copy,
181    Iter: Iterator<Item = P>,
182{
183    type Item = Segment<P>;
184
185    fn next(&mut self) -> Option<Self::Item> {
186        let next_point = self.points_iter.next()?;
187        let prev_point = self.prev_point.replace(next_point);
188
189        match prev_point {
190            Some(prev) => Some(Segment(prev, next_point)),
191            None => self.next(),
192        }
193    }
194}
195
196impl<C, Space> GeometrySpecialization<ContourGeometryType, Space> for C
197where
198    C: Contour + GeometryType<Type = ContourGeometryType, Space = Space>,
199{
200    type Point = C::Point;
201
202    fn project_spec<Proj>(&self, projection: &Proj) -> Option<Geom<Proj::OutPoint>>
203    where
204        Proj: Projection<InPoint = Self::Point> + ?Sized,
205    {
206        let points = self
207            .iter_points()
208            .map(|p| projection.project(&p))
209            .collect::<Option<Vec<Proj::OutPoint>>>()?;
210        Some(Geom::Contour(crate::impls::Contour::new(
211            points,
212            self.is_closed(),
213        )))
214    }
215}
216
217impl<P, C> CartesianGeometry2dSpecialization<P, ContourGeometryType> for C
218where
219    P: CartesianPoint2d + Copy,
220    C: Contour<Point = P>
221        + GeometryType<Type = ContourGeometryType, Space = CartesianSpace2d>
222        + Geometry<Point = P>,
223{
224    fn is_point_inside_spec<Other: CartesianPoint2d<Num = P::Num>>(
225        &self,
226        point: &Other,
227        tolerance: P::Num,
228    ) -> bool {
229        self.iter_segments()
230            .any(|segment| segment.distance_to_point_sq(point) <= tolerance * tolerance)
231    }
232
233    fn bounding_rectangle_spec(&self) -> Option<Rect<P::Num>> {
234        Rect::from_points(self.iter_points())
235    }
236}