1use proj4rs::transform::{Transform, TransformClosure};
19
20use geometry_cs::CoordinateSystem;
21use geometry_model::{Linestring, MultiPolygon, Point2D, Polygon, Ring};
22use geometry_trait::{Point, PointMut};
23
24use crate::crs::{Crs, CrsError};
25
26pub trait ReprojectPoints {
33 fn map_points<F>(&mut self, f: &mut F) -> Result<(), CrsError>
40 where
41 F: FnMut(f64, f64) -> Result<(f64, f64), CrsError>;
42}
43
44pub fn reproject<G: ReprojectPoints>(
80 geometry: &mut G,
81 from: &Crs,
82 to: &Crs,
83) -> Result<(), CrsError> {
84 let mut buf = CoordBuffer(alloc::vec::Vec::new());
87 geometry.map_points(&mut |x, y| {
88 buf.0.push((x, y));
89 Ok((x, y))
90 })?;
91
92 proj4rs::transform::transform(from.proj(), to.proj(), &mut buf).map_err(CrsError::Proj)?;
95
96 let mut it = buf.0.into_iter();
100 geometry.map_points(&mut |_x, _y| {
101 let (nx, ny) = it.next().expect(
102 "reproject: point count changed between walks over an exclusively borrowed geometry",
103 );
104 Ok((nx, ny))
105 })
106}
107
108struct CoordBuffer(alloc::vec::Vec<(f64, f64)>);
113
114impl Transform for CoordBuffer {
115 fn transform_coordinates<F: TransformClosure>(
116 &mut self,
117 f: &mut F,
118 ) -> proj4rs::errors::Result<()> {
119 for pair in &mut self.0 {
120 let (nx, ny, _nz) = f(pair.0, pair.1, 0.0)?;
121 *pair = (nx, ny);
122 }
123 Ok(())
124 }
125}
126
127impl<Cs: CoordinateSystem> ReprojectPoints for Point2D<f64, Cs> {
130 fn map_points<F>(&mut self, f: &mut F) -> Result<(), CrsError>
131 where
132 F: FnMut(f64, f64) -> Result<(f64, f64), CrsError>,
133 {
134 let (nx, ny) = f(self.get::<0>(), self.get::<1>())?;
135 self.set::<0>(nx);
136 self.set::<1>(ny);
137 Ok(())
138 }
139}
140
141impl<Cs: CoordinateSystem> ReprojectPoints for Linestring<Point2D<f64, Cs>> {
142 fn map_points<F>(&mut self, f: &mut F) -> Result<(), CrsError>
143 where
144 F: FnMut(f64, f64) -> Result<(f64, f64), CrsError>,
145 {
146 for p in &mut self.0 {
147 p.map_points(f)?;
148 }
149 Ok(())
150 }
151}
152
153impl<Cs: CoordinateSystem> ReprojectPoints for Ring<Point2D<f64, Cs>> {
154 fn map_points<F>(&mut self, f: &mut F) -> Result<(), CrsError>
155 where
156 F: FnMut(f64, f64) -> Result<(f64, f64), CrsError>,
157 {
158 for p in &mut self.0 {
159 p.map_points(f)?;
160 }
161 Ok(())
162 }
163}
164
165impl<Cs: CoordinateSystem> ReprojectPoints for Polygon<Point2D<f64, Cs>> {
166 fn map_points<F>(&mut self, f: &mut F) -> Result<(), CrsError>
167 where
168 F: FnMut(f64, f64) -> Result<(f64, f64), CrsError>,
169 {
170 self.outer.map_points(f)?;
171 for hole in &mut self.inners {
172 hole.map_points(f)?;
173 }
174 Ok(())
175 }
176}
177
178impl<Cs: CoordinateSystem> ReprojectPoints for MultiPolygon<Polygon<Point2D<f64, Cs>>> {
179 fn map_points<F>(&mut self, f: &mut F) -> Result<(), CrsError>
180 where
181 F: FnMut(f64, f64) -> Result<(f64, f64), CrsError>,
182 {
183 for pg in &mut self.0 {
184 pg.map_points(f)?;
185 }
186 Ok(())
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::reproject;
193 use crate::crs::Crs;
194 use geometry_cs::Cartesian;
195 use geometry_model::{Linestring, Point2D};
196 use geometry_trait::Point as _;
197
198 type P = Point2D<f64, Cartesian>;
199
200 fn wgs84() -> Crs {
201 Crs::from_epsg(4326).unwrap()
202 }
203 fn mercator() -> Crs {
204 Crs::from_epsg(3857).unwrap()
205 }
206
207 #[test]
208 fn origin_maps_to_origin() {
209 let mut p = P::new(0.0, 0.0);
210 reproject(&mut p, &wgs84(), &mercator()).unwrap();
211 assert!(p.get::<0>().abs() < 1e-6);
212 assert!(p.get::<1>().abs() < 1e-6);
213 }
214
215 #[test]
216 fn known_point_web_mercator() {
217 let mut p = P::new(45.0_f64.to_radians(), 0.0);
220 reproject(&mut p, &wgs84(), &mercator()).unwrap();
221 let expected_x = 6_378_137.0 * core::f64::consts::FRAC_PI_4;
222 assert!((p.get::<0>() - expected_x).abs() < 1.0);
223 assert!(p.get::<1>().abs() < 1e-3, "y = {}", p.get::<1>());
224 }
225
226 #[test]
227 fn round_trip_returns_original() {
228 let mut p = P::new(0.3_f64, 0.8);
229 let orig = (p.get::<0>(), p.get::<1>());
230 reproject(&mut p, &wgs84(), &mercator()).unwrap();
231 reproject(&mut p, &mercator(), &wgs84()).unwrap();
232 assert!((p.get::<0>() - orig.0).abs() < 1e-9);
233 assert!((p.get::<1>() - orig.1).abs() < 1e-9);
234 }
235
236 #[test]
237 fn reproject_a_linestring() {
238 let mut ls: Linestring<P> =
239 Linestring(vec![P::new(0.0, 0.0), P::new(0.1, 0.1), P::new(0.2, 0.05)]);
240 reproject(&mut ls, &wgs84(), &mercator()).unwrap();
241 assert!(ls.0[0].get::<0>().abs() < 1e-6);
243 assert!(ls.0[1].get::<0>() > 1000.0);
244 }
245
246 #[test]
249 fn reproject_a_ring() {
250 use geometry_model::Ring;
251 let mut ring: Ring<P> = Ring::from_vec(vec![
252 P::new(0.0, 0.0),
253 P::new(0.1, 0.0),
254 P::new(0.1, 0.1),
255 P::new(0.0, 0.0),
256 ]);
257 reproject(&mut ring, &wgs84(), &mercator()).unwrap();
258 assert!(ring.0[0].get::<0>().abs() < 1e-6); assert!(ring.0[1].get::<0>() > 1000.0); }
261
262 #[test]
264 fn reproject_a_polygon_with_hole() {
265 use geometry_model::{Polygon, Ring};
266 let outer: Ring<P> = Ring::from_vec(vec![
267 P::new(0.0, 0.0),
268 P::new(0.2, 0.0),
269 P::new(0.2, 0.2),
270 P::new(0.0, 0.0),
271 ]);
272 let hole: Ring<P> = Ring::from_vec(vec![
273 P::new(0.05, 0.05),
274 P::new(0.1, 0.05),
275 P::new(0.1, 0.1),
276 P::new(0.05, 0.05),
277 ]);
278 let mut pg = Polygon::with_inners(outer, vec![hole]);
279 reproject(&mut pg, &wgs84(), &mercator()).unwrap();
280 assert!(pg.outer.0[0].get::<0>().abs() < 1e-6);
283 assert!(pg.inners[0].0[0].get::<0>() > 1000.0);
284 }
285
286 #[test]
288 fn reproject_a_multipolygon() {
289 use geometry_model::{MultiPolygon, Polygon, Ring};
290 let member = Polygon::with_inners(
291 Ring::from_vec(vec![
292 P::new(0.1, 0.1),
293 P::new(0.2, 0.1),
294 P::new(0.2, 0.2),
295 P::new(0.1, 0.1),
296 ]),
297 vec![],
298 );
299 let mut mpg: MultiPolygon<Polygon<P>> = MultiPolygon(vec![member.clone(), member]);
300 reproject(&mut mpg, &wgs84(), &mercator()).unwrap();
301 let x0 = mpg.0[0].outer.0[0].get::<0>();
304 let x1 = mpg.0[1].outer.0[0].get::<0>();
305 assert!(x0 > 1000.0);
306 assert!((x0 - x1).abs() < 1e-9);
307 }
308
309 #[test]
310 fn failed_reproject_leaves_geometry_unchanged() {
311 let mut ls: Linestring<P> = Linestring(vec![
317 P::new(0.1, 0.1),
318 P::new(0.0, core::f64::consts::FRAC_PI_2),
319 ]);
320 let before: Vec<(u64, u64)> =
321 ls.0.iter()
322 .map(|p| (p.get::<0>().to_bits(), p.get::<1>().to_bits()))
323 .collect();
324 let result = reproject(&mut ls, &wgs84(), &mercator());
325 assert!(result.is_err(), "π/2 latitude must be rejected by proj4rs");
326 let after: Vec<(u64, u64)> =
327 ls.0.iter()
328 .map(|p| (p.get::<0>().to_bits(), p.get::<1>().to_bits()))
329 .collect();
330 assert_eq!(before, after, "geometry must be unchanged on Err");
331 }
332}