1use crate::proj::{
2 Proj, ProjectionTransform, SRS_WGS84_ESQUARED, SRS_WGS84_SEMIMAJOR, SRS_WGS84_SEMIMINOR,
3 TransformCoordinates,
4};
5use alloc::{vec, vec::Vec};
6use core::f64::consts::{FRAC_PI_2, PI, TAU};
7use libm::{atan, atan2, cos, sin, sqrt};
8
9pub fn check_not_wgs84(src: &ProjectionTransform, dest: &ProjectionTransform) -> bool {
11 let src_proj = src.proj.borrow();
12 let dest_proj = dest.proj.borrow();
13 !src_proj.datum_type.is_wgs84(&src_proj.datum_params)
14 || !dest_proj.datum_type.is_wgs84(&dest_proj.datum_params)
15}
16
17#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
19pub enum DatumType {
20 Param3 = 1,
22 Param7 = 2,
24 GridShift = 3,
26 WGS84 = 4,
28 #[default]
30 NoDatum = 5,
31}
32impl DatumType {
33 pub fn is_params(&self) -> bool {
35 matches!(self, DatumType::Param3 | DatumType::Param7)
36 }
37
38 pub fn is_wgs84(&self, params: &DatumParams) -> bool {
40 matches!(self, DatumType::WGS84) || params.is_wgs84()
41 }
42}
43
44#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
46pub enum DatumParams {
47 Param3(f64, f64, f64),
49 Param7(f64, f64, f64, f64, f64, f64, f64),
51}
52impl Default for DatumParams {
53 fn default() -> Self {
54 DatumParams::Param3(0.0, 0.0, 0.0)
55 }
56}
57impl DatumParams {
58 pub fn to_vec(&self) -> Vec<f64> {
60 match self {
61 DatumParams::Param3(x, y, z) => vec![*x, *y, *z],
62 DatumParams::Param7(x, y, z, rx, ry, rz, s) => vec![*x, *y, *z, *rx, *ry, *rz, *s],
63 }
64 }
65 pub fn from_vec(v: Vec<f64>) -> DatumParams {
67 if v.len() == 3 {
68 DatumParams::Param3(v[0], v[1], v[2])
69 } else {
70 DatumParams::Param7(v[0], v[1], v[2], v[3], v[4], v[5], v[6])
71 }
72 }
73 pub fn is_wgs84(&self) -> bool {
75 match self {
76 Self::Param3(p3_1, p3_2, p3_3) => *p3_1 == 0. && *p3_2 == 0. && *p3_3 == 0.,
77 Self::Param7(p7_1, p7_2, p7_3, p7_4, p7_5, p7_6, p7_7) => {
78 *p7_1 == 0.
79 && *p7_2 == 0.
80 && *p7_3 == 0.
81 && *p7_4 == 0.
82 && *p7_5 == 0.
83 && *p7_6 == 0.
84 && *p7_7 == 0.
85 }
86 }
87 }
88 pub fn to_wgs84<P: TransformCoordinates>(&self, p: &mut P) {
96 match self {
97 Self::Param3(p3_x, p3_y, p3_z) => {
98 p.set_x(p.x() + *p3_x);
99 p.set_y(p.y() + *p3_y);
100 p.set_z(p.z() - *p3_z);
101 }
102 Self::Param7(p7_dx, p7_dy, p7_dz, p7_rx, p7_ry, p7_rz, p7_s) => {
103 let z = p.z();
104 p.set_x(p7_s * (p.x() - p7_rz * p.y() + p7_ry * z) + *p7_dx);
105 p.set_y(p7_s * (p7_rz * p.x() + p.y() - p7_rx * z) + *p7_dy);
106 p.set_z(p7_s * (-p7_ry * p.x() + p7_rx * p.y() + z) + *p7_dz);
107 }
108 }
109 }
110
111 pub fn from_wgs84<P: TransformCoordinates>(&self, p: &mut P) {
115 match self {
116 Self::Param3(p3_x, p3_y, p3_z) => {
117 p.set_x(p.x() - *p3_x);
118 p.set_y(p.y() - *p3_y);
119 p.set_z(p.z() - *p3_z);
120 }
121 Self::Param7(p7_dx, p7_dy, p7_dz, p7_rx, p7_ry, p7_rz, p7_s) => {
122 let z = p.z();
123 let x_tmp = (p.x() - *p7_dx) / *p7_s;
124 let y_tmp = (p.y() - *p7_dy) / *p7_s;
125 let z_tmp = (z - *p7_dz) / *p7_s;
126 p.set_x(x_tmp + *p7_rz * y_tmp - *p7_ry * z_tmp);
127 p.set_y(-p7_rz * x_tmp + y_tmp + *p7_rx * z_tmp);
128 p.set_z(*p7_ry * x_tmp - *p7_rx * y_tmp + z_tmp);
129 }
130 }
131 }
132}
133
134pub fn datum_transform<P: TransformCoordinates>(point: &mut P, source: &Proj, dest: &Proj) {
139 if source.datum_params == dest.datum_params
141 || source.datum_type == DatumType::NoDatum
142 || dest.datum_type == DatumType::NoDatum
143 {
144 return;
145 }
146
147 let mut source_a = source.a;
149 let mut source_es = source.es;
150 if source.datum_type == DatumType::GridShift {
151 source_a = SRS_WGS84_SEMIMAJOR;
155 source_es = SRS_WGS84_ESQUARED;
156 }
157
158 let mut dest_a = dest.a;
159 let mut dest_b = dest.b;
160 let mut dest_es = dest.es;
161 if dest.datum_type == DatumType::GridShift {
162 dest_a = SRS_WGS84_SEMIMAJOR;
163 dest_b = SRS_WGS84_SEMIMINOR;
164 dest_es = SRS_WGS84_ESQUARED;
165 }
166
167 if source_es == dest_es
169 && source_a == dest_a
170 && !source.datum_type.is_params()
171 && !dest.datum_type.is_params()
172 {
173 return;
174 }
175
176 geodetic_to_geocentric(point, source_es, source_a);
178 if source.datum_type.is_params() {
180 source.datum_params.to_wgs84(point);
182 }
183 if dest.datum_type.is_params() {
184 dest.datum_params.from_wgs84(point);
186 }
187 geocentric_to_geodetic(point, dest_es, dest_a, dest_b);
189
190 if dest.datum_type == DatumType::GridShift {
191 }
194}
195
196pub fn geodetic_to_geocentric<P: TransformCoordinates>(p: &mut P, es: f64, a: f64) {
210 let mut longitude = p.x();
211 let mut latitude = p.y();
212 let height = p.z(); if latitude < -FRAC_PI_2 && latitude > -1.001 * FRAC_PI_2 {
217 latitude = -FRAC_PI_2;
218 } else if latitude > FRAC_PI_2 && latitude < 1.001 * FRAC_PI_2 {
219 latitude = FRAC_PI_2;
220 } else if !(-FRAC_PI_2..=FRAC_PI_2).contains(&latitude) {
221 panic!("geocent:lat out of range: {}", latitude);
222 }
223
224 if longitude > PI {
225 longitude -= TAU;
226 }
227 let sin_lat = sin(latitude); let cos_lat = cos(latitude); let sin2_lat = sin_lat * sin_lat; let r_n = a / sqrt(1.0 - es * sin2_lat); p.set_x((r_n + height) * cos_lat * cos(longitude));
233 p.set_y((r_n + height) * cos_lat * sin(longitude));
234 p.set_z((r_n * (1. - es) + height) * sin_lat);
235}
236
237pub fn geocentric_to_geodetic<P: TransformCoordinates>(point: &mut P, es: f64, a: f64, _b: f64) {
239 let genau = 1e-12;
242 let genau2 = genau * genau;
243 let maxiter = 30;
244
245 let mut rx;
246 let mut rk;
247 let mut rn; let mut cphi0; let mut sphi0; let mut cphi; let mut sphi; let mut sdphi; let mut iter; let x = point.x();
256 let y = point.y();
257 let z = point.z(); let p = sqrt(x * x + y * y); let rr = sqrt(x * x + y * y + z * z); let longitude = if p / a < genau { 0.0 } else { atan2(y, x) };
261 let mut height;
262
263 let ct = z / rr; let st = p / rr; rx = 1.0 / sqrt(1.0 - es * (2.0 - es) * st * st);
274 cphi0 = st * (1.0 - es) * rx;
275 sphi0 = ct * rx;
276 iter = 0;
277
278 loop {
281 iter += 1;
282 rn = a / sqrt(1.0 - es * sphi0 * sphi0);
283
284 height = p * cphi0 + z * sphi0 - rn * (1.0 - es * sphi0 * sphi0);
286
287 rk = (es * rn) / (rn + height);
288 rx = 1.0 / sqrt(1.0 - rk * (2.0 - rk) * st * st);
289 cphi = st * (1.0 - rk) * rx;
290 sphi = ct * rx;
291 sdphi = sphi * cphi0 - cphi * sphi0;
292 cphi0 = cphi;
293 sphi0 = sphi;
294 if sdphi * sdphi <= genau2 && iter >= maxiter {
295 break;
296 }
297 }
298
299 let latitude = atan(sphi / cphi.abs());
301
302 point.set_x(longitude);
303 point.set_y(latitude);
304 point.set_z(height);
305}
306
307#[derive(Debug, Copy, Clone, PartialEq)]
309pub struct ToWGS84Datum {
310 datum_params: DatumParams,
311 ellipse: &'static str,
312 datum_name: &'static str,
313}
314
315pub const TO_WGS84: ToWGS84Datum = ToWGS84Datum {
317 datum_params: DatumParams::Param7(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
318 ellipse: "WGS84",
319 datum_name: "WGS84",
320};
321
322pub const CH1903: ToWGS84Datum = ToWGS84Datum {
324 datum_params: DatumParams::Param3(674.374, 15.056, 405.346),
325 ellipse: "bessel",
326 datum_name: "swiss",
327};
328
329pub const GGRS87: ToWGS84Datum = ToWGS84Datum {
331 datum_params: DatumParams::Param3(-199.87, 74.79, 246.62),
332 ellipse: "GRS80",
333 datum_name: "Greek_Geodetic_Reference_System_1987",
334};
335
336pub const NAD83: ToWGS84Datum = ToWGS84Datum {
338 datum_params: DatumParams::Param3(0.0, 0.0, 0.0),
339 ellipse: "GRS80",
340 datum_name: "North_American_Datum_1983",
341};
342
343pub const POTSDAM: ToWGS84Datum = ToWGS84Datum {
345 datum_params: DatumParams::Param7(598.1, 73.7, 418.2, 0.202, 0.045, -2.455, 6.7),
346 ellipse: "bessel",
347 datum_name: "Potsdam Rauenberg 1950 DHDN",
348};
349
350pub const CARTHAGE: ToWGS84Datum = ToWGS84Datum {
352 datum_params: DatumParams::Param3(-263.0, 6.0, 431.0),
353 ellipse: "clark80",
354 datum_name: "Carthage 1934 Tunisia",
355};
356
357pub const HERMANNSKOGEL: ToWGS84Datum = ToWGS84Datum {
359 datum_params: DatumParams::Param7(577.326, 90.129, 463.919, 5.137, 1.474, 5.297, 2.4232),
360 ellipse: "bessel",
361 datum_name: "Hermannskogel",
362};
363
364pub const MILITARGEOGRAPHISCHE_INSTITUT: ToWGS84Datum = ToWGS84Datum {
366 datum_params: DatumParams::Param7(577.326, 90.129, 463.919, 5.137, 1.474, 5.297, 2.4232),
367 ellipse: "bessel",
368 datum_name: "Militar-Geographische Institut",
369};
370
371pub const OSNI52: ToWGS84Datum = ToWGS84Datum {
373 datum_params: DatumParams::Param7(482.53, -130.596, 564.557, -1.042, -0.214, -0.631, 8.15),
374 ellipse: "airy",
375 datum_name: "Irish National",
376};
377
378pub const IRE65: ToWGS84Datum = ToWGS84Datum {
380 datum_params: DatumParams::Param7(482.53, -130.596, 564.557, -1.042, -0.214, -0.631, 8.15),
381 ellipse: "mod_airy",
382 datum_name: "Ireland 1965",
383};
384
385pub const RASSADIRAN: ToWGS84Datum = ToWGS84Datum {
387 datum_params: DatumParams::Param3(-133.63, -157.5, -158.62),
388 ellipse: "intl",
389 datum_name: "Rassadiran",
390};
391
392pub const NZGD49: ToWGS84Datum = ToWGS84Datum {
394 datum_params: DatumParams::Param7(59.47, -5.04, 187.44, 0.47, -0.1, 1.024, -4.5993),
395 ellipse: "intl",
396 datum_name: "New Zealand Geodetic Datum 1949",
397};
398
399pub const OSGB36: ToWGS84Datum = ToWGS84Datum {
401 datum_params: DatumParams::Param7(446.448, -125.157, 542.06, 0.1502, 0.247, 0.8421, -20.4894),
402 ellipse: "airy",
403 datum_name: "Airy 1830",
404};
405
406pub const S_JTSK: ToWGS84Datum = ToWGS84Datum {
408 datum_params: DatumParams::Param3(589.0, 76.0, 480.0),
409 ellipse: "bessel",
410 datum_name: "S-JTSK (Ferro)",
411};
412
413pub const BEDUARAM: ToWGS84Datum = ToWGS84Datum {
415 datum_params: DatumParams::Param3(-106.0, -87.0, 188.0),
416 ellipse: "clrk80",
417 datum_name: "Beduaram",
418};
419
420pub const GUNUNG_SEGARA: ToWGS84Datum = ToWGS84Datum {
422 datum_params: DatumParams::Param3(-403.0, 684.0, 41.0),
423 ellipse: "bessel",
424 datum_name: "Gunung Segara Jakarta",
425};
426
427pub const RNB72: ToWGS84Datum = ToWGS84Datum {
429 datum_params: DatumParams::Param7(
430 106.869, -52.2978, 103.724, -0.33657, 0.456955, -1.84218, 1.0,
431 ),
432 ellipse: "intl",
433 datum_name: "Reseau National Belge 1972",
434};
435
436#[cfg_attr(feature = "nightly", coverage(off))]
438pub fn get_datum(name: &str) -> Option<ToWGS84Datum> {
439 let name = name.to_uppercase().replace("_", "");
441 match name.as_str() {
442 "WGS84" => Some(TO_WGS84),
443 "CH1903" => Some(CH1903),
444 "GGRS87" => Some(GGRS87),
445 "NAD83" => Some(NAD83),
446 "RASSADIRAN" => Some(RASSADIRAN),
447 "NZGD49" => Some(NZGD49),
448 "OSGB36" => Some(OSGB36),
449 "SJTSK" => Some(S_JTSK),
450 "BEDUARAM" => Some(BEDUARAM),
451 "POTSDAM" => Some(POTSDAM),
452 "CARTHAGE" => Some(CARTHAGE),
453 "HERMANNSKOGEL" => Some(HERMANNSKOGEL),
454 "MILITARGEOGRAPHISCHEINSTITUT" => Some(MILITARGEOGRAPHISCHE_INSTITUT),
455 "OSNI52" => Some(OSNI52),
456 "IRE65" => Some(IRE65),
457 "RNB72" => Some(RNB72),
458 "GUNUNG_SEGARA" => Some(GUNUNG_SEGARA),
459 _ => None,
460 }
461}