gistools/proj/
geodesic.rs

1use core::f64::consts::{FRAC_1_SQRT_2, PI};
2use libm::{
3    atan, atan2, atanh, cbrt, copysign, cos, fabs, fmax, fmin, hypot, remainder, remquo, sin, sqrt,
4};
5
6const GEOGRAPHICLIB_GEODESIC_ORDER: usize = 6;
7const N_A1: usize = GEOGRAPHICLIB_GEODESIC_ORDER;
8const N_A2: usize = GEOGRAPHICLIB_GEODESIC_ORDER;
9const N_A3: usize = GEOGRAPHICLIB_GEODESIC_ORDER;
10const N_C: usize = GEOGRAPHICLIB_GEODESIC_ORDER + 1;
11const N_C1: usize = GEOGRAPHICLIB_GEODESIC_ORDER;
12const N_C1_P: usize = GEOGRAPHICLIB_GEODESIC_ORDER;
13const N_C2: usize = GEOGRAPHICLIB_GEODESIC_ORDER;
14const N_C3: usize = GEOGRAPHICLIB_GEODESIC_ORDER;
15const N_C4: usize = GEOGRAPHICLIB_GEODESIC_ORDER;
16const TOL0: f64 = f64::EPSILON;
17const TOL1: f64 = 200. * TOL0;
18const TOL2: f64 = 1.4901161193847656e-8; // sqrt(EPSILON);
19const TOLB: f64 = TOL0;
20const DEGREE: f64 = core::f64::consts::PI / 180.0;
21const QD: f64 = 90.0;
22const HD: f64 = 180.0;
23const TD: f64 = 360.0;
24const TINY: f64 = 1.4916681462400413e-154; // f64::MIN_POSITIVE.sqrt();
25const DIGITS: u32 = f64::MANTISSA_DIGITS;
26const MAXIT1: u32 = 20;
27const MAXIT2: u32 = MAXIT1 + DIGITS + 10;
28const XTHRESH: f64 = 1000. * TOL2;
29
30/// mask values for the \e caps argument to geod_lineinit().
31#[derive(Debug, Default, Clone, Copy, PartialEq)]
32pub enum GeodMask {
33    /// Calculate nothing
34    #[default]
35    GeodNone = 0, // < Calculate nothing */
36    /// Calculate latitude
37    GeodLatitude = 1 << 7, // < Calculate latitude */
38    /// Calculate longitude
39    GeodLongitude = 1 << 8 | 1 << 3, // < Calculate longitude */
40    /// Calculate azimuth
41    GeodAzimuth = 1 << 9, // < Calculate azimuth */
42    /// Calculate distance
43    GeodDistance = 1 << 10 | 1 << 0, // < Calculate distance */
44    /// Allow distance as input
45    GeodDistanceIn = 1 << 11 | 1 << 0 | 1 << 1, // < Allow distance as input  */
46    /// Calculate reduced length
47    GeodReducedlength = 1 << 12 | 1 << 0 | 1 << 2, // < Calculate reduced length */
48    /// Calculate geodesic scale
49    GeodGeodesicScale = 1 << 13 | 1 << 0 | 1 << 2, // < Calculate geodesic scale */
50    /// Calculate area
51    GeodArea = 1 << 14 | 1 << 4, // < Calculate reduced length */
52    /// Calculate everything
53    GeodAll = 0x7F80 | 0x1F, // < Calculate everything */
54}
55
56/// flag values for the \e flags argument to geod_gendirect() and geod_genposition()
57#[derive(Debug, Default, Clone, Copy, PartialEq)]
58pub enum GeodFlags {
59    /// No flags
60    #[default]
61    GeodNoflags = 0, // < No flags */
62    /// Position given in terms of arc distance
63    GeodArcmode = 1 << 0, // < Position given in terms of arc distance */
64    /// Unroll the longitude
65    GeodLongUnroll = 1 << 15, // < Unroll the longitude */
66}
67
68/// Cap Types to build for the \e caps argument to geod_lineinit().
69#[derive(Debug, Default, Clone, Copy, PartialEq)]
70pub enum CapType {
71    /// No caps
72    #[default]
73    CapNone = 0,
74    /// Cap C1
75    CapC1 = 1 << 0,
76    /// Cap C1p
77    CapC1p = 1 << 1,
78    /// Cap C2
79    CapC2 = 1 << 2,
80    /// Cap C3
81    CapC3 = 1 << 3,
82    /// Cap C4
83    CapC4 = 1 << 4,
84    /// Cap C5
85    CapAll = 0x1F,
86    /// All caps
87    OutAll = 0x7F80,
88}
89
90/// The struct containing information about the ellipsoid. This must be
91/// initialized by geod_init() before use.
92#[derive(Debug, Default, Clone, Copy, PartialEq)]
93pub struct GeodGeodesic {
94    /// the equatorial radius
95    pub a: f64,
96    /// the flattening
97    pub f: f64,
98    /// the second flattening
99    pub f1: f64,
100    /// second eccentricity
101    pub e2: f64,
102    /// the second eccentricity squared
103    pub ep2: f64,
104    /// third  flattening
105    pub n: f64,
106    /// semiminor axis
107    pub b: f64,
108    /// TODO: I don't know what this represents
109    pub c2: f64,
110    /// the tolerance
111    pub etol2: f64,
112    /// TODO: I don't know what this represents
113    pub a3x: [f64; 6],
114    /// TODO: I don't know what this represents
115    pub c3x: [f64; 15],
116    /// TODO: I don't know what this represents
117    pub c4x: [f64; 21],
118}
119
120/// The struct containing information about a single geodesic.  This must be
121/// initialized by geod_lineinit(), geod_directline(), geod_gendirectline(),
122/// or geod_inverseline() before use.
123#[derive(Debug, Default, Clone, Copy, PartialEq)]
124pub struct GeodGeodesicline {
125    /// < the starting latitude
126    pub lat1: f64,
127    /// < the starting longitude  
128    pub lon1: f64,
129    /// < the starting azimuth
130    pub azi1: f64,
131    /// < the equatorial radius       
132    pub a: f64,
133    /// < the flattening        
134    pub f: f64,
135    /// < sine of \e azi1     
136    pub salp1: f64,
137    /// < cosine of \e azi1      
138    pub calp1: f64,
139    /// < arc length to reference point            
140    pub a13: f64,
141    /// < distance to reference point          
142    pub s13: f64,
143    /// < @cond SKIP
144    pub b: f64,
145    /// UNKNOWN
146    pub c2: f64,
147    /// UNKNOWN
148    pub f1: f64,
149    /// UNKNOWN
150    pub salp0: f64,
151    /// UNKNOWN
152    pub calp0: f64,
153    /// UNKNOWN
154    pub k2: f64,
155    /// UNKNOWN
156    pub ssig1: f64,
157    /// UNKNOWN
158    pub csig1: f64,
159    /// UNKNOWN
160    pub dn1: f64,
161    /// UNKNOWN
162    pub stau1: f64,
163    /// UNKNOWN
164    pub ctau1: f64,
165    /// UNKNOWN
166    pub somg1: f64,
167    /// UNKNOWN
168    pub comg1: f64,
169    /// UNKNOWN
170    pub a1m1: f64,
171    /// UNKNOWN
172    pub a2m1: f64,
173    /// UNKNOWN
174    pub a3c: f64,
175    /// UNKNOWN
176    pub b11: f64,
177    /// UNKNOWN
178    pub b21: f64,
179    /// UNKNOWN
180    pub b31: f64,
181    /// UNKNOWN
182    pub a4: f64,
183    /// UNKNOWN
184    pub b41: f64,
185    /// UNKNOWN
186    pub c1a: [f64; 7],
187    /// UNKNOWN
188    pub c1pa: [f64; 7],
189    /// UNKNOWN
190    pub c2a: [f64; 7],
191    /// UNKNOWN
192    pub c3a: [f64; 6],
193    /// UNKNOWN
194    pub c4a: [f64; 6],
195    /// < @endcond
196    /// < the capabilities
197    caps: u32,
198}
199
200fn sq(x: f64) -> f64 {
201    x * x
202}
203
204fn sumx(u: f64, v: f64, t: &mut f64) -> f64 {
205    let s = u + v;
206    let mut up = s - v;
207    let mut vpp = s - up;
208    up -= u;
209    vpp -= v;
210    if *t != 0. {
211        *t = if s != 0. { 0. - (up + vpp) } else { s };
212    }
213    // error-free sum:
214    // u + v =       s      + t
215    //       = round(u + v) + t
216    s
217}
218
219/// Initialize a GeodGeodesic object
220pub fn geod_init(g: &mut GeodGeodesic, a: f64, f: f64) {
221    g.a = a;
222    g.f = f;
223    g.f1 = 1. - g.f;
224    g.e2 = g.f * (2. - g.f);
225    g.ep2 = g.e2 / sq(g.f1); /* e2 / (1 - e2) */
226    g.n = g.f / (2. - g.f);
227    g.b = g.a * g.f1;
228    g.c2 = (sq(g.a)
229        + sq(g.b)
230            * (if g.e2 == 0. {
231                1.
232            } else {
233                (if g.e2 > 0. { atanh(sqrt(g.e2)) } else { atan(sqrt(-g.e2)) }) / sqrt(fabs(g.e2))
234            }))
235        / 2.; /* authalic radius squared */
236    // The sig12 threshold for "really short".  Using the auxiliary sphere
237    // solution with dnm computed at (bet1 + bet2) / 2, the relative error in the
238    // azimuth consistency check is sig12^2 * abs(f) * min(1, 1-f/2) / 2.  (Error
239    // measured for 1/100 < b/a < 100 and abs(f) >= 1/1000.  For a given f and
240    // sig12, the max error occurs for lines near the pole.  If the old rule for
241    // computing dnm = (dn1 + dn2)/2 is used, then the error increases by a
242    // factor of 2.)  Setting this equal to epsilon gives sig12 = etol2.  Here
243    // 0.1 is a safety factor (error decreased by 100) and max(0.001, abs(f))
244    // stops etol2 getting too large in the nearly spherical case.
245    g.etol2 = 0.1 * TOL2 / sqrt(fmax(0.001, fabs(g.f)) * fmin(1.0, 1. - g.f / 2.) / 2.);
246
247    a3coeff(g);
248    c3coeff(g);
249    c4coeff(g);
250}
251
252/// Compute the geodesic inverse between two points
253#[allow(clippy::too_many_arguments)]
254pub fn geod_inverse(
255    g: &mut GeodGeodesic,
256    lat1: f64,
257    lon1: f64,
258    lat2: f64,
259    lon2: f64,
260    ps12: &mut f64,
261    pazi1: &mut f64,
262    pazi2: &mut f64,
263) {
264    geod_geninverse(
265        g, lat1, lon1, lat2, lon2, ps12, pazi1, pazi2, &mut 0.0, &mut 0.0, &mut 0.0, &mut 0.0,
266    );
267}
268
269/// Compute the geodesic directly between two points
270#[allow(clippy::too_many_arguments)]
271pub fn geod_gendirect(
272    g: &mut GeodGeodesic,
273    lat1: f64,
274    lon1: f64,
275    azi1: f64,
276    flags: u32,
277    s12_a12: f64,
278    plat2: &mut f64,
279    plon2: &mut f64,
280    pazi2: &mut f64,
281    ps12: &mut f64,
282    pm12: &mut f64,
283    p_m12: &mut f64,
284    p_m21: &mut f64,
285    p_s12: &mut f64,
286) -> f64 {
287    let mut l = GeodGeodesicline::default();
288    let outmask: u32 =
289        (if *plat2 != 0. { GeodMask::GeodLatitude as u32 } else { GeodMask::GeodNone as u32 })
290            | (if *plon2 != 0. {
291                GeodMask::GeodLongitude as u32
292            } else {
293                GeodMask::GeodNone as u32
294            })
295            | (if *pazi2 != 0. { GeodMask::GeodAzimuth as u32 } else { GeodMask::GeodNone as u32 })
296            | (if *ps12 != 0. { GeodMask::GeodDistance as u32 } else { GeodMask::GeodNone as u32 })
297            | (if *pm12 != 0. {
298                GeodMask::GeodReducedlength as u32
299            } else {
300                GeodMask::GeodNone as u32
301            })
302            | (if *p_m12 != 0. || *p_m21 != 0. {
303                GeodMask::GeodGeodesicScale as u32
304            } else {
305                GeodMask::GeodNone as u32
306            })
307            | (if *p_s12 != 0. { GeodMask::GeodArea as u32 } else { GeodMask::GeodNone as u32 });
308
309    geod_lineinit(
310        &mut l,
311        g,
312        lat1,
313        lon1,
314        azi1,
315        // Automatically supply GeodMask::GeodDistance as u32_IN if necessary
316        outmask
317            | (if flags & (GeodFlags::GeodArcmode as u32) != 0 {
318                GeodMask::GeodNone as u32
319            } else {
320                GeodMask::GeodDistanceIn as u32
321            }),
322    );
323    geod_genposition(&l, flags, s12_a12, plat2, plon2, pazi2, ps12, pm12, p_m12, p_m21, p_s12)
324}
325
326/// Initialize a geodesic line
327pub fn geod_lineinit(
328    l: &mut GeodGeodesicline,
329    g: &GeodGeodesic,
330    lat1: f64,
331    lon1: f64,
332    mut azi1: f64,
333    caps: u32,
334) {
335    //   double salp1, calp1;
336    let mut salp1 = 0.0;
337    let mut calp1 = 0.0;
338    azi1 = ang_normalize(azi1);
339    // Guard against underflow in salp0
340    sincosdx(ang_round(azi1), &mut salp1, &mut calp1);
341    geod_lineinit_int(l, g, lat1, lon1, azi1, salp1, calp1, caps);
342}
343
344/// Compute the geodesic directly between two points
345#[allow(clippy::too_many_arguments)]
346pub fn geod_direct(
347    g: &mut GeodGeodesic,
348    lat1: f64,
349    lon1: f64,
350    azi1: f64,
351    s12: f64,
352    plat2: &mut f64,
353    plon2: &mut f64,
354    pazi2: &mut f64,
355) {
356    geod_gendirect(
357        g,
358        lat1,
359        lon1,
360        azi1,
361        GeodFlags::GeodNoflags as u32,
362        s12,
363        plat2,
364        plon2,
365        pazi2,
366        &mut 0.0,
367        &mut 0.0,
368        &mut 0.0,
369        &mut 0.0,
370        &mut 0.0,
371    );
372}
373
374/// The scale factor A3 = mean value of (d/dsigma)I3
375fn a3coeff(g: &mut GeodGeodesic) {
376    let coeff: [f64; 18] = [
377        // A3, coeff of eps^5, polynomial in n of order 0
378        -3., 128., // A3, coeff of eps^4, polynomial in n of order 1
379        -2., -3., 64., // A3, coeff of eps^3, polynomial in n of order 2
380        -1., -3., -1., 16., // A3, coeff of eps^2, polynomial in n of order 2
381        3., -1., -2., 8., // A3, coeff of eps^1, polynomial in n of order 1
382        1., -1., 2., // A3, coeff of eps^0, polynomial in n of order 0
383        1., 1.,
384    ];
385    let mut o = 0;
386    // coeff of eps^j
387    for (k, j) in (0..N_A3).rev().enumerate() {
388        let m = if N_A3 - j - 1 < j { N_A3 - j - 1 } else { j }; /* order of polynomial in n */
389        g.a3x[k] = polyvalx(m, &coeff[o..], g.n) / coeff[o + m + 1];
390        o += m + 2;
391    }
392}
393
394/// The coefficients C3[l] in the Fourier expansion of B3
395fn c3coeff(g: &mut GeodGeodesic) {
396    let coeff: [f64; 45] = [
397        // C3[1], coeff of eps^5, polynomial in n of order 0
398        3., 128., // C3[1], coeff of eps^4, polynomial in n of order 1
399        2., 5., 128., // C3[1], coeff of eps^3, polynomial in n of order 2
400        -1., 3., 3., 64., // C3[1], coeff of eps^2, polynomial in n of order 2
401        -1., 0., 1., 8., // C3[1], coeff of eps^1, polynomial in n of order 1
402        -1., 1., 4., // C3[2], coeff of eps^5, polynomial in n of order 0
403        5., 256., // C3[2], coeff of eps^4, polynomial in n of order 1
404        1., 3., 128., // C3[2], coeff of eps^3, polynomial in n of order 2
405        -3., -2., 3., 64., // C3[2], coeff of eps^2, polynomial in n of order 2
406        1., -3., 2., 32., // C3[3], coeff of eps^5, polynomial in n of order 0
407        7., 512., // C3[3], coeff of eps^4, polynomial in n of order 1
408        -10., 9., 384., // C3[3], coeff of eps^3, polynomial in n of order 2
409        5., -9., 5., 192., // C3[4], coeff of eps^5, polynomial in n of order 0
410        7., 512., // C3[4], coeff of eps^4, polynomial in n of order 1
411        -14., 7., 512., // C3[5], coeff of eps^5, polynomial in n of order 0
412        21., 2560.,
413    ];
414    let mut o = 0;
415    let mut k = 0;
416    // l is index of C3[l]
417    for l in 1..N_C3 {
418        // coeff of eps^j
419        for j in (l..N_C3).rev() {
420            let m = if N_C3 - j - 1 < j { N_C3 - j - 1 } else { j }; /* order of polynomial in n */
421            g.c3x[k] = polyvalx(m, &coeff[o..], g.n) / coeff[o + m + 1];
422            k += 1;
423            o += m + 2;
424        }
425    }
426}
427
428/// The coefficients C4[l] in the Fourier expansion of I4
429fn c4coeff(g: &mut GeodGeodesic) {
430    let coeff: [f64; 77] = [
431        // C4[0], coeff of eps^5, polynomial in n of order 0
432        97., 15015., // C4[0], coeff of eps^4, polynomial in n of order 1
433        1088., 156., 45045., // C4[0], coeff of eps^3, polynomial in n of order 2
434        -224., -4784., 1573., 45045.,
435        // C4[0], coeff of eps^2, polynomial in n of order 3
436        -10656., 14144., -4576., -858., 45045.,
437        // C4[0], coeff of eps^1, polynomial in n of order 4
438        64., 624., -4576., 6864., -3003., 15015.,
439        // C4[0], coeff of eps^0, polynomial in n of order 5
440        100., 208., 572., 3432., -12012., 30030., 45045.,
441        // C4[1], coeff of eps^5, polynomial in n of order 0
442        1., 9009., // C4[1], coeff of eps^4, polynomial in n of order 1
443        -2944., 468., 135135., // C4[1], coeff of eps^3, polynomial in n of order 2
444        5792., 1040., -1287., 135135.,
445        // C4[1], coeff of eps^2, polynomial in n of order 3
446        5952., -11648., 9152., -2574., 135135.,
447        // C4[1], coeff of eps^1, polynomial in n of order 4
448        -64., -624., 4576., -6864., 3003., 135135.,
449        // C4[2], coeff of eps^5, polynomial in n of order 0
450        8., 10725., // C4[2], coeff of eps^4, polynomial in n of order 1
451        1856., -936., 225225., // C4[2], coeff of eps^3, polynomial in n of order 2
452        -8448., 4992., -1144., 225225.,
453        // C4[2], coeff of eps^2, polynomial in n of order 3
454        -1440., 4160., -4576., 1716., 225225.,
455        // C4[3], coeff of eps^5, polynomial in n of order 0
456        -136., 63063., // C4[3], coeff of eps^4, polynomial in n of order 1
457        1024., -208., 105105., // C4[3], coeff of eps^3, polynomial in n of order 2
458        3584., -3328., 1144., 315315.,
459        // C4[4], coeff of eps^5, polynomial in n of order 0
460        -128., 135135., // C4[4], coeff of eps^4, polynomial in n of order 1
461        -2560., 832., 405405., // C4[5], coeff of eps^5, polynomial in n of order 0
462        128., 99099.,
463    ];
464    let mut o = 0;
465    let mut k = 0;
466    // l is index of C4[l]
467    for l in 0..N_C4 {
468        // coeff of eps^j
469        for j in (l..N_C4).rev() {
470            let m = N_C4 - j - 1; // order of polynomial in n
471            g.c4x[k] = polyvalx(m, &coeff[o..], g.n) / coeff[o + m + 1];
472            k += 1;
473            o += m + 2;
474        }
475    }
476}
477
478/// Evaluation `sum(p[i] * x^i, i, 0, N)` via Horner's method.
479pub fn polyvalx(n: usize, p: &[f64], x: f64) -> f64 {
480    if n == 0 {
481        return 0.0;
482    }
483    let mut y = p[0];
484    for val in p.iter().take(n).skip(1) {
485        y = y * x + val;
486    }
487
488    y
489}
490
491/// Normalize an angle in degrees to the range [-180, 180]
492pub fn ang_normalize(x: f64) -> f64 {
493    let y = x % TD;
494    if fabs(y) == HD {
495        return copysign(HD, x);
496    }
497    y
498}
499
500/// Round an angle in degrees to the nearest integer
501pub fn ang_round(x: f64) -> f64 {
502    // False positive in cppcheck requires "1.0" instead of "1"
503    let z = 1.0 / 16.0;
504    let mut y = fabs(x);
505    let w = z - y;
506    // The compiler mustn't "simplify" z - (z - y) to y
507    y = if w > 0. { z - w } else { y };
508    copysign(y, x)
509}
510
511/// Compute the difference between two angles in degrees
512pub fn ang_diff(x: f64, y: f64, e: &mut f64) -> f64 {
513    // Use remainder instead of AngNormalize, since we treat boundary cases
514    // later taking account of the error
515    let mut t = 0.;
516    let mut d = sumx(remainder(-x, TD), remainder(y, TD), &mut t);
517    // This second sum can only change d if abs(d) < 128, so don't need to
518    // apply remainder yet again.
519    d = sumx(remainder(d, TD), t, &mut t);
520    // Fix the sign if d = -180, 0, 180.
521    if d == 0. || fabs(d) == HD {
522        // If t == 0, take sign from y - x
523        // else (t != 0, implies d = +/-180), d and t must have opposite signs
524        d = copysign(d, if t == 0. { y - x } else { -t });
525    }
526    if *e != 0. {
527        *e = t;
528    }
529    d
530}
531
532/// In order to minimize round-off errors, this function exactly reduces
533/// the argument to the range [-45, 45] before converting it to radians.
534pub fn sincosdx(x: f64, sinx: &mut f64, cosx: &mut f64) {
535    let mut r = remquo(x, QD);
536    r.0 *= DEGREE;
537    // Possibly could call the gnu extension sincos
538    let s = sin(r.0);
539    let c = cos(r.0);
540    match r.1 & 3 {
541        0 => {
542            *sinx = s;
543            *cosx = c;
544        }
545        1 => {
546            *sinx = c;
547            *cosx = -s;
548        }
549        2 => {
550            *sinx = -s;
551            *cosx = -c;
552        }
553        _ => {
554            *sinx = -c;
555            *cosx = s;
556        }
557    }
558    // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf
559    *cosx += 0.; // special values from F.10.1.12
560    // special values from F.10.1.13
561    if *sinx == 0. {
562        *sinx = copysign(*sinx, x);
563    }
564}
565
566/// Limit x to the range [-qd, qd]
567pub fn lat_fix(x: f64) -> f64 {
568    if fabs(x) > QD { f64::NAN } else { x }
569}
570
571/// Swap x and y
572pub fn swapx(x: &mut f64, y: &mut f64) {
573    core::mem::swap(&mut (*x), &mut (*y));
574}
575
576/// Normalize sin(x) and cos(x)
577pub fn norm2(sinx: &mut f64, cosx: &mut f64) {
578    let r = hypot(*sinx, *cosx);
579    *sinx /= r;
580    *cosx /= r;
581}
582
583/// Evaluate
584/// ```cpp
585/// y = sinp
586///     ? sum(c[i] * sin( 2*i    * x), i, 1, n)
587///     : sum(c[i] * cos((2*i+1) * x), i, 0, n-1)
588/// ```
589/// using Clenshaw summation.  N.B. `c[0]` is unused for sin series
590/// Approx operation count = (n + 5) mult and (2 * n + 2) add
591pub fn sin_cos_series(sinp: bool, sinx: f64, cosx: f64, c: &[f64], mut n: usize) -> f64 {
592    // Point to one beyond last element
593    let mut c_index = if sinp { 1 } else { 0 } + n - 1;
594    let ar = 2. * (cosx - sinx) * (cosx + sinx); /* 2 * cos(2 * x) */
595    c_index -= 1;
596    let mut y0 = if n & 1 != 0 { c[c_index] } else { 0. };
597    let mut y1 = 0.;
598    // Now n is even
599    n /= 2;
600    for _ in 0..n {
601        // Unroll loop x 2, so accumulators return to their original role */
602        c_index -= 1;
603        y1 = ar * y0 - y1 + c[c_index];
604        c_index -= 1;
605        y0 = ar * y1 - y0 + c[c_index];
606    }
607
608    if sinp
609    { 2. * sinx * cosx * y0 }      /* sin(2 * x) * y0 */
610    else { cosx * (y0 - y1) } /* cos(x) * (y0 - y1) */
611}
612
613/// The scale factor A1-1 = mean value of (d/dsigma)I1 - 1
614pub fn a1m1f(eps: f64) -> f64 {
615    // (1-eps)*A1-1, polynomial in eps2 of order 3
616    let coeff: [f64; 5] = [1., 4., 64., 0., 256.];
617    let m = N_A1 / 2;
618    let t = polyvalx(m, &coeff, sq(eps)) / coeff[m + 1];
619    (t + eps) / (1. - eps)
620}
621
622/// In order to minimize round-off errors, this function rearranges the
623/// arguments so that result of atan2 is in the range [-pi/4, pi/4] before
624/// converting it to degrees and mapping the result to the correct
625/// quadrant.
626pub fn atan2dx(mut y: f64, mut x: f64) -> f64 {
627    let mut q = 0;
628    if fabs(y) > fabs(x) {
629        swapx(&mut x, &mut y);
630        q = 2;
631    }
632    if x.is_sign_negative() {
633        x = -x;
634        q += 1;
635    }
636    // here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4]
637    let mut ang = atan2(y, x) / DEGREE;
638    match q {
639        1 => {
640            ang = copysign(HD, y) - ang;
641        }
642        2 => {
643            ang = QD - ang;
644        }
645        3 => {
646            ang += -QD;
647        }
648        _ => {}
649    }
650    ang
651}
652
653/// Compute the geodesic inverse between two points
654#[allow(clippy::too_many_arguments)]
655pub fn geod_geninverse(
656    geod_geodesic: &mut GeodGeodesic,
657    lat1: f64,
658    lon1: f64,
659    lat2: f64,
660    lon2: f64,
661    ps12: &mut f64,
662    pazi1: &mut f64,
663    pazi2: &mut f64,
664    pm12: &mut f64,
665    p_m12: &mut f64,
666    p_m21: &mut f64,
667    p_s12: &mut f64,
668) -> f64 {
669    let mut salp1 = 0.0;
670    let mut calp1 = 0.0;
671    let mut salp2 = 0.0;
672    let mut calp2 = 0.0;
673    let a12 = geod_geninverse_int(
674        geod_geodesic,
675        lat1,
676        lon1,
677        lat2,
678        lon2,
679        ps12,
680        &mut salp1,
681        &mut calp1,
682        &mut salp2,
683        &mut calp2,
684        pm12,
685        p_m12,
686        p_m21,
687        p_s12,
688    );
689    if *pazi1 != 0. {
690        *pazi1 = atan2dx(salp1, calp1);
691    }
692    if *pazi2 != 0. {
693        *pazi2 = atan2dx(salp2, calp2);
694    }
695    a12
696}
697
698/// Compute the geodesic inverse between two points
699#[allow(clippy::too_many_arguments)]
700pub fn geod_geninverse_int(
701    g: &mut GeodGeodesic,
702    mut lat1: f64,
703    lon1: f64,
704    mut lat2: f64,
705    lon2: f64,
706    ps12: &mut f64,
707    psalp1: &mut f64,
708    pcalp1: &mut f64,
709    psalp2: &mut f64,
710    pcalp2: &mut f64,
711    pm12: &mut f64,
712    p_m12: &mut f64,
713    p_m21: &mut f64,
714    p_s12: &mut f64,
715) -> f64 {
716    //   double s12 = 0, m12 = 0, M12 = 0, M21 = 0, S12 = 0;
717    let mut s12 = 0.0;
718    let mut m12 = 0.0;
719    let mut _m12: f64 = 0.0;
720    let mut _m21 = 0.0;
721    let mut _s12 = 0.0;
722    //   double lon12, lon12s;
723    let mut lon12s = 0.0;
724    //   int latsign, lonsign, swapp;
725    //   double sbet1, cbet1, sbet2, cbet2, s12x = 0, m12x = 0;
726    let mut sbet1 = 0.0;
727    let mut cbet1 = 0.0;
728    let mut sbet2 = 0.0;
729    let mut cbet2 = 0.0;
730    let mut s12x = 0.0;
731    let mut m12x = 0.0;
732    //   double dn1, dn2, lam12, slam12, clam12;
733    let mut slam12 = 0.0;
734    let mut clam12 = 0.0;
735    //   double a12 = 0, sig12, calp1 = 0, salp1 = 0, calp2 = 0, salp2 = 0;
736    let mut a12 = 0.0;
737    let mut sig12;
738    let mut calp1 = 0.0;
739    let mut salp1 = 0.0;
740    let mut calp2 = 0.0;
741    let mut salp2 = 0.0;
742    //   double Ca[nC];
743    let mut ca = [0.0; N_C];
744    //   boolx meridian;
745    // somg12 == 2 marks that it needs to be calculated
746    let mut omg12 = 0.;
747    let mut somg12 = 2.;
748    let mut comg12 = 0.;
749
750    let mut outmask: u32 =
751        (if *ps12 != 0. { GeodMask::GeodDistance as u32 } else { GeodMask::GeodNone as u32 })
752            | (if *pm12 != 0. {
753                GeodMask::GeodReducedlength as u32
754            } else {
755                GeodMask::GeodNone as u32
756            })
757            | (if *p_m12 != 0. || *p_m21 != 0. {
758                GeodMask::GeodGeodesicScale as u32
759            } else {
760                GeodMask::GeodNone as u32
761            })
762            | (if *p_s12 != 0. {
763                GeodFlags::GeodLongUnroll as u32
764            } else {
765                GeodMask::GeodNone as u32
766            });
767
768    outmask &= CapType::OutAll as u32;
769    // Compute longitude difference (ang_diff does this carefully).  Result is
770    // in [-180, 180] but -180 is only for west-going geodesics.  180 is for
771    // east-going and meridional geodesics.
772    let mut lon12 = ang_diff(lon1, lon2, &mut lon12s);
773    // Make longitude difference positive.
774    let mut lonsign = if lon12.is_sign_positive() { -1. } else { 1. };
775    lon12 *= lonsign;
776    lon12s *= lonsign;
777    let lam12 = lon12 * DEGREE;
778    // Calculate sincos of lon12 + error (this applies ang_round internally).
779    sincosde(lon12, lon12s, &mut slam12, &mut clam12);
780    lon12s = (HD - lon12) - lon12s; /* the supplementary longitude difference */
781
782    // If really close to the equator, treat as on equator.
783    lat1 = ang_round(lat_fix(lat1));
784    lat2 = ang_round(lat_fix(lat2));
785    // Swap points so that point with higher (abs) latitude is point 1
786    // If one latitude is a nan, then it becomes lat1.
787    let swapp = if fabs(lat1) < fabs(lat2) { -1. } else { 1. };
788    if swapp < 0. {
789        lonsign *= -1.;
790        swapx(&mut lat1, &mut lat2);
791    }
792    // Make lat1 <= -0
793    let latsign = if lat1.is_sign_positive() { 1. } else { -1. };
794    lat1 *= latsign;
795    lat2 *= latsign;
796    // Now we have
797    //
798    //     0 <= lon12 <= 180
799    //     -90 <= lat1 <= -0
800    //     lat1 <= lat2 <= -lat1
801    //
802    // longsign, swapp, latsign register the transformation to bring the
803    // coordinates to this canonical form.  In all cases, 1 means no change was
804    // made.  We make these transformations so that there are few cases to
805    // check, e.g., on verifying quadrants in atan2.  In addition, this
806    // enforces some symmetries in the results returned.
807
808    sincosdx(lat1, &mut sbet1, &mut cbet1);
809    sbet1 *= g.f1;
810    // Ensure cbet1 = +epsilon at poles
811    norm2(&mut sbet1, &mut cbet1);
812    cbet1 = fmax(TINY, cbet1);
813
814    sincosdx(lat2, &mut sbet2, &mut cbet2);
815    sbet2 *= g.f1;
816    // Ensure cbet2 = +epsilon at poles
817    norm2(&mut sbet2, &mut cbet2);
818    cbet2 = fmax(TINY, cbet2);
819
820    // If cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure of the
821    // |bet1| - |bet2|.  Alternatively (cbet1 >= -sbet1), abs(sbet2) + sbet1 is
822    // a better measure.  This logic is used in assigning calp2 in Lambda12.
823    // Sometimes these quantities vanish and in that case we force bet2 = +/-
824    // bet1 exactly.  An example where is is necessary is the inverse problem
825    // 48.522876735459 0 -48.52287673545898293 179.599720456223079643
826    // which failed with Visual Studio 10 (Release and Debug)
827
828    if cbet1 < -sbet1 {
829        if cbet2 == cbet1 {
830            sbet2 = copysign(sbet1, sbet2);
831        }
832    } else if fabs(sbet2) == -sbet1 {
833        cbet2 = cbet1;
834    }
835
836    let dn1 = sqrt(1. + g.ep2 * sq(sbet1));
837    let dn2 = sqrt(1. + g.ep2 * sq(sbet2));
838
839    let mut meridian = lat1 == -QD || slam12 == 0.;
840
841    if meridian {
842        // Endpoints are on a single full meridian, so the geodesic might lie on
843        // a meridian.
844
845        calp1 = clam12;
846        salp1 = slam12; /* Head to the target longitude */
847        calp2 = 1.;
848        salp2 = 0.; /* At the target we're heading north */
849        // tan(bet) = tan(sig) * cos(alp)
850        let ssig1 = sbet1;
851        let csig1 = calp1 * cbet1;
852        let ssig2 = sbet2;
853        let csig2 = calp2 * cbet2;
854
855        // sig12 = sig2 - sig1
856        sig12 = atan2(fmax(0.0, csig1 * ssig2 - ssig1 * csig2) + 0., csig1 * csig2 + ssig1 * ssig2);
857        let mut _tmp_m12 = 0.;
858        let mut _tmp_m21 = 0.;
859        lengths(
860            g,
861            g.n,
862            sig12,
863            ssig1,
864            csig1,
865            dn1,
866            ssig2,
867            csig2,
868            dn2,
869            cbet1,
870            cbet2,
871            &mut s12x,
872            &mut m12x,
873            &mut 0.,
874            if (outmask & GeodMask::GeodGeodesicScale as u32) != 0 {
875                &mut _m12
876            } else {
877                &mut _tmp_m12
878            },
879            if (outmask & GeodMask::GeodGeodesicScale as u32) != 0 {
880                &mut _m21
881            } else {
882                &mut _tmp_m21
883            },
884            &mut ca,
885        );
886        // Add the check for sig12 since zero length geodesics might yield m12 <
887        // 0.  Test case was
888        //
889        //    echo 20.001 0 20.001 0 | GeodSolve -i
890        //
891        // In fact, we will have sig12 > pi/2 for meridional geodesic which is
892        // not a shortest path.
893        if sig12 < 1. || m12x >= 0. {
894            // Need at least 2, to handle 90 0 90 180
895            if sig12 < 3. * TINY ||
896              /* Prevent negative s12 or m12 for short lines */
897              (sig12 < TOL0 && (s12x < 0. || m12x < 0.))
898            {
899                s12x = 0.;
900                m12x = s12x;
901                sig12 = m12x;
902            }
903            m12x *= g.b;
904            s12x *= g.b;
905            a12 = sig12 / DEGREE;
906        } else {
907            // m12 < 0, i.e., prolate and too close to anti-podal
908            meridian = false;
909        }
910    }
911
912    if !meridian &&
913          sbet1 == 0. &&           /* and sbet2 == 0 */
914          /* Mimic the way Lambda12 works with calp1 = 0 */
915          (g.f <= 0. || lon12s >= g.f * HD)
916    {
917        // Geodesic runs along equator
918        calp2 = 0.;
919        calp1 = calp2;
920        salp2 = 1.;
921        salp1 = salp2;
922        s12x = g.a * lam12;
923        omg12 = lam12 / g.f1;
924        sig12 = omg12;
925        m12x = g.b * sin(sig12);
926        if (outmask & GeodMask::GeodGeodesicScale as u32) != 0 {
927            _m21 = cos(sig12);
928            _m12 = _m21;
929        }
930        a12 = lon12 / g.f1;
931    } else if !meridian {
932        // Now point1 and point2 belong within a hemisphere bounded by a
933        // meridian and geodesic is neither meridional or equatorial.
934
935        // Figure a starting point for Newton's method
936        let mut dnm = 0.;
937        sig12 = inverse_start(
938            g, sbet1, cbet1, dn1, sbet2, cbet2, dn2, lam12, slam12, clam12, &mut salp1, &mut calp1,
939            &mut salp2, &mut calp2, &mut dnm, &mut ca,
940        );
941
942        if sig12 >= 0. {
943            // Short lines (inverse_start sets salp2, calp2, dnm)
944            s12x = sig12 * g.b * dnm;
945            m12x = sq(dnm) * g.b * sin(sig12 / dnm);
946            if (outmask & GeodMask::GeodGeodesicScale as u32) != 0 {
947                _m21 = cos(sig12 / dnm);
948                _m12 = _m21;
949            }
950            a12 = sig12 / DEGREE;
951            omg12 = lam12 / (g.f1 * dnm);
952        } else {
953            // Newton's method.  This is a straightforward solution of f(alp1) =
954            // lambda12(alp1) - lam12 = 0 with one wrinkle.  f(alp) has exactly one
955            // root in the interval (0, pi) and its derivative is positive at the
956            // root.  Thus f(alp) is positive for alp > alp1 and negative for alp <
957            // alp1.  During the course of the iteration, a range (alp1a, alp1b) is
958            // maintained which brackets the root and with each evaluation of
959            // f(alp) the range is shrunk, if possible.  Newton's method is
960            // restarted whenever the derivative of f is negative (because the new
961            // value of alp1 is then further from the solution) or if the new
962            // estimate of alp1 lies outside (0,pi); in this case, the new starting
963            // guess is taken to be (alp1a + alp1b) / 2.
964            let mut ssig1 = 0.;
965            let mut csig1 = 0.;
966            let mut ssig2 = 0.;
967            let mut csig2 = 0.;
968            let mut eps = 0.;
969            let mut domg12 = 0.;
970            let mut numit: u32 = 0;
971            // Bracketing range
972            let mut salp1a = TINY;
973            let mut calp1a = 1.;
974            let mut salp1b = TINY;
975            let mut calp1b = -1.;
976            let mut tripn = false;
977            let mut tripb = false;
978            //   for (;; ++numit) {
979            loop {
980                numit += 1;
981                // the WGS84 test set: mean = 1.47, sd = 1.25, max = 16
982                // WGS84 and random input: mean = 2.85, sd = 0.60
983                let mut dv = 0.;
984                let v = lambda12(
985                    g,
986                    sbet1,
987                    cbet1,
988                    dn1,
989                    sbet2,
990                    cbet2,
991                    dn2,
992                    salp1,
993                    calp1,
994                    slam12,
995                    clam12,
996                    &mut salp2,
997                    &mut calp2,
998                    &mut sig12,
999                    &mut ssig1,
1000                    &mut csig1,
1001                    &mut ssig2,
1002                    &mut csig2,
1003                    &mut eps,
1004                    &mut domg12,
1005                    numit < MAXIT1,
1006                    &mut dv,
1007                    &mut ca,
1008                );
1009                if tripb ||
1010                /* Reversed test to allow escape with NaNs */
1011                (fabs(v) < (if tripn { 8. } else { 1. }) * TOL0) ||
1012                /* Enough bisections to get accurate result */
1013                numit == MAXIT2
1014                {
1015                    break;
1016                }
1017                // Update bracketing values
1018                if v > 0. && (numit > MAXIT1 || calp1 / salp1 > calp1b / salp1b) {
1019                    salp1b = salp1;
1020                    calp1b = calp1;
1021                } else if v < 0. && (numit > MAXIT1 || calp1 / salp1 < calp1a / salp1a) {
1022                    salp1a = salp1;
1023                    calp1a = calp1;
1024                }
1025                if numit < MAXIT1 && dv > 0. {
1026                    let dalp1 = -v / dv;
1027                    if fabs(dalp1) < PI {
1028                        let sdalp1 = sin(dalp1);
1029                        let cdalp1 = cos(dalp1);
1030                        let nsalp1 = salp1 * cdalp1 + calp1 * sdalp1;
1031                        if nsalp1 > 0. {
1032                            calp1 = calp1 * cdalp1 - salp1 * sdalp1;
1033                            salp1 = nsalp1;
1034                            norm2(&mut salp1, &mut calp1);
1035                            // In some regimes we don't get quadratic convergence because
1036                            // slope -> 0.  So use convergence conditions based on epsilon
1037                            // instead of sqrt(epsilon).
1038                            tripn = fabs(v) <= 16. * TOL0;
1039                            continue;
1040                        }
1041                    }
1042                }
1043                // Either dv was not positive or updated value was outside legal
1044                // range.  Use the midpoint of the bracket as the next estimate.
1045                // This mechanism is not needed for the WGS84 ellipsoid, but it does
1046                // catch problems with more eccentric ellipsoids.  Its efficacy is
1047                // such for the WGS84 test set with the starting guess set to alp1 =
1048                // 90deg:
1049                // the WGS84 test set: mean = 5.21, sd = 3.93, max = 24
1050                // WGS84 and random input: mean = 4.74, sd = 0.99
1051                salp1 = (salp1a + salp1b) / 2.;
1052                calp1 = (calp1a + calp1b) / 2.;
1053                norm2(&mut salp1, &mut calp1);
1054                tripn = false;
1055                tripb = fabs(salp1a - salp1) + (calp1a - calp1) < TOLB
1056                    || fabs(salp1 - salp1b) + (calp1 - calp1b) < TOLB;
1057            }
1058            let mut _tmp_m12 = 0.;
1059            let mut _tmp_m21 = 0.;
1060            lengths(
1061                g,
1062                eps,
1063                sig12,
1064                ssig1,
1065                csig1,
1066                dn1,
1067                ssig2,
1068                csig2,
1069                dn2,
1070                cbet1,
1071                cbet2,
1072                &mut s12x,
1073                &mut m12x,
1074                &mut 0.,
1075                if (outmask & GeodMask::GeodGeodesicScale as u32) != 0 {
1076                    &mut _m12
1077                } else {
1078                    &mut _tmp_m12
1079                },
1080                if (outmask & GeodMask::GeodGeodesicScale as u32) != 0 {
1081                    &mut _m21
1082                } else {
1083                    &mut _tmp_m21
1084                },
1085                &mut ca,
1086            );
1087            m12x *= g.b;
1088            s12x *= g.b;
1089            a12 = sig12 / DEGREE;
1090            if (outmask & GeodFlags::GeodLongUnroll as u32) != 0 {
1091                // omg12 = lam12 - domg12
1092                let sdomg12 = sin(domg12);
1093                let cdomg12 = cos(domg12);
1094                somg12 = slam12 * cdomg12 - clam12 * sdomg12;
1095                comg12 = clam12 * cdomg12 + slam12 * sdomg12;
1096            }
1097        }
1098    }
1099
1100    if (outmask & GeodMask::GeodDistance as u32) != 0 {
1101        s12 = 0. + s12x; /* Convert -0 to 0 */
1102    }
1103
1104    if (outmask & GeodMask::GeodReducedlength as u32) != 0 {
1105        m12 = 0. + m12x; /* Convert -0 to 0 */
1106    }
1107
1108    if (outmask & GeodFlags::GeodLongUnroll as u32) != 0 {
1109        let
1110          /* From Lambda12: sin(alp1) * cos(bet1) = sin(alp0) */
1111          salp0 = salp1 * cbet1;
1112        let calp0 = hypot(calp1, salp1 * sbet1); /* calp0 > 0 */
1113        let alp12;
1114        if calp0 != 0. && salp0 != 0. {
1115            // From Lambda12: tan(bet) = tan(sig) * cos(alp)
1116            let mut ssig1 = sbet1;
1117            let mut csig1 = calp1 * cbet1;
1118            let mut ssig2 = sbet2;
1119            let mut csig2 = calp2 * cbet2;
1120            let k2 = sq(calp0) * g.ep2;
1121            let eps = k2 / (2. * (1. + sqrt(1. + k2)) + k2);
1122            // Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0).
1123            let a4 = sq(g.a) * calp0 * salp0 * g.e2;
1124
1125            norm2(&mut ssig1, &mut csig1);
1126            norm2(&mut ssig2, &mut csig2);
1127            c4f(g, eps, &mut ca);
1128            let b41 = sin_cos_series(false, ssig1, csig1, &ca, N_C4);
1129            let b42 = sin_cos_series(false, ssig2, csig2, &ca, N_C4);
1130            _s12 = a4 * (b42 - b41);
1131        } else {
1132            // Avoid problems with indeterminate sig1, sig2 on equator
1133            _s12 = 0.;
1134        }
1135
1136        if !meridian && somg12 == 2. {
1137            somg12 = sin(omg12);
1138            comg12 = cos(omg12);
1139        }
1140
1141        if !meridian &&
1142            /* omg12 < 3/4 * pi */
1143            comg12 > FRAC_1_SQRT_2 &&     /* Long difference not too big */
1144            sbet2 - sbet1 < 1.75
1145        {
1146            /* Lat difference not too big */
1147            /* Use tan(Gamma/2) = tan(omg12/2)
1148             * * (tan(bet1/2)+tan(bet2/2))/(1+tan(bet1/2)*tan(bet2/2))
1149             * with tan(x/2) = sin(x)/(1+cos(x)) */
1150            let domg12 = 1. + comg12;
1151            let dbet1 = 1. + cbet1;
1152            let dbet2 = 1. + cbet2;
1153            alp12 = 2.
1154                * atan2(
1155                    somg12 * (sbet1 * dbet2 + sbet2 * dbet1),
1156                    domg12 * (sbet1 * sbet2 + dbet1 * dbet2),
1157                );
1158        } else {
1159            /* alp12 = alp2 - alp1, used in atan2 so no need to normalize */
1160            let mut salp12 = salp2 * calp1 - calp2 * salp1;
1161            let mut calp12 = calp2 * calp1 + salp2 * salp1;
1162            /* The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz
1163             * salp12 = -0 and alp12 = -180.  However this depends on the sign
1164             * being attached to 0 correctly.  The following ensures the correct
1165             * behavior. */
1166            if salp12 == 0. && calp12 < 0. {
1167                salp12 = TINY * calp1;
1168                calp12 = -1.;
1169            }
1170            alp12 = atan2(salp12, calp12);
1171        }
1172        _s12 += g.c2 * alp12;
1173        _s12 *= swapp * lonsign * latsign;
1174        // Convert -0 to 0
1175        _s12 += 0.;
1176    }
1177
1178    // Convert calp, salp to azimuth accounting for lonsign, swapp, latsign.
1179    if swapp < 0. {
1180        swapx(&mut salp1, &mut salp2);
1181        swapx(&mut calp1, &mut calp2);
1182        if (outmask & GeodMask::GeodGeodesicScale as u32) != 0 {
1183            swapx(&mut _m12, &mut _m21);
1184        }
1185    }
1186
1187    salp1 *= swapp * lonsign;
1188    calp1 *= swapp * latsign;
1189    salp2 *= swapp * lonsign;
1190    calp2 *= swapp * latsign;
1191
1192    if *psalp1 != 0. {
1193        *psalp1 = salp1;
1194    }
1195    if *pcalp1 != 0. {
1196        *pcalp1 = calp1;
1197    }
1198    if *psalp2 != 0. {
1199        *psalp2 = salp2;
1200    }
1201    if *pcalp2 != 0. {
1202        *pcalp2 = calp2;
1203    }
1204
1205    if (outmask & GeodMask::GeodDistance as u32) != 0 {
1206        *ps12 = s12;
1207    }
1208    if (outmask & GeodMask::GeodReducedlength as u32) != 0 {
1209        *pm12 = m12;
1210    }
1211    if (outmask & GeodMask::GeodGeodesicScale as u32) != 0 {
1212        if *p_m12 != 0. {
1213            *p_m12 = _m12;
1214        }
1215        if *p_m21 != 0. {
1216            *p_m21 = _m21;
1217        }
1218    }
1219    if (outmask & GeodFlags::GeodLongUnroll as u32) != 0 {
1220        *p_s12 = _s12;
1221    }
1222
1223    // Returned value in [0, 180]
1224    a12
1225}
1226
1227/// Compute the geodesic position between two points
1228#[allow(clippy::too_many_arguments)]
1229pub fn geod_genposition(
1230    l: &GeodGeodesicline,
1231    flags: u32,
1232    s12_a12: f64,
1233    plat2: &mut f64,
1234    plon2: &mut f64,
1235    pazi2: &mut f64,
1236    ps12: &mut f64,
1237    pm12: &mut f64,
1238    p_m12: &mut f64,
1239    p_m21: &mut f64,
1240    p_s12: &mut f64,
1241) -> f64 {
1242    let mut lat2 = 0.0;
1243    let mut lon2 = 0.0;
1244    let mut azi2 = 0.0;
1245    let mut s12 = 0.0;
1246    let mut m12 = 0.0;
1247    let mut _m12 = 0.0;
1248    let mut _m21 = 0.0;
1249    let mut _s12 = 0.0;
1250    // Avoid warning about uninitialized B12.
1251    let mut sig12;
1252    let mut ssig12 = 0.;
1253    let mut csig12 = 0.;
1254    let mut b12 = 0.;
1255    let mut ab1 = 0.;
1256    let omg12;
1257    let lam12;
1258    let lon12;
1259    let mut ssig2;
1260    let mut csig2;
1261    let mut cbet2;
1262    let somg2;
1263    let comg2;
1264
1265    let mut outmask: u32 =
1266        (if *plat2 != 0. { GeodMask::GeodLatitude as u32 } else { GeodMask::GeodNone as u32 })
1267            | (if *plon2 != 0. {
1268                GeodMask::GeodLongitude as u32
1269            } else {
1270                GeodMask::GeodNone as u32
1271            })
1272            | (if *pazi2 != 0. { GeodMask::GeodAzimuth as u32 } else { GeodMask::GeodNone as u32 })
1273            | (if *ps12 != 0. { GeodMask::GeodDistance as u32 } else { GeodMask::GeodNone as u32 })
1274            | (if *pm12 != 0. {
1275                GeodMask::GeodReducedlength as u32
1276            } else {
1277                GeodMask::GeodNone as u32
1278            })
1279            | (if *p_m12 != 0. || *p_m21 != 0. {
1280                GeodMask::GeodGeodesicScale as u32
1281            } else {
1282                GeodMask::GeodNone as u32
1283            })
1284            | (if *p_s12 != 0. { GeodMask::GeodArea as u32 } else { GeodMask::GeodNone as u32 });
1285
1286    outmask &= l.caps & CapType::OutAll as u32;
1287    if !(((flags & GeodFlags::GeodArcmode as u32) != 0)
1288        || (l.caps & (GeodMask::GeodDistanceIn as u32 & CapType::OutAll as u32)) != 0)
1289    {
1290        // Impossible distance calculation requested
1291        return f64::NAN;
1292    }
1293
1294    if (flags & GeodFlags::GeodArcmode as u32) != 0 {
1295        // Interpret s12_a12 as spherical arc length
1296        sig12 = s12_a12 * DEGREE;
1297        sincosdx(s12_a12, &mut ssig12, &mut csig12);
1298    } else {
1299        // Interpret s12_a12 as distance
1300        let tau12 = s12_a12 / (l.b * (1. + l.a1m1));
1301        let s = sin(tau12);
1302        let c = cos(tau12);
1303        // tau2 = tau1 + tau12
1304        b12 = -sin_cos_series(
1305            true,
1306            l.stau1 * c + l.ctau1 * s,
1307            l.ctau1 * c - l.stau1 * s,
1308            &l.c1pa,
1309            N_C1_P,
1310        );
1311        sig12 = tau12 - (b12 - l.b11);
1312        ssig12 = sin(sig12);
1313        csig12 = cos(sig12);
1314        if fabs(l.f) > 0.01 {
1315            // Reverted distance series is inaccurate for |f| > 1/100, so correct
1316            // sig12 with 1 Newton iteration.  The following table shows the
1317            // approximate maximum error for a = WGS_a() and various f relative to
1318            // GeodesicExact.
1319            //     erri = the error in the inverse solution (nm)
1320            //     errd = the error in the direct solution (series only) (nm)
1321            //     errda = the error in the direct solution (series + 1 Newton) (nm)
1322            //
1323            //       f     erri  errd errda
1324            //     -1/5    12e6 1.2e9  69e6
1325            //     -1/10  123e3  12e6 765e3
1326            //     -1/20   1110 108e3  7155
1327            //     -1/50  18.63 200.9 27.12
1328            //     -1/100 18.63 23.78 23.37
1329            //     -1/150 18.63 21.05 20.26
1330            //      1/150 22.35 24.73 25.83
1331            //      1/100 22.35 25.03 25.31
1332            //      1/50  29.80 231.9 30.44
1333            //      1/20   5376 146e3  10e3
1334            //      1/10  829e3  22e6 1.5e6
1335            //      1/5   157e6 3.8e9 280e6
1336
1337            ssig2 = l.ssig1 * csig12 + l.csig1 * ssig12;
1338            csig2 = l.csig1 * csig12 - l.ssig1 * ssig12;
1339            b12 = sin_cos_series(true, ssig2, csig2, &l.c1a, N_C1);
1340            let serr = (1. + l.a1m1) * (sig12 + (b12 - l.b11)) - s12_a12 / l.b;
1341            sig12 -= serr / sqrt(1. + l.k2 * sq(ssig2));
1342            ssig12 = sin(sig12);
1343            csig12 = cos(sig12);
1344            // Update B12 below
1345        }
1346    }
1347
1348    // sig2 = sig1 + sig12
1349    ssig2 = l.ssig1 * csig12 + l.csig1 * ssig12;
1350    csig2 = l.csig1 * csig12 - l.ssig1 * ssig12;
1351    let dn2 = sqrt(1. + l.k2 * sq(ssig2));
1352    if (outmask
1353        & (GeodMask::GeodDistance as u32
1354            | GeodMask::GeodReducedlength as u32
1355            | GeodMask::GeodGeodesicScale as u32))
1356        != 0
1357    {
1358        if (flags & GeodFlags::GeodArcmode as u32 != 0) || fabs(l.f) > 0.01 {
1359            b12 = sin_cos_series(true, ssig2, csig2, &l.c1a, N_C1);
1360        }
1361        ab1 = (1. + l.a1m1) * (b12 - l.b11);
1362    }
1363    // sin(bet2) = cos(alp0) * sin(sig2)
1364    let sbet2 = l.calp0 * ssig2;
1365    // Alt: cbet2 = hypot(csig2, salp0 * ssig2);
1366    cbet2 = hypot(l.salp0, l.calp0 * csig2);
1367    if cbet2 == 0. {
1368        // I.e., salp0 = 0, csig2 = 0.  Break the degeneracy in this case
1369        csig2 = TINY;
1370        cbet2 = csig2;
1371    }
1372    // tan(alp0) = cos(sig2)*tan(alp2)
1373    let salp2 = l.salp0;
1374    let calp2 = l.calp0 * csig2; /* No need to normalize */
1375
1376    if (outmask & GeodMask::GeodDistance as u32) != 0 {
1377        s12 = if (flags & GeodFlags::GeodArcmode as u32) != 0 {
1378            l.b * ((1. + l.a1m1) * sig12 + ab1)
1379        } else {
1380            s12_a12
1381        };
1382    }
1383
1384    if (outmask & GeodMask::GeodLongitude as u32) != 0 {
1385        let e = copysign(1., l.salp0); /* east or west going? */
1386        // tan(omg2) = sin(alp0) * tan(sig2)
1387        somg2 = l.salp0 * ssig2;
1388        comg2 = csig2; /* No need to normalize */
1389        // omg12 = omg2 - omg1
1390        omg12 = if (flags & GeodFlags::GeodLongUnroll as u32) != 0 {
1391            e * (sig12 - (atan2(ssig2, csig2) - atan2(l.ssig1, l.csig1))
1392                + (atan2(e * somg2, comg2) - atan2(e * l.somg1, l.comg1)))
1393        } else {
1394            atan2(somg2 * l.comg1 - comg2 * l.somg1, comg2 * l.comg1 + somg2 * l.somg1)
1395        };
1396        lam12 = omg12
1397            + l.a3c * (sig12 + (sin_cos_series(true, ssig2, csig2, &l.c3a, N_C3 - 1) - l.b31));
1398        lon12 = lam12 / DEGREE;
1399        lon2 = if (flags & GeodFlags::GeodLongUnroll as u32) != 0 {
1400            l.lon1 + lon12
1401        } else {
1402            ang_normalize(ang_normalize(l.lon1) + ang_normalize(lon12))
1403        };
1404    }
1405
1406    if (outmask & GeodMask::GeodLatitude as u32) != 0 {
1407        lat2 = atan2dx(sbet2, l.f1 * cbet2);
1408    }
1409
1410    if (outmask & GeodMask::GeodAzimuth as u32) != 0 {
1411        azi2 = atan2dx(salp2, calp2);
1412    }
1413
1414    if (outmask & (GeodMask::GeodReducedlength as u32 | GeodMask::GeodGeodesicScale as u32)) != 0 {
1415        let b22 = sin_cos_series(true, ssig2, csig2, &l.c2a, N_C2);
1416        let ab2 = (1. + l.a2m1) * (b22 - l.b21);
1417        let j12 = (l.a1m1 - l.a2m1) * sig12 + (ab1 - ab2);
1418        if (outmask & GeodMask::GeodReducedlength as u32) != 0 {
1419            // Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure
1420            // accurate cancellation in the case of coincident points.
1421            m12 = l.b
1422                * ((dn2 * (l.csig1 * ssig2) - l.dn1 * (l.ssig1 * csig2)) - l.csig1 * csig2 * j12);
1423        }
1424        if (outmask & GeodMask::GeodGeodesicScale as u32) != 0 {
1425            let t = l.k2 * (ssig2 - l.ssig1) * (ssig2 + l.ssig1) / (l.dn1 + dn2);
1426            _m12 = csig12 + (t * ssig2 - csig2 * j12) * l.ssig1 / l.dn1;
1427            _m21 = csig12 - (t * l.ssig1 - l.csig1 * j12) * ssig2 / dn2;
1428        }
1429    }
1430
1431    if (outmask & GeodFlags::GeodLongUnroll as u32) != 0 {
1432        let b42 = sin_cos_series(false, ssig2, csig2, &l.c4a, N_C4);
1433        let salp12;
1434        let calp12;
1435        if l.calp0 == 0. || l.salp0 == 0. {
1436            // alp12 = alp2 - alp1, used in atan2 so no need to normalize
1437            salp12 = salp2 * l.calp1 - calp2 * l.salp1;
1438            calp12 = calp2 * l.calp1 + salp2 * l.salp1;
1439        } else {
1440            /* tan(alp) = tan(alp0) * sec(sig)
1441             * tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1)
1442             * = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2)
1443             * If csig12 > 0, write
1444             *   csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1)
1445             * else
1446             *   csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1
1447             * No need to normalize */
1448            salp12 = l.calp0
1449                * l.salp0
1450                * (if csig12 <= 0. {
1451                    l.csig1 * (1. - csig12) + ssig12 * l.ssig1
1452                } else {
1453                    ssig12 * (l.csig1 * ssig12 / (1. + csig12) + l.ssig1)
1454                });
1455            calp12 = sq(l.salp0) + sq(l.calp0) * l.csig1 * csig2;
1456        }
1457        _s12 = l.c2 * atan2(salp12, calp12) + l.a4 * (b42 - l.b41);
1458    }
1459
1460    // In the pattern
1461    //
1462    //   if ((outmask & GEOD_XX) && pYY)
1463    //     *pYY = YY;
1464    //
1465    // the second check "&& pYY" is redundant.  It's there to make the CLang
1466    // static analyzer happy.
1467    if ((outmask & GeodMask::GeodLatitude as u32) != 0) && *plat2 != 0. {
1468        *plat2 = lat2;
1469    }
1470    if ((outmask & GeodMask::GeodLongitude as u32) != 0) && *plon2 != 0. {
1471        *plon2 = lon2;
1472    }
1473    if ((outmask & GeodMask::GeodAzimuth as u32) != 0) && *pazi2 != 0. {
1474        *pazi2 = azi2;
1475    }
1476    if (outmask & GeodMask::GeodDistance as u32 != 0) && *ps12 != 0. {
1477        *ps12 = s12;
1478    }
1479    if ((outmask & GeodMask::GeodReducedlength as u32) != 0) && *pm12 != 0. {
1480        *pm12 = m12;
1481    }
1482    if (outmask & GeodMask::GeodGeodesicScale as u32) != 0 {
1483        if *p_m12 != 0. {
1484            *p_m12 = _m12;
1485        }
1486        if *p_m21 != 0. {
1487            *p_m21 = _m21;
1488        }
1489    }
1490    if ((outmask & GeodFlags::GeodLongUnroll as u32) != 0) && *p_s12 != 0. {
1491        *p_s12 = _s12;
1492    }
1493
1494    if (flags & GeodFlags::GeodArcmode as u32) != 0 { s12_a12 } else { sig12 / DEGREE }
1495}
1496
1497/// Initialize a geodesic line
1498#[allow(clippy::too_many_arguments)]
1499pub fn geod_lineinit_int(
1500    l: &mut GeodGeodesicline,
1501    g: &GeodGeodesic,
1502    lat1: f64,
1503    lon1: f64,
1504    azi1: f64,
1505    salp1: f64,
1506    calp1: f64,
1507    caps: u32,
1508) {
1509    let mut cbet1 = 0.0;
1510    let mut sbet1 = 0.0;
1511    l.a = g.a;
1512    l.f = g.f;
1513    l.b = g.b;
1514    l.c2 = g.c2;
1515    l.f1 = g.f1;
1516    // If caps is 0 assume the standard direct calculation
1517    l.caps = (if caps != 0 { caps } else { GeodMask::GeodDistanceIn as u32 | GeodMask::GeodLongitude as u32 }) |
1518        // always allow latitude and azimuth and unrolling of longitude
1519        (GeodMask::GeodLatitude as u32 | GeodMask::GeodAzimuth as u32 | GeodFlags::GeodLongUnroll as u32);
1520
1521    l.lat1 = lat_fix(lat1);
1522    l.lon1 = lon1;
1523    l.azi1 = azi1;
1524    l.salp1 = salp1;
1525    l.calp1 = calp1;
1526
1527    sincosdx(ang_round(l.lat1), &mut sbet1, &mut cbet1);
1528    sbet1 *= l.f1;
1529    // Ensure cbet1 = +epsilon at poles
1530    norm2(&mut sbet1, &mut cbet1);
1531    cbet1 = fmax(TINY, cbet1);
1532    l.dn1 = sqrt(1. + g.ep2 * sq(sbet1));
1533
1534    // Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0),
1535    l.salp0 = l.salp1 * cbet1; /* alp0 in [0, pi/2 - |bet1|] */
1536    // Alt: calp0 = hypot(sbet1, calp1 * cbet1).  The following
1537    // is slightly better (consider the case salp1 = 0).
1538    l.calp0 = hypot(l.calp1, l.salp1 * sbet1);
1539    // Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1).
1540    // sig = 0 is nearest northward crossing of equator.
1541    // With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line).
1542    // With bet1 =  pi/2, alp1 = -pi, sig1 =  pi/2
1543    // With bet1 = -pi/2, alp1 =  0 , sig1 = -pi/2
1544    // Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1).
1545    // With alp0 in (0, pi/2], quadrants for sig and omg coincide.
1546    // No atan2(0,0) ambiguity at poles since cbet1 = +epsilon.
1547    // With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi.
1548    l.ssig1 = sbet1;
1549    l.somg1 = l.salp0 * sbet1;
1550    l.comg1 = sbet1;
1551    l.csig1 = if l.comg1 != 0. || l.calp1 != 0. { cbet1 * l.calp1 } else { 1. };
1552    norm2(&mut l.ssig1, &mut l.csig1); /* sig1 in (-pi, pi] */
1553    // norm2(somg1, comg1); -- don't need to normalize!
1554
1555    l.k2 = sq(l.calp0) * g.ep2;
1556    let eps = l.k2 / (2. * (1. + sqrt(1. + l.k2)) + l.k2);
1557
1558    if (l.caps & CapType::CapC1 as u32) != 0 {
1559        l.a1m1 = a1m1f(eps);
1560        c1f(eps, &mut l.c1a);
1561        l.b11 = sin_cos_series(true, l.ssig1, l.csig1, &l.c1a, N_C1);
1562        let s = sin(l.b11);
1563        let c = cos(l.b11);
1564        // tau1 = sig1 + b11
1565        l.stau1 = l.ssig1 * c + l.csig1 * s;
1566        l.ctau1 = l.csig1 * c - l.ssig1 * s;
1567        // Not necessary because c1pa reverts c1a
1568        //    b11 = -sin_cos_series(true, stau1, ctau1, c1pa, N_C1_P);
1569    }
1570
1571    if (l.caps & CapType::CapC1p as u32) != 0 {
1572        c1pf(eps, &mut l.c1pa);
1573    }
1574
1575    if (l.caps & CapType::CapC2 as u32) != 0 {
1576        l.a2m1 = a2m1f(eps);
1577        c2f(eps, &mut l.c2a);
1578        l.b21 = sin_cos_series(true, l.ssig1, l.csig1, &l.c2a, N_C2);
1579    }
1580
1581    if (l.caps & CapType::CapC3 as u32) != 0 {
1582        c3f(g, eps, &mut l.c3a);
1583        l.a3c = -l.f * l.salp0 * a3f(g, eps);
1584        l.b31 = sin_cos_series(true, l.ssig1, l.csig1, &l.c3a, N_C3 - 1);
1585    }
1586
1587    if (l.caps & CapType::CapC4 as u32) != 0 {
1588        c4f(g, eps, &mut l.c4a);
1589        // Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0)
1590        l.a4 = sq(l.a) * l.calp0 * l.salp0 * g.e2;
1591        l.b41 = sin_cos_series(false, l.ssig1, l.csig1, &l.c4a, N_C4);
1592    }
1593
1594    l.s13 = f64::NAN;
1595    l.a13 = f64::NAN;
1596}
1597
1598/// Evaluate A3
1599fn a3f(g: &GeodGeodesic, eps: f64) -> f64 {
1600    // Evaluate A3
1601    polyvalx(N_A3 - 1, &g.a3x, eps)
1602}
1603
1604/// Evaluate C3
1605fn c3f(g: &GeodGeodesic, eps: f64, c: &mut [f64]) {
1606    // Evaluate C3 coeffs Elements c[1] through c[N_C3 - 1] are set */
1607    let mut mult = 1.;
1608    let mut o = 0;
1609    #[allow(clippy::needless_range_loop)]
1610    for l in 1..N_C3 {
1611        // l is index of C3[l]
1612        let m = N_C3 - l - 1; // order of polynomial in eps
1613        mult *= eps;
1614        c[l] = mult * polyvalx(m, &g.c3x[o..], eps);
1615        o += m + 1;
1616    }
1617}
1618
1619/// Evaluate C4
1620fn c4f(g: &GeodGeodesic, eps: f64, c: &mut [f64]) {
1621    // Evaluate C4 coeffs Elements c[0] through c[N_C4 - 1] are set
1622    let mut mult = 1.;
1623    let mut o = 0;
1624    #[allow(clippy::needless_range_loop)]
1625    for l in 0..N_C4 {
1626        // l is index of C4[l]
1627        let m = N_C4 - l - 1; // order of polynomial in eps
1628        c[l] = mult * polyvalx(m, &g.c4x[o..], eps);
1629        o += m + 1;
1630        mult *= eps;
1631    }
1632}
1633
1634/// The coefficients C1[l] in the Fourier expansion of B1 */
1635fn c1f(eps: f64, c: &mut [f64]) {
1636    let coeff: [f64; 18] = [
1637        // C1[1]/eps^1, polynomial in eps2 of order 2
1638        -1., 6., -16., 32., // C1[2]/eps^2, polynomial in eps2 of order 2
1639        -9., 64., -128., 2048., // C1[3]/eps^3, polynomial in eps2 of order 1
1640        9., -16., 768., // C1[4]/eps^4, polynomial in eps2 of order 1
1641        3., -5., 512., // C1[5]/eps^5, polynomial in eps2 of order 0
1642        -7., 1280., // C1[6]/eps^6, polynomial in eps2 of order 0
1643        -7., 2048.,
1644    ];
1645    let eps2 = sq(eps);
1646    let mut d = eps;
1647    let mut o = 0;
1648    #[allow(clippy::needless_range_loop)]
1649    for l in 1..=N_C1 {
1650        // l is index of C1p[l]
1651        let m = (N_C1 - l) / 2; // order of polynomial in eps^2
1652        c[l] = d * polyvalx(m, &coeff[o..], eps2) / coeff[o + m + 1];
1653        o += m + 2;
1654        d *= eps;
1655    }
1656}
1657
1658/// The coefficients C1p[l] in the Fourier expansion of B1p
1659fn c1pf(eps: f64, c: &mut [f64]) {
1660    let coeff: [f64; 18] = [
1661        // C1p[1]/eps^1, polynomial in eps2 of order 2
1662        205., -432., 768., 1536., // C1p[2]/eps^2, polynomial in eps2 of order 2
1663        4005., -4736., 3840., 12288., // C1p[3]/eps^3, polynomial in eps2 of order 1
1664        -225., 116., 384., // C1p[4]/eps^4, polynomial in eps2 of order 1
1665        -7173., 2695., 7680., // C1p[5]/eps^5, polynomial in eps2 of order 0
1666        3467., 7680., // C1p[6]/eps^6, polynomial in eps2 of order 0
1667        38081., 61440.,
1668    ];
1669    let eps2 = sq(eps);
1670    let mut d = eps;
1671    let mut o = 0;
1672    #[allow(clippy::needless_range_loop)]
1673    for l in 1..=N_C1_P {
1674        // l is index of C1p[l]
1675        let m = (N_C1_P - l) / 2; // order of polynomial in eps^2
1676        c[l] = d * polyvalx(m, &coeff[o..], eps2) / coeff[o + m + 1];
1677        o += m + 2;
1678        d *= eps;
1679    }
1680}
1681
1682/// The scale factor A2-1 = mean value of (d/dsigma)I2 - 1
1683fn a2m1f(eps: f64) -> f64 {
1684    // (eps+1)*A2-1, polynomial in eps2 of order 3
1685    let coeff: [f64; 5] = [-11., -28., -192., 0., 256.];
1686    let m = N_A2 / 2;
1687    let t = polyvalx(m, &coeff, sq(eps)) / coeff[m + 1];
1688    (t - eps) / (1. + eps)
1689}
1690
1691/// The coefficients C2[l] in the Fourier expansion of B2
1692fn c2f(eps: f64, c: &mut [f64]) {
1693    let coeff: [f64; 18] = [
1694        // C2[1]/eps^1, polynomial in eps2 of order 2
1695        1., 2., 16., 32., // C2[2]/eps^2, polynomial in eps2 of order 2
1696        35., 64., 384., 2048., // C2[3]/eps^3, polynomial in eps2 of order 1
1697        15., 80., 768., // C2[4]/eps^4, polynomial in eps2 of order 1
1698        7., 35., 512., // C2[5]/eps^5, polynomial in eps2 of order 0
1699        63., 1280., // C2[6]/eps^6, polynomial in eps2 of order 0
1700        77., 2048.,
1701    ];
1702    let eps2 = sq(eps);
1703    let mut d = eps;
1704    let mut o = 0;
1705    #[allow(clippy::needless_range_loop)]
1706    for l in 1..=N_C2 {
1707        // l is index of C2[l]
1708        let m = (N_C2 - l) / 2; /* order of polynomial in eps^2 */
1709        c[l] = d * polyvalx(m, &coeff[o..], eps2) / coeff[o + m + 1];
1710        o += m + 2;
1711        d *= eps;
1712    }
1713}
1714
1715/// Inverse geodesic
1716#[allow(clippy::too_many_arguments)]
1717pub fn inverse_start(
1718    g: &GeodGeodesic,
1719    sbet1: f64,
1720    cbet1: f64,
1721    dn1: f64,
1722    sbet2: f64,
1723    cbet2: f64,
1724    dn2: f64,
1725    lam12: f64,
1726    slam12: f64,
1727    clam12: f64,
1728    psalp1: &mut f64,
1729    pcalp1: &mut f64,
1730    // Only updated if return val >= 0
1731    psalp2: &mut f64,
1732    pcalp2: &mut f64,
1733    // Only updated for short lines
1734    pdnm: &mut f64,
1735    // Scratch area of the right size
1736    ca: &mut [f64],
1737) -> f64 {
1738    let mut salp1;
1739    let mut calp1;
1740    let mut salp2 = 0.;
1741    let mut calp2 = 0.;
1742    let mut dnm = 0.;
1743
1744    // Return a starting point for Newton's method in salp1 and calp1 (function
1745    // value is -1).  If Newton's method doesn't need to be used, return also
1746    // salp2 and calp2 and function value is sig12.
1747    let mut sig12 = -1.; /* Return value */
1748    // bet12 = bet2 - bet1 in [0, pi); bet12a = bet2 + bet1 in (-pi, 0]
1749    let sbet12 = sbet2 * cbet1 - cbet2 * sbet1;
1750    let cbet12 = cbet2 * cbet1 + sbet2 * sbet1;
1751
1752    let shortline = cbet12 >= 0. && sbet12 < 0.5 && cbet2 * lam12 < 0.5;
1753    let mut somg12;
1754    let mut comg12;
1755
1756    let sbet12a = sbet2 * cbet1 + cbet2 * sbet1;
1757    if shortline {
1758        let mut sbetm2 = sq(sbet1 + sbet2);
1759
1760        // sin((bet1+bet2)/2)^2
1761        // =  (sbet1 + sbet2)^2 / ((sbet1 + sbet2)^2 + (cbet1 + cbet2)^2)
1762        sbetm2 /= sbetm2 + sq(cbet1 + cbet2);
1763        dnm = sqrt(1. + g.ep2 * sbetm2);
1764        let omg12 = lam12 / (g.f1 * dnm);
1765        somg12 = sin(omg12);
1766        comg12 = cos(omg12);
1767    } else {
1768        somg12 = slam12;
1769        comg12 = clam12;
1770    }
1771
1772    salp1 = cbet2 * somg12;
1773    calp1 = if comg12 >= 0. {
1774        sbet12 + cbet2 * sbet1 * sq(somg12) / (1. + comg12)
1775    } else {
1776        sbet12a - cbet2 * sbet1 * sq(somg12) / (1. - comg12)
1777    };
1778
1779    let ssig12 = hypot(salp1, calp1);
1780    let csig12 = sbet1 * sbet2 + cbet1 * cbet2 * comg12;
1781
1782    if shortline && ssig12 < g.etol2 {
1783        // really short lines
1784        salp2 = cbet1 * somg12;
1785        calp2 = sbet12
1786            - cbet1 * sbet2 * (if comg12 >= 0. { sq(somg12) / (1. + comg12) } else { 1. - comg12 });
1787        norm2(&mut salp2, &mut calp2);
1788        // Set return value
1789        sig12 = atan2(ssig12, csig12);
1790    } else if fabs(g.n) > 0.1 || /* No a calc if too eccentric */
1791                 csig12 >= 0. ||
1792                 ssig12 >= 6. * fabs(g.n) * PI * sq(cbet1)
1793    {
1794        // Nothing to do, zeroth order spherical approximation is OK
1795    } else {
1796        // Scale lam12 and bet2 to x, y coordinate system where antipodal point
1797        // is at origin and singular point is at y = 0, x = -1.
1798        let x;
1799        let y;
1800        let lamscale;
1801        let betscale;
1802        let lam12x = atan2(-slam12, -clam12); /* lam12 - pi */
1803        if g.f >= 0. {
1804            // In fact f == 0 does not get here
1805            // x = dlong, y = dlat
1806            {
1807                let k2 = sq(sbet1) * g.ep2;
1808                let eps = k2 / (2. * (1. + sqrt(1. + k2)) + k2);
1809                lamscale = g.f * cbet1 * a3f(g, eps) * PI;
1810            }
1811            betscale = lamscale * cbet1;
1812
1813            x = lam12x / lamscale;
1814            y = sbet12a / betscale;
1815        } else {
1816            // f < 0
1817            // x = dlat, y = dlong
1818            let cbet12a = cbet2 * cbet1 - sbet2 * sbet1;
1819            let bet12a = atan2(sbet12a, cbet12a);
1820            let mut m12b = 0.;
1821            let mut m0 = 0.;
1822            // In the case of lon12 = 180, this repeats a calculation made in
1823            // Inverse.
1824            lengths(
1825                g,
1826                g.n,
1827                PI + bet12a,
1828                sbet1,
1829                -cbet1,
1830                dn1,
1831                sbet2,
1832                cbet2,
1833                dn2,
1834                cbet1,
1835                cbet2,
1836                &mut 0.,
1837                &mut m12b,
1838                &mut m0,
1839                &mut 0.,
1840                &mut 0.,
1841                ca,
1842            );
1843            x = -1. + m12b / (cbet1 * cbet2 * m0 * PI);
1844            betscale = if x < -0.01 { sbet12a / x } else { -g.f * sq(cbet1) * PI };
1845            lamscale = betscale / cbet1;
1846            y = lam12x / lamscale;
1847        }
1848
1849        if y > -TOL1 && x > -1. - XTHRESH {
1850            // strip near cut
1851            if g.f >= 0. {
1852                salp1 = fmin(1.0, -x);
1853                calp1 = -sqrt(1. - sq(salp1));
1854            } else {
1855                calp1 = fmax(if x > -TOL1 { 0.0 } else { -1.0 }, x);
1856                salp1 = sqrt(1. - sq(calp1));
1857            }
1858        } else {
1859            // Estimate alp1, by solving the a problem.
1860            //
1861            // Could estimate alpha1 = theta + pi/2, directly, i.e.,
1862            //   calp1 = y/k; salp1 = -x/(1+k);  for f >= 0
1863            //   calp1 = x/(1+k); salp1 = -y/k;  for f < 0 (need to check)
1864            //
1865            // However, it's better to estimate omg12 from a and use
1866            // spherical formula to compute alp1.  This reduces the mean number of
1867            // Newton iterations for a cases from 2.24 (min 0, max 6) to 2.12
1868            // (min 0 max 5).  The changes in the number of iterations are as
1869            // follows:
1870            //
1871            // change percent
1872            //    1       5
1873            //    0      78
1874            //   -1      16
1875            //   -2       0.6
1876            //   -3       0.04
1877            //   -4       0.002
1878            //
1879            // The histogram of iterations is (m = number of iterations estimating
1880            // alp1 directly, n = number of iterations estimating via omg12, total
1881            // number of trials = 148605):
1882            //
1883            //  iter    m      n
1884            //    0   148    186
1885            //    1 13046  13845
1886            //    2 93315 102225
1887            //    3 36189  32341
1888            //    4  5396      7
1889            //    5   455      1
1890            //    6    56      0
1891            //
1892            // Because omg12 is near pi, estimate work with omg12a = pi - omg12
1893            let k = astroid(x, y);
1894            let omg12a = lamscale * (if g.f >= 0. { -x * k / (1. + k) } else { -y * (1. + k) / k });
1895            somg12 = sin(omg12a);
1896            comg12 = -cos(omg12a);
1897            // Update spherical estimate of alp1 using omg12 instead of lam12
1898            salp1 = cbet2 * somg12;
1899            calp1 = sbet12a - cbet2 * sbet1 * sq(somg12) / (1. - comg12);
1900        }
1901    }
1902    // Sanity check on starting guess.  Backwards check allows NaN through.
1903    if salp1 > 0. {
1904        norm2(&mut salp1, &mut calp1);
1905    } else {
1906        salp1 = 1.;
1907        calp1 = 0.;
1908    }
1909
1910    *psalp1 = salp1;
1911    *pcalp1 = calp1;
1912    if shortline {
1913        *pdnm = dnm;
1914    }
1915    if sig12 >= 0. {
1916        *psalp2 = salp2;
1917        *pcalp2 = calp2;
1918    }
1919
1920    sig12
1921}
1922
1923/// Compute lambda12
1924#[allow(clippy::too_many_arguments)]
1925pub fn lambda12(
1926    g: &GeodGeodesic,
1927    sbet1: f64,
1928    cbet1: f64,
1929    dn1: f64,
1930    sbet2: f64,
1931    cbet2: f64,
1932    dn2: f64,
1933    salp1: f64,
1934    mut calp1: f64,
1935    slam120: f64,
1936    clam120: f64,
1937    psalp2: &mut f64,
1938    pcalp2: &mut f64,
1939    psig12: &mut f64,
1940    pssig1: &mut f64,
1941    pcsig1: &mut f64,
1942    pssig2: &mut f64,
1943    pcsig2: &mut f64,
1944    peps: &mut f64,
1945    pdomg12: &mut f64,
1946    diffp: bool,
1947    pdlam12: &mut f64,
1948    // Scratch area of the right size
1949    ca: &mut [f64],
1950) -> f64 {
1951    let mut ssig1;
1952    let mut csig1;
1953    let mut ssig2;
1954    let mut csig2;
1955    let mut dlam12 = 0.0;
1956
1957    if sbet1 == 0. && calp1 == 0. {
1958        // Break degeneracy of equatorial line.  This case has already been handled.
1959        calp1 = -TINY;
1960    }
1961
1962    // sin(alp1) * cos(bet1) = sin(alp0)
1963    let salp0 = salp1 * cbet1;
1964    let calp0 = hypot(calp1, salp1 * sbet1); /* calp0 > 0 */
1965
1966    // tan(bet1) = tan(sig1) * cos(alp1)
1967    // tan(omg1) = sin(alp0) * tan(sig1) = tan(omg1)=tan(alp1)*sin(bet1)
1968    ssig1 = sbet1;
1969    let somg1 = salp0 * sbet1;
1970    let comg1 = calp1 * cbet1;
1971    csig1 = comg1;
1972    norm2(&mut ssig1, &mut csig1);
1973    // norm2(&somg1, &comg1); -- don't need to normalize!
1974
1975    // Enforce symmetries in the case abs(bet2) = -bet1.  Need to be careful
1976    // about this case, since this can yield singularities in the Newton
1977    // iteration.
1978    // sin(alp2) * cos(bet2) = sin(alp0)
1979    let salp2 = if cbet2 != cbet1 { salp0 / cbet2 } else { salp1 };
1980    // calp2 = sqrt(1 - sq(salp2))
1981    //       = sqrt(sq(calp0) - sq(sbet2)) / cbet2
1982    // and subst for calp0 and rearrange to give (choose positive sqrt
1983    // to give alp2 in [0, pi/2]).
1984
1985    let calp2 = if cbet2 != cbet1 || fabs(sbet2) != -sbet1 {
1986        sqrt(
1987            sq(calp1 * cbet1)
1988                + (if cbet1 < -sbet1 {
1989                    (cbet2 - cbet1) * (cbet1 + cbet2)
1990                } else {
1991                    (sbet1 - sbet2) * (sbet1 + sbet2)
1992                }),
1993        ) / cbet2
1994    } else {
1995        fabs(calp1)
1996    };
1997
1998    // tan(bet2) = tan(sig2) * cos(alp2)
1999    // tan(omg2) = sin(alp0) * tan(sig2).
2000    ssig2 = sbet2;
2001    let somg2 = salp0 * sbet2;
2002    let comg2 = calp2 * cbet2;
2003    csig2 = comg2;
2004    norm2(&mut ssig2, &mut csig2);
2005    // norm2(&somg2, &comg2); -- don't need to normalize!
2006
2007    // sig12 = sig2 - sig1, limit to [0, pi]
2008    let sig12 = atan2(fmax(0.0, csig1 * ssig2 - ssig1 * csig2) + 0., csig1 * csig2 + ssig1 * ssig2);
2009
2010    // omg12 = omg2 - omg1, limit to [0, pi]
2011    let somg12 = fmax(0.0, comg1 * somg2 - somg1 * comg2) + 0.;
2012    let comg12 = comg1 * comg2 + somg1 * somg2;
2013    // eta = omg12 - lam120
2014    let eta = atan2(somg12 * clam120 - comg12 * slam120, comg12 * clam120 + somg12 * slam120);
2015    let k2 = sq(calp0) * g.ep2;
2016    let eps = k2 / (2. * (1. + sqrt(1. + k2)) + k2);
2017    c3f(g, eps, ca);
2018    let b312 = sin_cos_series(true, ssig2, csig2, ca, N_C3 - 1)
2019        - sin_cos_series(true, ssig1, csig1, ca, N_C3 - 1);
2020    let domg12 = -g.f * a3f(g, eps) * salp0 * (sig12 + b312);
2021    let lam12 = eta + domg12;
2022
2023    if diffp {
2024        if calp2 == 0. {
2025            dlam12 = -2. * g.f1 * dn1 / sbet1;
2026        } else {
2027            lengths(
2028                g,
2029                eps,
2030                sig12,
2031                ssig1,
2032                csig1,
2033                dn1,
2034                ssig2,
2035                csig2,
2036                dn2,
2037                cbet1,
2038                cbet2,
2039                &mut 0.,
2040                &mut dlam12,
2041                &mut 0.,
2042                &mut 0.,
2043                &mut 0.,
2044                ca,
2045            );
2046            dlam12 *= g.f1 / (calp2 * cbet2);
2047        }
2048    }
2049
2050    *psalp2 = salp2;
2051    *pcalp2 = calp2;
2052    *psig12 = sig12;
2053    *pssig1 = ssig1;
2054    *pcsig1 = csig1;
2055    *pssig2 = ssig2;
2056    *pcsig2 = csig2;
2057    *peps = eps;
2058    *pdomg12 = domg12;
2059    if diffp {
2060        *pdlam12 = dlam12;
2061    }
2062
2063    lam12
2064}
2065
2066/// Compute length of geodesic
2067#[allow(clippy::too_many_arguments)]
2068fn lengths(
2069    g: &GeodGeodesic,
2070    eps: f64,
2071    sig12: f64,
2072    ssig1: f64,
2073    csig1: f64,
2074    dn1: f64,
2075    ssig2: f64,
2076    csig2: f64,
2077    dn2: f64,
2078    cbet1: f64,
2079    cbet2: f64,
2080    ps12b: &mut f64,
2081    pm12b: &mut f64,
2082    pm0: &mut f64,
2083    p_m12: &mut f64,
2084    p_m21: &mut f64,
2085    // Scratch area of the right size
2086    ca: &mut [f64],
2087) {
2088    //   double m0 = 0, J12 = 0, A1 = 0, A2 = 0;
2089    let mut m0 = 0.0;
2090    let mut j12 = 0.0;
2091    let mut a1 = 0.0;
2092    let mut a2 = 0.0;
2093    let mut cb = [0.0; N_C];
2094
2095    // Return m12b = (reduced length)/b; also calculate s12b = distance/b,
2096    // and m0 = coefficient of secular term in expression for reduced length.
2097    let redlp = *pm12b != 0. || *pm0 != 0. || *p_m12 != 0. || *p_m21 != 0.;
2098    if *ps12b != 0. || redlp {
2099        a1 = a1m1f(eps);
2100        c1f(eps, ca);
2101        if redlp {
2102            a2 = a2m1f(eps);
2103            c2f(eps, &mut cb);
2104            m0 = a1 - a2;
2105            a2 += 1.;
2106        }
2107        a1 += 1.;
2108    }
2109    if *ps12b != 0. {
2110        let b1 = sin_cos_series(true, ssig2, csig2, ca, N_C1)
2111            - sin_cos_series(true, ssig1, csig1, ca, N_C1);
2112        // Missing a factor of b
2113        *ps12b = a1 * (sig12 + b1);
2114        // if redlp {
2115        //     let b2 = sin_cos_series(true, ssig2, csig2, &cb, N_C2)
2116        //         - sin_cos_series(true, ssig1, csig1, &cb, N_C2);
2117        //     j12 = m0 * sig12 + (A1 * b1 - A2 * b2);
2118        // }
2119    } else if redlp {
2120        // Assume here that nC1 >= nC2
2121        // int l;
2122        // for (l = 1; l <= nC2; ++l)
2123        for l in 1..N_C2 {
2124            cb[l] = a1 * ca[l] - a2 * cb[l];
2125            j12 = m0 * sig12
2126                + (sin_cos_series(true, ssig2, csig2, &cb, N_C2)
2127                    - sin_cos_series(true, ssig1, csig1, &cb, N_C2));
2128        }
2129        if *pm0 != 0. {
2130            *pm0 = m0;
2131        }
2132        if *pm12b != 0. {
2133            /* Missing a factor of b.
2134             * Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure
2135             * accurate cancellation in the case of coincident points. */
2136            *pm12b = dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2) - csig1 * csig2 * j12;
2137        }
2138        if *p_m12 != 0. || *p_m21 != 0. {
2139            let csig12 = csig1 * csig2 + ssig1 * ssig2;
2140            let t = g.ep2 * (cbet1 - cbet2) * (cbet1 + cbet2) / (dn1 + dn2);
2141            if *p_m12 != 0. {
2142                *p_m12 = csig12 + (t * ssig2 - csig2 * j12) * ssig1 / dn1;
2143            }
2144            if *p_m21 != 0. {
2145                *p_m21 = csig12 - (t * ssig1 - csig1 * j12) * ssig2 / dn2;
2146            }
2147        }
2148    }
2149}
2150
2151fn sincosde(x: f64, t: f64, sinx: &mut f64, cosx: &mut f64) {
2152    // In order to minimize round-off errors, this function exactly reduces
2153    // the argument to the range [-45, 45] before converting it to radians.
2154    //   double r, s, c; int q = 0;
2155    let q = remquo(x, QD);
2156    let mut r = ang_round(q.0 + t);
2157    // now abs(r) <= 45
2158    r *= DEGREE;
2159    // Possibly could call the gnu extension sincos
2160    let s = sin(r);
2161    let c = cos(r);
2162    match q.1 & 3 {
2163        0 => {
2164            *sinx = s;
2165            *cosx = c;
2166        }
2167        1 => {
2168            *sinx = c;
2169            *cosx = -s;
2170        }
2171        2 => {
2172            *sinx = -s;
2173            *cosx = -c;
2174        }
2175        _ => {
2176            *sinx = -c;
2177            *cosx = s;
2178        } // case 3U
2179    }
2180    // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf
2181    *cosx += 0.; /* special values from F.10.1.12 */
2182    // special values from F.10.1.13
2183    if *sinx == 0. {
2184        *sinx = copysign(*sinx, x);
2185    }
2186}
2187
2188/// Solve k^4+2*k^3-(x^2+y^2-1)*k^2-2*y^2*k-y^2 = 0 for positive root k
2189/// This solution is adapted from Geocentric::Reverse
2190pub fn astroid(x: f64, y: f64) -> f64 {
2191    let p = sq(x);
2192    let q = sq(y);
2193    let r = (p + q - 1.) / 6.;
2194    if !(q == 0. && r <= 0.) {
2195        // Avoid possible division by zero when r = 0 by multiplying equations
2196        // for s and t by r^3 and r, resp.
2197        let _s = p * q / 4.; /* S = r^3 * s */
2198        let r2 = sq(r);
2199        let r3 = r * r2;
2200        // The discriminant of the quadratic equation for _t3.  This is zero on
2201        // the evolute curve p^(1/3)+q^(1/3) = 1
2202        let disc = _s * (_s + 2. * r3);
2203        let mut u = r;
2204
2205        if disc >= 0. {
2206            let mut _t3 = _s + r3;
2207            // Pick the sign on the sqrt to maximize abs(_t3).  This minimizes loss
2208            // of precision due to cancellation.  The result is unchanged because
2209            // of the way the T is used in definition of u.
2210            _t3 += if _t3 < 0. { -sqrt(disc) } else { sqrt(disc) }; /* _t3 = (r * t)^3 */
2211            // N.B. cbrt always returns the double root.  cbrt(-8) = -2.
2212            let t = cbrt(_t3); /* T = r * t */
2213            // T can be zero; but then r2 / T -> 0.
2214            u += t + (if t != 0. { r2 / t } else { 0. });
2215        } else {
2216            // T is complex, but the way u is defined the result is double.
2217            let ang = atan2(sqrt(-disc), -(_s + r3));
2218            // There are three possible cube roots.  We choose the root which
2219            // avoids cancellation.  Note that disc < 0 implies that r < 0.
2220            u += 2. * r * cos(ang / 3.);
2221        }
2222        let v = sqrt(sq(u) + q); /* guaranteed positive */
2223        // Avoid loss of accuracy when u < 0.
2224        let uv = if u < 0. { q / (v - u) } else { u + v }; /* u+v, guaranteed positive */
2225        let w = (uv - q) / (2. * v); /* positive? */
2226        // Rearrange expression for k to avoid loss of accuracy due to
2227        // subtraction.  Division by 0 not possible because uv > 0, w >= 0.
2228        uv / (sqrt(uv + sq(w)) + w) /* guaranteed positive */
2229    } else {
2230        // q == 0 && r <= 0
2231        // y = 0 with |x| <= 1.  Handle this case directly.
2232        // for y small, positive root is k = abs(y)/sqrt(1-x^2)
2233        0.
2234    }
2235}