gistools/proj/
common.rs

1use super::Proj;
2use crate::proj::{Complex, CoordinateStep, Coords, TransformCoordinates};
3use alloc::{vec, vec::Vec};
4use core::f64::consts::{FRAC_PI_2, PI, TAU};
5use libm::{acos, asin, atan, atan2, atanh, cos, exp, fabs, floor, fmax, sin, sinh, sqrt};
6
7const ONE_TOL: f64 = 1.00000000000001;
8const ATOL: f64 = 1e-50;
9
10/// List of auxiliary latitudes
11#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
12pub enum AuxLat {
13    /// Geographic latitude, phi
14    GEOGRAPHIC = 0,
15    /// Parametric latitude, beta
16    PARAMETRIC = 1,
17    /// Geocentric latitude, theta
18    GEOCENTRIC = 2,
19    /// Rectifying latitude, mu
20    RECTIFYING = 3,
21    /// Conformal latitude, chi
22    CONFORMAL = 4,
23    /// Authlatic latitude, xi
24    AUTHALIC = 5,
25    /// Number of auxiliary latitudes (6)
26    NUMBER = 6,
27}
28impl AuxLat {
29    /// The order of the expansion in n (ACCIDENTALLY equal to AUXNUMBER)
30    pub const ORDER: AuxLat = AuxLat::NUMBER;
31}
32
33/// Convert tau' = sinh(psi) = tan(chi) to tau = tan(phi).  The code is taken
34/// from GeographicLib::Math::tauf(taup, e).
35///
36/// Here
37///   phi = geographic latitude (radians)
38/// psi is the isometric latitude
39///   psi = asinh(tan(phi)) - e * atanh(e * sin(phi))
40///       = asinh(tan(chi))
41/// chi is the conformal latitude
42///
43/// The representation of latitudes via their tangents, tan(phi) and
44/// tan(chi), maintains full *relative* accuracy close to latitude = 0 and
45/// +/- pi/2. This is sometimes important, e.g., to compute the scale of the
46/// transverse Mercator projection which involves cos(phi)/cos(chi) tan(phi)
47///
48/// From Karney (2011), Eq. 7,
49///
50///   tau' = sinh(psi) = sinh(asinh(tan(phi)) - e * atanh(e * sin(phi)))
51///        = tan(phi) * cosh(e * atanh(e * sin(phi))) -
52///          sec(phi) * sinh(e * atanh(e * sin(phi)))
53///        = tau * sqrt(1 + sigma^2) - sqrt(1 + tau^2) * sigma
54/// where
55///   sigma = sinh(e * atanh( e * tau / sqrt(1 + tau^2) ))
56///
57/// For e small,
58///
59///    tau' = (1 - e^2) * tau
60///
61/// The relation tau'(tau) can therefore by reliably inverted by Newton's
62/// method with
63///
64///    tau = tau' / (1 - e^2)
65///
66/// as an initial guess.  Newton's method requires dtau'/dtau.  Noting that
67///
68///   dsigma/dtau = e^2 * sqrt(1 + sigma^2) /
69///                 (sqrt(1 + tau^2) * (1 + (1 - e^2) * tau^2))
70///   d(sqrt(1 + tau^2))/dtau = tau / sqrt(1 + tau^2)
71///
72/// we have
73///
74///   dtau'/dtau = (1 - e^2) * sqrt(1 + tau'^2) * sqrt(1 + tau^2) /
75///                (1 + (1 - e^2) * tau^2)
76///
77/// This works fine unless tau^2 and tau'^2 overflows.  This may be partially
78/// cured by writing, e.g., sqrt(1 + tau^2) as hypot(1, tau).  However, nan
79/// will still be generated with tau' = inf, since (inf - inf) will appear in
80/// the Newton iteration.
81///
82/// If we note that for sufficiently large |tau|, i.e., |tau| >= 2/sqrt(eps),
83/// sqrt(1 + tau^2) = |tau| and
84///
85///   tau' = exp(- e * atanh(e)) * tau
86///
87/// So
88///
89///   tau = exp(e * atanh(e)) * tau'
90///
91/// can be returned unless |tau| >= 2/sqrt(eps); this then avoids overflow
92/// problems for large tau' and returns the correct result for tau' = +/-inf
93/// and nan.
94///
95/// Newton's method usually take 2 iterations to converge to double precision
96/// accuracy (for WGS84 flattening).  However only 1 iteration is needed for
97/// |chi| < 3.35 deg.  In addition, only 1 iteration is needed for |chi| >
98/// 89.18 deg (tau' > 70), if tau = exp(e * atanh(e)) * tau' is used as the
99/// starting guess.
100///
101/// For small flattening, |f| <= 1/50, the series expansion in n can be
102/// used:
103///
104/// Assuming n = e^2 / (1 + sqrt(1 - e^2))^2 is passed as an argument
105///
106///   double F[int(AuxLat::AUXORDER)];
107///   auxlat_coeffs(n, AuxLat::CONFORMAL, AuxLat::GEOGRAPHIC, F);
108///   double sphi, cphi;
109///   //                schi                   cchi
110///   auxlat_convert(taup/hypot(1.0, taup), 1/hypot(1.0, taup),
111///                     sphi, cphi, F);
112///   return sphi/cphi;
113pub fn sinhpsi2tanphi(taup: f64, e: f64) -> f64 {
114    // min iterations = 1, max iterations = 2; mean = 1.954
115    let rooteps: f64 = sqrt(f64::EPSILON);
116    let tol: f64 = rooteps / 10.; // the criterion for Newton's method
117    let tmax: f64 = 2. / rooteps; // threshold for large arg limit exact
118    let e2m: f64 = 1. - e * e;
119    let stol: f64 = tol * fmax(1.0, fabs(taup));
120    // The initial guess.  70 corresponds to chi = 89.18 deg (see above)
121    let mut tau: f64 = if fabs(taup) > 70. { taup * exp(e * atanh(e)) } else { taup / e2m };
122    // handles +/-inf and nan and e = 1
123    if fabs(tau) >= tmax {
124        return tau;
125    }
126    // If we need to deal with e > 1, then we could include:
127    // if (e2m < 0) return std::numeric_limits<double>::quiet_NaN();
128    let mut i: i32 = 5;
129    loop {
130        let tau1 = sqrt(1. + tau * tau);
131        let sig = sinh(e * atanh(e * tau / tau1));
132        let taupa = sqrt(1. + sig * sig) * tau - sig * tau1;
133        let dtau =
134            (taup - taupa) * (1. + e2m * (tau * tau)) / (e2m * tau1 * sqrt(1. + taupa * taupa));
135        tau += dtau;
136        // backwards test to allow nans to succeed.
137        if fabs(dtau) < stol {
138            break;
139        }
140        i -= 1;
141        if i == 0 {
142            panic!("Newton's method failed to converge");
143        }
144    }
145
146    tau
147}
148
149/// Determine latitude angle phi-2.
150/// Inputs:
151///   ts = exp(-psi) where psi is the isometric latitude (dimensionless)
152///        this variable is defined in Snyder (1987), Eq. (7-10)
153///   e = eccentricity of the ellipsoid (dimensionless)
154/// Output:
155///   phi = geographic latitude (radians)
156/// Here isometric latitude is defined by
157///   psi = log( tan(pi/4 + phi/2) *
158///              ( (1 - e*sin(phi)) / (1 + e*sin(phi)) )^(e/2) )
159///       = asinh(tan(phi)) - e * atanh(e * sin(phi))
160///       = asinh(tan(chi))
161///   chi = conformal latitude
162///
163/// This routine converts t = exp(-psi) to
164///
165///   tau' = tan(chi) = sinh(psi) = (1/t - t)/2
166///
167/// returns atan(sinpsi2tanphi(tau'))
168pub fn phi2(ts0: f64, e: f64) -> f64 {
169    atan(sinhpsi2tanphi((1. / ts0 - ts0) / 2., e))
170}
171
172/// Compute the meridian scale factor
173pub fn _msfn(sinphi: f64, cosphi: f64, es: f64) -> f64 {
174    cosphi / sqrt(1. - es * sinphi * sinphi)
175}
176
177/// Compute the meridian scale factor
178pub fn msfn(phi: f64, e2: f64) -> f64 {
179    let sinphi = sin(phi);
180    let cosphi = cos(phi);
181    _msfn(sinphi, cosphi, e2)
182}
183
184/// Compute the geographic latitude from beta = authalic_latitude
185/// where APA = pj_compute_coefficients_for_inverse_authalic_lat() and
186/// qp = pj_authalic_lat_q(1, proj.e, proj.one_es)
187pub fn authalic_lat_inverse(beta: f64, apa: &[f64], proj: &Proj, qp: f64) -> f64 {
188    let mut phi = auxlat_convert(beta, apa, AuxLat::ORDER as i32);
189    if authalic_series_valid(proj.n) {
190        return phi;
191    }
192    // If the flattening is large, solve
193    //   f(phi) = qp*sin(beta)/(1-e^2) - q(phi)/(1-e^2) = 0
194    // for phi, using Newton's method, where
195    //   q(phi)/(1-e^2) = sin(phi)/(1 - e^2*sin(phi)^2) + atanh(e*sin(phi))/e
196    // and
197    //   df(phi)/dphi = - dq(phi)/dphi / (1-e^2)
198    //                = - 2 * (1-e^2) * cos(phi) / (1 - e^2*sinphi^2)^2
199    // This is subject to large roundoff errors near the poles, so only use
200    // this if the series isn't accurate.
201    let q = sin(beta) * qp;
202    let q_div_one_minus_es = q / proj.one_es;
203    for _ in 0..10 {
204        let sinphi = sin(phi);
205        let cosphi = cos(phi);
206        let one_minus_es_sin2phi = 1. - proj.es * (sinphi * sinphi);
207        // Snyder uses 0.5 * ln((1-e*sinphi)/(1+e*sinphi) which is
208        // -atanh(e*sinphi)
209        let dphi = (one_minus_es_sin2phi * one_minus_es_sin2phi) / (2. * cosphi)
210            * (q_div_one_minus_es
211                - sinphi / one_minus_es_sin2phi
212                - atanh(proj.e * sinphi) / proj.e);
213        if fabs(dphi) < 1e-15 {
214            break;
215        }
216        phi += dphi;
217    }
218    phi
219}
220
221/// Convert auxiliary latitude zeta to eta, given coefficients F produced by
222/// pj_auxlats_coeffs(n, zeta, eta, F).  K is the size of F (defaults to
223/// AuxLat::ORDER).
224pub fn auxlat_convert(zeta: f64, f: &[f64], k: i32) -> f64 {
225    auxlat_convert_mid(zeta, sin(zeta), cos(zeta), f, k)
226}
227
228/// Convert auxiliary latitude zeta to eta, given coefficients F produced by
229/// pj_auxlats_coeffs(n, zeta, eta, F).  In this signature, szeta and czeta (the
230/// sine and cosine of zeta) are given as inputs.  K is the size of F (defaults
231/// to AuxLat::ORDER).
232pub fn auxlat_convert_mid(zeta: f64, szeta: f64, czeta: f64, f: &[f64], k: i32) -> f64 {
233    zeta + clenshaw(szeta, czeta, f, k)
234}
235
236/// Convert auxiliary latitude zeta to eta, given coefficients F produced by
237/// pj_auxlats_coeffs(n, zeta, eta, F).  K is the size of F (defaults to
238/// AuxLat::ORDER).  In this signature, the sine and cosine of eta are returned.
239/// This provides high relative accuracy near the poles.
240pub fn auxlat_convert_full(
241    szeta: f64,
242    czeta: f64,
243    seta: &mut f64,
244    ceta: &mut f64,
245    f: &[f64],
246    k: i32,
247) {
248    let delta: f64 = clenshaw(szeta, czeta, f, k);
249    let sdelta = sin(delta);
250    let cdelta = cos(delta);
251    *seta = szeta * cdelta + czeta * sdelta;
252    *ceta = czeta * cdelta - szeta * sdelta;
253}
254
255/// Compute the rectifying radius = quarter meridian / (pi/2 * a).  The accuracy
256/// of this series is the same as those used for the computation of the
257/// auxiliary latitudes.
258pub fn rectifying_radius(n: f64) -> f64 {
259    // Expansion of (quarter meridian) / ((a+b)/2 * pi/2) as series in n^2;
260    // these coefficients are ( (2*k - 3)!! / (2*k)!! )^2 for k = 0..3
261    // static const double coeff_rad[] = {1, 1.0 / 4, 1.0 / 64, 1.0 / 256};
262    let coeff_rad = vec![1., 1. / 4., 1. / 64., 1. / 256.];
263    // return pj_polyval(n * n, coeff_rad, 3) / (1 + n);
264    polyval(n * n, &coeff_rad, 3) / (1. + n)
265}
266
267/// meridional distance for ellipsoid and inverse using 6th-order expansion in
268/// the third flattening n.  This gives full double precision accuracy for |f|
269/// <= 1/150.
270pub fn enfn(n: f64) -> Vec<f64> {
271    let l_max = AuxLat::ORDER as usize;
272    // 2*l_max for the Fourier coeffs for each direction of conversion + 1 for
273    // overall multiplier.
274    // double *en;
275    // en = (double *)malloc((2 * l_max + 1) * sizeof(double));
276    let mut en = vec![0.; 2 * l_max + 1];
277    en[0] = rectifying_radius(n);
278    auxlat_coeffs(n, AuxLat::GEOGRAPHIC, AuxLat::RECTIFYING, en[1..].as_mut());
279    auxlat_coeffs(n, AuxLat::RECTIFYING, AuxLat::GEOGRAPHIC, en[1 + l_max..].as_mut());
280    en
281}
282
283/// meridional distance for ellipsoid and inverse using 6th-order expansion in
284/// the third flattening n.
285pub fn mlfn(phi: f64, sphi: f64, cphi: f64, en: &[f64]) -> f64 {
286    en[0] * auxlat_convert_mid(phi, sphi, cphi, en[1..].as_ref(), 0)
287}
288
289/// inverse meridional distance
290pub fn inv_mlfn(mu: f64, en: &[f64]) -> f64 {
291    let l_max = AuxLat::ORDER as usize;
292    auxlat_convert(mu / en[0], &en[(1 + l_max)..], AuxLat::ORDER as i32)
293}
294
295/// Evaluate `y = sum(F[k] * sin((2*k+2) * zeta), k, 0, K-1)` by Clenshaw
296/// summation. zeta is specify by its sine and cosine, szeta and czeta.
297pub fn clenshaw(szeta: f64, czeta: f64, f: &[f64], mut k: i32) -> f64 {
298    // Approx operation count = (K + 5) mult and (2 * K + 2) add
299    let mut u0 = 0.; // accumulator for sum
300    let mut u1 = 0.; // accumulator for sum
301    let x = 2. * (czeta - szeta) * (czeta + szeta); // 2 * cos(2*zeta)
302    while k > 0 {
303        k -= 1;
304        let t = x * u0 - u1 + f.get(k as usize).copied().unwrap_or(0.0);
305        u1 = u0;
306        u0 = t;
307    }
308    2. * szeta * czeta * u0 // sin(2*zeta) * u0
309}
310
311/// Computes coefficient q such that authalic_latitude = beta = asin(q / qp)
312/// where qp is q at phi=90deg, i.e. qp = authalic_lat_q(1, e, one_es)
313/// Cf  Snyder (3-11) and (3-12)
314pub fn authalic_lat_q(sinphi: f64, proj: &Proj) -> f64 {
315    if proj.e >= 1e-7 {
316        let e_sinphi = proj.e * sinphi;
317        let one_minus_e_sinphi_sq = 1.0 - e_sinphi * e_sinphi;
318
319        // avoid zero division, fail gracefully
320        if one_minus_e_sinphi_sq == 0.0 {
321            return f64::INFINITY;
322        }
323
324        // Snyder uses 0.5 * ln((1-e*sinphi)/(1+e*sinphi) which is -atanh(e*sinphi)
325        proj.one_es * (sinphi / one_minus_e_sinphi_sq + atanh(e_sinphi) / proj.e)
326    } else {
327        2. * sinphi
328    }
329}
330
331/// Computes coefficients needed for conversions between geographic and authalic
332/// latitude.  These are preferred over the analytical expressions for |n| <
333/// 0.01.  However the inverse series is used to start the inverse method for
334/// large |n|.
335/// Ensure we use the cutoff in n consistently
336pub fn authalic_lat_compute_coeffs(n: f64) -> Vec<f64> {
337    let l_max = AuxLat::ORDER as usize;
338    let apa_size = l_max * if authalic_series_valid(n) { 2 } else { 1 };
339    let mut apa: Vec<f64> = vec![0.; apa_size];
340    auxlat_coeffs(n, AuxLat::AUTHALIC, AuxLat::GEOGRAPHIC, &mut apa);
341    if authalic_series_valid(n) {
342        auxlat_coeffs(n, AuxLat::GEOGRAPHIC, AuxLat::AUTHALIC, &mut apa[l_max..]);
343    }
344
345    apa
346}
347
348/// Computes authalic latitude from the geographic latitude.
349/// qp is q at phi=90deg, i.e. qp = pj_authalic_lat_q(1, e, one_es)
350pub fn authalic_lat(phi: f64, sinphi: f64, cosphi: f64, apa: &[f64], proj: &Proj, qp: f64) -> f64 {
351    if authalic_series_valid(proj.n) {
352        let l_max = AuxLat::ORDER as usize;
353        auxlat_convert_mid(phi, sinphi, cosphi, &apa[l_max..], 0)
354    } else {
355        // This result is ill-conditioned near the poles.  So don't use this if
356        // the series are accurate.
357        let q = authalic_lat_q(sinphi, proj);
358        let mut ratio = q / qp;
359
360        if fabs(ratio) > 1. {
361            // Rounding error.
362            ratio = if ratio > 0. { 1. } else { -1. };
363        }
364        asin(ratio)
365    }
366}
367
368/// Ensure we use the cutoff in n consistently
369pub fn authalic_series_valid(n: f64) -> bool {
370    fabs(n) < 0.01
371}
372
373/// The following routines auxlat_coeffs, polyvol, clenshaw,
374/// auxlat_convert (3 signatures) provide a uniform interface for converting
375/// between any pair of auxiliary latitudes using series expansions in the third
376/// flattening, n.  There are 6 (= AuxLat::NUMBER) auxiliary latitudes
377/// supported labeled by
378///
379///   AuxLat::GEOGRAPHIC for geographic latitude, phi
380///   AuxLat::PARAMETRIC for parametric latitude, beta
381///   AuxLat::GEOCENTRIC for geocentric latitude, theta
382///   AuxLat::RECTIFYING for rectifying latitude, mu
383///   AuxLat::CONFORMAL for conformal latitude, chi
384///   AuxLat::AUTHALIC for authlatic latitude, xi
385///
386/// This is adapted from
387///
388///   C. F. F. Karney, On auxiliary latitudes,
389///   Survey Review 56, 165-180 (2024)
390///   <https://doi.org/10.1080/00396265.2023.2217604>
391///   Preprint: <https://arxiv.org/abs/2212.05818>
392///
393/// The typical calling sequence is
394///
395///   `constexpr int L = int(AuxLat::ORDER);`
396///   // Managing the memory for the coefficent array is the
397///   // responibility of the calling routine.
398///   `double F[L];`
399///   // Fill F[] with coefficients to convert conformal to geographic
400///   `auxlat_coeffs(proj.n, AuxLat::CONFORMAL, AuxLat::GEOGRAPHIC, F);`
401///   ...
402///   `double chi = 1;`                 // known conformal latitude
403///   // compute corresponding geographic latitude
404///   `double phi = auxlat_convert(chi, F);`
405///
406/// The conversions are Fourier series in the auxiliary latitude where each
407/// coefficient is given as a Taylor series in n truncated at order 6 (=
408/// AuxLat::ORDER).  This suffices to give full double precision accuracy for
409/// |f| <= 1/150 and probably provide satisfactory results for |f| <= 1/50.  The
410/// coefficients for these Taylor series are given by matrics listed in
411/// Eqs. (A1-A28) of this paper.
412///
413/// These coefficients are bundled up into a single array coeffs in
414/// auxlat_coeffs.  Only the upper triangular portion of the matrices are
415/// included.  Furthermore, half the coefficients for the conversions between
416/// any of phi, bete, theta, and mu are zero (the Taylor series are expansions
417/// in n^2), these zero elements are excluded.
418///
419/// The coefficent array, coeffs, is machine-generated by the Maxima code
420/// auxlat.mac bundled with GeographicLib.  To use
421///
422/// * Ensure that l_max (set near the top of the file) is set to 6 (=
423///   AuxLat::ORDER).
424/// * run
425///   $ maxima
426///   Maxima 5.47.0 <https://maxima.sourceforge.io>
427///   (%i1) load("auxlat.mac")$
428///   (%i2) writecppproj()$
429///   ....
430///   "CLOSED OUTPUT BUFFERED FILE-STREAM CHARACTER auxvalsproj6.cpp"
431/// * The results are in the file auxvalsproj6.cpp
432///
433/// Only a subset of the conversion matrices are written out.  To add others,
434/// include them in the list "required" in writecppproj().  The conversions
435/// currently supported are
436///
437///   phi <-> mu for meridian distance
438///   phi <-> chi for tmerc
439///   phi <-> xi for authalic latitude conversions
440///   chi <-> mu for tmerc
441///
442/// Because all the matrices are concatenated together into a single array,
443/// coeff, an auxiliary array, ptrs, or length 37 = AUXNUMBER^2 + 1, is written
444/// out to give the starting point of any particular matrix.
445///
446/// Input:
447///   n -- the third flattening (a-b)/(a+b)
448///   auxin, auxout -- compute the coefficients for converting auxin (zeta) to
449///     auxout (eta).
450/// Output:
451///   F -- `F[eta,zeta] = C[eta,zeta]`. P(n), where C is a matrix of constants
452///     and `P(n) = [n, n^2, n^3, ...]^T`; the first AuxLat::ORDER elements of F
453///     are filled.
454pub fn auxlat_coeffs(n: f64, auxin: AuxLat, auxout: AuxLat, out: &mut [f64]) {
455    // Generated by Maxima on 2025-03-23 19:13:00-04:00 (rewritten for rust)
456    let l_max = 6;
457    let coeffs: [f64; 150] = [
458        // C[phi,phi] skipped
459        // C[phi,beta] skipped
460        // C[phi,theta] skipped
461        // C[phi,mu]; even coeffs only
462        3.0 / 2.0,
463        -27.0 / 32.0,
464        269.0 / 512.0,
465        21.0 / 16.0,
466        -55.0 / 32.0,
467        6759.0 / 4096.0,
468        151.0 / 96.0,
469        -417.0 / 128.0,
470        1097.0 / 512.0,
471        -15543.0 / 2560.0,
472        8011.0 / 2560.0,
473        293393.0 / 61440.0,
474        // C[phi,chi]
475        2.0,
476        -2.0 / 3.0,
477        -2.0,
478        116.0 / 45.0,
479        26.0 / 45.0,
480        -2854.0 / 675.0,
481        7.0 / 3.0,
482        -8.0 / 5.0,
483        -227.0 / 45.0,
484        2704.0 / 315.0,
485        2323.0 / 945.0,
486        56.0 / 15.0,
487        -136.0 / 35.0,
488        -1262.0 / 105.0,
489        73814.0 / 2835.0,
490        4279.0 / 630.0,
491        -332.0 / 35.0,
492        -399572.0 / 14175.0,
493        4174.0 / 315.0,
494        -144838.0 / 6237.0,
495        601676.0 / 22275.0,
496        // C[phi,xi]
497        4.0 / 3.0,
498        4.0 / 45.0,
499        -16.0 / 35.0,
500        -2582.0 / 14175.0,
501        60136.0 / 467775.0,
502        28112932.0 / 212837625.0,
503        46.0 / 45.0,
504        152.0 / 945.0,
505        -11966.0 / 14175.0,
506        -21016.0 / 51975.0,
507        251310128.0 / 638512875.0,
508        3044.0 / 2835.0,
509        3802.0 / 14175.0,
510        -94388.0 / 66825.0,
511        -8797648.0 / 10945935.0,
512        6059.0 / 4725.0,
513        41072.0 / 93555.0,
514        -1472637812.0 / 638512875.0,
515        768272.0 / 467775.0,
516        455935736.0 / 638512875.0,
517        4210684958.0 / 1915538625.0,
518        // C[beta,phi] skipped
519        // C[beta,beta] skipped
520        // C[beta,theta] skipped
521        // C[beta,mu] skipped
522        // C[beta,chi] skipped
523        // C[beta,xi] skipped
524        // C[theta,phi] skipped
525        // C[theta,beta] skipped
526        // C[theta,theta] skipped
527        // C[theta,mu] skipped
528        // C[theta,chi] skipped
529        // C[theta,xi] skipped
530        // C[mu,phi]; even coeffs only
531        -3.0 / 2.0,
532        9.0 / 16.0,
533        -3.0 / 32.0,
534        15.0 / 16.0,
535        -15.0 / 32.0,
536        135.0 / 2048.0,
537        -35.0 / 48.0,
538        105.0 / 256.0,
539        315.0 / 512.0,
540        -189.0 / 512.0,
541        -693.0 / 1280.0,
542        1001.0 / 2048.0,
543        // C[mu,beta] skipped
544        // C[mu,theta] skipped
545        // C[mu,mu] skipped
546        // C[mu,chi]
547        1.0 / 2.0,
548        -2.0 / 3.0,
549        5.0 / 16.0,
550        41.0 / 180.0,
551        -127.0 / 288.0,
552        7891.0 / 37800.0,
553        13.0 / 48.0,
554        -3.0 / 5.0,
555        557.0 / 1440.0,
556        281.0 / 630.0,
557        -1983433.0 / 1935360.0,
558        61.0 / 240.0,
559        -103.0 / 140.0,
560        15061.0 / 26880.0,
561        167603.0 / 181440.0,
562        49561.0 / 161280.0,
563        -179.0 / 168.0,
564        6601661.0 / 7257600.0,
565        34729.0 / 80640.0,
566        -3418889.0 / 1995840.0,
567        212378941.0 / 319334400.0,
568        // C[mu,xi] skipped
569        // C[chi,phi]
570        -2.0,
571        2.0 / 3.0,
572        4.0 / 3.0,
573        -82.0 / 45.0,
574        32.0 / 45.0,
575        4642.0 / 4725.0,
576        5.0 / 3.0,
577        -16.0 / 15.0,
578        -13.0 / 9.0,
579        904.0 / 315.0,
580        -1522.0 / 945.0,
581        -26.0 / 15.0,
582        34.0 / 21.0,
583        8.0 / 5.0,
584        -12686.0 / 2835.0,
585        1237.0 / 630.0,
586        -12.0 / 5.0,
587        -24832.0 / 14175.0,
588        -734.0 / 315.0,
589        109598.0 / 31185.0,
590        444337.0 / 155925.0,
591        // C[chi,beta] skipped
592        // C[chi,theta] skipped
593        // C[chi,mu]
594        -1.0 / 2.0,
595        2.0 / 3.0,
596        -37.0 / 96.0,
597        1.0 / 360.0,
598        81.0 / 512.0,
599        -96199.0 / 604800.0,
600        -1.0 / 48.0,
601        -1.0 / 15.0,
602        437.0 / 1440.0,
603        -46.0 / 105.0,
604        1118711.0 / 3870720.0,
605        -17.0 / 480.0,
606        37.0 / 840.0,
607        209.0 / 4480.0,
608        -5569.0 / 90720.0,
609        -4397.0 / 161280.0,
610        11.0 / 504.0,
611        830251.0 / 7257600.0,
612        -4583.0 / 161280.0,
613        108847.0 / 3991680.0,
614        -20648693.0 / 638668800.0,
615        // C[chi,chi] skipped
616        // C[chi,xi] skipped
617        // C[xi,phi]
618        -4.0 / 3.0,
619        -4.0 / 45.0,
620        88.0 / 315.0,
621        538.0 / 4725.0,
622        20824.0 / 467775.0,
623        -44732.0 / 2837835.0,
624        34.0 / 45.0,
625        8.0 / 105.0,
626        -2482.0 / 14175.0,
627        -37192.0 / 467775.0,
628        -12467764.0 / 212837625.0,
629        -1532.0 / 2835.0,
630        -898.0 / 14175.0,
631        54968.0 / 467775.0,
632        100320856.0 / 1915538625.0,
633        6007.0 / 14175.0,
634        24496.0 / 467775.0,
635        -5884124.0 / 70945875.0,
636        -23356.0 / 66825.0,
637        -839792.0 / 19348875.0,
638        570284222.0 / 1915538625.0,
639        // C[xi,beta] skipped
640        // C[xi,theta] skipped
641        // C[xi,mu] skipped
642        // C[xi,chi] skipped
643        // C[xi,xi] skipped
644    ];
645    let ptrs: [usize; 37] = [
646        0, 0, 0, 0, 12, 33, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 66, 66, 66, 66, 87,
647        87, 108, 108, 108, 129, 129, 129, 150, 150, 150, 150, 150, 150,
648    ];
649    assert!(
650        auxin >= AuxLat::GEOGRAPHIC
651            && auxin < AuxLat::NUMBER
652            && auxout >= AuxLat::GEOGRAPHIC
653            && auxout < AuxLat::NUMBER,
654        "Bad specification for auxiliary latitude"
655    );
656    let k = AuxLat::NUMBER as usize * auxout as usize + auxin as usize;
657    let mut o = ptrs[k];
658    // if (o == ptrs[k+1])
659    //     throw std::out_of_range
660    //         ("Unsupported conversion between auxiliary latitudes");
661    assert!(o != ptrs[k + 1], "Unsupported conversion between auxiliary latitudes");
662    let mut d = n;
663    let n2 = n * n;
664    if auxin <= AuxLat::RECTIFYING && auxout <= AuxLat::RECTIFYING {
665        for l in 0..l_max {
666            let m = (l_max - l - 1) / 2; // order of polynomial in n^2
667            out[l as usize] = d * polyval(n2, &coeffs[o..], m);
668            o += m as usize + 1;
669            d *= n;
670        }
671    } else {
672        for l in 0..l_max {
673            let m = l_max - l - 1; // order of polynomial in n
674            out[l as usize] = d * polyval(n, &coeffs[o..], m);
675            o += m as usize + 1;
676            d *= n;
677        }
678    }
679    assert!(o == ptrs[k + 1]);
680}
681
682/// Evaluation `sum(p[i] * x^i, i, 0, N)` via Horner's method.  N.B. p is of length N+1.
683pub fn polyval(x: f64, p: &[f64], mut m: i32) -> f64 {
684    let mut y = if m < 0 { 0. } else { p[m as usize] };
685    while m > 0 {
686        m -= 1;
687        y = y * x + p[m as usize];
688    }
689    y
690}
691
692/// Determine function ts(phi) defined in Snyder (1987), Eq. (7-10)
693/// Inputs:
694///   phi = geographic latitude (radians)
695///   e = eccentricity of the ellipsoid (dimensionless)
696/// Output:
697///   ts = exp(-psi) where psi is the isometric latitude (dimensionless)
698///      = 1 / (tan(chi) + sec(chi))
699/// Here isometric latitude is defined by
700///   psi = log( tan(pi/4 + phi/2) *
701///              ( (1 - e*sin(phi)) / (1 + e*sin(phi)) )^(e/2) )
702///       = asinh(tan(phi)) - e * atanh(e * sin(phi))
703///       = asinh(tan(chi))
704///   chi = conformal latitude
705pub fn tsfn(phi: f64, sinphi: f64, e: f64) -> f64 {
706    let cosphi = cos(phi);
707    // exp(-asinh(tan(phi))) = 1 / (tan(phi) + sec(phi))
708    //                       = cos(phi) / (1 + sin(phi)) good for phi > 0
709    //                       = (1 - sin(phi)) / cos(phi) good for phi < 0
710    exp(e * atanh(e * sinphi))
711        * (if sinphi > 0. { cosphi / (1. + sinphi) } else { (1. - sinphi) / cosphi })
712}
713
714/// Compute (lam, phi) corresponding to input (xy.x, xy.y) for projection P.
715///
716/// Uses Newton-Raphson method, extended to 2D variables, that is using
717/// inversion of the Jacobian 2D matrix of partial derivatives. The derivatives
718/// are estimated numerically from the proj.fwd method evaluated at close points.
719///
720/// Note: thresholds used have been verified to work with adams_ws2 and wink2
721///
722/// Starts with initial guess provided by user in lp_initial
723pub fn generic_inverse_2d<C: CoordinateStep, P: TransformCoordinates>(
724    xy: &P,
725    step: &C,
726    lp: &mut P,
727    delta_xy_tolerance: f64,
728) {
729    let mut deriv_lam_x = 0.;
730    let mut deriv_lam_y = 0.;
731    let mut deriv_phi_x = 0.;
732    let mut deriv_phi_y = 0.;
733    for i in 0..15 {
734        let mut xy_approx = lp.clone();
735        step.forward(&mut xy_approx);
736        let delta_x = xy_approx.x() - xy.x();
737        let delta_y = xy_approx.y() - xy.y();
738        if fabs(delta_x) < delta_xy_tolerance && fabs(delta_y) < delta_xy_tolerance {
739            return;
740        }
741
742        if i == 0 || fabs(delta_x) > 1e-6 || fabs(delta_y) > 1e-6 {
743            // Compute Jacobian matrix (only if we aren't close to the final
744            // result to speed things a bit)
745            let mut lp2 = Coords::default();
746            let d_lam = if lp.lam() > 0. { -1e-6 } else { 1e-6 };
747            lp2.set_lam(lp.lam() + d_lam);
748            lp2.set_phi(lp.phi());
749            step.forward(&mut lp2);
750            let mut xy2 = lp2;
751            let deriv_x_lam = (xy2.x() - xy_approx.x()) / d_lam;
752            let deriv_y_lam = (xy2.y() - xy_approx.y()) / d_lam;
753
754            let d_phi = if lp.phi() > 0. { -1e-6 } else { 1e-6 };
755            lp2.set_lam(lp.lam());
756            lp2.set_phi(lp.phi() + d_phi);
757            step.forward(&mut lp2);
758            xy2 = lp2;
759            let deriv_x_phi = (xy2.x() - xy_approx.x()) / d_phi;
760            let deriv_y_phi = (xy2.y() - xy_approx.y()) / d_phi;
761
762            // Inverse of Jacobian matrix
763            let det = deriv_x_lam * deriv_y_phi - deriv_x_phi * deriv_y_lam;
764            if det != 0. {
765                deriv_lam_x = deriv_y_phi / det;
766                deriv_lam_y = -deriv_x_phi / det;
767                deriv_phi_x = -deriv_y_lam / det;
768                deriv_phi_y = deriv_x_lam / det;
769            }
770        }
771
772        // Limit the amplitude of correction to avoid overshoots due to
773        // bad initial guess
774        let delta_lam = (delta_x * deriv_lam_x + delta_y * deriv_lam_y).clamp(-0.3, 0.3);
775        lp.set_lam(lp.lam() - delta_lam);
776        if lp.lam() < -PI {
777            lp.set_lam(-PI);
778        } else if lp.lam() > PI {
779            lp.set_lam(PI);
780        }
781
782        let delta_phi = (delta_x * deriv_phi_x + delta_y * deriv_phi_y).clamp(-0.3, 0.3);
783        lp.set_phi(lp.phi() - delta_phi);
784        if lp.phi() < -FRAC_PI_2 {
785            lp.set_phi(-FRAC_PI_2);
786        } else if lp.phi() > FRAC_PI_2 {
787            lp.set_phi(FRAC_PI_2);
788        }
789    }
790}
791
792/// Adjust longitude to be in -180..+180 range (but in radians)
793pub fn adjlon(mut longitude: f64) -> f64 {
794    // Let longitude slightly overshoot, to avoid spurious sign switching at the date line
795    if fabs(longitude) < PI + 1e-12 {
796        return longitude;
797    }
798    // adjust to 0..2pi range
799    longitude += PI;
800    // remove integral # of 'revolutions'*/
801    longitude -= TAU * floor(longitude / TAU);
802    // adjust back to -pi..pi range
803    longitude -= PI;
804
805    longitude
806}
807
808/// Adjusted ArcSine
809pub fn aasin(v: f64) -> f64 {
810    let av = fabs(v);
811    if av >= 1. {
812        if av > ONE_TOL {
813            panic!("Coordinate outside projection domain");
814        }
815        return if v < 0. { -FRAC_PI_2 } else { FRAC_PI_2 };
816    }
817    asin(v)
818}
819
820/// Adjusted ArcCosine
821pub fn aacos(v: f64) -> f64 {
822    let av = fabs(v);
823    if av >= 1. {
824        if av > ONE_TOL {
825            panic!("Coordinate outside projection domain");
826        }
827        return if v < 0. { PI } else { 0. };
828    }
829    acos(v)
830}
831
832/// Adjusted square root
833pub fn asqrt(v: f64) -> f64 {
834    if v <= 0. { 0. } else { sqrt(v) }
835}
836
837/// Adjusted atan2
838pub fn aatan2(n: f64, d: f64) -> f64 {
839    if fabs(n) < ATOL && fabs(d) < ATOL { 0. } else { atan2(n, d) }
840}
841
842/// note: coefficients are always from C_1 to C_n
843/// i.e. C_0 == (0., 0)
844/// n should always be >= 1 though no checks are made
845pub fn zpoly1(z: Complex, c: &[Complex], mut n: usize) -> Complex {
846    let mut t;
847    let mut a = c[n - 1];
848
849    while n > 0 {
850        n -= 1;
851        t = a.r;
852        a = c[n];
853        a.r = a.r + z.r * t - z.i * a.i;
854        a.i = a.i + z.r * a.i + z.i * t;
855    }
856
857    t = a.r;
858    a.r = z.r * t - z.i * a.i;
859    a.i = z.r * a.i + z.i * t;
860
861    a
862}
863
864/// evaluate complex polynomial and derivative
865pub fn zpolyd1(z: Complex, c: &[Complex], mut n: usize, der: &mut Complex) -> Complex {
866    let mut t;
867    let mut first = true;
868
869    let mut a = c[n - 1];
870    let mut b = a;
871    while n > 0 {
872        n -= 1;
873        if first {
874            first = false;
875        } else {
876            t = b.r;
877            b.r = a.r + z.r * t - z.i * b.i;
878            b.i = a.i + z.r * b.i + z.i * t;
879        }
880        t = a.r;
881        a = c[n];
882        a.r = a.r + z.r * t - z.i * a.i;
883        a.i = a.i + z.r * a.i + z.i * t;
884    }
885    t = b.r;
886    b.r = a.r + z.r * (t) - z.i * b.i;
887    b.i = a.i + z.r * b.i + z.i * t;
888    t = a.r;
889    a.r = z.r * (t) - z.i * a.i;
890    a.i = z.r * a.i + z.i * t;
891    *der = b;
892
893    a
894}