Skip to main content

geographiclib_rs/
geomath.rs

1#![allow(non_snake_case)]
2#![allow(clippy::excessive_precision)]
3
4use crate::geodesic::{CARR_SIZE, GEODESIC_ORDER};
5
6pub const DIGITS: u64 = 53;
7
8// Square
9pub fn sq(x: f64) -> f64 {
10    x.powi(2)
11}
12
13// Normalize a two-vector
14pub fn norm(x: &mut f64, y: &mut f64) {
15    let r = x.hypot(*y);
16    *x /= r;
17    *y /= r;
18}
19
20// Error free transformation of a sum
21pub fn sum(u: f64, v: f64) -> (f64, f64) {
22    let s = u + v;
23    let up = s - v;
24    let vpp = s - up;
25    let up = up - u;
26    let vpp = vpp - v;
27    let t = -(up + vpp);
28    (s, t)
29}
30
31// Evaluate a polynomial
32pub fn polyval(n: usize, p: &[f64], x: f64) -> f64 {
33    let mut y = p[0];
34    for val in &p[1..=n] {
35        y = y * x + val;
36    }
37    y
38}
39
40// Round an angle so taht small values underflow to 0
41pub fn ang_round(x: f64) -> f64 {
42    // The makes the smallest gap in x = 1/16 - nextafter(1/16, 0) = 1/2^57
43    // for reals = 0.7 pm on the earth if x is an angle in degrees.  (This
44    // is about 1000 times more resolution than we get with angles around 90
45    // degrees.)  We use this to avoid having to deal with near singular
46    // cases when x is non-zero but tiny (e.g., 1.0e-200).
47    let z = 1.0 / 16.0;
48    let mut y = x.abs();
49    // The compiler mustn't "simplify" z - (z - y) to y
50    if y < z {
51        y = z - (z - y);
52    };
53    if x == 0.0 {
54        0.0
55    } else if x < 0.0 {
56        -y
57    } else {
58        y
59    }
60}
61
62/// remainder of x/y in the range [-y/2, y/2]
63fn remainder(x: f64, y: f64) -> f64 {
64    // z = math.fmod(x, y) if Math.isfinite(x) else Math.nan
65    let z = if x.is_finite() { x % y } else { f64::NAN };
66
67    // # On Windows 32-bit with python 2.7, math.fmod(-0.0, 360) = +0.0
68    // # This fixes this bug.  See also Math::AngNormalize in the C++ library.
69    // # sincosd has a similar fix.
70    // z = x if x == 0 else z
71    let z = if x == 0.0 { x } else { z };
72
73    // return (z + y if z < -y/2 else
74    // (z if z < y/2 else z -y))
75    if z < -y / 2.0 {
76        z + y
77    } else if z < y / 2.0 {
78        z
79    } else {
80        z - y
81    }
82}
83
84/// reduce angle to (-180,180]
85pub fn ang_normalize(x: f64) -> f64 {
86    // y = Math.remainder(x, 360)
87    // return 180 if y == -180 else y
88    let y = remainder(x, 360.0);
89    if y == -180.0 {
90        180.0
91    } else {
92        y
93    }
94}
95
96// Replace angles outside [-90,90] with NaN
97pub fn lat_fix(x: f64) -> f64 {
98    if x.abs() > 90.0 {
99        f64::NAN
100    } else {
101        x
102    }
103}
104
105// compute y - x and reduce to [-180,180] accurately
106pub fn ang_diff(x: f64, y: f64) -> (f64, f64) {
107    let (d, t) = sum(ang_normalize(-x), ang_normalize(y));
108    let d = ang_normalize(d);
109    if d == 180.0 && t > 0.0 {
110        sum(-180.0, t)
111    } else {
112        sum(d, t)
113    }
114}
115
116/// Compute sine and cosine of x in degrees
117pub fn sincosd(x: f64) -> (f64, f64) {
118    let (mut r, q) = libm::remquo(x, 90.0);
119
120    r = r.to_radians();
121
122    let (mut sinx, mut cosx) = r.sin_cos();
123
124    (sinx, cosx) = match q as u32 & 3 {
125        0 => (sinx, cosx),
126        1 => (cosx, -sinx),
127        2 => (-sinx, -cosx),
128        3 => (-cosx, sinx),
129        _ => unreachable!(),
130    };
131
132    // special values from F.10.1.12
133    cosx += 0.0;
134
135    // special values from F.10.1.13
136    if sinx == 0.0 {
137        sinx = sinx.copysign(x);
138    }
139    (sinx, cosx)
140}
141
142// Compute atan2(y, x) with result in degrees
143pub fn atan2d(mut y: f64, mut x: f64) -> f64 {
144    let mut q = if y.abs() > x.abs() {
145        std::mem::swap(&mut x, &mut y);
146        2
147    } else {
148        0
149    };
150    if x < 0.0 {
151        q += 1;
152        x = -x;
153    }
154    let mut ang = y.atan2(x).to_degrees();
155    match q {
156        0 => {}
157        1 => {
158            ang = if y >= 0.0 { 180.0 - ang } else { -180.0 - ang };
159        }
160        2 => {
161            ang = 90.0 - ang;
162        }
163        3 => {
164            ang += -90.0;
165        }
166        _ => unreachable!(),
167    };
168    ang
169}
170
171pub fn eatanhe(x: f64, es: f64) -> f64 {
172    if es > 0.0 {
173        es * (es * x).atanh()
174    } else {
175        -es * (es * x).atan()
176    }
177}
178
179// Functions that used to be inside Geodesic
180pub fn sin_cos_series<const N: usize>(sinp: bool, sinx: f64, cosx: f64, c: &[f64; N]) -> f64 {
181    let mut k = c.len();
182    let mut n: i64 = k as i64 - if sinp { 1 } else { 0 };
183    let ar: f64 = 2.0 * (cosx - sinx) * (cosx + sinx);
184    let mut y1 = 0.0;
185    let mut y0: f64 = if n & 1 != 0 {
186        k -= 1;
187        c[k]
188    } else {
189        0.0
190    };
191    n /= 2;
192    while n > 0 {
193        n -= 1;
194        k -= 1;
195        y1 = ar * y0 - y1 + c[k];
196        k -= 1;
197        y0 = ar * y1 - y0 + c[k];
198    }
199    if sinp {
200        2.0 * sinx * cosx * y0
201    } else {
202        cosx * (y0 - y1)
203    }
204}
205
206// Solve astroid equation
207pub fn astroid(x: f64, y: f64) -> f64 {
208    let p = sq(x);
209    let q = sq(y);
210    let r = (p + q - 1.0) / 6.0;
211    if !(q == 0.0 && r <= 0.0) {
212        let s = p * q / 4.0;
213        let r2 = sq(r);
214        let r3 = r * r2;
215        let disc = s * (s + 2.0 * r3);
216        let mut u = r;
217        if disc >= 0.0 {
218            let mut t3 = s + r3;
219            t3 += if t3 < 0.0 { -disc.sqrt() } else { disc.sqrt() };
220            let t = t3.cbrt();
221            u += t + if t != 0.0 { r2 / t } else { 0.0 };
222        } else {
223            let ang = (-disc).sqrt().atan2(-(s + r3));
224            u += 2.0 * r * (ang / 3.0).cos();
225        }
226        let v = (sq(u) + q).sqrt();
227        let uv = if u < 0.0 { q / (v - u) } else { u + v };
228        let w = (uv - q) / (2.0 * v);
229        uv / ((uv + sq(w)).sqrt() + w)
230    } else {
231        0.0
232    }
233}
234
235pub fn _A1m1f(eps: f64) -> f64 {
236    const COEFF: [f64; 5] = [1.0, 4.0, 64.0, 0.0, 256.0];
237    let m = GEODESIC_ORDER / 2;
238    let t = polyval(m, &COEFF, sq(eps)) / COEFF[m + 1];
239    (t + eps) / (1.0 - eps)
240}
241
242pub fn _C1f(eps: f64, c: &mut [f64; CARR_SIZE]) {
243    const COEFF: [f64; 18] = [
244        -1.0, 6.0, -16.0, 32.0, -9.0, 64.0, -128.0, 2048.0, 9.0, -16.0, 768.0, 3.0, -5.0, 512.0,
245        -7.0, 1280.0, -7.0, 2048.0,
246    ];
247    let eps2 = sq(eps);
248    let mut d = eps;
249    let mut o = 0;
250    // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)`
251    // but benching (rust-1.75) shows that it would be slower.
252    #[allow(clippy::needless_range_loop)]
253    for l in 1..=GEODESIC_ORDER {
254        let m = (GEODESIC_ORDER - l) / 2;
255        c[l] = d * polyval(m, &COEFF[o..], eps2) / COEFF[o + m + 1];
256        o += m + 2;
257        d *= eps;
258    }
259}
260
261pub fn _C1pf(eps: f64, c: &mut [f64; CARR_SIZE]) {
262    const COEFF: [f64; 18] = [
263        205.0, -432.0, 768.0, 1536.0, 4005.0, -4736.0, 3840.0, 12288.0, -225.0, 116.0, 384.0,
264        -7173.0, 2695.0, 7680.0, 3467.0, 7680.0, 38081.0, 61440.0,
265    ];
266    let eps2 = sq(eps);
267    let mut d = eps;
268    let mut o = 0;
269    // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)`
270    // but benching (rust-1.75) shows that it would be slower.
271    #[allow(clippy::needless_range_loop)]
272    for l in 1..=GEODESIC_ORDER {
273        let m = (GEODESIC_ORDER - l) / 2;
274        c[l] = d * polyval(m, &COEFF[o..], eps2) / COEFF[o + m + 1];
275        o += m + 2;
276        d *= eps;
277    }
278}
279
280pub fn _A2m1f(eps: f64) -> f64 {
281    const COEFF: [f64; 5] = [-11.0, -28.0, -192.0, 0.0, 256.0];
282    let m = GEODESIC_ORDER / 2;
283    let t = polyval(m, &COEFF, sq(eps)) / COEFF[m + 1];
284    (t - eps) / (1.0 + eps)
285}
286
287pub fn _C2f(eps: f64, c: &mut [f64; CARR_SIZE]) {
288    const COEFF: [f64; 18] = [
289        1.0, 2.0, 16.0, 32.0, 35.0, 64.0, 384.0, 2048.0, 15.0, 80.0, 768.0, 7.0, 35.0, 512.0, 63.0,
290        1280.0, 77.0, 2048.0,
291    ];
292    let eps2 = sq(eps);
293    let mut d = eps;
294    let mut o = 0;
295    // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)`
296    // but benching (rust-1.75) shows that it would be slower.
297    #[allow(clippy::needless_range_loop)]
298    for l in 1..=GEODESIC_ORDER {
299        let m = (GEODESIC_ORDER - l) / 2;
300        c[l] = d * polyval(m, &COEFF[o..], eps2) / COEFF[o + m + 1];
301        o += m + 2;
302        d *= eps;
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309    use approx::assert_relative_eq;
310    // Results for the assertions are taken by running the python implementation
311
312    #[test]
313    fn test_sincosd() {
314        let res = sincosd(-77.03196);
315        assert_relative_eq!(res.0, -0.9744953925159129);
316        assert_relative_eq!(res.1, 0.22440750870961693);
317
318        let res = sincosd(69.48894);
319        assert_relative_eq!(res.0, 0.9366045700708676);
320        assert_relative_eq!(res.1, 0.3503881837653281);
321        let res = sincosd(-1.0);
322        assert_relative_eq!(res.0, -0.01745240643728351);
323        assert_relative_eq!(res.1, 0.9998476951563913);
324    }
325
326    #[test]
327    fn test__C2f() {
328        let mut c = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
329        _C2f(0.12, &mut c);
330        assert_eq!(
331            c,
332            [
333                1.0,
334                0.0601087776,
335                0.00270653103,
336                0.000180486,
337                1.4215824e-05,
338                1.22472e-06,
339                1.12266e-07
340            ]
341        )
342    }
343
344    #[test]
345    fn test__A2m1f() {
346        assert_eq!(_A2m1f(0.12), -0.11680607884285714);
347    }
348
349    #[test]
350    fn test__C1pf() {
351        let mut c = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
352        _C1pf(0.12, &mut c);
353        assert_eq!(
354            c,
355            [
356                1.0,
357                0.059517321000000005,
358                0.004421053215,
359                0.0005074200000000001,
360                6.997613759999999e-05,
361                1.1233080000000001e-05,
362                1.8507366e-06
363            ]
364        )
365    }
366
367    #[test]
368    fn test__C1f() {
369        let mut c = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
370        _C1f(0.12, &mut c);
371        assert_eq!(
372            c,
373            [
374                1.0,
375                -0.059676777599999994,
376                -0.000893533122,
377                -3.57084e-05,
378                -2.007504e-06,
379                -1.3607999999999999e-07,
380                -1.0205999999999999e-08
381            ]
382        )
383    }
384
385    #[test]
386    fn test__A1m1f() {
387        assert_eq!(_A1m1f(0.12), 0.1404582405272727);
388    }
389
390    #[test]
391    fn test_astroid() {
392        assert_eq!(astroid(21.0, 12.0), 23.44475767500982);
393    }
394
395    #[test]
396    fn test_sin_cos_series() {
397        assert_eq!(
398            sin_cos_series(
399                false,
400                -0.8928657853278468,
401                0.45032287238256896,
402                &[
403                    0.6660771734724675,
404                    1.5757752625233906e-05,
405                    3.8461688963148916e-09,
406                    1.3040960748120204e-12,
407                    5.252912023008548e-16,
408                    2.367770858285795e-19
409                ],
410            ),
411            0.29993425660538664
412        );
413
414        assert_eq!(
415            sin_cos_series(
416                false,
417                -0.8928657853278468,
418                0.45032287238256896,
419                &[0., 1., 2., 3., 4., 5.],
420            ),
421            1.8998562852254026
422        );
423        assert_eq!(
424            sin_cos_series(
425                true,
426                0.2969032234925426,
427                0.9549075745221299,
428                &[
429                    0.0,
430                    -0.0003561309485314716,
431                    -3.170731714689771e-08,
432                    -7.527972480734327e-12,
433                    -2.5133854116682488e-15,
434                    -1.0025061462383107e-18,
435                    -4.462794158625518e-22
436                ],
437            ),
438            -0.00020196665516199853
439        );
440        assert_eq!(
441            sin_cos_series(
442                true,
443                -0.8928657853278468,
444                0.45032287238256896,
445                &[
446                    0.0,
447                    -0.0003561309485314716,
448                    -3.170731714689771e-08,
449                    -7.527972480734327e-12,
450                    -2.5133854116682488e-15,
451                    -1.0025061462383107e-18,
452                    -4.462794158625518e-22
453                ],
454            ),
455            0.00028635444718997857
456        );
457
458        assert_eq!(
459            sin_cos_series(true, 0.12, 0.21, &[1.0, 2.0]),
460            0.10079999999999999
461        );
462        assert_eq!(
463            sin_cos_series(
464                true,
465                -0.024679833885152578,
466                0.9996954065111039,
467                &[
468                    0.0,
469                    -0.0008355098973052918,
470                    -1.7444619952659748e-07,
471                    -7.286557795511902e-11,
472                    -3.80472772706481e-14,
473                    -2.2251271876594078e-17,
474                    1.2789961247944744e-20
475                ],
476            ),
477            4.124513511893872e-05
478        );
479    }
480
481    // corresponding to tests/signtest.cpp
482    mod sign_test {
483        use super::*;
484        fn is_equiv(x: f64, y: f64) -> bool {
485            (x.is_nan() && y.is_nan()) || (x == y && x.is_sign_positive() == y.is_sign_positive())
486        }
487
488        macro_rules! check_sincosd {
489            ($x: expr, $expected_sin: expr, $expected_cos: expr) => {
490                let (sinx, cosx) = sincosd($x);
491                assert!(
492                    is_equiv(sinx, $expected_sin),
493                    "sinx({}) = {}, but got {}",
494                    $x,
495                    $expected_sin,
496                    sinx
497                );
498                assert!(
499                    is_equiv(cosx, $expected_cos),
500                    "cosx({}) = {}, but got {}",
501                    $x,
502                    $expected_cos,
503                    cosx
504                );
505            };
506        }
507
508        #[test]
509        fn sin_cosd() {
510            check_sincosd!(f64::NEG_INFINITY, f64::NAN, f64::NAN);
511            check_sincosd!(-810.0, -1.0, 0.0);
512            check_sincosd!(-720.0, -0.0, 1.0);
513            check_sincosd!(-630.0, 1.0, 0.0);
514            check_sincosd!(-540.0, -0.0, -1.0);
515            check_sincosd!(-450.0, -1.0, 0.0);
516            check_sincosd!(-360.0, -0.0, 1.0);
517            check_sincosd!(-270.0, 1.0, 0.0);
518            check_sincosd!(-180.0, -0.0, -1.0);
519            check_sincosd!(-90.0, -1.0, 0.0);
520            check_sincosd!(-0.0, -0.0, 1.0);
521            check_sincosd!(0.0, 0.0, 1.0);
522            check_sincosd!(90.0, 1.0, 0.0);
523            check_sincosd!(180.0, 0.0, -1.0);
524            check_sincosd!(270.0, -1.0, 0.0);
525            check_sincosd!(360.0, 0.0, 1.0);
526            check_sincosd!(450.0, 1.0, 0.0);
527            check_sincosd!(540.0, 0.0, -1.0);
528            check_sincosd!(630.0, -1.0, 0.0);
529            check_sincosd!(720.0, 0.0, 1.0);
530            check_sincosd!(810.0, 1.0, 0.0);
531            check_sincosd!(f64::INFINITY, f64::NAN, f64::NAN);
532            check_sincosd!(f64::NAN, f64::NAN, f64::NAN);
533        }
534    }
535}