hipparchus_geo/earth/
models.rs

1use num::Zero;
2
3use crate::earth::ellipsoid::Ellipsoid;
4
5/// Radius of the earth
6pub enum Radius
7{
8    /// equatorial radius: a
9    Equatorial,
10
11    /// polar radius: b
12    Polar,
13
14    /// average radius of equatorial & polar radius: (a + b) / 2
15    Mixed,
16
17    /// arithmetic mean of length of radius of the earth: (2a + b) / 3
18    ArithmeticMean,
19
20    /// radius of the sphere of equal surface area of the earth
21    SurfaceAreaMean,
22
23    /// radius of the sphere of equal volume of the earth
24    VolumeMean,
25}
26
27/// Ellipsoid Model
28pub trait Model
29{
30    /// The datum name of the ellipsoid model
31    const NAME: &'static str;
32
33    /// The equatorial radius (semi-major axis)
34    const A:f64;
35
36    /// flattening
37    const F_INV:f64;
38
39    /// The 1st flattening of the ellipsoid.
40    /// - zero: sphere
41    /// - positive: oblate ellipsoid
42    /// - negative: prolate ellipsoid
43    const F:f64 = 1.0 / Self::F_INV;
44
45    /// The 2nd flattening
46    const M:f64 = Self::F / (1.0 - Self::F);
47
48    /// The 3rd flattening
49    const N:f64 = Self::F / (2.0 - Self::F);
50
51    /// The polar radius (semi-minor axis)
52    const B:f64 = Self::A * (1.0 - Self::F);
53
54    /// E => E1^2, square of the 1st eccentricity
55    const E1SQ:f64 = Self::F * (2.0 - Self::F);
56
57    /// E' => E2^2, square of the 2nd eccentricity
58    const E2SQ:f64 = (Self::A * Self::A - Self::B * Self::B) / (Self::B * Self::B);
59
60    /// E" => E3^2, square of the 3rd eccentricity
61    const E3SQ:f64 = (Self::A * Self::A - Self::B * Self::B) / (Self::A * Self::A + Self::B * Self::B);
62
63    /// E1, the 1st eccentricity
64    fn e1() ->f64
65    {
66        f64::sqrt(Self::E1SQ)
67    }
68
69    /// E2, the 2nd eccentricity
70    fn e2() -> f64
71    {
72        f64::sqrt(Self::E2SQ)
73    }
74    
75    /// E2, the 3rd eccentricity
76    fn e3() -> f64
77    {
78        f64::sqrt(Self::E3SQ)
79    }
80
81    /// E(L)^2 => A^2 - B^2, square of linear eccentricity
82    const P:f64 = Self::A * Self::A - Self::B * Self::B;
83
84    /// E(L) = sqrt(a^2-b^2), linear eccentricity, also known as focal distance
85    fn linear_eccentricity() -> f64
86    {
87        f64::sqrt(Self::P)
88    }
89
90    /// The radius ratio: Q = B / A
91    const Q:f64 = 1.0 - Self::F;
92
93    /// E(A) = acos(b/a), angular eccentricity
94    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    /// Flattening is a measure of the compression of a circle or sphere along a diameter to form an ellipse or an ellipsoid of revolution (spheroid) respectively. 
105    /// See also in https://en.wikipedia.org/wiki/Flattening
106    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
187// World-wide Ellipsoid Models
188// - https://en.wikipedia.org/wiki/Earth_ellipsoid
189ellipsoid_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
199// Regional Ellipsoid Models
200// - https://en.wikipedia.org/wiki/Earth_ellipsoid
201ellipsoid_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
215// Sphere Models
216ellipsoid_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}