Skip to main content

geographiclib_rs/
geodesic.rs

1#![allow(non_snake_case)]
2#![allow(clippy::excessive_precision)]
3
4use crate::geodesic_capability as caps;
5use crate::geodesic_line;
6use crate::geomath;
7use std::sync;
8
9use std::f64::consts::{FRAC_1_SQRT_2, PI};
10
11pub const WGS84_A: f64 = 6378137.0;
12// Evaluating this as 1000000000.0 / (298257223563f64) reduces the
13// round-off error by about 10%.  However, expressing the flattening as
14// 1/298.257223563 is well ingrained.
15pub const WGS84_F: f64 = 1.0 / ((298257223563f64) / 1000000000.0);
16
17#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
18pub struct Geodesic {
19    pub a: f64,
20    pub f: f64,
21    pub _f1: f64,
22    pub _e2: f64,
23    pub _ep2: f64,
24    _n: f64,
25    pub _b: f64,
26    pub _c2: f64,
27    _etol2: f64,
28    _A3x: [f64; GEODESIC_ORDER],
29    _C3x: [f64; _nC3x_],
30    _C4x: [f64; _nC4x_],
31
32    _nC3x_: usize,
33    _nC4x_: usize,
34    maxit1_: u64,
35    maxit2_: u64,
36
37    pub tiny_: f64,
38    tol0_: f64,
39    tol1_: f64,
40    _tol2_: f64,
41    tolb_: f64,
42    xthresh_: f64,
43}
44
45static WGS84_GEOD: sync::OnceLock<Geodesic> = sync::OnceLock::new();
46
47impl Geodesic {
48    pub fn wgs84() -> Self {
49        *WGS84_GEOD.get_or_init(|| Geodesic::new(WGS84_A, WGS84_F))
50    }
51
52    pub fn equatorial_radius(&self) -> f64 {
53        self.a
54    }
55
56    pub fn flattening(&self) -> f64 {
57        self.f
58    }
59}
60
61const COEFF_A3: [f64; 18] = [
62    -3.0, 128.0, -2.0, -3.0, 64.0, -1.0, -3.0, -1.0, 16.0, 3.0, -1.0, -2.0, 8.0, 1.0, -1.0, 2.0,
63    1.0, 1.0,
64];
65
66const COEFF_C3: [f64; 45] = [
67    3.0, 128.0, 2.0, 5.0, 128.0, -1.0, 3.0, 3.0, 64.0, -1.0, 0.0, 1.0, 8.0, -1.0, 1.0, 4.0, 5.0,
68    256.0, 1.0, 3.0, 128.0, -3.0, -2.0, 3.0, 64.0, 1.0, -3.0, 2.0, 32.0, 7.0, 512.0, -10.0, 9.0,
69    384.0, 5.0, -9.0, 5.0, 192.0, 7.0, 512.0, -14.0, 7.0, 512.0, 21.0, 2560.0,
70];
71
72const COEFF_C4: [f64; 77] = [
73    97.0, 15015.0, 1088.0, 156.0, 45045.0, -224.0, -4784.0, 1573.0, 45045.0, -10656.0, 14144.0,
74    -4576.0, -858.0, 45045.0, 64.0, 624.0, -4576.0, 6864.0, -3003.0, 15015.0, 100.0, 208.0, 572.0,
75    3432.0, -12012.0, 30030.0, 45045.0, 1.0, 9009.0, -2944.0, 468.0, 135135.0, 5792.0, 1040.0,
76    -1287.0, 135135.0, 5952.0, -11648.0, 9152.0, -2574.0, 135135.0, -64.0, -624.0, 4576.0, -6864.0,
77    3003.0, 135135.0, 8.0, 10725.0, 1856.0, -936.0, 225225.0, -8448.0, 4992.0, -1144.0, 225225.0,
78    -1440.0, 4160.0, -4576.0, 1716.0, 225225.0, -136.0, 63063.0, 1024.0, -208.0, 105105.0, 3584.0,
79    -3328.0, 1144.0, 315315.0, -128.0, 135135.0, -2560.0, 832.0, 405405.0, 128.0, 99099.0,
80];
81
82pub const GEODESIC_ORDER: usize = 6;
83pub(crate) const CARR_SIZE: usize = GEODESIC_ORDER + 1;
84
85#[allow(non_upper_case_globals)]
86const _nC3x_: usize = 15;
87#[allow(non_upper_case_globals)]
88const _nC4x_: usize = 21;
89
90impl Geodesic {
91    pub fn new(a: f64, f: f64) -> Self {
92        let maxit1_ = 20;
93        let maxit2_ = maxit1_ + geomath::DIGITS + 10;
94        let tiny_ = f64::MIN_POSITIVE.sqrt();
95        let tol0_ = f64::EPSILON;
96        let tol1_ = 200.0 * tol0_;
97        let _tol2_ = tol0_.sqrt();
98        let tolb_ = tol0_ * _tol2_;
99        let xthresh_ = 1000.0 * _tol2_;
100
101        let _f1 = 1.0 - f;
102        let _e2 = f * (2.0 - f);
103        let _ep2 = _e2 / geomath::sq(_f1);
104        let _n = f / (2.0 - f);
105        let _b = a * _f1;
106        let _c2 = (geomath::sq(a)
107            + geomath::sq(_b)
108                * (if _e2 == 0.0 {
109                    1.0
110                } else {
111                    geomath::eatanhe(1.0, (if f < 0.0 { -1.0 } else { 1.0 }) * _e2.abs().sqrt())
112                        / _e2
113                }))
114            / 2.0;
115        let _etol2 = 0.1 * _tol2_ / (f.abs().max(0.001) * (1.0 - f / 2.0).min(1.0) / 2.0).sqrt();
116
117        let mut _A3x: [f64; GEODESIC_ORDER] = [0.0; GEODESIC_ORDER];
118        let mut _C3x: [f64; _nC3x_] = [0.0; _nC3x_];
119        let mut _C4x: [f64; _nC4x_] = [0.0; _nC4x_];
120
121        // Call a3coeff
122        let mut o: usize = 0;
123        for (k, j) in (0..GEODESIC_ORDER).rev().enumerate() {
124            let m = j.min(GEODESIC_ORDER - j - 1);
125            _A3x[k] = geomath::polyval(m, &COEFF_A3[o..], _n) / COEFF_A3[o + m + 1];
126            o += m + 2;
127        }
128
129        // c3coeff
130        let mut o = 0;
131        let mut k = 0;
132        for l in 1..GEODESIC_ORDER {
133            for j in (l..GEODESIC_ORDER).rev() {
134                let m = j.min(GEODESIC_ORDER - j - 1);
135                _C3x[k] = geomath::polyval(m, &COEFF_C3[o..], _n) / COEFF_C3[o + m + 1];
136                k += 1;
137                o += m + 2;
138            }
139        }
140
141        // c4coeff
142        let mut o = 0;
143        let mut k = 0;
144        for l in 0..GEODESIC_ORDER {
145            for j in (l..GEODESIC_ORDER).rev() {
146                let m = GEODESIC_ORDER - j - 1;
147                _C4x[k] = geomath::polyval(m, &COEFF_C4[o..], _n) / COEFF_C4[o + m + 1];
148                k += 1;
149                o += m + 2;
150            }
151        }
152
153        Geodesic {
154            a,
155            f,
156            _f1,
157            _e2,
158            _ep2,
159            _n,
160            _b,
161            _c2,
162            _etol2,
163            _A3x,
164            _C3x,
165            _C4x,
166
167            _nC3x_,
168            _nC4x_,
169            maxit1_,
170            maxit2_,
171
172            tiny_,
173            tol0_,
174            tol1_,
175            _tol2_,
176            tolb_,
177            xthresh_,
178        }
179    }
180
181    pub fn _A3f(&self, eps: f64) -> f64 {
182        geomath::polyval(GEODESIC_ORDER - 1, &self._A3x, eps)
183    }
184
185    pub fn _C3f(&self, eps: f64, c: &mut [f64; GEODESIC_ORDER]) {
186        let mut mult = 1.0;
187        let mut o = 0;
188        // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)`
189        // but benching (rust-1.75) shows that it would be slower.
190        #[allow(clippy::needless_range_loop)]
191        for l in 1..GEODESIC_ORDER {
192            let m = GEODESIC_ORDER - l - 1;
193            mult *= eps;
194            c[l] = mult * geomath::polyval(m, &self._C3x[o..], eps);
195            o += m + 1;
196        }
197    }
198
199    pub fn _C4f(&self, eps: f64, c: &mut [f64; GEODESIC_ORDER]) {
200        let mut mult = 1.0;
201        let mut o = 0;
202        // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)`
203        // but benching (rust-1.75) shows that it would be slower.
204        #[allow(clippy::needless_range_loop)]
205        for l in 0..GEODESIC_ORDER {
206            let m = GEODESIC_ORDER - l - 1;
207            c[l] = mult * geomath::polyval(m, &self._C4x[o..], eps);
208            o += m + 1;
209            mult *= eps;
210        }
211    }
212
213    #[allow(clippy::too_many_arguments)]
214    pub fn _Lengths(
215        &self,
216        eps: f64,
217        sig12: f64,
218        ssig1: f64,
219        csig1: f64,
220        dn1: f64,
221        ssig2: f64,
222        csig2: f64,
223        dn2: f64,
224        cbet1: f64,
225        cbet2: f64,
226        outmask: u64,
227        C1a: &mut [f64; CARR_SIZE],
228        C2a: &mut [f64; CARR_SIZE],
229    ) -> (f64, f64, f64, f64, f64) {
230        let outmask = outmask & caps::OUT_MASK;
231        let mut s12b = f64::NAN;
232        let mut m12b = f64::NAN;
233        let mut m0 = f64::NAN;
234        let mut M12 = f64::NAN;
235        let mut M21 = f64::NAN;
236
237        let mut A1 = 0.0;
238        let mut A2 = 0.0;
239        let mut m0x = 0.0;
240        let mut J12 = 0.0;
241
242        if outmask & (caps::DISTANCE | caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 {
243            A1 = geomath::_A1m1f(eps);
244            geomath::_C1f(eps, C1a);
245            if outmask & (caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 {
246                A2 = geomath::_A2m1f(eps);
247                geomath::_C2f(eps, C2a);
248                m0x = A1 - A2;
249                A2 += 1.0;
250            }
251            A1 += 1.0;
252        }
253        if outmask & caps::DISTANCE != 0 {
254            let B1 = geomath::sin_cos_series(true, ssig2, csig2, C1a)
255                - geomath::sin_cos_series(true, ssig1, csig1, C1a);
256            s12b = A1 * (sig12 + B1);
257            if outmask & (caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 {
258                let B2 = geomath::sin_cos_series(true, ssig2, csig2, C2a)
259                    - geomath::sin_cos_series(true, ssig1, csig1, C2a);
260                J12 = m0x * sig12 + (A1 * B1 - A2 * B2);
261            }
262        } else if outmask & (caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 {
263            for l in 1..=GEODESIC_ORDER {
264                C2a[l] = A1 * C1a[l] - A2 * C2a[l];
265            }
266            J12 = m0x * sig12
267                + (geomath::sin_cos_series(true, ssig2, csig2, C2a)
268                    - geomath::sin_cos_series(true, ssig1, csig1, C2a));
269        }
270        if outmask & caps::REDUCEDLENGTH != 0 {
271            m0 = m0x;
272            // J12 is wrong
273            m12b = dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2) - csig1 * csig2 * J12;
274        }
275        if outmask & caps::GEODESICSCALE != 0 {
276            let csig12 = csig1 * csig2 + ssig1 * ssig2;
277            let t = self._ep2 * (cbet1 - cbet2) * (cbet1 + cbet2) / (dn1 + dn2);
278            M12 = csig12 + (t * ssig2 - csig2 * J12) * ssig1 / dn1;
279            M21 = csig12 - (t * ssig1 - csig1 * J12) * ssig2 / dn2;
280        }
281        (s12b, m12b, m0, M12, M21)
282    }
283
284    #[allow(clippy::too_many_arguments)]
285    pub fn _InverseStart(
286        &self,
287        sbet1: f64,
288        cbet1: f64,
289        dn1: f64,
290        sbet2: f64,
291        cbet2: f64,
292        dn2: f64,
293        lam12: f64,
294        slam12: f64,
295        clam12: f64,
296        C1a: &mut [f64; CARR_SIZE],
297        C2a: &mut [f64; CARR_SIZE],
298    ) -> (f64, f64, f64, f64, f64, f64) {
299        let mut sig12 = -1.0;
300        let mut salp2 = f64::NAN;
301        let mut calp2 = f64::NAN;
302        let mut dnm = f64::NAN;
303
304        let mut somg12: f64;
305        let mut comg12: f64;
306
307        let sbet12 = sbet2 * cbet1 - cbet2 * sbet1;
308        let cbet12 = cbet2 * cbet1 + sbet2 * sbet1;
309
310        let mut sbet12a = sbet2 * cbet1;
311        sbet12a += cbet2 * sbet1;
312
313        let shortline = cbet12 >= 0.0 && sbet12 < 0.5 && cbet2 * lam12 < 0.5;
314        if shortline {
315            let mut sbetm2 = geomath::sq(sbet1 + sbet2);
316            sbetm2 /= sbetm2 + geomath::sq(cbet1 + cbet2);
317            dnm = (1.0 + self._ep2 * sbetm2).sqrt();
318            let omg12 = lam12 / (self._f1 * dnm);
319            somg12 = omg12.sin();
320            comg12 = omg12.cos();
321        } else {
322            somg12 = slam12;
323            comg12 = clam12;
324        }
325
326        let mut salp1 = cbet2 * somg12;
327
328        let mut calp1 = if comg12 >= 0.0 {
329            sbet12 + cbet2 * sbet1 * geomath::sq(somg12) / (1.0 + comg12)
330        } else {
331            sbet12a - cbet2 * sbet1 * geomath::sq(somg12) / (1.0 - comg12)
332        };
333
334        let ssig12 = salp1.hypot(calp1);
335        let csig12 = sbet1 * sbet2 + cbet1 * cbet2 * comg12;
336
337        if shortline && ssig12 < self._etol2 {
338            salp2 = cbet1 * somg12;
339            calp2 = sbet12
340                - cbet1
341                    * sbet2
342                    * (if comg12 >= 0.0 {
343                        geomath::sq(somg12) / (1.0 + comg12)
344                    } else {
345                        1.0 - comg12
346                    });
347            geomath::norm(&mut salp2, &mut calp2);
348            sig12 = ssig12.atan2(csig12);
349        } else if self._n.abs() > 0.1
350            || csig12 >= 0.0
351            || ssig12 >= 6.0 * self._n.abs() * PI * geomath::sq(cbet1)
352        {
353        } else {
354            let x: f64;
355            let y: f64;
356            let betscale: f64;
357            let lamscale: f64;
358            let lam12x = (-slam12).atan2(-clam12);
359            if self.f >= 0.0 {
360                let k2 = geomath::sq(sbet1) * self._ep2;
361                let eps = k2 / (2.0 * (1.0 + (1.0 + k2).sqrt()) + k2);
362                lamscale = self.f * cbet1 * self._A3f(eps) * PI;
363                betscale = lamscale * cbet1;
364                x = lam12x / lamscale;
365                y = sbet12a / betscale;
366            } else {
367                let cbet12a = cbet2 * cbet1 - sbet2 * sbet1;
368                let bet12a = sbet12a.atan2(cbet12a);
369                let (_, m12b, m0, _, _) = self._Lengths(
370                    self._n,
371                    PI + bet12a,
372                    sbet1,
373                    -cbet1,
374                    dn1,
375                    sbet2,
376                    cbet2,
377                    dn2,
378                    cbet1,
379                    cbet2,
380                    caps::REDUCEDLENGTH,
381                    C1a,
382                    C2a,
383                );
384                x = -1.0 + m12b / (cbet1 * cbet2 * m0 * PI);
385                betscale = if x < -0.01 {
386                    sbet12a / x
387                } else {
388                    -self.f * geomath::sq(cbet1) * PI
389                };
390                lamscale = betscale / cbet1;
391                y = lam12x / lamscale;
392            }
393            if y > -self.tol1_ && x > -1.0 - self.xthresh_ {
394                if self.f >= 0.0 {
395                    salp1 = (-x).min(1.0);
396                    calp1 = -(1.0 - geomath::sq(salp1)).sqrt()
397                } else {
398                    calp1 = x.max(if x > -self.tol1_ { 0.0 } else { -1.0 });
399                    salp1 = (1.0 - geomath::sq(calp1)).sqrt();
400                }
401            } else {
402                let k = geomath::astroid(x, y);
403                let omg12a = lamscale
404                    * if self.f >= 0.0 {
405                        -x * k / (1.0 + k)
406                    } else {
407                        -y * (1.0 + k) / k
408                    };
409                somg12 = omg12a.sin();
410                comg12 = -(omg12a.cos());
411                salp1 = cbet2 * somg12;
412                calp1 = sbet12a - cbet2 * sbet1 * geomath::sq(somg12) / (1.0 - comg12);
413            }
414        }
415
416        if salp1 > 0.0 || salp1.is_nan() {
417            geomath::norm(&mut salp1, &mut calp1);
418        } else {
419            salp1 = 1.0;
420            calp1 = 0.0;
421        };
422        (sig12, salp1, calp1, salp2, calp2, dnm)
423    }
424
425    #[allow(clippy::too_many_arguments)]
426    pub fn _Lambda12(
427        &self,
428        sbet1: f64,
429        cbet1: f64,
430        dn1: f64,
431        sbet2: f64,
432        cbet2: f64,
433        dn2: f64,
434        salp1: f64,
435        mut calp1: f64,
436        slam120: f64,
437        clam120: f64,
438        diffp: bool,
439        C1a: &mut [f64; CARR_SIZE],
440        C2a: &mut [f64; CARR_SIZE],
441        C3a: &mut [f64; GEODESIC_ORDER],
442    ) -> (f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64) {
443        if sbet1 == 0.0 && calp1 == 0.0 {
444            calp1 = -self.tiny_;
445        }
446        let salp0 = salp1 * cbet1;
447        let calp0 = calp1.hypot(salp1 * sbet1);
448
449        let mut ssig1 = sbet1;
450        let somg1 = salp0 * sbet1;
451        let mut csig1 = calp1 * cbet1;
452        let comg1 = calp1 * cbet1;
453        geomath::norm(&mut ssig1, &mut csig1);
454
455        let salp2 = if cbet2 != cbet1 { salp0 / cbet2 } else { salp1 };
456        let calp2 = if cbet2 != cbet1 || sbet2.abs() != -sbet1 {
457            (geomath::sq(calp1 * cbet1)
458                + if cbet1 < -sbet1 {
459                    (cbet2 - cbet1) * (cbet1 + cbet2)
460                } else {
461                    (sbet1 - sbet2) * (sbet1 + sbet2)
462                })
463            .sqrt()
464                / cbet2
465        } else {
466            calp1.abs()
467        };
468        let mut ssig2 = sbet2;
469        let somg2 = salp0 * sbet2;
470        let mut csig2 = calp2 * cbet2;
471        let comg2 = calp2 * cbet2;
472        geomath::norm(&mut ssig2, &mut csig2);
473
474        let sig12 = ((csig1 * ssig2 - ssig1 * csig2).max(0.0)).atan2(csig1 * csig2 + ssig1 * ssig2);
475        let somg12 = (comg1 * somg2 - somg1 * comg2).max(0.0);
476        let comg12 = comg1 * comg2 + somg1 * somg2;
477        let eta = (somg12 * clam120 - comg12 * slam120).atan2(comg12 * clam120 + somg12 * slam120);
478
479        let k2 = geomath::sq(calp0) * self._ep2;
480        let eps = k2 / (2.0 * (1.0 + (1.0 + k2).sqrt()) + k2);
481        self._C3f(eps, C3a);
482        let B312 = geomath::sin_cos_series(true, ssig2, csig2, C3a)
483            - geomath::sin_cos_series(true, ssig1, csig1, C3a);
484        let domg12 = -self.f * self._A3f(eps) * salp0 * (sig12 + B312);
485        let lam12 = eta + domg12;
486
487        let mut dlam12: f64;
488        if diffp {
489            if calp2 == 0.0 {
490                dlam12 = -2.0 * self._f1 * dn1 / sbet1;
491            } else {
492                let res = self._Lengths(
493                    eps,
494                    sig12,
495                    ssig1,
496                    csig1,
497                    dn1,
498                    ssig2,
499                    csig2,
500                    dn2,
501                    cbet1,
502                    cbet2,
503                    caps::REDUCEDLENGTH,
504                    C1a,
505                    C2a,
506                );
507                dlam12 = res.1;
508                dlam12 *= self._f1 / (calp2 * cbet2);
509            }
510        } else {
511            dlam12 = f64::NAN;
512        }
513        (
514            lam12, salp2, calp2, sig12, ssig1, csig1, ssig2, csig2, eps, domg12, dlam12,
515        )
516    }
517
518    // returns (a12, s12, azi1, azi2, m12, M12, M21, S12)
519    pub fn _gen_inverse_azi(
520        &self,
521        lat1: f64,
522        lon1: f64,
523        lat2: f64,
524        lon2: f64,
525        outmask: u64,
526    ) -> (f64, f64, f64, f64, f64, f64, f64, f64) {
527        let mut azi1 = f64::NAN;
528        let mut azi2 = f64::NAN;
529        let outmask = outmask & caps::OUT_MASK;
530
531        let (a12, s12, salp1, calp1, salp2, calp2, m12, M12, M21, S12) =
532            self._gen_inverse(lat1, lon1, lat2, lon2, outmask);
533        if outmask & caps::AZIMUTH != 0 {
534            azi1 = geomath::atan2d(salp1, calp1);
535            azi2 = geomath::atan2d(salp2, calp2);
536        }
537        (a12, s12, azi1, azi2, m12, M12, M21, S12)
538    }
539
540    // returns (a12, s12, salp1, calp1, salp2, calp2, m12, M12, M21, S12)
541    pub fn _gen_inverse(
542        &self,
543        lat1: f64,
544        lon1: f64,
545        lat2: f64,
546        lon2: f64,
547        outmask: u64,
548    ) -> (f64, f64, f64, f64, f64, f64, f64, f64, f64, f64) {
549        let mut lat1 = lat1;
550        let mut lat2 = lat2;
551        let mut a12 = f64::NAN;
552        let mut s12 = f64::NAN;
553        let mut m12 = f64::NAN;
554        let mut M12 = f64::NAN;
555        let mut M21 = f64::NAN;
556        let mut S12 = f64::NAN;
557        let outmask = outmask & caps::OUT_MASK;
558
559        let (mut lon12, mut lon12s) = geomath::ang_diff(lon1, lon2);
560        let mut lonsign = if lon12 >= 0.0 { 1.0 } else { -1.0 };
561
562        lon12 = lonsign * geomath::ang_round(lon12);
563        lon12s = geomath::ang_round((180.0 - lon12) - lonsign * lon12s);
564        let lam12 = lon12.to_radians();
565        let slam12: f64;
566        let mut clam12: f64;
567        if lon12 > 90.0 {
568            let res = geomath::sincosd(lon12s);
569            slam12 = res.0;
570            clam12 = res.1;
571            clam12 = -clam12;
572        } else {
573            let res = geomath::sincosd(lon12);
574            slam12 = res.0;
575            clam12 = res.1;
576        };
577        lat1 = geomath::ang_round(geomath::lat_fix(lat1));
578        lat2 = geomath::ang_round(geomath::lat_fix(lat2));
579
580        let swapp = if lat1.abs() < lat2.abs() { -1.0 } else { 1.0 };
581        if swapp < 0.0 {
582            lonsign *= -1.0;
583            std::mem::swap(&mut lat2, &mut lat1);
584        }
585        let latsign = if lat1 < 0.0 { 1.0 } else { -1.0 };
586        lat1 *= latsign;
587        lat2 *= latsign;
588
589        let (mut sbet1, mut cbet1) = geomath::sincosd(lat1);
590        sbet1 *= self._f1;
591
592        geomath::norm(&mut sbet1, &mut cbet1);
593        cbet1 = cbet1.max(self.tiny_);
594
595        let (mut sbet2, mut cbet2) = geomath::sincosd(lat2);
596        sbet2 *= self._f1;
597
598        geomath::norm(&mut sbet2, &mut cbet2);
599        cbet2 = cbet2.max(self.tiny_);
600
601        if cbet1 < -sbet1 {
602            if cbet2 == cbet1 {
603                sbet2 = if sbet2 < 0.0 { sbet1 } else { -sbet1 };
604            }
605        } else if sbet2.abs() == -sbet1 {
606            cbet2 = cbet1;
607        }
608
609        let dn1 = (1.0 + self._ep2 * geomath::sq(sbet1)).sqrt();
610        let dn2 = (1.0 + self._ep2 * geomath::sq(sbet2)).sqrt();
611
612        let mut C1a: [f64; CARR_SIZE] = [0.0; CARR_SIZE];
613        let mut C2a: [f64; CARR_SIZE] = [0.0; CARR_SIZE];
614        let mut C3a: [f64; GEODESIC_ORDER] = [0.0; GEODESIC_ORDER];
615
616        let mut meridian = lat1 == -90.0 || slam12 == 0.0;
617        let mut calp1 = 0.0;
618        let mut salp1 = 0.0;
619        let mut calp2 = 0.0;
620        let mut salp2 = 0.0;
621        let mut ssig1 = 0.0;
622        let mut csig1 = 0.0;
623        let mut ssig2 = 0.0;
624        let mut csig2 = 0.0;
625        let mut sig12: f64;
626        let mut s12x = 0.0;
627        let mut m12x = 0.0;
628
629        if meridian {
630            calp1 = clam12;
631            salp1 = slam12;
632            calp2 = 1.0;
633            salp2 = 0.0;
634
635            ssig1 = sbet1;
636            csig1 = calp1 * cbet1;
637            ssig2 = sbet2;
638            csig2 = calp2 * cbet2;
639
640            sig12 = ((csig1 * ssig2 - ssig1 * csig2).max(0.0)).atan2(csig1 * csig2 + ssig1 * ssig2);
641            let res = self._Lengths(
642                self._n,
643                sig12,
644                ssig1,
645                csig1,
646                dn1,
647                ssig2,
648                csig2,
649                dn2,
650                cbet1,
651                cbet2,
652                outmask | caps::DISTANCE | caps::REDUCEDLENGTH,
653                &mut C1a,
654                &mut C2a,
655            );
656            s12x = res.0;
657            m12x = res.1;
658            M12 = res.3;
659            M21 = res.4;
660
661            if sig12 < 1.0 || m12x >= 0.0 {
662                if sig12 < 3.0 * self.tiny_ {
663                    sig12 = 0.0;
664                    m12x = 0.0;
665                    s12x = 0.0;
666                }
667                m12x *= self._b;
668                s12x *= self._b;
669                a12 = sig12.to_degrees();
670            } else {
671                meridian = false;
672            }
673        }
674
675        let mut somg12 = 2.0;
676        let mut comg12 = 0.0;
677        let mut omg12 = 0.0;
678        let dnm: f64;
679        let mut eps = 0.0;
680        if !meridian && sbet1 == 0.0 && (self.f <= 0.0 || lon12s >= self.f * 180.0) {
681            calp1 = 0.0;
682            calp2 = 0.0;
683            salp1 = 1.0;
684            salp2 = 1.0;
685
686            s12x = self.a * lam12;
687            sig12 = lam12 / self._f1;
688            omg12 = lam12 / self._f1;
689            m12x = self._b * sig12.sin();
690            if outmask & caps::GEODESICSCALE != 0 {
691                M12 = sig12.cos();
692                M21 = sig12.cos();
693            }
694            a12 = lon12 / self._f1;
695        } else if !meridian {
696            let res = self._InverseStart(
697                sbet1, cbet1, dn1, sbet2, cbet2, dn2, lam12, slam12, clam12, &mut C1a, &mut C2a,
698            );
699            sig12 = res.0;
700            salp1 = res.1;
701            calp1 = res.2;
702            salp2 = res.3;
703            calp2 = res.4;
704            dnm = res.5;
705
706            if sig12 >= 0.0 {
707                s12x = sig12 * self._b * dnm;
708                m12x = geomath::sq(dnm) * self._b * (sig12 / dnm).sin();
709                if outmask & caps::GEODESICSCALE != 0 {
710                    M12 = (sig12 / dnm).cos();
711                    M21 = (sig12 / dnm).cos();
712                }
713                a12 = sig12.to_degrees();
714                omg12 = lam12 / (self._f1 * dnm);
715            } else {
716                let mut tripn = false;
717                let mut tripb = false;
718                let mut salp1a = self.tiny_;
719                let mut calp1a = 1.0;
720                let mut salp1b = self.tiny_;
721                let mut calp1b = -1.0;
722                let mut domg12 = 0.0;
723                for numit in 0..self.maxit2_ {
724                    let res = self._Lambda12(
725                        sbet1,
726                        cbet1,
727                        dn1,
728                        sbet2,
729                        cbet2,
730                        dn2,
731                        salp1,
732                        calp1,
733                        slam12,
734                        clam12,
735                        numit < self.maxit1_,
736                        &mut C1a,
737                        &mut C2a,
738                        &mut C3a,
739                    );
740                    let v = res.0;
741                    salp2 = res.1;
742                    calp2 = res.2;
743                    sig12 = res.3;
744                    ssig1 = res.4;
745                    csig1 = res.5;
746                    ssig2 = res.6;
747                    csig2 = res.7;
748                    eps = res.8;
749                    domg12 = res.9;
750                    let dv = res.10;
751
752                    if tripb
753                        || v.abs() < if tripn { 8.0 } else { 1.0 } * self.tol0_
754                        || v.abs().is_nan()
755                    {
756                        break;
757                    };
758                    if v > 0.0 && (numit > self.maxit1_ || calp1 / salp1 > calp1b / salp1b) {
759                        salp1b = salp1;
760                        calp1b = calp1;
761                    } else if v < 0.0 && (numit > self.maxit1_ || calp1 / salp1 < calp1a / salp1a) {
762                        salp1a = salp1;
763                        calp1a = calp1;
764                    }
765                    if numit < self.maxit1_ && dv > 0.0 {
766                        let dalp1 = -v / dv;
767                        let sdalp1 = dalp1.sin();
768                        let cdalp1 = dalp1.cos();
769                        let nsalp1 = salp1 * cdalp1 + calp1 * sdalp1;
770                        if nsalp1 > 0.0 && dalp1.abs() < PI {
771                            calp1 = calp1 * cdalp1 - salp1 * sdalp1;
772                            salp1 = nsalp1;
773                            geomath::norm(&mut salp1, &mut calp1);
774                            tripn = v.abs() <= 16.0 * self.tol0_;
775                            continue;
776                        }
777                    }
778
779                    salp1 = (salp1a + salp1b) / 2.0;
780                    calp1 = (calp1a + calp1b) / 2.0;
781                    geomath::norm(&mut salp1, &mut calp1);
782                    tripn = false;
783                    tripb = (salp1a - salp1).abs() + (calp1a - calp1) < self.tolb_
784                        || (salp1 - salp1b).abs() + (calp1 - calp1b) < self.tolb_;
785                }
786                let lengthmask = outmask
787                    | if outmask & (caps::REDUCEDLENGTH | caps::GEODESICSCALE) != 0 {
788                        caps::DISTANCE
789                    } else {
790                        caps::EMPTY
791                    };
792                let res = self._Lengths(
793                    eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, lengthmask,
794                    &mut C1a, &mut C2a,
795                );
796                s12x = res.0;
797                m12x = res.1;
798                M12 = res.3;
799                M21 = res.4;
800
801                m12x *= self._b;
802                s12x *= self._b;
803                a12 = sig12.to_degrees();
804                if outmask & caps::AREA != 0 {
805                    let sdomg12 = domg12.sin();
806                    let cdomg12 = domg12.cos();
807                    somg12 = slam12 * cdomg12 - clam12 * sdomg12;
808                    comg12 = clam12 * cdomg12 + slam12 * sdomg12;
809                }
810            }
811        }
812        if outmask & caps::DISTANCE != 0 {
813            s12 = 0.0 + s12x;
814        }
815        if outmask & caps::REDUCEDLENGTH != 0 {
816            m12 = 0.0 + m12x;
817        }
818        if outmask & caps::AREA != 0 {
819            let salp0 = salp1 * cbet1;
820            let calp0 = calp1.hypot(salp1 * sbet1);
821            if calp0 != 0.0 && salp0 != 0.0 {
822                ssig1 = sbet1;
823                csig1 = calp1 * cbet1;
824                ssig2 = sbet2;
825                csig2 = calp2 * cbet2;
826                let k2 = geomath::sq(calp0) * self._ep2;
827                eps = k2 / (2.0 * (1.0 + (1.0 + k2).sqrt()) + k2);
828                let A4 = geomath::sq(self.a) * calp0 * salp0 * self._e2;
829                geomath::norm(&mut ssig1, &mut csig1);
830                geomath::norm(&mut ssig2, &mut csig2);
831                let mut C4a: [f64; GEODESIC_ORDER] = [0.0; GEODESIC_ORDER];
832                self._C4f(eps, &mut C4a);
833                let B41 = geomath::sin_cos_series(false, ssig1, csig1, &C4a);
834                let B42 = geomath::sin_cos_series(false, ssig2, csig2, &C4a);
835                S12 = A4 * (B42 - B41);
836            } else {
837                S12 = 0.0;
838            }
839
840            if !meridian && somg12 > 1.0 {
841                somg12 = omg12.sin();
842                comg12 = omg12.cos();
843            }
844
845            // We're diverging from Karney's implementation here
846            // which uses the hardcoded constant: -0.7071 for FRAC_1_SQRT_2
847            let alp12: f64;
848            if !meridian && comg12 > -FRAC_1_SQRT_2 && sbet2 - sbet1 < 1.75 {
849                let domg12 = 1.0 + comg12;
850                let dbet1 = 1.0 + cbet1;
851                let dbet2 = 1.0 + cbet2;
852                alp12 = 2.0
853                    * (somg12 * (sbet1 * dbet2 + sbet2 * dbet1))
854                        .atan2(domg12 * (sbet1 * sbet2 + dbet1 * dbet2));
855            } else {
856                let mut salp12 = salp2 * calp1 - calp2 * salp1;
857                let mut calp12 = calp2 * calp1 + salp2 * salp1;
858
859                if salp12 == 0.0 && calp12 < 0.0 {
860                    salp12 = self.tiny_ * calp1;
861                    calp12 = -1.0;
862                }
863                alp12 = salp12.atan2(calp12);
864            }
865            S12 += self._c2 * alp12;
866            S12 *= swapp * lonsign * latsign;
867            S12 += 0.0;
868        }
869
870        if swapp < 0.0 {
871            std::mem::swap(&mut salp2, &mut salp1);
872
873            std::mem::swap(&mut calp2, &mut calp1);
874
875            if outmask & caps::GEODESICSCALE != 0 {
876                std::mem::swap(&mut M21, &mut M12);
877            }
878        }
879        salp1 *= swapp * lonsign;
880        calp1 *= swapp * latsign;
881        salp2 *= swapp * lonsign;
882        calp2 *= swapp * latsign;
883        (a12, s12, salp1, calp1, salp2, calp2, m12, M12, M21, S12)
884    }
885
886    ///  returns (a12, lat2, lon2, azi2, s12, m12, M12, M21, S12)
887    pub fn _gen_direct(
888        &self,
889        lat1: f64,
890        lon1: f64,
891        azi1: f64,
892        arcmode: bool,
893        s12_a12: f64,
894        mut outmask: u64,
895    ) -> (f64, f64, f64, f64, f64, f64, f64, f64, f64) {
896        if !arcmode {
897            outmask |= caps::DISTANCE_IN
898        };
899
900        let line =
901            geodesic_line::GeodesicLine::new(self, lat1, lon1, azi1, Some(outmask), None, None);
902        line._gen_position(arcmode, s12_a12, outmask)
903    }
904
905    /// Get the area of the geodesic in square meters
906    pub fn area(&self) -> f64 {
907        self._c2 * 4.0 * std::f64::consts::PI
908    }
909}
910
911/// Place a second point, given the first point, an azimuth, and a distance.
912///
913/// # Arguments
914///   - lat1 - Latitude of 1st point (degrees) [-90.,90.]
915///   - lon1 - Longitude of 1st point (degrees) [-180., 180.]
916///   - azi1 - Azimuth at 1st point (degrees) [-180., 180.]
917///   - s12 - Distance from 1st to 2nd point (meters) Value may be negative
918///
919/// # Returns
920///
921/// There are a variety of outputs associated with this calculation. We save computation by
922/// only calculating the outputs you need. See the following impls which return different subsets of
923/// the following outputs:
924///
925///  - lat2 latitude of point 2 (degrees).
926///  - lon2 longitude of point 2 (degrees).
927///  - azi2 (forward) azimuth at point 2 (degrees).
928///  - m12 reduced length of geodesic (meters).
929///  - M12 geodesic scale of point 2 relative to point 1 (dimensionless).
930///  - M21 geodesic scale of point 1 relative to point 2 (dimensionless).
931///  - S12 area under the geodesic (meters<sup>2</sup>).
932///  - a12 arc length between point 1 and point 2 (degrees).
933///
934///  If either point is at a pole, the azimuth is defined by keeping the
935///  longitude fixed, writing lat = ±(90° − ε), and taking the limit ε → 0+.
936///  An arc length greater that 180° signifies a geodesic which is not a
937///  shortest path. (For a prolate ellipsoid, an additional condition is
938///  necessary for a shortest path: the longitudinal extent must not
939///  exceed of 180°.)
940/// ```rust
941/// // Example, determine the point 10000 km NE of JFK:
942/// use geographiclib_rs::{Geodesic, DirectGeodesic};
943///
944/// let g = Geodesic::wgs84();
945/// let (lat, lon, az) = g.direct(40.64, -73.78, 45.0, 10e6);
946///
947/// use approx::assert_relative_eq;
948/// assert_relative_eq!(lat, 32.621100463725796);
949/// assert_relative_eq!(lon, 49.052487092959836);
950/// assert_relative_eq!(az,  140.4059858768007);
951/// ```
952pub trait DirectGeodesic<T> {
953    fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> T;
954}
955
956impl DirectGeodesic<(f64, f64)> for Geodesic {
957    /// See the documentation for the DirectGeodesic trait.
958    ///
959    /// # Returns
960    ///  - lat2 latitude of point 2 (degrees).
961    ///  - lon2 longitude of point 2 (degrees).
962    fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64) {
963        let capabilities = caps::LATITUDE | caps::LONGITUDE;
964        let (_a12, lat2, lon2, _azi2, _s12, _m12, _M12, _M21, _S12) =
965            self._gen_direct(lat1, lon1, azi1, false, s12, capabilities);
966
967        (lat2, lon2)
968    }
969}
970
971impl DirectGeodesic<(f64, f64, f64)> for Geodesic {
972    /// See the documentation for the DirectGeodesic trait.
973    ///
974    /// # Returns
975    ///  - lat2 latitude of point 2 (degrees).
976    ///  - lon2 longitude of point 2 (degrees).
977    ///  - azi2 (forward) azimuth at point 2 (degrees).
978    fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64, f64) {
979        let capabilities = caps::LATITUDE | caps::LONGITUDE | caps::AZIMUTH;
980        let (_a12, lat2, lon2, azi2, _s12, _m12, _M12, _M21, _S12) =
981            self._gen_direct(lat1, lon1, azi1, false, s12, capabilities);
982
983        (lat2, lon2, azi2)
984    }
985}
986
987impl DirectGeodesic<(f64, f64, f64, f64)> for Geodesic {
988    /// See the documentation for the DirectGeodesic trait.
989    ///
990    /// # Returns
991    ///  - lat2 latitude of point 2 (degrees).
992    ///  - lon2 longitude of point 2 (degrees).
993    ///  - azi2 (forward) azimuth at point 2 (degrees).
994    ///  - m12 reduced length of geodesic (meters).
995    fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64, f64, f64) {
996        let capabilities = caps::LATITUDE | caps::LONGITUDE | caps::AZIMUTH | caps::REDUCEDLENGTH;
997        let (_a12, lat2, lon2, azi2, _s12, m12, _M12, _M21, _S12) =
998            self._gen_direct(lat1, lon1, azi1, false, s12, capabilities);
999
1000        (lat2, lon2, azi2, m12)
1001    }
1002}
1003
1004impl DirectGeodesic<(f64, f64, f64, f64, f64)> for Geodesic {
1005    /// See the documentation for the DirectGeodesic trait.
1006    ///
1007    /// # Returns
1008    ///  - lat2 latitude of point 2 (degrees).
1009    ///  - lon2 longitude of point 2 (degrees).
1010    ///  - azi2 (forward) azimuth at point 2 (degrees).
1011    ///  - M12 geodesic scale of point 2 relative to point 1 (dimensionless).
1012    ///  - M21 geodesic scale of point 1 relative to point 2 (dimensionless).
1013    fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64, f64, f64, f64) {
1014        let capabilities = caps::LATITUDE | caps::LONGITUDE | caps::AZIMUTH | caps::GEODESICSCALE;
1015        let (_a12, lat2, lon2, azi2, _s12, _m12, M12, M21, _S12) =
1016            self._gen_direct(lat1, lon1, azi1, false, s12, capabilities);
1017
1018        (lat2, lon2, azi2, M12, M21)
1019    }
1020}
1021
1022impl DirectGeodesic<(f64, f64, f64, f64, f64, f64)> for Geodesic {
1023    /// See the documentation for the DirectGeodesic trait.
1024    ///
1025    /// # Returns
1026    ///  - lat2 latitude of point 2 (degrees).
1027    ///  - lon2 longitude of point 2 (degrees).
1028    ///  - azi2 (forward) azimuth at point 2 (degrees).
1029    ///  - m12 reduced length of geodesic (meters).
1030    ///  - M12 geodesic scale of point 2 relative to point 1 (dimensionless).
1031    ///  - M21 geodesic scale of point 1 relative to point 2 (dimensionless).
1032    fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64, f64, f64, f64, f64) {
1033        let capabilities = caps::LATITUDE
1034            | caps::LONGITUDE
1035            | caps::AZIMUTH
1036            | caps::REDUCEDLENGTH
1037            | caps::GEODESICSCALE;
1038        let (_a12, lat2, lon2, azi2, _s12, m12, M12, M21, _S12) =
1039            self._gen_direct(lat1, lon1, azi1, false, s12, capabilities);
1040
1041        (lat2, lon2, azi2, m12, M12, M21)
1042    }
1043}
1044
1045impl DirectGeodesic<(f64, f64, f64, f64, f64, f64, f64, f64)> for Geodesic {
1046    /// See the documentation for the DirectGeodesic trait.
1047    ///
1048    /// # Returns
1049    ///  - lat2 latitude of point 2 (degrees).
1050    ///  - lon2 longitude of point 2 (degrees).
1051    ///  - azi2 (forward) azimuth at point 2 (degrees).
1052    ///  - m12 reduced length of geodesic (meters).
1053    ///  - M12 geodesic scale of point 2 relative to point 1 (dimensionless).
1054    ///  - M21 geodesic scale of point 1 relative to point 2 (dimensionless).
1055    ///  - S12 area under the geodesic (meters<sup>2</sup>).
1056    ///  - a12 arc length between point 1 and point 2 (degrees).
1057    fn direct(
1058        &self,
1059        lat1: f64,
1060        lon1: f64,
1061        azi1: f64,
1062        s12: f64,
1063    ) -> (f64, f64, f64, f64, f64, f64, f64, f64) {
1064        let capabilities = caps::LATITUDE
1065            | caps::LONGITUDE
1066            | caps::AZIMUTH
1067            | caps::REDUCEDLENGTH
1068            | caps::GEODESICSCALE
1069            | caps::AREA;
1070        let (a12, lat2, lon2, azi2, _s12, m12, M12, M21, S12) =
1071            self._gen_direct(lat1, lon1, azi1, false, s12, capabilities);
1072
1073        (lat2, lon2, azi2, m12, M12, M21, S12, a12)
1074    }
1075}
1076
1077/// Measure the distance (and other values) between two points.
1078///
1079/// # Arguments
1080/// - lat1 latitude of point 1 (degrees).
1081/// - lon1 longitude of point 1 (degrees).
1082/// - lat2 latitude of point 2 (degrees).
1083/// - lon2 longitude of point 2 (degrees).
1084///
1085/// # Returns
1086///
1087/// There are a variety of outputs associated with this calculation. We save computation by
1088/// only calculating the outputs you need. See the following impls which return different subsets of
1089/// the following outputs:
1090///
1091/// - s12 distance between point 1 and point 2 (meters).
1092/// - azi1 azimuth at point 1 (degrees).
1093/// - azi2 (forward) azimuth at point 2 (degrees).
1094/// - m12 reduced length of geodesic (meters).
1095/// - M12 geodesic scale of point 2 relative to point 1 (dimensionless).
1096/// - M21 geodesic scale of point 1 relative to point 2 (dimensionless).
1097/// - S12 area under the geodesic (meters<sup>2</sup>).
1098/// - a12 arc length between point 1 and point 2 (degrees).
1099///
1100///  `lat1` and `lat2` should be in the range [&minus;90&deg;, 90&deg;].
1101///  The values of `azi1` and `azi2` returned are in the range
1102///  [&minus;180&deg;, 180&deg;].
1103///
1104/// If either point is at a pole, the azimuth is defined by keeping the
1105/// longitude fixed, writing `lat` = &plusmn;(90&deg; &minus; &epsilon;),
1106/// and taking the limit &epsilon; &rarr; 0+.
1107///
1108/// The solution to the inverse problem is found using Newton's method.  If
1109/// this fails to converge (this is very unlikely in geodetic applications
1110/// but does occur for very eccentric ellipsoids), then the bisection method
1111/// is used to refine the solution.
1112///
1113/// ```rust
1114/// // Example, determine the distance between two points
1115/// use geographiclib_rs::{Geodesic, InverseGeodesic};
1116///
1117/// let g = Geodesic::wgs84();
1118/// let p1 = (34.095925, -118.2884237);
1119/// let p2 = (59.4323439, 24.7341649);
1120/// let s12: f64 = g.inverse(p1.0, p1.1, p2.0, p2.1);
1121///
1122/// use approx::assert_relative_eq;
1123/// assert_relative_eq!(s12, 9094718.72751138);
1124/// ```
1125pub trait InverseGeodesic<T> {
1126    fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> T;
1127}
1128
1129impl InverseGeodesic<f64> for Geodesic {
1130    /// See the documentation for the InverseGeodesic trait.
1131    ///
1132    /// # Returns
1133    /// - s12 distance between point 1 and point 2 (meters).
1134    fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
1135        let capabilities = caps::DISTANCE;
1136        let (_a12, s12, _azi1, _azi2, _m12, _M12, _M21, _S12) =
1137            self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities);
1138
1139        s12
1140    }
1141}
1142
1143impl InverseGeodesic<(f64, f64)> for Geodesic {
1144    /// See the documentation for the InverseGeodesic trait.
1145    ///
1146    /// # Returns
1147    /// - s12 distance between point 1 and point 2 (meters).
1148    /// - a12 arc length between point 1 and point 2 (degrees).
1149    fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> (f64, f64) {
1150        let capabilities = caps::DISTANCE;
1151        let (a12, s12, _azi1, _azi2, _m12, _M12, _M21, _S12) =
1152            self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities);
1153
1154        (s12, a12)
1155    }
1156}
1157
1158impl InverseGeodesic<(f64, f64, f64)> for Geodesic {
1159    /// See the documentation for the InverseGeodesic trait.
1160    ///
1161    /// # Returns
1162    /// - azi1 azimuth at point 1 (degrees).
1163    /// - azi2 (forward) azimuth at point 2 (degrees).
1164    /// - a12 arc length between point 1 and point 2 (degrees).
1165    fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> (f64, f64, f64) {
1166        let capabilities = caps::AZIMUTH;
1167        let (a12, _s12, azi1, azi2, _m12, _M12, _M21, _S12) =
1168            self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities);
1169
1170        (azi1, azi2, a12)
1171    }
1172}
1173
1174impl InverseGeodesic<(f64, f64, f64, f64)> for Geodesic {
1175    /// See the documentation for the InverseGeodesic trait.
1176    ///
1177    /// # Returns
1178    /// - s12 distance between point 1 and point 2 (meters).
1179    /// - azi1 azimuth at point 1 (degrees).
1180    /// - azi2 (forward) azimuth at point 2 (degrees).
1181    /// - a12 arc length between point 1 and point 2 (degrees).
1182    fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> (f64, f64, f64, f64) {
1183        let capabilities = caps::DISTANCE | caps::AZIMUTH;
1184        let (a12, s12, azi1, azi2, _m12, _M12, _M21, _S12) =
1185            self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities);
1186
1187        (s12, azi1, azi2, a12)
1188    }
1189}
1190
1191impl InverseGeodesic<(f64, f64, f64, f64, f64)> for Geodesic {
1192    /// See the documentation for the InverseGeodesic trait.
1193    ///
1194    /// # Returns
1195    /// - s12 distance between point 1 and point 2 (meters).
1196    /// - azi1 azimuth at point 1 (degrees).
1197    /// - azi2 (forward) azimuth at point 2 (degrees).
1198    /// - m12 reduced length of geodesic (meters).
1199    /// - a12 arc length between point 1 and point 2 (degrees).
1200    fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> (f64, f64, f64, f64, f64) {
1201        let capabilities = caps::DISTANCE | caps::AZIMUTH | caps::REDUCEDLENGTH;
1202        let (a12, s12, azi1, azi2, m12, _M12, _M21, _S12) =
1203            self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities);
1204
1205        (s12, azi1, azi2, m12, a12)
1206    }
1207}
1208
1209impl InverseGeodesic<(f64, f64, f64, f64, f64, f64)> for Geodesic {
1210    /// See the documentation for the InverseGeodesic trait.
1211    ///
1212    /// # Returns
1213    /// - s12 distance between point 1 and point 2 (meters).
1214    /// - azi1 azimuth at point 1 (degrees).
1215    /// - azi2 (forward) azimuth at point 2 (degrees).
1216    /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless).
1217    /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless).
1218    /// - a12 arc length between point 1 and point 2 (degrees).
1219    fn inverse(
1220        &self,
1221        lat1: f64,
1222        lon1: f64,
1223        lat2: f64,
1224        lon2: f64,
1225    ) -> (f64, f64, f64, f64, f64, f64) {
1226        let capabilities = caps::DISTANCE | caps::AZIMUTH | caps::GEODESICSCALE;
1227        let (a12, s12, azi1, azi2, _m12, M12, M21, _S12) =
1228            self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities);
1229
1230        (s12, azi1, azi2, M12, M21, a12)
1231    }
1232}
1233
1234impl InverseGeodesic<(f64, f64, f64, f64, f64, f64, f64)> for Geodesic {
1235    /// See the documentation for the InverseGeodesic trait.
1236    ///
1237    /// # Returns
1238    /// - s12 distance between point 1 and point 2 (meters).
1239    /// - azi1 azimuth at point 1 (degrees).
1240    /// - azi2 (forward) azimuth at point 2 (degrees).
1241    /// - m12 reduced length of geodesic (meters).
1242    /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless).
1243    /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless).
1244    /// - a12 arc length between point 1 and point 2 (degrees).
1245    fn inverse(
1246        &self,
1247        lat1: f64,
1248        lon1: f64,
1249        lat2: f64,
1250        lon2: f64,
1251    ) -> (f64, f64, f64, f64, f64, f64, f64) {
1252        let capabilities =
1253            caps::DISTANCE | caps::AZIMUTH | caps::REDUCEDLENGTH | caps::GEODESICSCALE;
1254        let (a12, s12, azi1, azi2, m12, M12, M21, _S12) =
1255            self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities);
1256
1257        (s12, azi1, azi2, m12, M12, M21, a12)
1258    }
1259}
1260
1261impl InverseGeodesic<(f64, f64, f64, f64, f64, f64, f64, f64)> for Geodesic {
1262    /// See the documentation for the InverseGeodesic trait.
1263    ///
1264    /// # Returns
1265    /// - s12 distance between point 1 and point 2 (meters).
1266    /// - azi1 azimuth at point 1 (degrees).
1267    /// - azi2 (forward) azimuth at point 2 (degrees).
1268    /// - m12 reduced length of geodesic (meters).
1269    /// - M12 geodesic scale of point 2 relative to point 1 (dimensionless).
1270    /// - M21 geodesic scale of point 1 relative to point 2 (dimensionless).
1271    /// - S12 area under the geodesic (meters<sup>2</sup>).
1272    /// - a12 arc length between point 1 and point 2 (degrees).
1273    fn inverse(
1274        &self,
1275        lat1: f64,
1276        lon1: f64,
1277        lat2: f64,
1278        lon2: f64,
1279    ) -> (f64, f64, f64, f64, f64, f64, f64, f64) {
1280        let capabilities =
1281            caps::DISTANCE | caps::AZIMUTH | caps::REDUCEDLENGTH | caps::GEODESICSCALE | caps::AREA;
1282        let (a12, s12, azi1, azi2, m12, M12, M21, S12) =
1283            self._gen_inverse_azi(lat1, lon1, lat2, lon2, capabilities);
1284
1285        (s12, azi1, azi2, m12, M12, M21, S12, a12)
1286    }
1287}
1288
1289#[cfg(test)]
1290mod tests {
1291    use super::*;
1292    use crate::geodesic_line::GeodesicLine;
1293    use approx::assert_relative_eq;
1294    use std::io::BufRead;
1295
1296    #[allow(clippy::type_complexity)]
1297    const TESTCASES: &[(f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64)] = &[
1298        (
1299            35.60777,
1300            -139.44815,
1301            111.098748429560326,
1302            -11.17491,
1303            -69.95921,
1304            129.289270889708762,
1305            8935244.5604818305,
1306            80.50729714281974,
1307            6273170.2055303837,
1308            0.16606318447386067,
1309            0.16479116945612937,
1310            12841384694976.432,
1311        ),
1312        (
1313            55.52454,
1314            106.05087,
1315            22.020059880982801,
1316            77.03196,
1317            197.18234,
1318            109.112041110671519,
1319            4105086.1713924406,
1320            36.892740690445894,
1321            3828869.3344387607,
1322            0.80076349608092607,
1323            0.80101006984201008,
1324            61674961290615.615,
1325        ),
1326        (
1327            -21.97856,
1328            142.59065,
1329            -32.44456876433189,
1330            41.84138,
1331            98.56635,
1332            -41.84359951440466,
1333            8394328.894657671,
1334            75.62930491011522,
1335            6161154.5773110616,
1336            0.24816339233950381,
1337            0.24930251203627892,
1338            -6637997720646.717,
1339        ),
1340        (
1341            -66.99028,
1342            112.2363,
1343            173.73491240878403,
1344            -12.70631,
1345            285.90344,
1346            2.512956620913668,
1347            11150344.2312080241,
1348            100.278634181155759,
1349            6289939.5670446687,
1350            -0.17199490274700385,
1351            -0.17722569526345708,
1352            -121287239862139.744,
1353        ),
1354        (
1355            -17.42761,
1356            173.34268,
1357            -159.033557661192928,
1358            -15.84784,
1359            5.93557,
1360            -20.787484651536988,
1361            16076603.1631180673,
1362            144.640108810286253,
1363            3732902.1583877189,
1364            -0.81273638700070476,
1365            -0.81299800519154474,
1366            97825992354058.708,
1367        ),
1368        (
1369            32.84994,
1370            48.28919,
1371            150.492927788121982,
1372            -56.28556,
1373            202.29132,
1374            48.113449399816759,
1375            16727068.9438164461,
1376            150.565799985466607,
1377            3147838.1910180939,
1378            -0.87334918086923126,
1379            -0.86505036767110637,
1380            -72445258525585.010,
1381        ),
1382        (
1383            6.96833,
1384            52.74123,
1385            92.581585386317712,
1386            -7.39675,
1387            206.17291,
1388            90.721692165923907,
1389            17102477.2496958388,
1390            154.147366239113561,
1391            2772035.6169917581,
1392            -0.89991282520302447,
1393            -0.89986892177110739,
1394            -1311796973197.995,
1395        ),
1396        (
1397            -50.56724,
1398            -16.30485,
1399            -105.439679907590164,
1400            -33.56571,
1401            -94.97412,
1402            -47.348547835650331,
1403            6455670.5118668696,
1404            58.083719495371259,
1405            5409150.7979815838,
1406            0.53053508035997263,
1407            0.52988722644436602,
1408            41071447902810.047,
1409        ),
1410        (
1411            -58.93002,
1412            -8.90775,
1413            140.965397902500679,
1414            -8.91104,
1415            133.13503,
1416            19.255429433416599,
1417            11756066.0219864627,
1418            105.755691241406877,
1419            6151101.2270708536,
1420            -0.26548622269867183,
1421            -0.27068483874510741,
1422            -86143460552774.735,
1423        ),
1424        (
1425            -68.82867,
1426            -74.28391,
1427            93.774347763114881,
1428            -50.63005,
1429            -8.36685,
1430            34.65564085411343,
1431            3956936.926063544,
1432            35.572254987389284,
1433            3708890.9544062657,
1434            0.81443963736383502,
1435            0.81420859815358342,
1436            -41845309450093.787,
1437        ),
1438        (
1439            -10.62672,
1440            -32.0898,
1441            -86.426713286747751,
1442            5.883,
1443            -134.31681,
1444            -80.473780971034875,
1445            11470869.3864563009,
1446            103.387395634504061,
1447            6184411.6622659713,
1448            -0.23138683500430237,
1449            -0.23155097622286792,
1450            4198803992123.548,
1451        ),
1452        (
1453            -21.76221,
1454            166.90563,
1455            29.319421206936428,
1456            48.72884,
1457            213.97627,
1458            43.508671946410168,
1459            9098627.3986554915,
1460            81.963476716121964,
1461            6299240.9166992283,
1462            0.13965943368590333,
1463            0.14152969707656796,
1464            10024709850277.476,
1465        ),
1466        (
1467            -19.79938,
1468            -174.47484,
1469            71.167275780171533,
1470            -11.99349,
1471            -154.35109,
1472            65.589099775199228,
1473            2319004.8601169389,
1474            20.896611684802389,
1475            2267960.8703918325,
1476            0.93427001867125849,
1477            0.93424887135032789,
1478            -3935477535005.785,
1479        ),
1480        (
1481            -11.95887,
1482            -116.94513,
1483            92.712619830452549,
1484            4.57352,
1485            7.16501,
1486            78.64960934409585,
1487            13834722.5801401374,
1488            124.688684161089762,
1489            5228093.177931598,
1490            -0.56879356755666463,
1491            -0.56918731952397221,
1492            -9919582785894.853,
1493        ),
1494        (
1495            -87.85331,
1496            85.66836,
1497            -65.120313040242748,
1498            66.48646,
1499            16.09921,
1500            -4.888658719272296,
1501            17286615.3147144645,
1502            155.58592449699137,
1503            2635887.4729110181,
1504            -0.90697975771398578,
1505            -0.91095608883042767,
1506            42667211366919.534,
1507        ),
1508        (
1509            1.74708,
1510            128.32011,
1511            -101.584843631173858,
1512            -11.16617,
1513            11.87109,
1514            -86.325793296437476,
1515            12942901.1241347408,
1516            116.650512484301857,
1517            5682744.8413270572,
1518            -0.44857868222697644,
1519            -0.44824490340007729,
1520            10763055294345.653,
1521        ),
1522        (
1523            -25.72959,
1524            -144.90758,
1525            -153.647468693117198,
1526            -57.70581,
1527            -269.17879,
1528            -48.343983158876487,
1529            9413446.7452453107,
1530            84.664533838404295,
1531            6356176.6898881281,
1532            0.09492245755254703,
1533            0.09737058264766572,
1534            74515122850712.444,
1535        ),
1536        (
1537            -41.22777,
1538            122.32875,
1539            14.285113402275739,
1540            -7.57291,
1541            130.37946,
1542            10.805303085187369,
1543            3812686.035106021,
1544            34.34330804743883,
1545            3588703.8812128856,
1546            0.82605222593217889,
1547            0.82572158200920196,
1548            -2456961531057.857,
1549        ),
1550        (
1551            11.01307,
1552            138.25278,
1553            79.43682622782374,
1554            6.62726,
1555            247.05981,
1556            103.708090215522657,
1557            11911190.819018408,
1558            107.341669954114577,
1559            6070904.722786735,
1560            -0.29767608923657404,
1561            -0.29785143390252321,
1562            17121631423099.696,
1563        ),
1564        (
1565            -29.47124,
1566            95.14681,
1567            -163.779130441688382,
1568            -27.46601,
1569            -69.15955,
1570            -15.909335945554969,
1571            13487015.8381145492,
1572            121.294026715742277,
1573            5481428.9945736388,
1574            -0.51527225545373252,
1575            -0.51556587964721788,
1576            104679964020340.318,
1577        ),
1578    ];
1579
1580    #[test]
1581    fn test_inverse_and_direct() -> Result<(), String> {
1582        // See python/test_geodesic.py
1583        let geod = Geodesic::wgs84();
1584        let (_a12, s12, _azi1, _azi2, _m12, _M12, _M21, _S12) =
1585            geod._gen_inverse_azi(0.0, 0.0, 1.0, 1.0, caps::STANDARD);
1586        assert_eq!(s12, 156899.56829134026);
1587
1588        // Test inverse
1589        for (lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12) in TESTCASES.iter() {
1590            let (
1591                computed_a12,
1592                computed_s12,
1593                computed_azi1,
1594                computed_azi2,
1595                computed_m12,
1596                computed_M12,
1597                computed_M21,
1598                computed_S12,
1599            ) = geod._gen_inverse_azi(*lat1, *lon1, *lat2, *lon2, caps::ALL | caps::LONG_UNROLL);
1600            assert_relative_eq!(computed_azi1, azi1, epsilon = 1e-13f64);
1601            assert_relative_eq!(computed_azi2, azi2, epsilon = 1e-13f64);
1602            assert_relative_eq!(computed_s12, s12, epsilon = 1e-8f64);
1603            assert_relative_eq!(computed_a12, a12, epsilon = 1e-13f64);
1604            assert_relative_eq!(computed_m12, m12, epsilon = 1e-8f64);
1605            assert_relative_eq!(computed_M12, M12, epsilon = 1e-15f64);
1606            assert_relative_eq!(computed_M21, M21, epsilon = 1e-15f64);
1607            assert_relative_eq!(computed_S12, S12, epsilon = 0.1f64);
1608        }
1609
1610        // Test direct
1611        for (lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12) in TESTCASES.iter() {
1612            let (
1613                computed_a12,
1614                computed_lat2,
1615                computed_lon2,
1616                computed_azi2,
1617                _computed_s12,
1618                computed_m12,
1619                computed_M12,
1620                computed_M21,
1621                computed_S12,
1622            ) = geod._gen_direct(
1623                *lat1,
1624                *lon1,
1625                *azi1,
1626                false,
1627                *s12,
1628                caps::ALL | caps::LONG_UNROLL,
1629            );
1630            assert_relative_eq!(computed_lat2, lat2, epsilon = 1e-13f64);
1631            assert_relative_eq!(computed_lon2, lon2, epsilon = 1e-13f64);
1632            assert_relative_eq!(computed_azi2, azi2, epsilon = 1e-13f64);
1633            assert_relative_eq!(computed_a12, a12, epsilon = 1e-13f64);
1634            assert_relative_eq!(computed_m12, m12, epsilon = 1e-8f64);
1635            assert_relative_eq!(computed_M12, M12, epsilon = 1e-15f64);
1636            assert_relative_eq!(computed_M21, M21, epsilon = 1e-15f64);
1637            assert_relative_eq!(computed_S12, S12, epsilon = 0.1f64);
1638        }
1639        Ok(())
1640    }
1641
1642    #[test]
1643    fn test_arcdirect() {
1644        // Corresponds with ArcDirectCheck from Java, or test_arcdirect from Python
1645        let geod = Geodesic::wgs84();
1646        for (lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12) in TESTCASES.iter() {
1647            let (
1648                _computed_a12,
1649                computed_lat2,
1650                computed_lon2,
1651                computed_azi2,
1652                computed_s12,
1653                computed_m12,
1654                computed_M12,
1655                computed_M21,
1656                computed_S12,
1657            ) = geod._gen_direct(
1658                *lat1,
1659                *lon1,
1660                *azi1,
1661                true,
1662                *a12,
1663                caps::ALL | caps::LONG_UNROLL,
1664            );
1665            assert_relative_eq!(computed_lat2, lat2, epsilon = 1e-13);
1666            assert_relative_eq!(computed_lon2, lon2, epsilon = 1e-13);
1667            assert_relative_eq!(computed_azi2, azi2, epsilon = 1e-13);
1668            assert_relative_eq!(computed_s12, s12, epsilon = 1e-8);
1669            assert_relative_eq!(computed_m12, m12, epsilon = 1e-8);
1670            assert_relative_eq!(computed_M12, M12, epsilon = 1e-15);
1671            assert_relative_eq!(computed_M21, M21, epsilon = 1e-15);
1672            assert_relative_eq!(computed_S12, S12, epsilon = 0.1);
1673        }
1674    }
1675
1676    #[test]
1677    fn test_geninverse() {
1678        let geod = Geodesic::wgs84();
1679        let res = geod._gen_inverse(0.0, 0.0, 1.0, 1.0, caps::STANDARD);
1680        assert_eq!(res.0, 1.4141938478710363);
1681        assert_eq!(res.1, 156899.56829134026);
1682        assert_eq!(res.2, 0.7094236375834774);
1683        assert_eq!(res.3, 0.7047823085448635);
1684        assert_eq!(res.4, 0.7095309793242709);
1685        assert_eq!(res.5, 0.7046742434480923);
1686        assert!(res.6.is_nan());
1687        assert!(res.7.is_nan());
1688        assert!(res.8.is_nan());
1689        assert!(res.9.is_nan());
1690    }
1691
1692    #[test]
1693    fn test_inverse_start() {
1694        let geod = Geodesic::wgs84();
1695        let res = geod._InverseStart(
1696            -0.017393909556108908,
1697            0.9998487145115275,
1698            1.0000010195104125,
1699            -0.0,
1700            1.0,
1701            1.0,
1702            0.017453292519943295,
1703            0.01745240643728351,
1704            0.9998476951563913,
1705            &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
1706            &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
1707        );
1708        assert_eq!(res.0, -1.0);
1709        assert_relative_eq!(res.1, 0.7095310092765433, epsilon = 1e-13);
1710        assert_relative_eq!(res.2, 0.7046742132893822, epsilon = 1e-13);
1711        assert!(res.3.is_nan());
1712        assert!(res.4.is_nan());
1713        assert_eq!(res.5, 1.0000002548969817);
1714
1715        let res = geod._InverseStart(
1716            -0.017393909556108908,
1717            0.9998487145115275,
1718            1.0000010195104125,
1719            -0.0,
1720            1.0,
1721            1.0,
1722            0.017453292519943295,
1723            0.01745240643728351,
1724            0.9998476951563913,
1725            &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
1726            &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
1727        );
1728        assert_eq!(res.0, -1.0);
1729        assert_relative_eq!(res.1, 0.7095310092765433, epsilon = 1e-13);
1730        assert_relative_eq!(res.2, 0.7046742132893822, epsilon = 1e-13);
1731        assert!(res.3.is_nan());
1732        assert!(res.4.is_nan());
1733        assert_eq!(res.5, 1.0000002548969817);
1734    }
1735
1736    #[test]
1737    fn test_lambda12() {
1738        let geod = Geodesic::wgs84();
1739        let res1 = geod._Lambda12(
1740            -0.017393909556108908,
1741            0.9998487145115275,
1742            1.0000010195104125,
1743            -0.0,
1744            1.0,
1745            1.0,
1746            0.7095310092765433,
1747            0.7046742132893822,
1748            0.01745240643728351,
1749            0.9998476951563913,
1750            true,
1751            &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
1752            &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
1753            &mut [0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
1754        );
1755        assert_eq!(res1.0, 1.4834408705897495e-09);
1756        assert_eq!(res1.1, 0.7094236675312185);
1757        assert_eq!(res1.2, 0.7047822783999007);
1758        assert_eq!(res1.3, 0.024682339962725352);
1759        assert_eq!(res1.4, -0.024679833885152578);
1760        assert_eq!(res1.5, 0.9996954065111039);
1761        assert_eq!(res1.6, -0.0);
1762        assert_eq!(res1.7, 1.0);
1763        assert_relative_eq!(res1.8, 0.0008355095326524276, epsilon = 1e-13);
1764        assert_eq!(res1.9, -5.8708496511415445e-05);
1765        assert_eq!(res1.10, 0.034900275148485);
1766
1767        let res2 = geod._Lambda12(
1768            -0.017393909556108908,
1769            0.9998487145115275,
1770            1.0000010195104125,
1771            -0.0,
1772            1.0,
1773            1.0,
1774            0.7095309793242709,
1775            0.7046742434480923,
1776            0.01745240643728351,
1777            0.9998476951563913,
1778            true,
1779            &mut [
1780                0.0,
1781                -0.00041775465696698233,
1782                -4.362974596862037e-08,
1783                -1.2151022357848552e-11,
1784                -4.7588881620421004e-15,
1785                -2.226614930167366e-18,
1786                -1.1627237498131586e-21,
1787            ],
1788            &mut [
1789                0.0,
1790                -0.0008355098973052918,
1791                -1.7444619952659748e-07,
1792                -7.286557795511902e-11,
1793                -3.80472772706481e-14,
1794                -2.2251271876594078e-17,
1795                1.2789961247944744e-20,
1796            ],
1797            &mut [
1798                0.0,
1799                0.00020861391868413911,
1800                4.3547247296823945e-08,
1801                1.515432276542012e-11,
1802                6.645637323698485e-15,
1803                3.3399223952510497e-18,
1804            ],
1805        );
1806        assert_eq!(res2.0, 6.046459990680098e-17);
1807        assert_eq!(res2.1, 0.7094236375834774);
1808        assert_eq!(res2.2, 0.7047823085448635);
1809        assert_eq!(res2.3, 0.024682338906797385);
1810        assert_eq!(res2.4, -0.02467983282954624);
1811        assert_eq!(res2.5, 0.9996954065371639);
1812        assert_eq!(res2.6, -0.0);
1813        assert_eq!(res2.7, 1.0);
1814        assert_relative_eq!(res2.8, 0.0008355096040059597, epsilon = 1e-18);
1815        assert_eq!(res2.9, -5.870849152149326e-05);
1816        assert_eq!(res2.10, 0.03490027216297455);
1817    }
1818
1819    #[test]
1820    fn test_lengths() {
1821        // Results taken from the python implementation
1822        let geod = Geodesic::wgs84();
1823        let mut c1a = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1824        let mut c2a = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1825        let res1 = geod._Lengths(
1826            0.0008355095326524276,
1827            0.024682339962725352,
1828            -0.024679833885152578,
1829            0.9996954065111039,
1830            1.0000010195104125,
1831            -0.0,
1832            1.0,
1833            1.0,
1834            0.9998487145115275,
1835            1.0,
1836            4101,
1837            &mut c1a,
1838            &mut c2a,
1839        );
1840        assert!(res1.0.is_nan());
1841        assert_eq!(res1.1, 0.024679842274314294);
1842        assert_eq!(res1.2, 0.0016717180169067588);
1843        assert!(res1.3.is_nan());
1844        assert!(res1.4.is_nan());
1845
1846        let res2 = geod._Lengths(
1847            0.0008355096040059597,
1848            0.024682338906797385,
1849            -0.02467983282954624,
1850            0.9996954065371639,
1851            1.0000010195104125,
1852            -0.0,
1853            1.0,
1854            1.0,
1855            0.9998487145115275,
1856            1.0,
1857            4101,
1858            &mut [
1859                0.0,
1860                -0.00041775465696698233,
1861                -4.362974596862037e-08,
1862                -1.2151022357848552e-11,
1863                -4.7588881620421004e-15,
1864                -2.226614930167366e-18,
1865                -1.1627237498131586e-21,
1866            ],
1867            &mut [
1868                0.0,
1869                -0.0008355098973052918,
1870                -1.7444619952659748e-07,
1871                -7.286557795511902e-11,
1872                -3.80472772706481e-14,
1873                -2.2251271876594078e-17,
1874                1.2789961247944744e-20,
1875            ],
1876        );
1877        assert!(res2.0.is_nan());
1878        assert_eq!(res2.1, 0.02467984121870759);
1879        assert_eq!(res2.2, 0.0016717181597332804);
1880        assert!(res2.3.is_nan());
1881        assert!(res2.4.is_nan());
1882
1883        let res3 = geod._Lengths(
1884            0.0008355096040059597,
1885            0.024682338906797385,
1886            -0.02467983282954624,
1887            0.9996954065371639,
1888            1.0000010195104125,
1889            -0.0,
1890            1.0,
1891            1.0,
1892            0.9998487145115275,
1893            1.0,
1894            1920,
1895            &mut [
1896                0.0,
1897                -0.00041775469264372037,
1898                -4.362975342068502e-08,
1899                -1.215102547098435e-11,
1900                -4.758889787701359e-15,
1901                -2.2266158809456692e-18,
1902                -1.1627243456014359e-21,
1903            ],
1904            &mut [
1905                0.0,
1906                -0.0008355099686589174,
1907                -1.744462293162189e-07,
1908                -7.286559662008413e-11,
1909                -3.804729026574989e-14,
1910                -2.2251281376754273e-17,
1911                1.2789967801615795e-20,
1912            ],
1913        );
1914        assert_eq!(res3.0, 0.024682347295447677);
1915        assert!(res3.1.is_nan());
1916        assert!(res3.2.is_nan());
1917        assert!(res3.3.is_nan());
1918        assert!(res3.4.is_nan());
1919
1920        let res = geod._Lengths(
1921            0.0007122620325664751,
1922            1.405117407023628,
1923            -0.8928657853278468,
1924            0.45032287238256896,
1925            1.0011366173804046,
1926            0.2969032234925426,
1927            0.9549075745221299,
1928            1.0001257451360057,
1929            0.8139459053827204,
1930            0.9811634781422108,
1931            1920,
1932            &mut [
1933                0.0,
1934                -0.0003561309485314716,
1935                -3.170731714689771e-08,
1936                -7.527972480734327e-12,
1937                -2.5133854116682488e-15,
1938                -1.0025061462383107e-18,
1939                -4.462794158625518e-22,
1940            ],
1941            &mut [
1942                0.0,
1943                -0.0007122622584701569,
1944                -1.2678416507678478e-07,
1945                -4.514641118748122e-11,
1946                -2.0096353119518367e-14,
1947                -1.0019350865558619e-17,
1948                4.90907357448807e-21,
1949            ],
1950        );
1951        assert_eq!(res.0, 1.4056304412645388);
1952        assert!(res.1.is_nan());
1953        assert!(res.2.is_nan());
1954        assert!(res.3.is_nan());
1955        assert!(res.4.is_nan());
1956    }
1957
1958    #[test]
1959    fn test_goed__C4f() {
1960        let geod = Geodesic::wgs84();
1961        let mut c = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1962        geod._C4f(0.12, &mut c);
1963        assert_eq!(
1964            c,
1965            [
1966                0.6420952961066771,
1967                0.0023680700061156517,
1968                9.96704067834604e-05,
1969                5.778187189466089e-06,
1970                3.9979026199316593e-07,
1971                3.2140078103714466e-08,
1972            ]
1973        );
1974    }
1975
1976    #[test]
1977    fn test_goed__C3f() {
1978        let geod = Geodesic::wgs84();
1979        let mut c = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1980        geod._C3f(0.12, &mut c);
1981        assert_eq!(
1982            c,
1983            [
1984                1.0,
1985                0.031839442894193756,
1986                0.0009839921354137713,
1987                5.0055242248766214e-05,
1988                3.1656788204092044e-06,
1989                2.0412e-07,
1990            ]
1991        );
1992    }
1993
1994    #[test]
1995    fn test_goed__A3f() {
1996        let geod = Geodesic::wgs84();
1997        assert_eq!(geod._A3f(0.12), 0.9363788874000158);
1998    }
1999
2000    #[test]
2001    fn test_geod_init() {
2002        // Check that after the init the variables are correctly set.
2003        // Actual values are taken from the python implementation
2004        let geod = Geodesic::wgs84();
2005        assert_eq!(geod.a, 6378137.0, "geod.a wrong");
2006        assert_eq!(geod.f, 0.0033528106647474805, "geod.f wrong");
2007        assert_eq!(geod._f1, 0.9966471893352525, "geod._f1 wrong");
2008        assert_eq!(geod._e2, 0.0066943799901413165, "geod._e2 wrong");
2009        assert_eq!(geod._ep2, 0.006739496742276434, "geod._ep2 wrong");
2010        assert_eq!(geod._n, 0.0016792203863837047, "geod._n wrong");
2011        assert_eq!(geod._b, 6356752.314245179, "geod._b wrong");
2012        assert_eq!(geod._c2, 40589732499314.76, "geod._c2 wrong");
2013        assert_eq!(geod._etol2, 3.6424611488788524e-08, "geod._etol2 wrong");
2014        assert_eq!(
2015            geod._A3x,
2016            [
2017                -0.0234375,
2018                -0.046927475637074494,
2019                -0.06281503005876607,
2020                -0.2502088451303832,
2021                -0.49916038980680816,
2022                1.0
2023            ],
2024            "geod._A3x wrong"
2025        );
2026
2027        assert_eq!(
2028            geod._C3x,
2029            [
2030                0.0234375,
2031                0.03908873781853724,
2032                0.04695366939653196,
2033                0.12499964752736174,
2034                0.24958019490340408,
2035                0.01953125,
2036                0.02345061890926862,
2037                0.046822392185686165,
2038                0.062342661206936094,
2039                0.013671875,
2040                0.023393770302437927,
2041                0.025963026642854565,
2042                0.013671875,
2043                0.01362595881755982,
2044                0.008203125
2045            ],
2046            "geod._C3x wrong"
2047        );
2048        assert_eq!(
2049            geod._C4x,
2050            [
2051                0.00646020646020646,
2052                0.0035037627212872787,
2053                0.034742279454780166,
2054                -0.01921732223244865,
2055                -0.19923321555984239,
2056                0.6662190894642603,
2057                0.000111000111000111,
2058                0.003426620602971002,
2059                -0.009510765372597735,
2060                -0.01893413691235592,
2061                0.0221370239510936,
2062                0.0007459207459207459,
2063                -0.004142006291321442,
2064                -0.00504225176309005,
2065                0.007584982177746079,
2066                -0.0021565735851450138,
2067                -0.001962613370670692,
2068                0.0036104265913438913,
2069                -0.0009472009472009472,
2070                0.0020416649913317735,
2071                0.0012916376552740189
2072            ],
2073            "geod._C4x wrong"
2074        );
2075    }
2076
2077    // The test_std_geodesic_* tests below are based on Karney's GeodSolve unit
2078    // tests, found in many geographiclib variants.
2079    // The versions below are mostly adapted from their Java counterparts,
2080    // which use a testing structure more similar to Rust than do the C++ versions.
2081    // Note that the Java tests often incorporate more than one of the C++ tests,
2082    // and take their name from the lowest-numbered test in the set.
2083    // These tests use that convention as well.
2084
2085    #[test]
2086    fn test_std_geodesic_geodsolve0() {
2087        let geod = Geodesic::wgs84();
2088        let (s12, azi1, azi2, _a12) = geod.inverse(40.6, -73.8, 49.01666667, 2.55);
2089        assert_relative_eq!(azi1, 53.47022, epsilon = 0.5e-5);
2090        assert_relative_eq!(azi2, 111.59367, epsilon = 0.5e-5);
2091        assert_relative_eq!(s12, 5853226.0, epsilon = 0.5);
2092    }
2093
2094    #[test]
2095    fn test_std_geodesic_geodsolve1() {
2096        let geod = Geodesic::wgs84();
2097        let (lat2, lon2, azi2) = geod.direct(40.63972222, -73.77888889, 53.5, 5850e3);
2098        assert_relative_eq!(lat2, 49.01467, epsilon = 0.5e-5);
2099        assert_relative_eq!(lon2, 2.56106, epsilon = 0.5e-5);
2100        assert_relative_eq!(azi2, 111.62947, epsilon = 0.5e-5);
2101    }
2102
2103    #[test]
2104    fn test_std_geodesic_geodsolve2() {
2105        // Check fix for antipodal prolate bug found 2010-09-04
2106        let geod = Geodesic::new(6.4e6, -1f64 / 150.0);
2107        let (s12, azi1, azi2, _a12) = geod.inverse(0.07476, 0.0, -0.07476, 180.0);
2108        assert_relative_eq!(azi1, 90.00078, epsilon = 0.5e-5);
2109        assert_relative_eq!(azi2, 90.00078, epsilon = 0.5e-5);
2110        assert_relative_eq!(s12, 20106193.0, epsilon = 0.5);
2111        let (s12, azi1, azi2, _a12) = geod.inverse(0.1, 0.0, -0.1, 180.0);
2112        assert_relative_eq!(azi1, 90.00105, epsilon = 0.5e-5);
2113        assert_relative_eq!(azi2, 90.00105, epsilon = 0.5e-5);
2114        assert_relative_eq!(s12, 20106193.0, epsilon = 0.5);
2115    }
2116
2117    #[test]
2118    fn test_std_geodesic_geodsolve4() {
2119        // Check fix for short line bug found 2010-05-21
2120        let geod = Geodesic::wgs84();
2121        let s12: f64 = geod.inverse(36.493349428792, 0.0, 36.49334942879201, 0.0000008);
2122        assert_relative_eq!(s12, 0.072, epsilon = 0.5e-3);
2123    }
2124
2125    #[test]
2126    fn test_std_geodesic_geodsolve5() {
2127        // Check fix for point2=pole bug found 2010-05-03
2128        let geod = Geodesic::wgs84();
2129        let (lat2, lon2, azi2) = geod.direct(0.01777745589997, 30.0, 0.0, 10e6);
2130        assert_relative_eq!(lat2, 90.0, epsilon = 0.5e-5);
2131        if lon2 < 0.0 {
2132            assert_relative_eq!(lon2, -150.0, epsilon = 0.5e-5);
2133            assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5);
2134        } else {
2135            assert_relative_eq!(lon2, 30.0, epsilon = 0.5e-5);
2136            assert_relative_eq!(azi2, 0.0, epsilon = 0.5e-5);
2137        }
2138    }
2139
2140    #[test]
2141    fn test_std_geodesic_geodsolve6() {
2142        // Check fix for volatile sbet12a bug found 2011-06-25 (gcc 4.4.4
2143        // x86 -O3).  Found again on 2012-03-27 with tdm-mingw32 (g++ 4.6.1).
2144        let geod = Geodesic::wgs84();
2145        let s12: f64 = geod.inverse(
2146            88.202499451857,
2147            0.0,
2148            -88.202499451857,
2149            179.981022032992859592,
2150        );
2151        assert_relative_eq!(s12, 20003898.214, epsilon = 0.5e-3);
2152        let s12: f64 = geod.inverse(
2153            89.333123580033,
2154            0.0,
2155            -89.333123580032997687,
2156            179.99295812360148422,
2157        );
2158        assert_relative_eq!(s12, 20003926.881, epsilon = 0.5e-3);
2159    }
2160
2161    #[test]
2162    fn test_std_geodesic_geodsolve9() {
2163        // Check fix for volatile x bug found 2011-06-25 (gcc 4.4.4 x86 -O3)
2164        let geod = Geodesic::wgs84();
2165        let s12: f64 = geod.inverse(
2166            56.320923501171,
2167            0.0,
2168            -56.320923501171,
2169            179.664747671772880215,
2170        );
2171        assert_relative_eq!(s12, 19993558.287, epsilon = 0.5e-3);
2172    }
2173
2174    #[test]
2175    fn test_std_geodesic_geodsolve10() {
2176        // Check fix for adjust tol1_ bug found 2011-06-25 (Visual Studio
2177        // 10 rel + debug)
2178        let geod = Geodesic::wgs84();
2179        let s12: f64 = geod.inverse(
2180            52.784459512564,
2181            0.0,
2182            -52.784459512563990912,
2183            179.634407464943777557,
2184        );
2185        assert_relative_eq!(s12, 19991596.095, epsilon = 0.5e-3);
2186    }
2187
2188    #[test]
2189    fn test_std_geodesic_geodsolve11() {
2190        // Check fix for bet2 = -bet1 bug found 2011-06-25 (Visual Studio
2191        // 10 rel + debug)
2192        let geod = Geodesic::wgs84();
2193        let s12: f64 = geod.inverse(
2194            48.522876735459,
2195            0.0,
2196            -48.52287673545898293,
2197            179.599720456223079643,
2198        );
2199        assert_relative_eq!(s12, 19989144.774, epsilon = 0.5e-3);
2200    }
2201
2202    #[test]
2203    fn test_std_geodesic_geodsolve12() {
2204        // Check fix for inverse geodesics on extreme prolate/oblate
2205        // ellipsoids Reported 2012-08-29 Stefan Guenther
2206        // <stefan.gunther@embl.de>; fixed 2012-10-07
2207        let geod = Geodesic::new(89.8, -1.83);
2208        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, -10.0, 160.0);
2209        assert_relative_eq!(azi1, 120.27, epsilon = 1e-2);
2210        assert_relative_eq!(azi2, 105.15, epsilon = 1e-2);
2211        assert_relative_eq!(s12, 266.7, epsilon = 1e-1);
2212    }
2213
2214    #[test]
2215    fn test_std_geodesic_geodsolve14() {
2216        // Check fix for inverse ignoring lon12 = nan
2217        let geod = Geodesic::wgs84();
2218        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 1.0, f64::NAN);
2219        assert!(azi1.is_nan());
2220        assert!(azi2.is_nan());
2221        assert!(s12.is_nan());
2222    }
2223
2224    #[test]
2225    fn test_std_geodesic_geodsolve15() {
2226        // Initial implementation of Math::eatanhe was wrong for e^2 < 0.  This
2227        // checks that this is fixed.
2228        let geod = Geodesic::new(6.4e6, -1f64 / 150.0);
2229        let (_lat2, _lon2, _azi2, _m12, _M12, _M21, S12, _a12) = geod.direct(1.0, 2.0, 3.0, 4.0);
2230        assert_relative_eq!(S12, 23700.0, epsilon = 0.5);
2231    }
2232
2233    #[test]
2234    fn test_std_geodesic_geodsolve17() {
2235        // Check fix for LONG_UNROLL bug found on 2015-05-07
2236        let geod = Geodesic::new(6.4e6, -1f64 / 150.0);
2237        let (_a12, lat2, lon2, azi2, _s12, _m12, _M12, _M21, _S12) = geod._gen_direct(
2238            40.0,
2239            -75.0,
2240            -10.0,
2241            false,
2242            2e7,
2243            caps::STANDARD | caps::LONG_UNROLL,
2244        );
2245        assert_relative_eq!(lat2, -39.0, epsilon = 1.0);
2246        assert_relative_eq!(lon2, -254.0, epsilon = 1.0);
2247        assert_relative_eq!(azi2, -170.0, epsilon = 1.0);
2248
2249        let line = GeodesicLine::new(&geod, 40.0, -75.0, -10.0, None, None, None);
2250        let (_a12, lat2, lon2, azi2, _s12, _m12, _M12, _M21, _S12) =
2251            line._gen_position(false, 2e7, caps::STANDARD | caps::LONG_UNROLL);
2252        assert_relative_eq!(lat2, -39.0, epsilon = 1.0);
2253        assert_relative_eq!(lon2, -254.0, epsilon = 1.0);
2254        assert_relative_eq!(azi2, -170.0, epsilon = 1.0);
2255
2256        let (lat2, lon2, azi2) = geod.direct(40.0, -75.0, -10.0, 2e7);
2257        assert_relative_eq!(lat2, -39.0, epsilon = 1.0);
2258        assert_relative_eq!(lon2, 105.0, epsilon = 1.0);
2259        assert_relative_eq!(azi2, -170.0, epsilon = 1.0);
2260
2261        let (_a12, lat2, lon2, azi2, _s12, _m12, _M12, _M21, _S12) =
2262            line._gen_position(false, 2e7, caps::STANDARD);
2263        assert_relative_eq!(lat2, -39.0, epsilon = 1.0);
2264        assert_relative_eq!(lon2, 105.0, epsilon = 1.0);
2265        assert_relative_eq!(azi2, -170.0, epsilon = 1.0);
2266    }
2267
2268    #[test]
2269    fn test_std_geodesic_geodsolve26() {
2270        // Check 0/0 problem with area calculation on sphere 2015-09-08
2271        let geod = Geodesic::new(6.4e6, 0.0);
2272        let (_a12, _s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, S12) =
2273            geod._gen_inverse(1.0, 2.0, 3.0, 4.0, caps::AREA);
2274        assert_relative_eq!(S12, 49911046115.0, epsilon = 0.5);
2275    }
2276
2277    #[test]
2278    fn test_std_geodesic_geodsolve28() {
2279        // Check for bad placement of assignment of r.a12 with |f| > 0.01 (bug in
2280        // Java implementation fixed on 2015-05-19).
2281        let geod = Geodesic::new(6.4e6, 0.1);
2282        let (a12, _lat2, _lon2, _azi2, _s12, _m12, _M12, _M21, _S12) =
2283            geod._gen_direct(1.0, 2.0, 10.0, false, 5e6, caps::STANDARD);
2284        assert_relative_eq!(a12, 48.55570690, epsilon = 0.5e-8);
2285    }
2286
2287    #[test]
2288    fn test_std_geodesic_geodsolve29() {
2289        // Check longitude unrolling with inverse calculation 2015-09-16
2290        let geod = Geodesic::wgs84();
2291        let (_a12, s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, _S12) =
2292            geod._gen_inverse(0.0, 539.0, 0.0, 181.0, caps::STANDARD);
2293        // Note: This is also supposed to check adjusted longitudes, but geographiclib-rs
2294        //       doesn't seem to support that as of 2021/01/18.
2295        // assert_relative_eq!(lon1, 179, epsilon = 1e-10);
2296        // assert_relative_eq!(lon2, -179, epsilon = 1e-10);
2297        assert_relative_eq!(s12, 222639.0, epsilon = 0.5);
2298        let (_a12, s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, _S12) =
2299            geod._gen_inverse(0.0, 539.0, 0.0, 181.0, caps::STANDARD | caps::LONG_UNROLL);
2300        // assert_relative_eq!(lon1, 539, epsilon = 1e-10);
2301        // assert_relative_eq!(lon2, 541, epsilon = 1e-10);
2302        assert_relative_eq!(s12, 222639.0, epsilon = 0.5);
2303    }
2304
2305    #[test]
2306    fn test_std_geodesic_geodsolve33() {
2307        // Check max(-0.0,+0.0) issues 2015-08-22 (triggered by bugs in Octave --
2308        // sind(-0.0) = +0.0 -- and in some version of Visual Studio --
2309        // fmod(-0.0, 360.0) = +0.0.
2310        let geod = Geodesic::wgs84();
2311        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 179.0);
2312        assert_relative_eq!(azi1, 90.0, epsilon = 0.5e-5);
2313        assert_relative_eq!(azi2, 90.0, epsilon = 0.5e-5);
2314        assert_relative_eq!(s12, 19926189.0, epsilon = 0.5);
2315        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 179.5);
2316        assert_relative_eq!(azi1, 55.96650, epsilon = 0.5e-5);
2317        assert_relative_eq!(azi2, 124.03350, epsilon = 0.5e-5);
2318        assert_relative_eq!(s12, 19980862.0, epsilon = 0.5);
2319        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 180.0);
2320        assert_relative_eq!(azi1, 0.0, epsilon = 0.5e-5);
2321        assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5);
2322        assert_relative_eq!(s12, 20003931.0, epsilon = 0.5);
2323        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 1.0, 180.0);
2324        assert_relative_eq!(azi1, 0.0, epsilon = 0.5e-5);
2325        assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5);
2326        assert_relative_eq!(s12, 19893357.0, epsilon = 0.5);
2327
2328        let geod = Geodesic::new(6.4e6, 0.0);
2329        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 179.0);
2330        assert_relative_eq!(azi1, 90.0, epsilon = 0.5e-5);
2331        assert_relative_eq!(azi2, 90.0, epsilon = 0.5e-5);
2332        assert_relative_eq!(s12, 19994492.0, epsilon = 0.5);
2333        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 180.0);
2334        assert_relative_eq!(azi1, 0.0, epsilon = 0.5e-5);
2335        assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5);
2336        assert_relative_eq!(s12, 20106193.0, epsilon = 0.5);
2337        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 1.0, 180.0);
2338        assert_relative_eq!(azi1, 0.0, epsilon = 0.5e-5);
2339        assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5);
2340        assert_relative_eq!(s12, 19994492.0, epsilon = 0.5);
2341
2342        let geod = Geodesic::new(6.4e6, -1.0 / 300.0);
2343        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 179.0);
2344        assert_relative_eq!(azi1, 90.0, epsilon = 0.5e-5);
2345        assert_relative_eq!(azi2, 90.0, epsilon = 0.5e-5);
2346        assert_relative_eq!(s12, 19994492.0, epsilon = 0.5);
2347        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.0, 180.0);
2348        assert_relative_eq!(azi1, 90.0, epsilon = 0.5e-5);
2349        assert_relative_eq!(azi2, 90.0, epsilon = 0.5e-5);
2350        assert_relative_eq!(s12, 20106193.0, epsilon = 0.5);
2351        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 0.5, 180.0);
2352        assert_relative_eq!(azi1, 33.02493, epsilon = 0.5e-5);
2353        assert_relative_eq!(azi2, 146.97364, epsilon = 0.5e-5);
2354        assert_relative_eq!(s12, 20082617.0, epsilon = 0.5);
2355        let (s12, azi1, azi2, _a12) = geod.inverse(0.0, 0.0, 1.0, 180.0);
2356        assert_relative_eq!(azi1, 0.0, epsilon = 0.5e-5);
2357        assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5);
2358        assert_relative_eq!(s12, 20027270.0, epsilon = 0.5);
2359    }
2360
2361    #[test]
2362    fn test_std_geodesic_geodsolve55() {
2363        // Check fix for nan + point on equator or pole not returning all nans in
2364        // Geodesic::Inverse, found 2015-09-23.
2365        let geod = Geodesic::wgs84();
2366        let (s12, azi1, azi2, _a12) = geod.inverse(f64::NAN, 0.0, 0.0, 90.0);
2367        assert!(azi1.is_nan());
2368        assert!(azi2.is_nan());
2369        assert!(s12.is_nan());
2370        let (s12, azi1, azi2, _a12) = geod.inverse(f64::NAN, 0.0, 90.0, 3.0);
2371        assert!(azi1.is_nan());
2372        assert!(azi2.is_nan());
2373        assert!(s12.is_nan());
2374    }
2375
2376    #[test]
2377    fn test_std_geodesic_geodsolve59() {
2378        // Check for points close with longitudes close to 180 deg apart.
2379        let geod = Geodesic::wgs84();
2380        let (s12, azi1, azi2, _a12) = geod.inverse(5.0, 0.00000000000001, 10.0, 180.0);
2381        assert_relative_eq!(azi1, 0.000000000000035, epsilon = 1.5e-14);
2382        assert_relative_eq!(azi2, 179.99999999999996, epsilon = 1.5e-14);
2383        assert_relative_eq!(s12, 18345191.174332713, epsilon = 5e-9);
2384    }
2385
2386    #[test]
2387    fn test_std_geodesic_geodsolve61() {
2388        // Make sure small negative azimuths are west-going
2389        let geod = Geodesic::wgs84();
2390        let (_a12, lat2, lon2, azi2, _s12, _m12, _M12, _M21, _S12) = geod._gen_direct(
2391            45.0,
2392            0.0,
2393            -0.000000000000000003,
2394            false,
2395            1e7,
2396            caps::STANDARD | caps::LONG_UNROLL,
2397        );
2398        assert_relative_eq!(lat2, 45.30632, epsilon = 0.5e-5);
2399        assert_relative_eq!(lon2, -180.0, epsilon = 0.5e-5);
2400        assert_relative_eq!(azi2.abs(), 180.0, epsilon = 0.5e-5);
2401        // geographiclib-rs does not appear to support Geodesic.inverse_line or
2402        // or GeodesicLine.position as of 2021/01/18.
2403        // let line = geod.inverse_line(45, 0, 80, -0.000000000000000003);
2404        // let res = line.position(1e7, caps::STANDARD | caps::LONG_UNROLL);
2405        // assert_relative_eq!(lat2, 45.30632, epsilon = 0.5e-5);
2406        // assert_relative_eq!(lon2, -180, epsilon = 0.5e-5);
2407        // assert_relative_eq!(azi2.abs(), 180, epsilon = 0.5e-5);
2408    }
2409
2410    // #[test]
2411    // fn test_std_geodesic_geodsolve65() {
2412    //     // Check for bug in east-going check in GeodesicLine (needed to check for
2413    //     // sign of 0) and sign error in area calculation due to a bogus override
2414    //     // of the code for alp12.  Found/fixed on 2015-12-19.
2415    //     // These tests rely on Geodesic.inverse_line, which is not supported by
2416    //     // geographiclib-rs as of 2021/01/18.
2417    // }
2418
2419    // #[test]
2420    // fn test_std_geodesic_geodsolve69() {
2421    //     // Check for InverseLine if line is slightly west of S and that s13 is
2422    //     // correctly set.
2423    //     // These tests rely on Geodesic.inverse_line, which is not supported by
2424    //     // geographiclib-rs as of 2021/01/18.
2425    // }
2426
2427    // #[test]
2428    // fn test_std_geodesic_geodsolve71() {
2429    //     // Check that DirectLine sets s13.
2430    //     // These tests rely on Geodesic.direct_line, which is not supported by
2431    //     // geographiclib-rs as of 2021/01/18.
2432    // }
2433
2434    #[test]
2435    fn test_std_geodesic_geodsolve73() {
2436        // Check for backwards from the pole bug reported by Anon on 2016-02-13.
2437        // This only affected the Java implementation.  It was introduced in Java
2438        // version 1.44 and fixed in 1.46-SNAPSHOT on 2016-01-17.
2439        // Also the + sign on azi2 is a check on the normalizing of azimuths
2440        // (converting -0.0 to +0.0).
2441        let geod = Geodesic::wgs84();
2442        let (lat2, lon2, azi2) = geod.direct(90.0, 10.0, 180.0, -1e6);
2443        assert_relative_eq!(lat2, 81.04623, epsilon = 0.5e-5);
2444        assert_relative_eq!(lon2, -170.0, epsilon = 0.5e-5);
2445        assert_relative_eq!(azi2, 0.0, epsilon = 0.5e-5);
2446        assert!(azi2.is_sign_positive());
2447    }
2448
2449    #[test]
2450    fn test_std_geodesic_geodsolve74() {
2451        // Check fix for inaccurate areas, bug introduced in v1.46, fixed
2452        // 2015-10-16.
2453        let geod = Geodesic::wgs84();
2454        let (a12, s12, azi1, azi2, m12, M12, M21, S12) =
2455            geod._gen_inverse_azi(54.1589, 15.3872, 54.1591, 15.3877, caps::ALL);
2456        assert_relative_eq!(azi1, 55.723110355, epsilon = 5e-9);
2457        assert_relative_eq!(azi2, 55.723515675, epsilon = 5e-9);
2458        assert_relative_eq!(s12, 39.527686385, epsilon = 5e-9);
2459        assert_relative_eq!(a12, 0.000355495, epsilon = 5e-9);
2460        assert_relative_eq!(m12, 39.527686385, epsilon = 5e-9);
2461        assert_relative_eq!(M12, 0.999999995, epsilon = 5e-9);
2462        assert_relative_eq!(M21, 0.999999995, epsilon = 5e-9);
2463        assert_relative_eq!(S12, 286698586.30197, epsilon = 5e-4);
2464    }
2465
2466    #[test]
2467    fn test_std_geodesic_geodsolve76() {
2468        // The distance from Wellington and Salamanca (a classic failure of
2469        // Vincenty)
2470        let geod = Geodesic::wgs84();
2471        let (s12, azi1, azi2, _a12) = geod.inverse(
2472            -(41.0 + 19.0 / 60.0),
2473            174.0 + 49.0 / 60.0,
2474            40.0 + 58.0 / 60.0,
2475            -(5.0 + 30.0 / 60.0),
2476        );
2477        assert_relative_eq!(azi1, 160.39137649664, epsilon = 0.5e-11);
2478        assert_relative_eq!(azi2, 19.50042925176, epsilon = 0.5e-11);
2479        assert_relative_eq!(s12, 19960543.857179, epsilon = 0.5e-6);
2480    }
2481
2482    #[test]
2483    fn test_std_geodesic_geodsolve78() {
2484        // An example where the NGS calculator fails to converge
2485        let geod = Geodesic::wgs84();
2486        let (s12, azi1, azi2, _a12) = geod.inverse(27.2, 0.0, -27.1, 179.5);
2487        assert_relative_eq!(azi1, 45.82468716758, epsilon = 0.5e-11);
2488        assert_relative_eq!(azi2, 134.22776532670, epsilon = 0.5e-11);
2489        assert_relative_eq!(s12, 19974354.765767, epsilon = 0.5e-6);
2490    }
2491
2492    #[test]
2493    fn test_std_geodesic_geodsolve80() {
2494        // Some tests to add code coverage: computing scale in special cases + zero
2495        // length geodesic (includes GeodSolve80 - GeodSolve83).
2496        let geod = Geodesic::wgs84();
2497        let (_a12, _s12, _salp1, _calp1, _salp2, _calp2, _m12, M12, M21, _S12) =
2498            geod._gen_inverse(0.0, 0.0, 0.0, 90.0, caps::GEODESICSCALE);
2499        assert_relative_eq!(M12, -0.00528427534, epsilon = 0.5e-10);
2500        assert_relative_eq!(M21, -0.00528427534, epsilon = 0.5e-10);
2501
2502        let (_a12, _s12, _salp1, _calp1, _salp2, _calp2, _m12, M12, M21, _S12) =
2503            geod._gen_inverse(0.0, 0.0, 1e-6, 1e-6, caps::GEODESICSCALE);
2504        assert_relative_eq!(M12, 1.0, epsilon = 0.5e-10);
2505        assert_relative_eq!(M21, 1.0, epsilon = 0.5e-10);
2506
2507        let (a12, s12, azi1, azi2, m12, M12, M21, S12) =
2508            geod._gen_inverse_azi(20.001, 0.0, 20.001, 0.0, caps::ALL);
2509        assert_relative_eq!(a12, 0.0, epsilon = 1e-13);
2510        assert_relative_eq!(s12, 0.0, epsilon = 1e-8);
2511        assert_relative_eq!(azi1, 180.0, epsilon = 1e-13);
2512        assert_relative_eq!(azi2, 180.0, epsilon = 1e-13);
2513        assert_relative_eq!(m12, 0.0, epsilon = 1e-8);
2514        assert_relative_eq!(M12, 1.0, epsilon = 1e-15);
2515        assert_relative_eq!(M21, 1.0, epsilon = 1e-15);
2516        assert_relative_eq!(S12, 0.0, epsilon = 1e-10);
2517
2518        let (a12, s12, azi1, azi2, m12, M12, M21, S12) =
2519            geod._gen_inverse_azi(90.0, 0.0, 90.0, 180.0, caps::ALL);
2520        assert_relative_eq!(a12, 0.0, epsilon = 1e-13);
2521        assert_relative_eq!(s12, 0.0, epsilon = 1e-8);
2522        assert_relative_eq!(azi1, 0.0, epsilon = 1e-13);
2523        assert_relative_eq!(azi2, 180.0, epsilon = 1e-13);
2524        assert_relative_eq!(m12, 0.0, epsilon = 1e-8);
2525        assert_relative_eq!(M12, 1.0, epsilon = 1e-15);
2526        assert_relative_eq!(M21, 1.0, epsilon = 1e-15);
2527        assert_relative_eq!(S12, 127516405431022.0, epsilon = 0.5);
2528
2529        // An incapable line which can't take distance as input
2530        let line = GeodesicLine::new(&geod, 1.0, 2.0, 90.0, Some(caps::LATITUDE), None, None);
2531        let (a12, _lat2, _lon2, _azi2, _s12, _m12, _M12, _M21, _S12) =
2532            line._gen_position(false, 1000.0, caps::CAP_NONE);
2533        assert!(a12.is_nan());
2534    }
2535
2536    #[test]
2537    fn test_std_geodesic_geodsolve84() {
2538        // Tests for python implementation to check fix for range errors with
2539        // {fmod,sin,cos}(inf) (includes GeodSolve84 - GeodSolve91).
2540        let geod = Geodesic::wgs84();
2541        let (lat2, lon2, azi2) = geod.direct(0.0, 0.0, 90.0, f64::INFINITY);
2542        assert!(lat2.is_nan());
2543        assert!(lon2.is_nan());
2544        assert!(azi2.is_nan());
2545        let (lat2, lon2, azi2) = geod.direct(0.0, 0.0, 90.0, f64::NAN);
2546        assert!(lat2.is_nan());
2547        assert!(lon2.is_nan());
2548        assert!(azi2.is_nan());
2549        let (lat2, lon2, azi2) = geod.direct(0.0, 0.0, f64::INFINITY, 1000.0);
2550        assert!(lat2.is_nan());
2551        assert!(lon2.is_nan());
2552        assert!(azi2.is_nan());
2553        let (lat2, lon2, azi2) = geod.direct(0.0, 0.0, f64::NAN, 1000.0);
2554        assert!(lat2.is_nan());
2555        assert!(lon2.is_nan());
2556        assert!(azi2.is_nan());
2557        let (lat2, lon2, azi2) = geod.direct(0.0, f64::INFINITY, 90.0, 1000.0);
2558        assert_eq!(lat2, 0.0);
2559        assert!(lon2.is_nan());
2560        assert_eq!(azi2, 90.0);
2561        let (lat2, lon2, azi2) = geod.direct(0.0, f64::NAN, 90.0, 1000.0);
2562        assert_eq!(lat2, 0.0);
2563        assert!(lon2.is_nan());
2564        assert_eq!(azi2, 90.0);
2565        let (lat2, lon2, azi2) = geod.direct(f64::INFINITY, 0.0, 90.0, 1000.0);
2566        assert!(lat2.is_nan());
2567        assert!(lon2.is_nan());
2568        assert!(azi2.is_nan());
2569        let (lat2, lon2, azi2) = geod.direct(f64::NAN, 0.0, 90.0, 1000.0);
2570        assert!(lat2.is_nan());
2571        assert!(lon2.is_nan());
2572        assert!(azi2.is_nan());
2573    }
2574
2575    // *_geodtest_* tests are based on Karney's GeodTest*.dat test datasets.
2576    // A description of these files' content can be found at:
2577    //     https://geographiclib.sourceforge.io/html/geodesic.html#testgeod
2578    // Here are some key excerpts...
2579    //    This consists of a set of geodesics for the WGS84 ellipsoid.
2580    //     Each line of the test set gives 10 space delimited numbers
2581    //         latitude at point 1, lat1 (degrees, exact)
2582    //         longitude at point 1, lon1 (degrees, always 0)
2583    //         azimuth at point 1, azi1 (clockwise from north in degrees, exact)
2584    //         latitude at point 2, lat2 (degrees, accurate to 10−18 deg)
2585    //         longitude at point 2, lon2 (degrees, accurate to 10−18 deg)
2586    //         azimuth at point 2, azi2 (degrees, accurate to 10−18 deg)
2587    //         geodesic distance from point 1 to point 2, s12 (meters, exact)
2588    //         arc distance on the auxiliary sphere, a12 (degrees, accurate to 10−18 deg)
2589    //         reduced length of the geodesic, m12 (meters, accurate to 0.1 pm)
2590    //         the area under the geodesic, S12 (m2, accurate to 1 mm2)
2591
2592    static FULL_TEST_PATH: &str = "test_fixtures/test_data_unzipped/GeodTest.dat";
2593    static SHORT_TEST_PATH: &str = "test_fixtures/test_data_unzipped/GeodTest-short.dat";
2594    static BUILTIN_TEST_PATH: &str = "test_fixtures/GeodTest-100.dat";
2595    fn test_input_path() -> &'static str {
2596        if cfg!(feature = "test_full") {
2597            FULL_TEST_PATH
2598        } else if cfg!(feature = "test_short") {
2599            SHORT_TEST_PATH
2600        } else {
2601            BUILTIN_TEST_PATH
2602        }
2603    }
2604
2605    fn geodtest_basic<T>(path: &str, f: T)
2606    where
2607        T: Fn(usize, &(f64, f64, f64, f64, f64, f64, f64, f64, f64, f64)),
2608    {
2609        let dir_base = std::env::current_dir().expect("Failed to determine current directory");
2610        let path_base = dir_base.as_path();
2611        let pathbuf = std::path::Path::new(path_base).join(path);
2612        let path = pathbuf.as_path();
2613        let file = match std::fs::File::open(path) {
2614            Ok(val) => val,
2615            Err(_error) => {
2616                let path_str = path
2617                    .to_str()
2618                    .expect("Failed to convert GeodTest path to string during error reporting");
2619                panic!("Failed to open test input file. Run `script/download-test-data.sh` to download test input to: {}\nFor details see https://geographiclib.sourceforge.io/html/geodesic.html#testgeod", path_str)
2620            }
2621        };
2622        let reader = std::io::BufReader::new(file);
2623        reader.lines().enumerate().for_each(|(i, line)| {
2624            let line_safe = line.expect("Failed to read line");
2625            let items: Vec<f64> = line_safe
2626                .split(' ')
2627                .enumerate()
2628                .map(|(j, item)| match item.parse::<f64>() {
2629                    Ok(parsed) => parsed,
2630                    Err(_error) => {
2631                        panic!("Error parsing item {} on line {}: {}", j + 1, i + 1, item)
2632                    }
2633                })
2634                .collect();
2635            assert_eq!(items.len(), 10);
2636            let tuple = (
2637                items[0], items[1], items[2], items[3], items[4], items[5], items[6], items[7],
2638                items[8], items[9],
2639            );
2640            f(i + 1, &tuple); // report 1-based line number rather than 0-based
2641        });
2642    }
2643
2644    #[test]
2645    fn test_geodtest_geodesic_direct12() {
2646        let g = std::sync::Arc::new(std::sync::Mutex::new(Geodesic::wgs84()));
2647
2648        geodtest_basic(
2649            test_input_path(),
2650            |_line_num, &(lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, S12)| {
2651                let g = g.lock().unwrap();
2652                let (lat2_out, lon2_out, azi2_out, m12_out, _M12_out, _M21_out, S12_out, a12_out) =
2653                    g.direct(lat1, lon1, azi1, s12);
2654                assert_relative_eq!(lat2, lat2_out, epsilon = 1e-13);
2655                assert_relative_eq!(lon2, lon2_out, epsilon = 2e-8);
2656                assert_relative_eq!(azi2, azi2_out, epsilon = 2e-8);
2657                assert_relative_eq!(m12, m12_out, epsilon = 9e-9);
2658                assert_relative_eq!(S12, S12_out, epsilon = 2e4); // Note: unreasonable tolerance
2659                assert_relative_eq!(a12, a12_out, epsilon = 9e-14);
2660            },
2661        );
2662    }
2663
2664    #[test]
2665    fn test_geodtest_geodesic_direct21() {
2666        let g = std::sync::Arc::new(std::sync::Mutex::new(Geodesic::wgs84()));
2667
2668        geodtest_basic(
2669            test_input_path(),
2670            |_line_num, &(lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, S12)| {
2671                let g = g.lock().unwrap();
2672                // Reverse some values for 2->1 instead of 1->2
2673                let (lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, S12) =
2674                    (lat2, lon2, azi2, lat1, lon1, azi1, -s12, -a12, -m12, -S12);
2675                let (lat2_out, lon2_out, azi2_out, m12_out, _M12_out, _M21_out, S12_out, a12_out) =
2676                    g.direct(lat1, lon1, azi1, s12);
2677                assert_relative_eq!(lat2, lat2_out, epsilon = 8e-14);
2678                assert_relative_eq!(lon2, lon2_out, epsilon = 4e-6);
2679                assert_relative_eq!(azi2, azi2_out, epsilon = 4e-6);
2680                assert_relative_eq!(m12, m12_out, epsilon = 1e-8);
2681                assert_relative_eq!(S12, S12_out, epsilon = 3e6); // Note: unreasonable tolerance
2682                assert_relative_eq!(a12, a12_out, epsilon = 9e-14);
2683            },
2684        );
2685    }
2686
2687    #[test]
2688    fn test_geodtest_geodesic_inverse12() {
2689        let g = std::sync::Arc::new(std::sync::Mutex::new(Geodesic::wgs84()));
2690
2691        geodtest_basic(
2692            test_input_path(),
2693            |line_num, &(lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, S12)| {
2694                let g = g.lock().unwrap();
2695                let (s12_out, azi1_out, azi2_out, m12_out, _M12_out, _M21_out, S12_out, a12_out) =
2696                    g.inverse(lat1, lon1, lat2, lon2);
2697                assert_relative_eq!(s12, s12_out, epsilon = 8e-9);
2698                assert_relative_eq!(azi1, azi1_out, epsilon = 2e-2);
2699                assert_relative_eq!(azi2, azi2_out, epsilon = 2e-2);
2700                assert_relative_eq!(m12, m12_out, epsilon = 5e-5);
2701                // Our area calculation differs significantly (~1e7) from the value in GeodTest.dat for
2702                // line 400001, BUT our value also perfectly matches the value returned by GeographicLib
2703                // (C++) 1.51. Here's the problem line, for reference:
2704                // 4.199535552987 0 90 -4.199535552987 179.398106343454992238 90 19970505.608097404994 180 0
2705                if line_num != 400001 {
2706                    assert_relative_eq!(S12, S12_out, epsilon = 3e10); // Note: unreasonable tolerance
2707                }
2708                assert_relative_eq!(a12, a12_out, epsilon = 2e-10);
2709            },
2710        );
2711    }
2712
2713    #[test]
2714    fn test_turnaround() {
2715        let g = Geodesic::wgs84();
2716
2717        let start = (0.0, 0.0);
2718        let destination = (0.0, 1.0);
2719
2720        let (distance, azi1, _, _) = g.inverse(start.0, start.1, destination.0, destination.1);
2721
2722        // Confirm that we've gone due-east
2723        assert_eq!(azi1, 90.0);
2724
2725        // Turn around by adding 180 degrees to the azimuth
2726        let turn_around = azi1 + 180.0;
2727
2728        // Confirm that turn around is due west
2729        assert_eq!(turn_around, 270.0);
2730
2731        // Test that we can turn around and get back to the starting point.
2732        let (lat, lon) = g.direct(destination.0, destination.1, turn_around, distance);
2733        assert_relative_eq!(lat, start.0, epsilon = 1.0e-3);
2734        assert_relative_eq!(lon, start.1, epsilon = 1.0e-3);
2735    }
2736}