1use super::TransformCoordinates;
2use crate::proj::{
3 Direction, IoUnits, ProjJSON, ProjectionTransform, Step, adjlon, check_not_wgs84,
4 datum_transform, geocentric_latitude,
5};
6use alloc::{collections::BTreeMap, fmt::Debug, string::String};
7use core::f64::consts::FRAC_PI_2;
8
9#[derive(Debug, Default, Clone, PartialEq)]
47pub struct Transformer {
48 epsgs: BTreeMap<String, String>,
49 src: ProjectionTransform,
50 dest: ProjectionTransform,
51}
52impl Transformer {
53 #[allow(clippy::new_without_default)]
55 pub fn new() -> Self {
56 Self {
57 epsgs: BTreeMap::new(),
58 src: ProjectionTransform::wgs84(),
59 dest: ProjectionTransform::wgs84(),
60 }
61 }
62 pub fn forward<P: TransformCoordinates + Debug>(&self, p: &P) -> P {
64 let mut res = p.clone();
65 transform_point(&self.src, &self.dest, &mut res);
66 res
67 }
68 pub fn forward_mut<P: TransformCoordinates + Debug>(&self, p: &mut P) {
70 transform_point(&self.src, &self.dest, p);
71 }
72 pub fn inverse<P: TransformCoordinates + Debug>(&self, p: &P) -> P {
74 let mut res = p.clone();
75 transform_point(&self.dest, &self.src, &mut res);
76 res
77 }
78 pub fn inverse_mut<P: TransformCoordinates + Debug>(&self, p: &mut P) {
80 transform_point(&self.dest, &self.src, p);
81 }
82
83 pub fn insert_epsg_code(&mut self, code: String, value: String) {
95 self.epsgs.insert(code, value);
96 }
97
98 pub fn set_source(&mut self, code: String) {
103 self.src = self.build_transformer(code);
104 }
105
106 pub fn set_source_def(&mut self, def: ProjectionTransform) {
111 self.src = def;
112 }
113
114 pub fn set_destination(&mut self, code: String) {
119 self.dest = self.build_transformer(code);
120 }
121
122 pub fn set_destination_def(&mut self, def: ProjectionTransform) {
127 self.dest = def;
128 }
129
130 fn build_transformer(&mut self, mut code: String) -> ProjectionTransform {
138 if let Some(epsg) = self.epsgs.get(&code) {
139 code = epsg.clone();
140 }
141 if let Ok(json) = serde_json::from_str::<ProjJSON>(&code) {
143 return json.to_projection_transform();
144 }
145 ProjJSON::parse_wkt(&code).to_projection_transform()
147 }
148
149 pub fn get_epsg_code(&self, code: String) -> Option<String> {
151 self.epsgs.get(&code).cloned()
152 }
153}
154
155pub fn transform_point<P: TransformCoordinates + Debug>(
162 src: &ProjectionTransform,
163 dest: &ProjectionTransform,
164 point: &mut P,
165) {
166 if src == dest || (Step::same_step(&src.method, &dest.method)) {
168 return;
169 }
170 let has_z = point.has_z();
171
172 if check_not_wgs84(src, dest) {
175 transform_point(src, &ProjectionTransform::wgs84(), point);
176 }
177
178 transform_inv(src, point);
180 datum_transform(point, &src.proj.borrow(), &dest.proj.borrow());
182 transform_fwd(dest, point);
184
185 if !has_z {
186 point.set_z(0.0);
187 }
188}
189
190fn transform_inv<P: TransformCoordinates + Debug>(
191 proj_trans: &ProjectionTransform,
192 coords: &mut P,
193) {
194 transform_inv_prepare(proj_trans, coords);
195 proj_trans.method.inverse(coords);
196 transform_inv_finalize(proj_trans, coords);
197}
198
199fn transform_inv_prepare<P: TransformCoordinates + Debug>(
200 proj_trans: &ProjectionTransform,
201 coords: &mut P,
202) {
203 let proj = &proj_trans.proj.borrow();
204 if f64::INFINITY == coords.z() && proj_trans.helmert.is_some() {
206 coords.set_z(0.0);
207 }
208 if f64::INFINITY == coords.t() && proj_trans.helmert.is_some() {
209 coords.set_t(0.0);
210 }
211
212 if let Some(axisswap) = &proj_trans.axisswap {
213 axisswap.method.inverse(coords);
214 }
215
216 match proj.right {
218 IoUnits::CARTESIAN => {
220 let to_meter = proj.to_meter;
221 coords.set_x(coords.x() * to_meter);
222 coords.set_y(coords.y() * to_meter);
223 coords.set_z(coords.z() * to_meter);
224 if proj.is_geocent
225 && let Some(cart) = &proj_trans.cart
226 {
227 transform_inv(cart, coords);
228 }
229 }
230
231 IoUnits::PROJECTED | IoUnits::CLASSIC => {
232 let to_meter = proj.to_meter;
233 let vto_meter = proj.vto_meter;
234 coords.set_x(coords.x() * to_meter - proj.x0);
235 coords.set_y(coords.y() * to_meter - proj.y0);
237 coords.set_z(coords.z() * vto_meter - proj.z0);
238 if proj.right == IoUnits::PROJECTED {
239 return;
240 }
241
242 if proj.ra != 0. {
247 coords.set_x(coords.x() * proj.ra);
248 coords.set_y(coords.y() * proj.ra);
249 }
250 }
251
252 IoUnits::RADIANS => {
253 coords.set_z(coords.z() * proj.vto_meter - proj.z0);
254 }
255 _ => {}
256 }
257}
258
259fn transform_inv_finalize<P: TransformCoordinates + Debug>(
260 proj_trans: &ProjectionTransform,
261 coords: &mut P,
262) {
263 let proj = &proj_trans.proj.borrow();
264 if proj.left == IoUnits::RADIANS {
265 coords.set_lam(coords.lam() + proj.from_greenwich + proj.lam0);
267
268 if !proj.over {
270 coords.set_lam(adjlon(coords.lam()));
271 }
272
273 if let Some(vgridshift) = &proj_trans.vgridshift {
274 transform_inv(vgridshift, coords);
276 }
277 if let Some(hgridshift) = &proj_trans.hgridshift {
278 transform_fwd(hgridshift, coords);
280 } else if (proj_trans.cart_wgs84.is_some() && proj_trans.cart.is_some())
281 || proj_trans.helmert.is_some()
282 {
283 if let Some(cart) = &proj_trans.cart {
285 transform_fwd(cart, coords);
286 }
287 if let Some(helmert) = &proj_trans.helmert {
289 transform_fwd(helmert, coords);
290 }
291 if let Some(cart_wgs84) = &proj_trans.cart_wgs84 {
293 transform_inv(cart_wgs84, coords);
294 }
295 }
296
297 if proj.geoc {
299 geocentric_latitude(proj, Direction::FWD, coords);
300 }
301 }
302}
303
304fn transform_fwd<P: TransformCoordinates + Debug>(
305 proj_trans: &ProjectionTransform,
306 coords: &mut P,
307) {
308 transform_fwd_prepare(proj_trans, coords);
309 proj_trans.method.forward(coords);
310 transform_fwd_finalize(proj_trans, coords);
311}
312
313fn transform_fwd_prepare<P: TransformCoordinates + Debug>(
314 proj_trans: &ProjectionTransform,
315 coords: &mut P,
316) {
317 let proj = &proj_trans.proj.borrow();
318 if coords.z() == f64::INFINITY && proj_trans.helmert.is_some() {
320 coords.set_z(0.0);
321 }
322 if coords.t() == f64::INFINITY && proj_trans.helmert.is_some() {
323 coords.set_t(0.0);
324 }
325
326 if proj.left == IoUnits::RADIANS {
328 let t = (if coords.phi() < 0. { -coords.phi() } else { coords.phi() }) - FRAC_PI_2;
330 if t > 1e-12 || coords.lam() > 10. || coords.lam() < -10. {
331 panic!("Invalid latitude");
332 }
333 if coords.phi() > FRAC_PI_2 {
335 coords.set_phi(FRAC_PI_2);
336 }
337 if coords.phi() < -FRAC_PI_2 {
338 coords.set_phi(-FRAC_PI_2);
339 }
340 if proj.geoc {
342 geocentric_latitude(proj, Direction::INV, coords);
343 }
344 if !proj.over {
346 coords.set_lam(adjlon(coords.lam()));
347 }
348
349 if let Some(hgridshift) = &proj_trans.hgridshift {
350 transform_inv(hgridshift, coords);
351 } else if (proj_trans.cart_wgs84.is_some() && proj_trans.cart.is_some())
352 || proj_trans.helmert.is_some()
353 {
354 if let Some(cart_wgs84) = &proj_trans.cart_wgs84 {
356 transform_fwd(cart_wgs84, coords);
357 }
358 if let Some(helmert) = &proj_trans.helmert {
360 transform_inv(helmert, coords);
361 }
362 if let Some(cart) = &proj_trans.cart {
364 transform_inv(cart, coords);
365 }
366 }
367 if let Some(vgridshift) = &proj_trans.vgridshift {
369 transform_fwd(vgridshift, coords);
370 }
371 coords.set_lam((coords.lam() - proj.from_greenwich) - proj.lam0);
373
374 if !proj.over {
376 coords.set_lam(adjlon(coords.lam()));
377 }
378
379 return;
380 }
381
382 if proj.left == IoUnits::CARTESIAN
384 && let Some(helmert) = &proj_trans.helmert
385 {
386 transform_inv(helmert, coords);
387 }
388}
389
390fn transform_fwd_finalize<P: TransformCoordinates + Debug>(
391 proj_trans: &ProjectionTransform,
392 coords: &mut P,
393) {
394 let proj = &proj_trans.proj.borrow();
395 match proj.right {
396 IoUnits::CARTESIAN => {
398 if proj.is_geocent
399 && let Some(cart) = &proj_trans.cart
400 {
401 transform_fwd(cart, coords);
402 }
403 coords.set_x(coords.x() * proj.fr_meter);
404 coords.set_y(coords.y() * proj.fr_meter);
405 coords.set_z(coords.z() * proj.vfr_meter);
406 }
407
408 IoUnits::CLASSIC => {
410 if proj.a != 0. {
411 coords.set_x(coords.x() * proj.a);
412 coords.set_y(coords.y() * proj.a);
413 }
414 coords.set_x(proj.fr_meter * (coords.x() + proj.x0));
415 coords.set_y(proj.fr_meter * (coords.y() + proj.y0));
416 coords.set_z(proj.vfr_meter * (coords.z() + proj.z0));
417 }
418
419 IoUnits::PROJECTED => {
421 coords.set_x(proj.fr_meter * (coords.x() + proj.x0));
422 coords.set_y(proj.fr_meter * (coords.y() + proj.y0));
423 coords.set_z(proj.vfr_meter * (coords.z() + proj.z0));
424 }
425
426 IoUnits::RADIANS => {
427 coords.set_z(proj.vfr_meter * (coords.z() + proj.z0));
428 }
436 _ => {}
437 }
438
439 if let Some(axisswap) = &proj_trans.axisswap {
440 axisswap.method.forward(coords);
441 }
442}