1use crate::ellipsoid::{self, Ellipsoid};
2
3#[derive(Debug, Clone, Copy)]
5pub struct Datum {
6 pub ellipsoid: Ellipsoid,
8 pub to_wgs84: Option<HelmertParams>,
11}
12
13impl Datum {
14 pub fn is_wgs84_compatible(&self) -> bool {
16 self.to_wgs84.is_none()
17 }
18
19 pub fn same_datum(&self, other: &Datum) -> bool {
21 let same_ellipsoid = (self.ellipsoid.a - other.ellipsoid.a).abs() < 1e-6
24 && (self.ellipsoid.f - other.ellipsoid.f).abs() < 1e-12;
25
26 match (&self.to_wgs84, &other.to_wgs84) {
27 (None, None) => same_ellipsoid,
28 (Some(a), Some(b)) => same_ellipsoid && a.approx_eq(b),
29 _ => false,
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy)]
43pub struct HelmertParams {
44 pub dx: f64,
46 pub dy: f64,
48 pub dz: f64,
50 pub rx: f64,
52 pub ry: f64,
54 pub rz: f64,
56 pub ds: f64,
58}
59
60impl HelmertParams {
61 pub const fn translation(dx: f64, dy: f64, dz: f64) -> Self {
63 Self {
64 dx,
65 dy,
66 dz,
67 rx: 0.0,
68 ry: 0.0,
69 rz: 0.0,
70 ds: 0.0,
71 }
72 }
73
74 pub fn inverse(&self) -> Self {
76 Self {
77 dx: -self.dx,
78 dy: -self.dy,
79 dz: -self.dz,
80 rx: -self.rx,
81 ry: -self.ry,
82 rz: -self.rz,
83 ds: -self.ds,
84 }
85 }
86
87 fn approx_eq(&self, other: &Self) -> bool {
88 (self.dx - other.dx).abs() < 1e-6
89 && (self.dy - other.dy).abs() < 1e-6
90 && (self.dz - other.dz).abs() < 1e-6
91 && (self.rx - other.rx).abs() < 1e-9
92 && (self.ry - other.ry).abs() < 1e-9
93 && (self.rz - other.rz).abs() < 1e-9
94 && (self.ds - other.ds).abs() < 1e-9
95 }
96}
97
98pub const WGS84: Datum = Datum {
104 ellipsoid: ellipsoid::WGS84,
105 to_wgs84: None,
106};
107
108pub const NAD83: Datum = Datum {
110 ellipsoid: ellipsoid::GRS80,
111 to_wgs84: None,
112};
113
114pub const NAD27: Datum = Datum {
117 ellipsoid: ellipsoid::CLARKE1866,
118 to_wgs84: Some(HelmertParams::translation(-8.0, 160.0, 176.0)),
119};
120
121pub const ETRS89: Datum = Datum {
124 ellipsoid: ellipsoid::GRS80,
125 to_wgs84: None,
126};
127
128pub const OSGB36: Datum = Datum {
130 ellipsoid: ellipsoid::AIRY1830,
131 to_wgs84: Some(HelmertParams {
132 dx: 446.448,
133 dy: -125.157,
134 dz: 542.060,
135 rx: 0.1502,
136 ry: 0.2470,
137 rz: 0.8421,
138 ds: -20.4894,
139 }),
140};
141
142pub const PULKOVO1942: Datum = Datum {
144 ellipsoid: ellipsoid::KRASSOWSKY,
145 to_wgs84: Some(HelmertParams::translation(23.92, -141.27, -80.9)),
146};
147
148pub const ED50: Datum = Datum {
150 ellipsoid: ellipsoid::INTL1924,
151 to_wgs84: Some(HelmertParams::translation(-87.0, -98.0, -121.0)),
152};
153
154pub const TOKYO: Datum = Datum {
156 ellipsoid: ellipsoid::BESSEL1841,
157 to_wgs84: Some(HelmertParams::translation(-146.414, 507.337, 680.507)),
158};
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn wgs84_is_wgs84_compatible() {
166 assert!(WGS84.is_wgs84_compatible());
167 assert!(NAD83.is_wgs84_compatible());
168 assert!(ETRS89.is_wgs84_compatible());
169 }
170
171 #[test]
172 fn nad27_is_not_wgs84_compatible() {
173 assert!(!NAD27.is_wgs84_compatible());
174 assert!(!OSGB36.is_wgs84_compatible());
175 }
176
177 #[test]
178 fn same_datum_identity() {
179 assert!(WGS84.same_datum(&WGS84));
180 assert!(NAD27.same_datum(&NAD27));
181 }
182
183 #[test]
184 fn different_datums() {
185 assert!(!WGS84.same_datum(&NAD27));
186 assert!(!NAD27.same_datum(&OSGB36));
187 }
188
189 #[test]
190 fn helmert_inverse_negates() {
191 let h = HelmertParams {
192 dx: 1.0,
193 dy: 2.0,
194 dz: 3.0,
195 rx: 0.1,
196 ry: 0.2,
197 rz: 0.3,
198 ds: 0.5,
199 };
200 let inv = h.inverse();
201 assert_eq!(inv.dx, -1.0);
202 assert_eq!(inv.rx, -0.1);
203 assert_eq!(inv.ds, -0.5);
204 }
205}