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}