miniproj_ops/ops/
ellipsoid.rs1use crate::PseudoSerialize;
4
5#[derive(Copy, Clone, Debug)]
7pub struct Ellipsoid {
8    pub a: f64,
10    pub b: f64,
12    pub f: f64,
14    pub e: f64,
16    pub e_squared: f64,
18}
19impl Ellipsoid {
20    #[must_use]
22    pub fn from_a_b(a: f64, b: f64) -> Self {
23        let f = (a - b) / a;
24        let e_squared = (2f64 * f) - f.powi(2);
25        Self {
26            a,
27            b,
28            f,
29            e_squared,
30            e: e_squared.sqrt(),
31        }
32    }
33
34    #[must_use]
36    pub fn from_a_f_inv(a: f64, f_inv: f64) -> Self {
37        let f = 1.0 / f_inv;
38        let e_squared = (2f64 / f_inv) - f_inv.powi(-2);
39        Self {
40            a,
41            b: a - a / f_inv,
42            f,
43            e_squared,
44            e: e_squared.sqrt(),
45        }
46    }
47
48    pub fn a(&self) -> f64 {
50        self.a
51    }
52
53    pub fn b(&self) -> f64 {
55        self.b
56    }
57
58    #[deprecated(since = "0.8.0")]
60    pub fn f_inv(&self) -> f64 {
61        1f64 / self.f
62    }
63
64    pub fn f(&self) -> f64 {
66        self.f
67    }
68
69    pub fn e(&self) -> f64 {
71        self.e
72    }
73
74    pub fn e_squared(&self) -> f64 {
76        self.e_squared
77    }
78
79    pub fn e_2(&self) -> f64 {
81        f64::sqrt(self.e_squared() / (1.0 - self.e_squared()))
82    }
83
84    pub fn rho(&self, lat: f64) -> f64 {
86        self.a * (1.0 - self.e_squared()) / (1.0 - self.e_squared() * lat.sin().powi(2)).powf(1.5)
87    }
88
89    pub fn ny(&self, lat: f64) -> f64 {
91        self.a / (1.0 - self.e_squared() * lat.sin().powi(2)).sqrt()
92    }
93
94    pub fn rad_auth(&self) -> f64 {
96        self.a
97            * ((1.0
98                - ((1.0 - self.e_squared()) / (2.0 * self.e()))
99                    * f64::ln((1.0 - self.e()) / (1.0 + self.e())))
100                * 0.5)
101                .sqrt()
102    }
103
104    pub fn rad_conformal(&self, lat: f64) -> f64 {
106        f64::sqrt(self.rho(lat) * self.ny(lat))
107    }
108
109    pub fn geocentric_to_deg(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) {
111        let (lon, lat, h) = self.geocentric_to_rad(x, y, z);
112        (lon.to_degrees(), lat.to_degrees(), h)
113    }
114
115    pub fn deg_to_geocentric(&self, lon: f64, lat: f64, height: f64) -> (f64, f64, f64) {
117        self.rad_to_geocentric(lon.to_radians(), lat.to_radians(), height)
118    }
119
120    pub fn geocentric_to_rad(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) {
122        let lon = y.atan2(x);
123        let epsilon = self.e_squared() / (1f64 - self.e_squared());
124        let p = (x.powi(2) + y.powi(2)).sqrt();
125        let q = (z * self.a).atan2(p * self.b);
126        let lat = (z + epsilon * self.b * q.sin().powi(3))
127            .atan2(p - self.e_squared() * self.a * q.cos().powi(3));
128        let h = (p / lat.cos()) - self.ny(lat);
129        (lon, lat, h)
130    }
131
132    pub fn rad_to_geocentric(&self, lon: f64, lat: f64, height: f64) -> (f64, f64, f64) {
134        let ny = self.ny(lat);
135        let r = ny + height;
136        (
137            r * lat.cos() * lon.cos(),
138            r * lat.cos() * lon.sin(),
139            ((1f64 - self.e_squared()) * ny + height) * lat.sin(),
140        )
141    }
142}
143
144impl PseudoSerialize for Ellipsoid {
145    fn to_constructed(&self) -> String {
146        format! {
147r"Ellipsoid{{
148    a: {}f64,
149    b: {}f64,
150    e: {}f64,
151    e_squared: {}f64,
152    f: {}f64,
153}}",
154            self.a,
155            self.b,
156            self.e,
157            self.e_squared,
158            self.f,
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::Ellipsoid;
166
167    #[test]
168    fn geocentric_roundtrip() {
169        let ell = Ellipsoid::from_a_f_inv(6378137.000, 298.2572236);
170        let expected_lat = 53f64 + 48f64 / 60f64 + 33.820 / 3600f64;
171        let expected_lon = 2f64 + 7f64 / 60f64 + 46.380 / 3600f64;
172        let expected_eh = 73.0;
173
174        let expected_x = 3771793.968;
175        let expected_y = 140253.342;
176        let expected_z = 5124304.349;
177
178        let (lon, lat, eh) = ell.geocentric_to_deg(expected_x, expected_y, expected_z);
179
180        let (x, y, z) = ell.deg_to_geocentric(lon, lat, eh);
181        eprintln!("lon: {expected_lon} - {lon}");
182        eprintln!("lat: {expected_lat} - {lat}");
183        eprintln!("eh: {expected_eh} - {eh}");
184
185        eprintln!("X: {expected_x} - {x}");
186        eprintln!("Y: {expected_y} - {y}");
187        eprintln!("Z: {expected_z} - {z}");
188        assert!((expected_lon - lon).abs() < 0.01 / 3600.0);
189        assert!((expected_lat - lat).abs() < 0.01 / 3600.0);
190        assert!((expected_eh - eh).abs() < 0.01);
191        assert!((expected_x - x).abs() < 0.01);
192        assert!((expected_y - y).abs() < 0.01);
193        assert!((expected_z - z).abs() < 0.01);
194    }
195}