Skip to main content

geometry_algorithm/
transform.rs

1//! `transform(&g, &strategy)` — return a fresh geometry whose every
2//! point has been mapped through `strategy`.
3//!
4//! Mirrors `boost::geometry::transform(g_src, g_dst, strategy)` from
5//! `boost/geometry/algorithms/transform.hpp`. The Boost overload
6//! mutates `g_dst` through an out-parameter; the Rust port returns the
7//! result by value (same observable behaviour).
8
9use geometry_model::{Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point, Polygon, Ring};
10use geometry_strategy::TransformStrategy;
11use geometry_trait::{
12    Linestring as LinestringTrait, MultiPoint as MultiPointTrait, Point as PointTrait,
13    Polygon as PolygonTrait, Ring as RingTrait,
14};
15
16/// Map every point of `g` through `s`, returning a new geometry of the
17/// same kind (with the strategy's output point type).
18///
19/// Mirrors `boost::geometry::transform(src, dst, strategy)` from
20/// `boost/geometry/algorithms/transform.hpp`.
21pub fn transform<G, S>(g: &G, s: &S) -> G::Output
22where
23    G: Transform<S>,
24{
25    g.transform(s)
26}
27
28/// Per-kind transform dispatch.
29#[doc(hidden)]
30pub trait Transform<S> {
31    type Output;
32    fn transform(&self, s: &S) -> Self::Output;
33}
34
35impl<T, const D: usize, Cs, S> Transform<S> for Point<T, D, Cs>
36where
37    T: geometry_coords::CoordinateScalar,
38    Cs: geometry_cs::CoordinateSystem,
39    Self: PointTrait,
40    S: TransformStrategy<Self>,
41{
42    type Output = S::Output;
43    fn transform(&self, s: &S) -> Self::Output {
44        s.transform(self)
45    }
46}
47
48impl<P, S> Transform<S> for Linestring<P>
49where
50    P: PointTrait,
51    S: TransformStrategy<P>,
52{
53    type Output = Linestring<S::Output>;
54    fn transform(&self, s: &S) -> Self::Output {
55        Linestring(self.points().map(|p| s.transform(p)).collect())
56    }
57}
58
59impl<P, S, const CW: bool, const CL: bool> Transform<S> for Ring<P, CW, CL>
60where
61    P: PointTrait,
62    S: TransformStrategy<P>,
63{
64    type Output = Ring<S::Output, CW, CL>;
65    fn transform(&self, s: &S) -> Self::Output {
66        Ring::from_vec(self.points().map(|p| s.transform(p)).collect())
67    }
68}
69
70impl<P, S, const CW: bool, const CL: bool> Transform<S> for Polygon<P, CW, CL>
71where
72    P: PointTrait,
73    S: TransformStrategy<P>,
74{
75    type Output = Polygon<S::Output, CW, CL>;
76    fn transform(&self, s: &S) -> Self::Output {
77        Polygon::with_inners(
78            self.exterior().transform(s),
79            self.interiors().map(|r| r.transform(s)).collect(),
80        )
81    }
82}
83
84impl<P, S> Transform<S> for MultiPoint<P>
85where
86    P: PointTrait,
87    S: TransformStrategy<P>,
88{
89    type Output = MultiPoint<S::Output>;
90    fn transform(&self, s: &S) -> Self::Output {
91        MultiPoint(self.points().map(|p| s.transform(p)).collect())
92    }
93}
94
95impl<L, S> Transform<S> for MultiLinestring<L>
96where
97    L: LinestringTrait + Transform<S>,
98    <L as Transform<S>>::Output: LinestringTrait,
99{
100    type Output = MultiLinestring<<L as Transform<S>>::Output>;
101    fn transform(&self, s: &S) -> Self::Output {
102        MultiLinestring(self.0.iter().map(|l| l.transform(s)).collect())
103    }
104}
105
106impl<Pg, S> Transform<S> for MultiPolygon<Pg>
107where
108    Pg: PolygonTrait + Transform<S>,
109    <Pg as Transform<S>>::Output: PolygonTrait,
110{
111    type Output = MultiPolygon<<Pg as Transform<S>>::Output>;
112    fn transform(&self, s: &S) -> Self::Output {
113        MultiPolygon(self.0.iter().map(|p| p.transform(s)).collect())
114    }
115}
116
117#[cfg(test)]
118#[allow(
119    clippy::float_cmp,
120    reason = "Affine outputs of integer inputs are exact."
121)]
122mod tests {
123    //! Reference behaviour from
124    //! `boost/geometry/test/algorithms/transform.cpp`.
125
126    use super::transform;
127    use geometry_cs::Cartesian;
128    use geometry_model::{Linestring, Point2D, linestring};
129    use geometry_strategy::Affine2;
130    use geometry_trait::{Linestring as _, Point as _};
131
132    type Pt = Point2D<f64, Cartesian>;
133
134    #[test]
135    fn point_identity_is_unchanged() {
136        let s = Affine2::<f64>::identity();
137        let q = transform(&Pt::new(3.0, 4.0), &s);
138        assert_eq!((q.get::<0>(), q.get::<1>()), (3.0, 4.0));
139    }
140
141    #[test]
142    fn linestring_translated() {
143        let ls: Linestring<Pt> = linestring![(0.0, 0.0), (1.0, 1.0)];
144        let s = Affine2::translation(10.0, 20.0);
145        let out = transform(&ls, &s);
146        let pts: Vec<(f64, f64)> = out.points().map(|p| (p.get::<0>(), p.get::<1>())).collect();
147        assert_eq!(pts, vec![(10.0, 20.0), (11.0, 21.0)]);
148    }
149}