1use num::Zero;
2
3use crate::earth::ellipsoid::Ellipsoid;
4
5pub enum Radius
7{
8 Equatorial,
10
11 Polar,
13
14 Mixed,
16
17 ArithmeticMean,
19
20 SurfaceAreaMean,
22
23 VolumeMean,
25}
26
27pub trait Model
29{
30 const NAME: &'static str;
32
33 const A:f64;
35
36 const F_INV:f64;
38
39 const F:f64 = 1.0 / Self::F_INV;
44
45 const M:f64 = Self::F / (1.0 - Self::F);
47
48 const N:f64 = Self::F / (2.0 - Self::F);
50
51 const B:f64 = Self::A * (1.0 - Self::F);
53
54 const E1SQ:f64 = Self::F * (2.0 - Self::F);
56
57 const E2SQ:f64 = (Self::A * Self::A - Self::B * Self::B) / (Self::B * Self::B);
59
60 const E3SQ:f64 = (Self::A * Self::A - Self::B * Self::B) / (Self::A * Self::A + Self::B * Self::B);
62
63 fn e1() ->f64
65 {
66 f64::sqrt(Self::E1SQ)
67 }
68
69 fn e2() -> f64
71 {
72 f64::sqrt(Self::E2SQ)
73 }
74
75 fn e3() -> f64
77 {
78 f64::sqrt(Self::E3SQ)
79 }
80
81 const P:f64 = Self::A * Self::A - Self::B * Self::B;
83
84 fn linear_eccentricity() -> f64
86 {
87 f64::sqrt(Self::P)
88 }
89
90 const Q:f64 = 1.0 - Self::F;
92
93 fn angular_eccentricity() -> f64
95 {
96 f64::acos(Self::Q)
97 }
98
99 fn elps() -> Ellipsoid
100 {
101 Ellipsoid::new(Self::A, Self::F_INV)
102 }
103
104 fn flattening(index: usize) -> f64
107 {
108 match index
109 {
110 1 => Self::F,
111 2 => Self::M,
112 3 => Self::N,
113 _ => panic!("flattening index must be 1, 2 or 3"),
114 }
115 }
116
117 fn eccentricity(index: usize) -> f64
118 {
119 match index
120 {
121 0 => Self::linear_eccentricity(),
122 1 => Self::e1(),
123 2 => Self::e2(),
124 3 => Self::e3(),
125 4 => Self::angular_eccentricity(),
126 _ => panic!("eccentricity index must be 0, 1, 2, 3 or 4"),
127 }
128 }
129
130 fn eccentricity_square(index: usize) -> f64
131 {
132 match index
133 {
134 0 => Self::P,
135 1 => Self::E1SQ,
136 2 => Self::E2SQ,
137 3 => Self::E3SQ,
138 _ => panic!("eccentricity index must be 0, 1, 2 or 3"),
139 }
140 }
141
142 fn radius(r:Radius) -> f64
143 {
144 match r
145 {
146 Radius::Equatorial => Self::A,
147 Radius::Polar => Self::B,
148 Radius::Mixed => (Self::A + Self::B) / 2.0,
149 Radius::ArithmeticMean => (Self::A * 2.0 + Self::B) / 3.0,
150 Radius::SurfaceAreaMean => f64::sqrt(Self::surface_area() / (4.0 * std::f64::consts::PI)),
151 Radius::VolumeMean => f64::powf(Self::A * Self::A * Self::B, 1.0/3.0),
152 }
153 }
154
155 fn surface_area() -> f64
156 {
157 let a = Self::A;
158 let b = Self::B;
159 let e = Self::angular_eccentricity();
160 let esin = f64::sin(e);
161 let k = if e.is_zero() { 1.0 } else { f64::atanh(esin) / esin };
162 2.0 * std::f64::consts::PI * ( a * a + b * b * k )
163 }
164
165 fn volume() -> f64
166 {
167 Self::A * Self::A * Self::B * std::f64::consts::PI / 0.75
168 }
169}
170
171#[macro_export]
172macro_rules! ellipsoid_model
173{
174 ($tt:tt, $n:expr, $a:expr, $finv:expr) =>
175 {
176 #[derive(Debug, Copy, Clone, PartialEq)]
177 pub struct $tt { }
178 impl Model for $tt
179 {
180 const NAME: &'static str = $n;
181 const A:f64 = $a;
182 const F_INV:f64 = $finv;
183 }
184 }
185}
186
187ellipsoid_model!( GRS67, "GRS-67 (1967)", 6_378_160.0, 298.247_167_427 );
190ellipsoid_model!( GRS80, "GRS-80 (1979)", 6_378_137.0, 298.257_222_101 );
191ellipsoid_model!( IERS1989, "IERS (1989)", 6_378_136.0, 298.257 );
192ellipsoid_model!( IERS1992, "IERS (1992)", 6_378_136.6, 298.256_42 );
193ellipsoid_model!( Intl1924, "International (1924)", 6_378_388.0, 297.0 );
194ellipsoid_model!( Intl1967, "New International (1967)", 6_378_157.5, 298.249_615_39 );
195ellipsoid_model!( WGS66, "WGS66 (1966)", 6_378_145.0, 298.25 );
196ellipsoid_model!( WGS72, "WGS-72 (1972)", 6_378_135.0, 298.26 );
197ellipsoid_model!( WGS84, "WGS-84 (1984)", 6_378_137.0, 298.257_223_563 );
198
199ellipsoid_model!( Airy1830, "Airy (1830)", 6_377_563.396, 299.324_964_6 );
202ellipsoid_model!( ANS66, "Australian National (1966)", 6_378_160.0, 298.25 );
203ellipsoid_model!( Bessel1841, "Bessel (1841)", 6_377_397.155, 299.152_812_8 );
204ellipsoid_model!( CGCS2000, "CGCS 2000", 6_378_137.0, 298.257_222_1 );
205ellipsoid_model!( Clarke1866, "Clarke (1866)", 6_378_206.4, 294.978_698_2 );
206ellipsoid_model!( Clarke1878, "Clarke (1878)", 6_378_190.0, 293.465_998_0 );
207ellipsoid_model!( Clarke1880, "Clarke (1880)", 6_378_249.145, 293.465 );
208ellipsoid_model!( Helmert1906, "Helmert (1906)", 6_378_200.0, 298.3 );
209ellipsoid_model!( Hayford, "Hayford (1910)", 6_378_388.0, 297.0 );
210ellipsoid_model!( Krasov40, "Krassovsky (1940)", 6_378_245.0, 298.3 );
211ellipsoid_model!( Maupertuis1738, "Maupertuis (1738)", 6_397_300.0, 191.0 );
212ellipsoid_model!( Plessis1817, "Plessis (1817)", 6_376_523.0, 308.64 );
213ellipsoid_model!( SA1969, "South American (1969)", 6_378_160.0, 298.25 );
214
215ellipsoid_model!( Sphere, "Sphere (Mean)", 6_371_008.771_415, f64::INFINITY );
217ellipsoid_model!( SphereAuthalic, "Sphere, Authalic", 6_371_000.0, f64::INFINITY );
218ellipsoid_model!( SpherePopular, "Sphere, Popular", 6_378_137.0, f64::INFINITY );
219ellipsoid_model!( SphereNormal, "Sphere, Normal", 6_370_997.0, f64::INFINITY );
220
221#[cfg(test)]
222mod tests
223{
224 use super::*;
225 use rstest::*;
226 use float_cmp::assert_approx_eq;
227
228 macro_rules! assert_model_nfab
229 {
230 ($t:tt, $n:expr, $a:expr, $finv:expr) =>
231 {
232 assert_eq!($n, $t::NAME);
233 assert_approx_eq!(f64, $a, $t::A);
234 assert_approx_eq!(f64, $finv, $t::F_INV);
235
236 assert_approx_eq!(f64, 1.0, $t::F * $t::F_INV);
237 assert_approx_eq!(f64, $t::A * (1.0 - $t::F), $t::B);
238 }
239 }
240
241 #[rstest]
242 #[case(GRS67{}, "GRS-67 (1967)", 6_378_160.0, 298.247_167_427 )]
243 #[case(GRS80{}, "GRS-80 (1979)", 6_378_137.0, 298.257_222_101 )]
244 #[case(IERS1989{}, "IERS (1989)", 6_378_136.0, 298.257 )]
245 #[case(IERS1992{}, "IERS (1992)", 6_378_136.6, 298.256_42 )]
246 #[case(Intl1924{}, "International (1924)", 6_378_388.0, 297.0 )]
247 #[case(Intl1967{}, "New International (1967)", 6_378_157.5, 298.249_615_39 )]
248 #[case(WGS66{}, "WGS66 (1966)", 6_378_145.0, 298.25 )]
249 #[case(WGS72{}, "WGS-72 (1972)", 6_378_135.0, 298.26 )]
250 #[case(WGS84{}, "WGS-84 (1984)", 6_378_137.0, 298.257_223_563 )]
251 fn test_model_worldwide<T>(#[case] _elps:T, #[case] n: &str, #[case] a: f64, #[case] finv: f64) where T: Model
252 {
253 assert_model_nfab!(T, n, a, finv);
254 }
255
256 #[rstest]
257 #[case(WGS84{}, "WGS-84 (1984)", 6_378_137.0, 298.257_223_563 )]
258 #[case(Airy1830{}, "Airy (1830)", 6_377_563.396, 299.324_964_6 )]
259 #[case(ANS66{}, "Australian National (1966)", 6_378_160.0, 298.25 )]
260 #[case(Bessel1841{}, "Bessel (1841)", 6_377_397.155, 299.152_812_8 )]
261 #[case(CGCS2000{}, "CGCS 2000", 6_378_137.0, 298.257_222_1 )]
262 #[case(Clarke1866{}, "Clarke (1866)", 6_378_206.4, 294.978_698_2 )]
263 #[case(Clarke1878{}, "Clarke (1878)", 6_378_190.0, 293.465_998_0 )]
264 #[case(Clarke1880{}, "Clarke (1880)", 6_378_249.145, 293.465 )]
265 #[case(Helmert1906{}, "Helmert (1906)", 6_378_200.0, 298.3 )]
266 #[case(Hayford{}, "Hayford (1910)", 6_378_388.0, 297.0 )]
267 #[case(Krasov40{}, "Krassovsky (1940)", 6_378_245.0, 298.3 )]
268 #[case(Maupertuis1738{}, "Maupertuis (1738)", 6_397_300.0, 191.0 )]
269 #[case(Plessis1817{}, "Plessis (1817)", 6_376_523.0, 308.64 )]
270 #[case(SA1969{}, "South American (1969)", 6_378_160.0, 298.25 )]
271 fn test_model_regional<T>(#[case] _elps:T, #[case] n: &str, #[case] a: f64, #[case] finv: f64) where T: Model
272 {
273 assert_model_nfab!(T, n, a, finv);
274 }
275
276 #[rstest]
277 #[case(Sphere{}, "Sphere (Mean)", 6_371_008.771_415)]
278 #[case(SphereAuthalic{}, "Sphere, Authalic", 6_371_000.0)]
279 #[case(SpherePopular{}, "Sphere, Popular", 6_378_137.0)]
280 #[case(SphereNormal{}, "Sphere, Normal", 6_370_997.0)]
281 fn test_model_sphere<T>(#[case] _elps:T, #[case] n: &str, #[case] r: f64) where T: Model
282 {
283 assert_eq!(n, T::NAME);
284 assert_approx_eq!(f64, r, T::A);
285 assert_approx_eq!(f64, f64::INFINITY, T::F_INV);
286
287 assert_approx_eq!(f64, 0.0, T::F);
288 assert_approx_eq!(f64, 1.0, T::Q + T::F);
289 assert_approx_eq!(f64, r, T::B);
290 }
291
292 macro_rules! assert_model_flattening
293 {
294 ($t:tt) =>
295 {
296 let f = $t::F;
297 let m = $t::M;
298 let n = $t::N;
299 let a = $t::A;
300 let b = $t::B;
301 assert_approx_eq!(f64, f, T::flattening(1));
302 assert_approx_eq!(f64, m, T::flattening(2));
303 assert_approx_eq!(f64, n, T::flattening(3));
304 assert_approx_eq!(f64, f, (a - b) / a);
305 assert_approx_eq!(f64, m, (a - b) / b);
306 assert_approx_eq!(f64, n, (a - b) / (a + b));
307 }
308 }
309
310 #[rstest]
311 #[case(GRS67{})]
312 #[case(GRS80{})]
313 #[case(IERS1989{})]
314 #[case(IERS1992{})]
315 #[case(Intl1924{})]
316 #[case(Intl1967{})]
317 #[case(WGS66{})]
318 #[case(WGS72{})]
319 #[case(WGS84{})]
320 fn test_flattening_worldwide<T>(#[case] _elps:T) where T: Model
321 {
322 assert_model_flattening!(T);
323 }
324
325 #[rstest]
326 #[case(Airy1830{})]
327 #[case(ANS66{})]
328 #[case(Bessel1841{})]
329 #[case(CGCS2000{})]
330 #[case(Clarke1866{})]
331 #[case(Clarke1878{})]
332 #[case(Clarke1880{})]
333 #[case(Helmert1906{})]
334 #[case(Hayford{})]
335 #[case(Krasov40{})]
336 #[case(Maupertuis1738{})]
337 #[case(Plessis1817{})]
338 #[case(SA1969{})]
339 fn test_flattening_regional<T>(#[case] _elps:T) where T: Model
340 {
341 assert_model_flattening!(T);
342 }
343
344 #[rstest]
345 #[case(Sphere{})]
346 #[case(SphereAuthalic{})]
347 #[case(SpherePopular{})]
348 #[case(SphereNormal{})]
349 fn test_flattening_sphere<T>(#[case] _elps:T) where T: Model
350 {
351 assert_approx_eq!(f64, 0.0, T::flattening(1));
352 assert_approx_eq!(f64, 0.0, T::flattening(2));
353 assert_approx_eq!(f64, 0.0, T::flattening(3));
354 }
355
356 #[rstest]
357 #[case(WGS84{}, 4)]
358 #[case(WGS84{}, 0)]
359 #[should_panic]
360 fn test_flattening_panic<T>(#[case] _elps:T, #[case] i:usize) where T: Model
361 {
362 T::flattening(i);
363 }
364
365 macro_rules! assert_model_eccentricity
366 {
367 ($t:tt) =>
368 {
369 let f = T::F;
370 let a = T::A;
371 let b = T::B;
372 let e1 = T::eccentricity(1);
373 let e2 = T::eccentricity(2);
374 let e3 = T::eccentricity(3);
375 assert_approx_eq!(f64, e1 * e1, T::E1SQ);
376 assert_approx_eq!(f64, e2 * e2, T::E2SQ);
377 assert_approx_eq!(f64, e3 * e3, T::E3SQ);
378 assert_approx_eq!(f64, f * (2.0 - f), T::E1SQ);
379 assert_approx_eq!(f64, (e1 * e1 ) / (1.0 - e1 * e1), T::E2SQ);
380 assert_approx_eq!(f64, (e1 * e1 ) / (2.0 - e1 * e1), T::E3SQ);
381 assert_approx_eq!(f64, (a * a - b * b) / (a * a), T::E1SQ);
382 assert_approx_eq!(f64, (a * a - b * b) / (b * b), T::E2SQ);
383 assert_approx_eq!(f64, (a * a - b * b) / (a * a + b * b), T::E3SQ);
384 }
385 }
386
387 macro_rules! assert_model_eccentricity_special
388 {
389 ($t:tt, $ulps:expr) =>
390 {
391 let a = T::A;
392 let b = T::B;
393 let e0 = T::eccentricity(0);
394 let e1 = T::eccentricity(1);
395 let e2 = T::eccentricity(2);
396 let e3 = T::eccentricity(3);
397 let e4 = T::eccentricity(4);
398 assert_approx_eq!(f64, a * a, b * b + T::P);
399 assert_approx_eq!(f64, e0 * e0, T::P);
400 assert_approx_eq!(f64, 1.0, $t::Q + $t::F);
401 assert_approx_eq!(f64, f64::cos(e4), T::Q);
402 assert_approx_eq!(f64, e1, f64::sin(e4), ulps=$ulps);
403 assert_approx_eq!(f64, e2, f64::tan(e4), ulps=$ulps);
404 assert_approx_eq!(f64, e3, f64::sin(e4) / f64::sqrt(2.0 - f64::sin(e4) * f64::sin(e4)), ulps=$ulps);
405 }
406 }
407
408 macro_rules! assert_model_eccentricity_square
409 {
410 ($t:tt) =>
411 {
412 let e0 = T::eccentricity(0);
413 let e1 = T::eccentricity(1);
414 let e2 = T::eccentricity(2);
415 let e3 = T::eccentricity(3);
416 let e0sq = T::eccentricity_square(0);
417 let e1sq = T::eccentricity_square(1);
418 let e2sq = T::eccentricity_square(2);
419 let e3sq = T::eccentricity_square(3);
420 assert_approx_eq!(f64, e0 * e0, e0sq);
421 assert_approx_eq!(f64, e1 * e1, e1sq);
422 assert_approx_eq!(f64, e2 * e2, e2sq);
423 assert_approx_eq!(f64, e3 * e3, e3sq);
424 }
425 }
426
427 #[rstest]
428 #[case(GRS67{}, 80)]
429 #[case(GRS80{}, 80)]
430 #[case(IERS1989{}, 80)]
431 #[case(IERS1992{}, 80)]
432 #[case(Intl1924{}, 80)]
433 #[case(Intl1967{}, 80)]
434 #[case(WGS66{}, 80)]
435 #[case(WGS72{}, 80)]
436 #[case(WGS84{}, 80)]
437 fn test_eccentricity_worldwide<T>(#[case] _elps:T, #[case] ulps:i64) where T: Model
438 {
439 assert_model_eccentricity!(T);
440 assert_model_eccentricity_special!(T, ulps);
441 assert_model_eccentricity_square!(T);
442 }
443
444 #[rstest]
445 #[case(Airy1830{}, 80)]
446 #[case(ANS66{}, 80)]
447 #[case(Bessel1841{}, 80)]
448 #[case(CGCS2000{}, 80)]
449 #[case(Clarke1866{}, 80)]
450 #[case(Clarke1878{}, 80)]
451 #[case(Clarke1880{}, 80)]
452 #[case(Helmert1906{}, 120)]
453 #[case(Hayford{}, 80)]
454 #[case(Krasov40{}, 80)]
455 #[case(Maupertuis1738{}, 80)]
456 #[case(Plessis1817{}, 80)]
457 #[case(SA1969{}, 80)]
458 fn test_eccentricity_regional<T>(#[case] _elps:T, #[case] ulps:i64) where T: Model
459 {
460 assert_model_eccentricity!(T);
461 assert_model_eccentricity_special!(T, ulps);
462 assert_model_eccentricity_square!(T);
463 }
464
465 #[rstest]
466 #[case(Sphere{})]
467 #[case(SphereAuthalic{})]
468 #[case(SpherePopular{})]
469 #[case(SphereNormal{})]
470 fn test_eccentricity_sphere<T>(#[case] _elps:T) where T: Model
471 {
472 assert_approx_eq!(f64, 0.0, T::eccentricity(0));
473 assert_approx_eq!(f64, 0.0, T::eccentricity(1));
474 assert_approx_eq!(f64, 0.0, T::eccentricity(2));
475 assert_approx_eq!(f64, 0.0, T::eccentricity(3));
476 assert_approx_eq!(f64, 0.0, T::eccentricity(4));
477 assert_approx_eq!(f64, 0.0, T::P);
478 assert_approx_eq!(f64, 1.0, T::Q);
479 assert_approx_eq!(f64, 0.0, T::E1SQ);
480 assert_approx_eq!(f64, 0.0, T::E2SQ);
481 assert_approx_eq!(f64, 0.0, T::E3SQ);
482 }
483
484 #[rstest]
485 #[case(WGS84{}, 5)]
486 #[should_panic]
487 fn test_eccentricity_panic<T>(#[case] _elps:T, #[case] i:usize) where T: Model
488 {
489 T::eccentricity(i);
490 }
491
492 #[rstest]
493 #[case(WGS84{}, 4)]
494 #[should_panic]
495 fn test_eccentricity_square_panic<T>(#[case] _elps:T, #[case] i:usize) where T: Model
496 {
497 T::eccentricity_square(i);
498 }
499
500 #[rstest]
501 #[case(WGS84{}, Radius::Mixed)]
502 #[case(WGS84{}, Radius::ArithmeticMean)]
503 #[case(WGS84{}, Radius::SurfaceAreaMean)]
504 #[case(WGS84{}, Radius::VolumeMean)]
505 fn test_radius<T>(#[case] _elps:T, #[case] r:Radius) where T: Model
506 {
507 let a = T::radius(Radius::Equatorial);
508 let b = T::radius(Radius::Polar);
509 let rad = T::radius(r);
510 assert!(a >= rad && rad >= b);
511 }
512
513 #[rstest]
514 #[case(WGS84{})]
515 fn test_radius_arithmetic<T>(#[case] _elps:T) where T: Model
516 {
517 let a = T::radius(Radius::Equatorial);
518 let b = T::radius(Radius::Polar);
519 let r = T::radius(Radius::ArithmeticMean);
520 assert_approx_eq!(f64, 2.0 * a + b, r * 3.0);
521 }
522
523 #[rstest]
524 #[case(WGS84{})]
525 fn test_radius_surfacearea<T>(#[case] _elps:T) where T: Model
526 {
527 let r = T::radius(Radius::SurfaceAreaMean);
528 assert_approx_eq!(f64, T::surface_area(), 4.0 * std::f64::consts::PI * r * r );
529 }
530
531 #[rstest]
532 #[case(WGS84{}, 20)]
533 fn test_radius_volume<T>(#[case] _elps:T, #[case] ulps:i64) where T: Model
534 {
535 let r = T::radius(Radius::VolumeMean);
536 assert_approx_eq!(f64, T::volume(), r * r * r * std::f64::consts::PI / 0.75, ulps=ulps);
537 }
538
539 #[rstest]
540 #[case(Sphere{}, 10)]
541 #[case(SphereAuthalic{}, 10)]
542 #[case(SphereNormal{}, 10)]
543 #[case(SpherePopular{}, 10)]
544 fn test_radius_sphere<T>(#[case] _elps:T, #[case] ulps:i64) where T: Model
545 {
546 let a = T::radius(Radius::Equatorial);
547 let b = T::radius(Radius::Polar);
548 assert_approx_eq!(f64, a, b);
549 assert_approx_eq!(f64, a, T::radius(Radius::Mixed));
550 assert_approx_eq!(f64, a, T::radius(Radius::ArithmeticMean));
551 assert_approx_eq!(f64, a, T::radius(Radius::SurfaceAreaMean));
552 assert_approx_eq!(f64, a, T::radius(Radius::VolumeMean), ulps=ulps);
553 }
554
555 #[rstest]
556 #[case(Sphere{})]
557 #[case(SphereAuthalic{})]
558 #[case(SphereNormal{})]
559 #[case(SpherePopular{})]
560 fn test_surfacearea_sphere<T>(#[case] _elps:T) where T: Model
561 {
562 let a = T::A;
563 assert_approx_eq!(f64, 4.0 * std::f64::consts::PI * a * a, T::surface_area());
564 }
565
566 #[rstest]
567 #[case(Sphere{})]
568 #[case(SphereAuthalic{})]
569 #[case(SphereNormal{})]
570 #[case(SpherePopular{})]
571 fn test_volume_sphere<T>(#[case] _elps:T) where T: Model
572 {
573 let a = T::A;
574 assert_approx_eq!(f64, std::f64::consts::PI * a * a * a / 0.75, T::volume());
575 }
576}