1use crate::ellipsoid::{self, Ellipsoid};
2use crate::error::{Error, Result};
3use crate::grid::GridDefinition;
4use smallvec::SmallVec;
5
6#[derive(Debug, Clone)]
8pub struct Datum {
9 ellipsoid: Ellipsoid,
11 to_wgs84: DatumToWgs84,
13}
14
15impl Datum {
16 pub fn new(ellipsoid: Ellipsoid, to_wgs84: DatumToWgs84) -> Result<Self> {
18 to_wgs84.validate()?;
19 Ok(Self {
20 ellipsoid,
21 to_wgs84,
22 })
23 }
24
25 const fn new_unchecked(ellipsoid: Ellipsoid, to_wgs84: DatumToWgs84) -> Self {
26 Self {
27 ellipsoid,
28 to_wgs84,
29 }
30 }
31
32 pub const fn ellipsoid(&self) -> Ellipsoid {
34 self.ellipsoid
35 }
36
37 pub const fn to_wgs84(&self) -> &DatumToWgs84 {
39 &self.to_wgs84
40 }
41
42 pub fn is_wgs84_compatible(&self) -> bool {
44 matches!(self.to_wgs84, DatumToWgs84::Identity)
45 }
46
47 pub fn has_known_wgs84_transform(&self) -> bool {
49 !matches!(self.to_wgs84, DatumToWgs84::Unknown)
50 }
51
52 pub fn uses_grid_shift(&self) -> bool {
54 self.to_wgs84.uses_grid_shift()
55 }
56
57 pub fn helmert_to_wgs84(&self) -> Option<&HelmertParams> {
59 match &self.to_wgs84 {
60 DatumToWgs84::Helmert(params) => Some(params),
61 DatumToWgs84::Identity | DatumToWgs84::GridShift(_) | DatumToWgs84::Unknown => None,
62 }
63 }
64
65 pub fn approximate_helmert_to(&self, target: &Datum) -> Option<HelmertParams> {
66 if self.uses_grid_shift() || target.uses_grid_shift() {
67 return None;
68 }
69 if !self.has_known_wgs84_transform() || !target.has_known_wgs84_transform() {
70 return None;
71 }
72
73 let source_to_wgs84 = self
74 .helmert_to_wgs84()
75 .copied()
76 .unwrap_or_else(HelmertParams::identity);
77 let wgs84_to_target = target
78 .helmert_to_wgs84()
79 .map(HelmertParams::inverse)
80 .unwrap_or_else(HelmertParams::identity);
81
82 source_to_wgs84.compose_approx(&wgs84_to_target).ok()
83 }
84
85 pub fn same_datum(&self, other: &Datum) -> bool {
87 let same_ellipsoid =
88 (self.ellipsoid.semi_major_axis() - other.ellipsoid.semi_major_axis()).abs() < 1e-6
89 && (self.ellipsoid.flattening() - other.ellipsoid.flattening()).abs() < 1e-12;
90
91 match (&self.to_wgs84, &other.to_wgs84) {
92 (DatumToWgs84::Identity, DatumToWgs84::Identity) => same_ellipsoid,
93 (DatumToWgs84::Helmert(a), DatumToWgs84::Helmert(b)) => {
94 same_ellipsoid && a.approx_eq(b)
95 }
96 (DatumToWgs84::GridShift(a), DatumToWgs84::GridShift(b)) => same_ellipsoid && a == b,
97 (DatumToWgs84::Unknown, DatumToWgs84::Unknown) => false,
98 _ => false,
99 }
100 }
101}
102
103#[derive(Debug, Clone, PartialEq)]
105pub enum DatumToWgs84 {
106 Identity,
108 Helmert(HelmertParams),
110 GridShift(Box<DatumGridShift>),
112 Unknown,
114}
115
116impl DatumToWgs84 {
117 pub fn uses_grid_shift(&self) -> bool {
118 matches!(self, DatumToWgs84::GridShift(shift) if shift.uses_grid_shift())
119 }
120
121 pub fn validate(&self) -> Result<()> {
122 match self {
123 Self::Helmert(params) => params.validate(),
124 Self::Identity | Self::GridShift(_) | Self::Unknown => Ok(()),
125 }
126 }
127}
128
129#[derive(Debug, Clone, PartialEq)]
131pub struct DatumGridShift {
132 entries: SmallVec<[DatumGridShiftEntry; 4]>,
133}
134
135impl DatumGridShift {
136 pub fn new(entries: SmallVec<[DatumGridShiftEntry; 4]>) -> Self {
137 Self { entries }
138 }
139
140 pub fn from_vec(entries: Vec<DatumGridShiftEntry>) -> Self {
141 Self {
142 entries: SmallVec::from_vec(entries),
143 }
144 }
145
146 pub fn entries(&self) -> &[DatumGridShiftEntry] {
147 &self.entries
148 }
149
150 pub fn uses_grid_shift(&self) -> bool {
151 self.entries
152 .iter()
153 .any(|entry| matches!(entry, DatumGridShiftEntry::Grid { .. }))
154 }
155}
156
157#[derive(Debug, Clone, PartialEq)]
159pub enum DatumGridShiftEntry {
160 Grid {
162 definition: GridDefinition,
163 optional: bool,
164 },
165 Null,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq)]
178pub struct HelmertParams {
179 dx: f64,
181 dy: f64,
183 dz: f64,
185 rx: f64,
187 ry: f64,
189 rz: f64,
191 ds: f64,
193}
194
195impl HelmertParams {
196 pub fn new(dx: f64, dy: f64, dz: f64, rx: f64, ry: f64, rz: f64, ds: f64) -> Result<Self> {
198 let params = Self::new_unchecked(dx, dy, dz, rx, ry, rz, ds);
199 params.validate()?;
200 Ok(params)
201 }
202
203 pub fn translation(dx: f64, dy: f64, dz: f64) -> Result<Self> {
205 Self::new(dx, dy, dz, 0.0, 0.0, 0.0, 0.0)
206 }
207
208 pub const fn identity() -> Self {
210 Self::new_unchecked(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
211 }
212
213 const fn translation_unchecked(dx: f64, dy: f64, dz: f64) -> Self {
214 Self::new_unchecked(dx, dy, dz, 0.0, 0.0, 0.0, 0.0)
215 }
216
217 const fn new_unchecked(dx: f64, dy: f64, dz: f64, rx: f64, ry: f64, rz: f64, ds: f64) -> Self {
218 Self {
219 dx,
220 dy,
221 dz,
222 rx,
223 ry,
224 rz,
225 ds,
226 }
227 }
228
229 pub const fn dx(&self) -> f64 {
230 self.dx
231 }
232
233 pub const fn dy(&self) -> f64 {
234 self.dy
235 }
236
237 pub const fn dz(&self) -> f64 {
238 self.dz
239 }
240
241 pub const fn rx(&self) -> f64 {
242 self.rx
243 }
244
245 pub const fn ry(&self) -> f64 {
246 self.ry
247 }
248
249 pub const fn rz(&self) -> f64 {
250 self.rz
251 }
252
253 pub const fn ds(&self) -> f64 {
254 self.ds
255 }
256
257 pub fn validate(&self) -> Result<()> {
258 if self.dx.is_finite()
259 && self.dy.is_finite()
260 && self.dz.is_finite()
261 && self.rx.is_finite()
262 && self.ry.is_finite()
263 && self.rz.is_finite()
264 && self.ds.is_finite()
265 {
266 return Ok(());
267 }
268
269 Err(Error::InvalidDefinition(
270 "Helmert parameters must be finite".into(),
271 ))
272 }
273
274 pub fn inverse(&self) -> Self {
276 Self {
277 dx: -self.dx,
278 dy: -self.dy,
279 dz: -self.dz,
280 rx: -self.rx,
281 ry: -self.ry,
282 rz: -self.rz,
283 ds: -self.ds,
284 }
285 }
286
287 pub fn compose_approx(&self, next: &Self) -> Result<Self> {
288 Self::new(
289 self.dx + next.dx,
290 self.dy + next.dy,
291 self.dz + next.dz,
292 self.rx + next.rx,
293 self.ry + next.ry,
294 self.rz + next.rz,
295 self.ds + next.ds,
296 )
297 }
298
299 fn approx_eq(&self, other: &Self) -> bool {
300 (self.dx - other.dx).abs() < 1e-6
301 && (self.dy - other.dy).abs() < 1e-6
302 && (self.dz - other.dz).abs() < 1e-6
303 && (self.rx - other.rx).abs() < 1e-9
304 && (self.ry - other.ry).abs() < 1e-9
305 && (self.rz - other.rz).abs() < 1e-9
306 && (self.ds - other.ds).abs() < 1e-9
307 }
308}
309
310pub const WGS84: Datum = Datum::new_unchecked(ellipsoid::WGS84, DatumToWgs84::Identity);
316
317pub const NAD83: Datum = Datum::new_unchecked(ellipsoid::GRS80, DatumToWgs84::Identity);
319
320pub const NAD27: Datum = Datum::new_unchecked(
323 ellipsoid::CLARKE1866,
324 DatumToWgs84::Helmert(HelmertParams::translation_unchecked(-8.0, 160.0, 176.0)),
325);
326
327pub const ETRS89: Datum = Datum::new_unchecked(ellipsoid::GRS80, DatumToWgs84::Identity);
330
331pub const OSGB36: Datum = Datum::new_unchecked(
333 ellipsoid::AIRY1830,
334 DatumToWgs84::Helmert(HelmertParams::new_unchecked(
335 446.448, -125.157, 542.060, 0.1502, 0.2470, 0.8421, -20.4894,
336 )),
337);
338
339pub const PULKOVO1942: Datum = Datum::new_unchecked(
341 ellipsoid::KRASSOWSKY,
342 DatumToWgs84::Helmert(HelmertParams::translation_unchecked(23.92, -141.27, -80.9)),
343);
344
345pub const ED50: Datum = Datum::new_unchecked(
347 ellipsoid::INTL1924,
348 DatumToWgs84::Helmert(HelmertParams::translation_unchecked(-87.0, -98.0, -121.0)),
349);
350
351pub const TOKYO: Datum = Datum::new_unchecked(
353 ellipsoid::BESSEL1841,
354 DatumToWgs84::Helmert(HelmertParams::translation_unchecked(
355 -146.414, 507.337, 680.507,
356 )),
357);
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn wgs84_is_wgs84_compatible() {
365 assert!(WGS84.is_wgs84_compatible());
366 assert!(NAD83.is_wgs84_compatible());
367 assert!(ETRS89.is_wgs84_compatible());
368 }
369
370 #[test]
371 fn nad27_is_not_wgs84_compatible() {
372 assert!(!NAD27.is_wgs84_compatible());
373 assert!(!OSGB36.is_wgs84_compatible());
374 }
375
376 #[test]
377 fn same_datum_identity() {
378 assert!(WGS84.same_datum(&WGS84));
379 assert!(NAD27.same_datum(&NAD27));
380 }
381
382 #[test]
383 fn different_datums() {
384 assert!(!WGS84.same_datum(&NAD27));
385 assert!(!NAD27.same_datum(&OSGB36));
386 }
387
388 #[test]
389 fn unknown_datums_are_not_collapsed_by_ellipsoid() {
390 let a = Datum::new(ellipsoid::WGS84, DatumToWgs84::Unknown).unwrap();
391 let b = Datum::new(ellipsoid::WGS84, DatumToWgs84::Unknown).unwrap();
392
393 assert!(!a.same_datum(&b));
394 }
395
396 #[test]
397 fn helmert_inverse_negates() {
398 let h = HelmertParams::new(1.0, 2.0, 3.0, 0.1, 0.2, 0.3, 0.5).unwrap();
399 let inv = h.inverse();
400 assert_eq!(inv.dx(), -1.0);
401 assert_eq!(inv.rx(), -0.1);
402 assert_eq!(inv.ds(), -0.5);
403 }
404
405 #[test]
406 fn helmert_params_reject_non_finite_values() {
407 let err = HelmertParams::new(f64::NAN, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0).unwrap_err();
408 assert!(matches!(err, Error::InvalidDefinition(_)), "got {err}");
409
410 let err = HelmertParams::new(0.0, 0.0, 0.0, 0.0, 0.0, f64::INFINITY, 0.0).unwrap_err();
411 assert!(err.to_string().contains("Helmert parameters"), "{err}");
412 }
413}