1use 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
150 use geometry_model::{MultiLinestring, MultiPoint, MultiPolygon, Polygon, Ring, polygon};
151 use geometry_trait::{
152 MultiLinestring as _, MultiPoint as _, MultiPolygon as _, Polygon as _, Ring as _,
153 };
154
155 #[test]
158 fn ring_scaled() {
159 let r: Ring<Pt> = Ring::from_vec(vec![Pt::new(1.0, 1.0), Pt::new(2.0, 3.0)]);
160 let out = transform(&r, &Affine2::scale(2.0, 10.0));
161 let pts: Vec<(f64, f64)> = out.points().map(|p| (p.get::<0>(), p.get::<1>())).collect();
162 assert_eq!(pts, vec![(2.0, 10.0), (4.0, 30.0)]);
163 }
164
165 #[test]
167 fn polygon_translated_exterior_and_holes() {
168 let pg: Polygon<Pt> = polygon![
169 [(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (0.0, 0.0)],
170 [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 1.0)]
171 ];
172 let out = transform(&pg, &Affine2::translation(100.0, 0.0));
173 let ext0 = out.exterior().points().next().unwrap();
175 assert_eq!((ext0.get::<0>(), ext0.get::<1>()), (100.0, 0.0));
176 let hole0 = out.interiors().next().unwrap().points().next().unwrap();
178 assert_eq!((hole0.get::<0>(), hole0.get::<1>()), (101.0, 1.0));
179 }
180
181 #[test]
183 fn multipoint_scaled() {
184 let mp = MultiPoint(vec![Pt::new(1.0, 1.0), Pt::new(2.0, 2.0)]);
185 let out = transform(&mp, &Affine2::scale(3.0, 3.0));
186 let pts: Vec<(f64, f64)> = out.points().map(|p| (p.get::<0>(), p.get::<1>())).collect();
187 assert_eq!(pts, vec![(3.0, 3.0), (6.0, 6.0)]);
188 }
189
190 #[test]
192 fn multilinestring_translated() {
193 let mls: MultiLinestring<Linestring<Pt>> = MultiLinestring(vec![
194 linestring![(0.0, 0.0), (1.0, 0.0)],
195 linestring![(0.0, 1.0), (1.0, 1.0)],
196 ]);
197 let out = transform(&mls, &Affine2::translation(0.0, 5.0));
198 let first = out.linestrings().next().unwrap().points().next().unwrap();
199 assert_eq!((first.get::<0>(), first.get::<1>()), (0.0, 5.0));
200 assert_eq!(out.linestrings().count(), 2);
201 }
202
203 #[test]
205 fn multipolygon_scaled() {
206 let member: Polygon<Pt> = polygon![[(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 1.0)]];
207 let mpg: MultiPolygon<Polygon<Pt>> = MultiPolygon(vec![member.clone(), member]);
208 let out = transform(&mpg, &Affine2::scale(10.0, 10.0));
209 assert_eq!(out.polygons().count(), 2);
210 let v = out
211 .polygons()
212 .next()
213 .unwrap()
214 .exterior()
215 .points()
216 .next()
217 .unwrap();
218 assert_eq!((v.get::<0>(), v.get::<1>()), (10.0, 10.0));
219 }
220
221 use geometry_model::Point3D;
224 use geometry_strategy::Affine3;
225
226 type P3 = Point3D<f64, Cartesian>;
227
228 #[test]
230 fn affine3_identity_is_a_noop() {
231 let out = transform(&P3::new(2.0, 3.0, 4.0), &Affine3::identity());
232 assert_eq!(out.get::<0>(), 2.0);
233 assert_eq!(out.get::<1>(), 3.0);
234 assert_eq!(out.get::<2>(), 4.0);
235 }
236
237 #[test]
239 fn affine3_translation_shifts_all_three_axes() {
240 let t = Affine3::translation(1.0, 2.0, 3.0);
241 let out = transform(&P3::new(10.0, 20.0, 30.0), &t);
242 assert_eq!(out.get::<0>(), 11.0);
243 assert_eq!(out.get::<1>(), 22.0);
244 assert_eq!(out.get::<2>(), 33.0);
245 }
246
247 #[test]
249 fn affine3_scale_multiplies_each_axis() {
250 let s = Affine3::scale(2.0, 3.0, 4.0);
251 let out = transform(&P3::new(1.0, 1.0, 1.0), &s);
252 assert_eq!(out.get::<0>(), 2.0);
253 assert_eq!(out.get::<1>(), 3.0);
254 assert_eq!(out.get::<2>(), 4.0);
255 }
256
257 #[test]
260 fn affine3_full_matrix_applies_scale_and_translation() {
261 let mut a = Affine3::scale(2.0, 2.0, 2.0);
264 a.m[3] = 1.0;
265 a.m[7] = 2.0;
266 a.m[11] = 3.0;
267 let out = transform(&P3::new(1.0, 1.0, 1.0), &a);
268 assert_eq!(out.get::<0>(), 3.0); assert_eq!(out.get::<1>(), 4.0); assert_eq!(out.get::<2>(), 5.0); }
272}