jord/ellipsoidal/
ellipsoid.rs1use crate::{
2 surface::Surface, Angle, Cartesian3DVector, GeocentricPosition, GeodeticPosition, Length,
3 NVector, Vec3,
4};
5
6#[derive(PartialEq, Clone, Copy, Debug, Default)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Ellipsoid {
10 equatorial_radius: Length,
11 polar_radius: Length,
12 eccentricity: f64,
13 flattening: f64,
14}
15
16impl Ellipsoid {
17 pub const WGS84: Ellipsoid = Ellipsoid {
19 equatorial_radius: Length::from_metres(6_378_137.0f64),
20 polar_radius: Length::from_metres(6_356_752.314245179f64),
21 eccentricity: 0.08181919084262157f64,
22 flattening: 0.0033528106647474805f64,
23 };
24
25 pub const GRS80: Ellipsoid = Ellipsoid {
27 equatorial_radius: Length::from_metres(6_378_137.0f64),
28 polar_radius: Length::from_metres(6_356_752.314140356f64),
29 eccentricity: 0.08181919104281514f64,
30 flattening: 0.003352810681182319f64,
31 };
32
33 pub const WGS72: Ellipsoid = Ellipsoid {
35 equatorial_radius: Length::from_metres(6_378_135.0f64),
36 polar_radius: Length::from_metres(6_356_750.520016094f64),
37 eccentricity: 0.08181881066274845f64,
38 flattening: 0.003352779454167505,
39 };
40
41 pub const MOLA: Ellipsoid = Ellipsoid {
43 equatorial_radius: Length::from_metres(3_396_200f64),
44 polar_radius: Length::from_metres(3_376_198.822143698f64),
45 eccentricity: 0.10836918094475001f64,
46 flattening: 0.005889281507656065f64,
47 };
48
49 pub fn new(equatorial_radius: Length, inverse_flattening: f64) -> Self {
52 let a = equatorial_radius.as_metres();
53 let f = 1.0 / inverse_flattening;
54 let b = a * (1.0 - f);
55 let e = (1.0 - (b * b) / (a * a)).sqrt();
56 Ellipsoid {
57 equatorial_radius,
58 polar_radius: Length::from_metres(b),
59 eccentricity: e,
60 flattening: f,
61 }
62 }
63
64 #[inline]
66 pub fn equatorial_radius(&self) -> Length {
67 self.equatorial_radius
68 }
69
70 #[inline]
72 pub fn polar_radius(&self) -> Length {
73 self.polar_radius
74 }
75
76 #[inline]
78 pub fn eccentricity(&self) -> f64 {
79 self.eccentricity
80 }
81
82 #[inline]
84 pub fn flattening(&self) -> f64 {
85 self.flattening
86 }
87
88 pub fn geocentric_radius(&self, latitude: Angle) -> Length {
107 let cos_lat = latitude.as_radians().cos();
108 let sin_lat = latitude.as_radians().sin();
109 let a = self.equatorial_radius.as_metres();
110 let b = self.polar_radius.as_metres();
111 let f1 = a * a * cos_lat;
112 let f2 = b * b * sin_lat;
113 let f3 = a * cos_lat;
114 let f4 = b * sin_lat;
115 let r = (((f1 * f1) + (f2 * f2)) / ((f3 * f3) + (f4 * f4))).sqrt();
116 Length::from_metres(r)
117 }
118
119 pub fn latitude_radius(&self, latitude: Angle) -> Length {
134 if latitude == Angle::QUARTER_CIRCLE || latitude == Angle::NEG_QUARTER_CIRCLE {
135 Length::ZERO
136 } else {
137 self.prime_vertical_radius(latitude) * latitude.as_radians().cos()
138 }
139 }
140
141 pub fn prime_vertical_radius(&self, latitude: Angle) -> Length {
158 let e2: f64 = self.eccentricity * self.eccentricity;
159 let sin_lat = latitude.as_radians().sin();
160 let sin_lat2 = sin_lat * sin_lat;
161 let r = self.equatorial_radius.as_metres() / (1.0 - e2 * sin_lat2).sqrt();
162 Length::from_metres(r)
163 }
164
165 pub fn meridian_radius(&self, latitude: Angle) -> Length {
169 let e2: f64 = self.eccentricity * self.eccentricity;
170 let sin_lat = latitude.as_radians().sin();
171 let sin_lat2 = sin_lat * sin_lat;
172 let r =
173 self.equatorial_radius.as_metres() * (1.0 - e2) / (1.0 - e2 * sin_lat2).powf(3.0 / 2.0);
174 Length::from_metres(r)
175 }
176
177 pub fn mean_radius(&self) -> Length {
188 let a = self.equatorial_radius();
189 let b = self.polar_radius();
190 (2.0 * a + b) / 3.0
191 }
192
193 pub fn volumetric_radius(&self) -> Length {
206 let a = self.equatorial_radius().as_metres();
207 let b = self.polar_radius().as_metres();
208 let r = (a * a * b).cbrt();
209 Length::from_metres(r)
210 }
211}
212
213impl Surface for Ellipsoid {
214 fn geodetic_to_geocentric_position(&self, pos: GeodeticPosition) -> GeocentricPosition {
215 let nv = pos.horizontal_position().as_vec3();
216 let nx = nv.x();
217 let ny = nv.y();
218 let nz = nv.z();
219 let a = self.equatorial_radius.as_metres();
220 let b = self.polar_radius.as_metres();
221 let m = (a * a) / (b * b);
222 let n = b / ((nx * nx * m) + (ny * ny * m) + (nz * nz)).sqrt();
223 let h = pos.height().as_metres();
224 let cx = n * m * nx + h * nx;
225 let cy = n * m * ny + h * ny;
226 let cz = n * nz + h * nz;
227 GeocentricPosition::from_metres(cx, cy, cz)
228 }
229
230 fn geocentric_to_geodetic_position(&self, pos: GeocentricPosition) -> GeodeticPosition {
231 let pv = pos.as_metres();
232 let px = pv.x();
233 let py = pv.y();
234 let pz = pv.z();
235 let e2 = self.eccentricity * self.eccentricity;
236 let e4 = e2 * e2;
237 let a = self.equatorial_radius.as_metres();
238 let a2 = a * a;
239 let p = (px * px + py * py) / a2;
240 let q = ((1.0 - e2) / a2) * (pz * pz);
241 let r = (p + q - e4) / 6.0;
242 let s = (e4 * p * q) / (4.0 * r * r * r);
243 let t = (1.0 + s + (s * (2.0 + s)).sqrt()).powf(1.0 / 3.0);
244 let u = r * (1.0 + t + 1.0 / t);
245 let v = (u * u + q * e4).sqrt();
246 let w = e2 * (u + v - q) / (2.0 * v);
247 let k = (u + v + w * w).sqrt() - w;
248 let d = k * (px * px + py * py).sqrt() / (k + e2);
249 let h = ((k + e2 - 1.0) / k) * (d * d + pz * pz).sqrt();
250
251 let fs = 1.0 / (d * d + pz * pz).sqrt();
252 let fa = k / (k + e2);
253 let nx = fs * fa * px;
254 let ny = fs * fa * py;
255 let nz = fs * pz;
256 GeodeticPosition::new(NVector::new(Vec3::new(nx, ny, nz)), Length::from_metres(h))
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use crate::{spherical::Sphere, Angle, Length};
263
264 use super::Ellipsoid;
265
266 #[test]
267 fn wgs84() {
268 let wgs84 = Ellipsoid::new(Length::from_metres(6_378_137.0), 298.257223563);
269 assert_eq!(
270 Ellipsoid::WGS84.equatorial_radius(),
271 wgs84.equatorial_radius()
272 );
273 assert_eq!(Ellipsoid::WGS84.polar_radius(), wgs84.polar_radius());
274 assert_eq!(Ellipsoid::WGS84.eccentricity(), wgs84.eccentricity());
275 assert_eq!(Ellipsoid::WGS84.flattening(), wgs84.flattening());
276 }
277
278 #[test]
279 fn grs80() {
280 let grs80 = Ellipsoid::new(Length::from_metres(6_378_137.0), 298.257222101);
281 assert_eq!(
282 Ellipsoid::GRS80.equatorial_radius(),
283 grs80.equatorial_radius()
284 );
285 assert_eq!(Ellipsoid::GRS80.polar_radius(), grs80.polar_radius());
286 assert_eq!(Ellipsoid::GRS80.eccentricity(), grs80.eccentricity());
287 assert_eq!(Ellipsoid::GRS80.flattening(), grs80.flattening());
288 }
289
290 #[test]
291 fn wgs72() {
292 let wgs72 = Ellipsoid::new(Length::from_metres(6_378_135.0), 298.26);
293 assert_eq!(
294 Ellipsoid::WGS72.equatorial_radius(),
295 wgs72.equatorial_radius()
296 );
297 assert_eq!(Ellipsoid::WGS72.polar_radius(), wgs72.polar_radius());
298 assert_eq!(Ellipsoid::WGS72.eccentricity(), wgs72.eccentricity());
299 assert_eq!(Ellipsoid::WGS72.flattening(), wgs72.flattening());
300 }
301
302 #[test]
303 fn mola() {
304 let mola = Ellipsoid::new(Length::from_metres(3_396_200.0), 169.8);
305 assert_eq!(
306 Ellipsoid::MOLA.equatorial_radius(),
307 mola.equatorial_radius()
308 );
309 assert_eq!(Ellipsoid::MOLA.polar_radius(), mola.polar_radius());
310 assert_eq!(Ellipsoid::MOLA.eccentricity(), mola.eccentricity());
311 assert_eq!(Ellipsoid::MOLA.flattening(), mola.flattening());
312 }
313
314 #[test]
315 fn geocentric_radius() {
316 assert_eq!(
317 Ellipsoid::WGS84.equatorial_radius(),
318 Ellipsoid::WGS84.geocentric_radius(Angle::ZERO)
319 );
320 assert_eq!(
321 Ellipsoid::WGS84.polar_radius(),
322 Ellipsoid::WGS84.geocentric_radius(Angle::from_degrees(90.0))
323 );
324 assert_eq!(
325 Length::from_metres(6_367_490.0),
326 Ellipsoid::WGS84
327 .geocentric_radius(Angle::from_degrees(45.0))
328 .round_m()
329 );
330 }
331
332 #[test]
333 fn latitude_radius() {
334 assert_eq!(
335 Ellipsoid::WGS84.equatorial_radius(),
336 Ellipsoid::WGS84.latitude_radius(Angle::ZERO)
337 );
338 assert_eq!(
339 Length::ZERO,
340 Ellipsoid::WGS84.latitude_radius(Angle::from_degrees(90.0))
341 );
342 assert_eq!(
343 Length::ZERO,
344 Ellipsoid::WGS84.latitude_radius(Angle::from_degrees(-90.0))
345 );
346 }
347
348 #[test]
349 fn prime_vertical_radius() {
350 assert_eq!(
351 Ellipsoid::WGS84.equatorial_radius(),
352 Ellipsoid::WGS84.prime_vertical_radius(Angle::ZERO)
353 );
354 assert_eq!(
355 Length::from_metres(6388838.29),
356 Ellipsoid::WGS84
357 .prime_vertical_radius(Angle::from_degrees(45.0))
358 .round_mm()
359 );
360 }
361
362 #[test]
363 fn mean_radius() {
364 assert_eq!(
365 Length::from_metres(6_371_008.8),
366 Ellipsoid::WGS84.mean_radius().round_dm()
367 );
368 }
369
370 #[test]
371 fn volumetric_radius() {
372 let r = (Ellipsoid::WGS84.volumetric_radius().as_metres() * 10.0).round() / 10.0;
373 assert_eq!(Sphere::EARTH.radius().as_metres(), r);
374 }
375}