geometry_algorithm/
transform.rs1use 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
16pub fn transform<G, S>(g: &G, s: &S) -> G::Output
22where
23 G: Transform<S>,
24{
25 g.transform(s)
26}
27
28#[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 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}