Skip to main content

geometry_algorithm/
map_coords.rs

1//! Coordinate mapping for the stock geometry models.
2
3use geometry_model::{
4    Box as ModelBox, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point as ModelPoint,
5    Polygon, Ring, Segment,
6};
7use geometry_trait::{Geometry, Point as PointTrait};
8
9/// Map every point in a stock geometry into a newly constructed geometry.
10///
11/// The point closure may change the scalar or coordinate-system type while the
12/// geometry's topology and ring parameters are preserved.
13pub fn map_coords<G, Q, F>(geometry: &G, mut map: F) -> G::Output
14where
15    G: MapCoords<Q>,
16    Q: PointTrait,
17    F: FnMut(&G::Point) -> Q,
18{
19    geometry.map_coords(&mut map)
20}
21
22/// Stock geometry support for [`map_coords`].
23#[doc(hidden)]
24pub trait MapCoords<Q: PointTrait>: Geometry {
25    /// The mapped stock geometry.
26    type Output;
27
28    /// Reconstruct this geometry by mapping each point.
29    fn map_coords<F>(&self, map: &mut F) -> Self::Output
30    where
31        F: FnMut(&Self::Point) -> Q;
32}
33
34impl<A, const D: usize, Cs, Q> MapCoords<Q> for ModelPoint<A, D, Cs>
35where
36    A: geometry_coords::CoordinateScalar,
37    Cs: geometry_cs::CoordinateSystem,
38    Q: PointTrait,
39{
40    type Output = Q;
41
42    fn map_coords<F>(&self, map: &mut F) -> Self::Output
43    where
44        F: FnMut(&Self::Point) -> Q,
45    {
46        map(self)
47    }
48}
49
50impl<P, Q> MapCoords<Q> for Linestring<P>
51where
52    P: PointTrait,
53    Q: PointTrait,
54{
55    type Output = Linestring<Q>;
56
57    fn map_coords<F>(&self, map: &mut F) -> Self::Output
58    where
59        F: FnMut(&Self::Point) -> Q,
60    {
61        Linestring::from_vec(self.0.iter().map(map).collect())
62    }
63}
64
65impl<P, Q, const CW: bool, const CL: bool> MapCoords<Q> for Ring<P, CW, CL>
66where
67    P: PointTrait,
68    Q: PointTrait,
69{
70    type Output = Ring<Q, CW, CL>;
71
72    fn map_coords<F>(&self, map: &mut F) -> Self::Output
73    where
74        F: FnMut(&Self::Point) -> Q,
75    {
76        Ring::from_vec(self.0.iter().map(map).collect())
77    }
78}
79
80impl<P, Q, const CW: bool, const CL: bool> MapCoords<Q> for Polygon<P, CW, CL>
81where
82    P: PointTrait,
83    Q: PointTrait,
84{
85    type Output = Polygon<Q, CW, CL>;
86
87    fn map_coords<F>(&self, map: &mut F) -> Self::Output
88    where
89        F: FnMut(&Self::Point) -> Q,
90    {
91        Polygon::with_inners(
92            Ring::from_vec(self.outer.0.iter().map(&mut *map).collect()),
93            self.inners
94                .iter()
95                .map(|ring| Ring::from_vec(ring.0.iter().map(&mut *map).collect()))
96                .collect(),
97        )
98    }
99}
100
101impl<P, Q> MapCoords<Q> for MultiPoint<P>
102where
103    P: PointTrait,
104    Q: PointTrait,
105{
106    type Output = MultiPoint<Q>;
107
108    fn map_coords<F>(&self, map: &mut F) -> Self::Output
109    where
110        F: FnMut(&Self::Point) -> Q,
111    {
112        MultiPoint::from_vec(self.0.iter().map(map).collect())
113    }
114}
115
116impl<P, Q> MapCoords<Q> for MultiLinestring<Linestring<P>>
117where
118    P: PointTrait,
119    Q: PointTrait,
120{
121    type Output = MultiLinestring<Linestring<Q>>;
122
123    fn map_coords<F>(&self, map: &mut F) -> Self::Output
124    where
125        F: FnMut(&Self::Point) -> Q,
126    {
127        MultiLinestring::from_vec(
128            self.0
129                .iter()
130                .map(|line| Linestring::from_vec(line.0.iter().map(&mut *map).collect()))
131                .collect(),
132        )
133    }
134}
135
136impl<P, Q, const CW: bool, const CL: bool> MapCoords<Q> for MultiPolygon<Polygon<P, CW, CL>>
137where
138    P: PointTrait,
139    Q: PointTrait,
140{
141    type Output = MultiPolygon<Polygon<Q, CW, CL>>;
142
143    fn map_coords<F>(&self, map: &mut F) -> Self::Output
144    where
145        F: FnMut(&Self::Point) -> Q,
146    {
147        MultiPolygon::from_vec(
148            self.0
149                .iter()
150                .map(|polygon| polygon.map_coords(&mut *map))
151                .collect(),
152        )
153    }
154}
155
156impl<P, Q> MapCoords<Q> for ModelBox<P>
157where
158    P: PointTrait,
159    Q: PointTrait,
160{
161    type Output = ModelBox<Q>;
162
163    fn map_coords<F>(&self, map: &mut F) -> Self::Output
164    where
165        F: FnMut(&Self::Point) -> Q,
166    {
167        ModelBox::from_corners(map(self.min()), map(self.max()))
168    }
169}
170
171impl<P, Q> MapCoords<Q> for Segment<P>
172where
173    P: PointTrait,
174    Q: PointTrait,
175{
176    type Output = Segment<Q>;
177
178    fn map_coords<F>(&self, map: &mut F) -> Self::Output
179    where
180        F: FnMut(&Self::Point) -> Q,
181    {
182        Segment::new(map(self.start()), map(self.end()))
183    }
184}
185
186/// Mutate every stored point in a stock geometry in place.
187pub fn map_coords_in_place<G, F>(geometry: &mut G, mut map: F)
188where
189    G: MapCoordsInPlace,
190    F: FnMut(&mut G::Point),
191{
192    geometry.map_coords_in_place(&mut map);
193}
194
195/// Stock geometry support for [`map_coords_in_place`].
196#[doc(hidden)]
197pub trait MapCoordsInPlace: Geometry {
198    /// Visit every stored point mutably.
199    fn map_coords_in_place<F>(&mut self, map: &mut F)
200    where
201        F: FnMut(&mut Self::Point);
202}
203
204impl<A, const D: usize, Cs> MapCoordsInPlace for ModelPoint<A, D, Cs>
205where
206    A: geometry_coords::CoordinateScalar,
207    Cs: geometry_cs::CoordinateSystem,
208{
209    fn map_coords_in_place<F>(&mut self, map: &mut F)
210    where
211        F: FnMut(&mut Self::Point),
212    {
213        map(self);
214    }
215}
216
217impl<P: PointTrait> MapCoordsInPlace for Linestring<P> {
218    fn map_coords_in_place<F>(&mut self, map: &mut F)
219    where
220        F: FnMut(&mut Self::Point),
221    {
222        self.0.iter_mut().for_each(map);
223    }
224}
225
226impl<P: PointTrait, const CW: bool, const CL: bool> MapCoordsInPlace for Ring<P, CW, CL> {
227    fn map_coords_in_place<F>(&mut self, map: &mut F)
228    where
229        F: FnMut(&mut Self::Point),
230    {
231        self.0.iter_mut().for_each(map);
232    }
233}
234
235impl<P: PointTrait, const CW: bool, const CL: bool> MapCoordsInPlace for Polygon<P, CW, CL> {
236    fn map_coords_in_place<F>(&mut self, map: &mut F)
237    where
238        F: FnMut(&mut Self::Point),
239    {
240        self.outer.0.iter_mut().for_each(&mut *map);
241        for ring in &mut self.inners {
242            ring.0.iter_mut().for_each(&mut *map);
243        }
244    }
245}
246
247impl<P: PointTrait> MapCoordsInPlace for MultiPoint<P> {
248    fn map_coords_in_place<F>(&mut self, map: &mut F)
249    where
250        F: FnMut(&mut Self::Point),
251    {
252        self.0.iter_mut().for_each(map);
253    }
254}
255
256impl<P: PointTrait> MapCoordsInPlace for MultiLinestring<Linestring<P>> {
257    fn map_coords_in_place<F>(&mut self, map: &mut F)
258    where
259        F: FnMut(&mut Self::Point),
260    {
261        for line in &mut self.0 {
262            line.0.iter_mut().for_each(&mut *map);
263        }
264    }
265}
266
267impl<P: PointTrait, const CW: bool, const CL: bool> MapCoordsInPlace
268    for MultiPolygon<Polygon<P, CW, CL>>
269{
270    fn map_coords_in_place<F>(&mut self, map: &mut F)
271    where
272        F: FnMut(&mut Self::Point),
273    {
274        for polygon in &mut self.0 {
275            polygon.map_coords_in_place(&mut *map);
276        }
277    }
278}
279
280impl<P> MapCoordsInPlace for ModelBox<P>
281where
282    P: PointTrait + Clone,
283{
284    fn map_coords_in_place<F>(&mut self, map: &mut F)
285    where
286        F: FnMut(&mut Self::Point),
287    {
288        let mut min = self.min().clone();
289        let mut max = self.max().clone();
290        map(&mut min);
291        map(&mut max);
292        *self = ModelBox::from_corners(min, max);
293    }
294}
295
296impl<P> MapCoordsInPlace for Segment<P>
297where
298    P: PointTrait + Clone,
299{
300    fn map_coords_in_place<F>(&mut self, map: &mut F)
301    where
302        F: FnMut(&mut Self::Point),
303    {
304        let mut start = self.start().clone();
305        let mut end = self.end().clone();
306        map(&mut start);
307        map(&mut end);
308        *self = Segment::new(start, end);
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use geometry_cs::Cartesian;
315    use geometry_model::{Linestring, Point2D};
316    use geometry_trait::{Point as _, PointMut as _};
317
318    use super::{map_coords, map_coords_in_place};
319
320    #[test]
321    #[allow(
322        clippy::cast_possible_truncation,
323        reason = "small exact fixtures intentionally exercise scalar rebinding"
324    )]
325    fn linestring_rebinds_and_mutates() {
326        let line = Linestring::from_vec(alloc::vec![
327            Point2D::<f64, Cartesian>::new(1.0, 2.0),
328            Point2D::new(3.0, 4.0),
329        ]);
330        let mapped: Linestring<Point2D<f32, Cartesian>> = map_coords(&line, |point| {
331            Point2D::new(point.get::<0>() as f32, point.get::<1>() as f32)
332        });
333        assert_eq!(mapped.0[0], Point2D::new(1.0, 2.0));
334
335        let mut shifted = line;
336        map_coords_in_place(&mut shifted, |point| {
337            point.set::<0>(point.get::<0>() + 1.0);
338        });
339        assert_eq!(shifted.0[0], Point2D::new(2.0, 2.0));
340    }
341}