use super::Proj;
use crate::proj::{Complex, CoordinateStep, Coords, TransformCoordinates};
use alloc::{vec, vec::Vec};
use core::f64::consts::{FRAC_PI_2, PI, TAU};
use libm::{acos, asin, atan, atan2, atanh, cos, exp, fabs, floor, fmax, sin, sinh, sqrt};
const ONE_TOL: f64 = 1.00000000000001;
const ATOL: f64 = 1e-50;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum AuxLat {
GEOGRAPHIC = 0,
PARAMETRIC = 1,
GEOCENTRIC = 2,
RECTIFYING = 3,
CONFORMAL = 4,
AUTHALIC = 5,
NUMBER = 6,
}
impl AuxLat {
pub const ORDER: AuxLat = AuxLat::NUMBER;
}
pub fn sinhpsi2tanphi(taup: f64, e: f64) -> f64 {
let rooteps: f64 = sqrt(f64::EPSILON);
let tol: f64 = rooteps / 10.; let tmax: f64 = 2. / rooteps; let e2m: f64 = 1. - e * e;
let stol: f64 = tol * fmax(1.0, fabs(taup));
let mut tau: f64 = if fabs(taup) > 70. { taup * exp(e * atanh(e)) } else { taup / e2m };
if fabs(tau) >= tmax {
return tau;
}
let mut i: i32 = 5;
loop {
let tau1 = sqrt(1. + tau * tau);
let sig = sinh(e * atanh(e * tau / tau1));
let taupa = sqrt(1. + sig * sig) * tau - sig * tau1;
let dtau =
(taup - taupa) * (1. + e2m * (tau * tau)) / (e2m * tau1 * sqrt(1. + taupa * taupa));
tau += dtau;
if fabs(dtau) < stol {
break;
}
i -= 1;
if i == 0 {
panic!("Newton's method failed to converge");
}
}
tau
}
pub fn phi2(ts0: f64, e: f64) -> f64 {
atan(sinhpsi2tanphi((1. / ts0 - ts0) / 2., e))
}
pub fn _msfn(sinphi: f64, cosphi: f64, es: f64) -> f64 {
cosphi / sqrt(1. - es * sinphi * sinphi)
}
pub fn msfn(phi: f64, e2: f64) -> f64 {
let sinphi = sin(phi);
let cosphi = cos(phi);
_msfn(sinphi, cosphi, e2)
}
pub fn authalic_lat_inverse(beta: f64, apa: &[f64], proj: &Proj, qp: f64) -> f64 {
let mut phi = auxlat_convert(beta, apa, AuxLat::ORDER as i32);
if authalic_series_valid(proj.n) {
return phi;
}
let q = sin(beta) * qp;
let q_div_one_minus_es = q / proj.one_es;
for _ in 0..10 {
let sinphi = sin(phi);
let cosphi = cos(phi);
let one_minus_es_sin2phi = 1. - proj.es * (sinphi * sinphi);
let dphi = (one_minus_es_sin2phi * one_minus_es_sin2phi) / (2. * cosphi)
* (q_div_one_minus_es
- sinphi / one_minus_es_sin2phi
- atanh(proj.e * sinphi) / proj.e);
if fabs(dphi) < 1e-15 {
break;
}
phi += dphi;
}
phi
}
pub fn auxlat_convert(zeta: f64, f: &[f64], k: i32) -> f64 {
auxlat_convert_mid(zeta, sin(zeta), cos(zeta), f, k)
}
pub fn auxlat_convert_mid(zeta: f64, szeta: f64, czeta: f64, f: &[f64], k: i32) -> f64 {
zeta + clenshaw(szeta, czeta, f, k)
}
pub fn auxlat_convert_full(
szeta: f64,
czeta: f64,
seta: &mut f64,
ceta: &mut f64,
f: &[f64],
k: i32,
) {
let delta: f64 = clenshaw(szeta, czeta, f, k);
let sdelta = sin(delta);
let cdelta = cos(delta);
*seta = szeta * cdelta + czeta * sdelta;
*ceta = czeta * cdelta - szeta * sdelta;
}
pub fn rectifying_radius(n: f64) -> f64 {
let coeff_rad = vec![1., 1. / 4., 1. / 64., 1. / 256.];
polyval(n * n, &coeff_rad, 3) / (1. + n)
}
pub fn enfn(n: f64) -> Vec<f64> {
let l_max = AuxLat::ORDER as usize;
let mut en = vec![0.; 2 * l_max + 1];
en[0] = rectifying_radius(n);
auxlat_coeffs(n, AuxLat::GEOGRAPHIC, AuxLat::RECTIFYING, en[1..].as_mut());
auxlat_coeffs(n, AuxLat::RECTIFYING, AuxLat::GEOGRAPHIC, en[1 + l_max..].as_mut());
en
}
pub fn mlfn(phi: f64, sphi: f64, cphi: f64, en: &[f64]) -> f64 {
en[0] * auxlat_convert_mid(phi, sphi, cphi, en[1..].as_ref(), 0)
}
pub fn inv_mlfn(mu: f64, en: &[f64]) -> f64 {
let l_max = AuxLat::ORDER as usize;
auxlat_convert(mu / en[0], &en[(1 + l_max)..], AuxLat::ORDER as i32)
}
pub fn clenshaw(szeta: f64, czeta: f64, f: &[f64], mut k: i32) -> f64 {
let mut u0 = 0.; let mut u1 = 0.; let x = 2. * (czeta - szeta) * (czeta + szeta); while k > 0 {
k -= 1;
let t = x * u0 - u1 + f.get(k as usize).copied().unwrap_or(0.0);
u1 = u0;
u0 = t;
}
2. * szeta * czeta * u0 }
pub fn authalic_lat_q(sinphi: f64, proj: &Proj) -> f64 {
if proj.e >= 1e-7 {
let e_sinphi = proj.e * sinphi;
let one_minus_e_sinphi_sq = 1.0 - e_sinphi * e_sinphi;
if one_minus_e_sinphi_sq == 0.0 {
return f64::INFINITY;
}
proj.one_es * (sinphi / one_minus_e_sinphi_sq + atanh(e_sinphi) / proj.e)
} else {
2. * sinphi
}
}
pub fn authalic_lat_compute_coeffs(n: f64) -> Vec<f64> {
let l_max = AuxLat::ORDER as usize;
let apa_size = l_max * if authalic_series_valid(n) { 2 } else { 1 };
let mut apa: Vec<f64> = vec![0.; apa_size];
auxlat_coeffs(n, AuxLat::AUTHALIC, AuxLat::GEOGRAPHIC, &mut apa);
if authalic_series_valid(n) {
auxlat_coeffs(n, AuxLat::GEOGRAPHIC, AuxLat::AUTHALIC, &mut apa[l_max..]);
}
apa
}
pub fn authalic_lat(phi: f64, sinphi: f64, cosphi: f64, apa: &[f64], proj: &Proj, qp: f64) -> f64 {
if authalic_series_valid(proj.n) {
let l_max = AuxLat::ORDER as usize;
auxlat_convert_mid(phi, sinphi, cosphi, &apa[l_max..], 0)
} else {
let q = authalic_lat_q(sinphi, proj);
let mut ratio = q / qp;
if fabs(ratio) > 1. {
ratio = if ratio > 0. { 1. } else { -1. };
}
asin(ratio)
}
}
pub fn authalic_series_valid(n: f64) -> bool {
fabs(n) < 0.01
}
pub fn auxlat_coeffs(n: f64, auxin: AuxLat, auxout: AuxLat, out: &mut [f64]) {
let l_max = 6;
let coeffs: [f64; 150] = [
3.0 / 2.0,
-27.0 / 32.0,
269.0 / 512.0,
21.0 / 16.0,
-55.0 / 32.0,
6759.0 / 4096.0,
151.0 / 96.0,
-417.0 / 128.0,
1097.0 / 512.0,
-15543.0 / 2560.0,
8011.0 / 2560.0,
293393.0 / 61440.0,
2.0,
-2.0 / 3.0,
-2.0,
116.0 / 45.0,
26.0 / 45.0,
-2854.0 / 675.0,
7.0 / 3.0,
-8.0 / 5.0,
-227.0 / 45.0,
2704.0 / 315.0,
2323.0 / 945.0,
56.0 / 15.0,
-136.0 / 35.0,
-1262.0 / 105.0,
73814.0 / 2835.0,
4279.0 / 630.0,
-332.0 / 35.0,
-399572.0 / 14175.0,
4174.0 / 315.0,
-144838.0 / 6237.0,
601676.0 / 22275.0,
4.0 / 3.0,
4.0 / 45.0,
-16.0 / 35.0,
-2582.0 / 14175.0,
60136.0 / 467775.0,
28112932.0 / 212837625.0,
46.0 / 45.0,
152.0 / 945.0,
-11966.0 / 14175.0,
-21016.0 / 51975.0,
251310128.0 / 638512875.0,
3044.0 / 2835.0,
3802.0 / 14175.0,
-94388.0 / 66825.0,
-8797648.0 / 10945935.0,
6059.0 / 4725.0,
41072.0 / 93555.0,
-1472637812.0 / 638512875.0,
768272.0 / 467775.0,
455935736.0 / 638512875.0,
4210684958.0 / 1915538625.0,
-3.0 / 2.0,
9.0 / 16.0,
-3.0 / 32.0,
15.0 / 16.0,
-15.0 / 32.0,
135.0 / 2048.0,
-35.0 / 48.0,
105.0 / 256.0,
315.0 / 512.0,
-189.0 / 512.0,
-693.0 / 1280.0,
1001.0 / 2048.0,
1.0 / 2.0,
-2.0 / 3.0,
5.0 / 16.0,
41.0 / 180.0,
-127.0 / 288.0,
7891.0 / 37800.0,
13.0 / 48.0,
-3.0 / 5.0,
557.0 / 1440.0,
281.0 / 630.0,
-1983433.0 / 1935360.0,
61.0 / 240.0,
-103.0 / 140.0,
15061.0 / 26880.0,
167603.0 / 181440.0,
49561.0 / 161280.0,
-179.0 / 168.0,
6601661.0 / 7257600.0,
34729.0 / 80640.0,
-3418889.0 / 1995840.0,
212378941.0 / 319334400.0,
-2.0,
2.0 / 3.0,
4.0 / 3.0,
-82.0 / 45.0,
32.0 / 45.0,
4642.0 / 4725.0,
5.0 / 3.0,
-16.0 / 15.0,
-13.0 / 9.0,
904.0 / 315.0,
-1522.0 / 945.0,
-26.0 / 15.0,
34.0 / 21.0,
8.0 / 5.0,
-12686.0 / 2835.0,
1237.0 / 630.0,
-12.0 / 5.0,
-24832.0 / 14175.0,
-734.0 / 315.0,
109598.0 / 31185.0,
444337.0 / 155925.0,
-1.0 / 2.0,
2.0 / 3.0,
-37.0 / 96.0,
1.0 / 360.0,
81.0 / 512.0,
-96199.0 / 604800.0,
-1.0 / 48.0,
-1.0 / 15.0,
437.0 / 1440.0,
-46.0 / 105.0,
1118711.0 / 3870720.0,
-17.0 / 480.0,
37.0 / 840.0,
209.0 / 4480.0,
-5569.0 / 90720.0,
-4397.0 / 161280.0,
11.0 / 504.0,
830251.0 / 7257600.0,
-4583.0 / 161280.0,
108847.0 / 3991680.0,
-20648693.0 / 638668800.0,
-4.0 / 3.0,
-4.0 / 45.0,
88.0 / 315.0,
538.0 / 4725.0,
20824.0 / 467775.0,
-44732.0 / 2837835.0,
34.0 / 45.0,
8.0 / 105.0,
-2482.0 / 14175.0,
-37192.0 / 467775.0,
-12467764.0 / 212837625.0,
-1532.0 / 2835.0,
-898.0 / 14175.0,
54968.0 / 467775.0,
100320856.0 / 1915538625.0,
6007.0 / 14175.0,
24496.0 / 467775.0,
-5884124.0 / 70945875.0,
-23356.0 / 66825.0,
-839792.0 / 19348875.0,
570284222.0 / 1915538625.0,
];
let ptrs: [usize; 37] = [
0, 0, 0, 0, 12, 33, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 66, 66, 66, 66, 87,
87, 108, 108, 108, 129, 129, 129, 150, 150, 150, 150, 150, 150,
];
assert!(
auxin >= AuxLat::GEOGRAPHIC
&& auxin < AuxLat::NUMBER
&& auxout >= AuxLat::GEOGRAPHIC
&& auxout < AuxLat::NUMBER,
"Bad specification for auxiliary latitude"
);
let k = AuxLat::NUMBER as usize * auxout as usize + auxin as usize;
let mut o = ptrs[k];
assert!(o != ptrs[k + 1], "Unsupported conversion between auxiliary latitudes");
let mut d = n;
let n2 = n * n;
if auxin <= AuxLat::RECTIFYING && auxout <= AuxLat::RECTIFYING {
for l in 0..l_max {
let m = (l_max - l - 1) / 2; out[l as usize] = d * polyval(n2, &coeffs[o..], m);
o += m as usize + 1;
d *= n;
}
} else {
for l in 0..l_max {
let m = l_max - l - 1; out[l as usize] = d * polyval(n, &coeffs[o..], m);
o += m as usize + 1;
d *= n;
}
}
assert!(o == ptrs[k + 1]);
}
pub fn polyval(x: f64, p: &[f64], mut m: i32) -> f64 {
let mut y = if m < 0 { 0. } else { p[m as usize] };
while m > 0 {
m -= 1;
y = y * x + p[m as usize];
}
y
}
pub fn tsfn(phi: f64, sinphi: f64, e: f64) -> f64 {
let cosphi = cos(phi);
exp(e * atanh(e * sinphi))
* (if sinphi > 0. { cosphi / (1. + sinphi) } else { (1. - sinphi) / cosphi })
}
pub fn generic_inverse_2d<C: CoordinateStep, P: TransformCoordinates>(
xy: &P,
step: &C,
lp: &mut P,
delta_xy_tolerance: f64,
) {
let mut deriv_lam_x = 0.;
let mut deriv_lam_y = 0.;
let mut deriv_phi_x = 0.;
let mut deriv_phi_y = 0.;
for i in 0..15 {
let mut xy_approx = lp.clone();
step.forward(&mut xy_approx);
let delta_x = xy_approx.x() - xy.x();
let delta_y = xy_approx.y() - xy.y();
if fabs(delta_x) < delta_xy_tolerance && fabs(delta_y) < delta_xy_tolerance {
return;
}
if i == 0 || fabs(delta_x) > 1e-6 || fabs(delta_y) > 1e-6 {
let mut lp2 = Coords::default();
let d_lam = if lp.lam() > 0. { -1e-6 } else { 1e-6 };
lp2.set_lam(lp.lam() + d_lam);
lp2.set_phi(lp.phi());
step.forward(&mut lp2);
let mut xy2 = lp2;
let deriv_x_lam = (xy2.x() - xy_approx.x()) / d_lam;
let deriv_y_lam = (xy2.y() - xy_approx.y()) / d_lam;
let d_phi = if lp.phi() > 0. { -1e-6 } else { 1e-6 };
lp2.set_lam(lp.lam());
lp2.set_phi(lp.phi() + d_phi);
step.forward(&mut lp2);
xy2 = lp2;
let deriv_x_phi = (xy2.x() - xy_approx.x()) / d_phi;
let deriv_y_phi = (xy2.y() - xy_approx.y()) / d_phi;
let det = deriv_x_lam * deriv_y_phi - deriv_x_phi * deriv_y_lam;
if det != 0. {
deriv_lam_x = deriv_y_phi / det;
deriv_lam_y = -deriv_x_phi / det;
deriv_phi_x = -deriv_y_lam / det;
deriv_phi_y = deriv_x_lam / det;
}
}
let delta_lam = (delta_x * deriv_lam_x + delta_y * deriv_lam_y).clamp(-0.3, 0.3);
lp.set_lam(lp.lam() - delta_lam);
if lp.lam() < -PI {
lp.set_lam(-PI);
} else if lp.lam() > PI {
lp.set_lam(PI);
}
let delta_phi = (delta_x * deriv_phi_x + delta_y * deriv_phi_y).clamp(-0.3, 0.3);
lp.set_phi(lp.phi() - delta_phi);
if lp.phi() < -FRAC_PI_2 {
lp.set_phi(-FRAC_PI_2);
} else if lp.phi() > FRAC_PI_2 {
lp.set_phi(FRAC_PI_2);
}
}
}
pub fn adjlon(mut longitude: f64) -> f64 {
if fabs(longitude) < PI + 1e-12 {
return longitude;
}
longitude += PI;
longitude -= TAU * floor(longitude / TAU);
longitude -= PI;
longitude
}
pub fn aasin(v: f64) -> f64 {
let av = fabs(v);
if av >= 1. {
if av > ONE_TOL {
panic!("Coordinate outside projection domain");
}
return if v < 0. { -FRAC_PI_2 } else { FRAC_PI_2 };
}
asin(v)
}
pub fn aacos(v: f64) -> f64 {
let av = fabs(v);
if av >= 1. {
if av > ONE_TOL {
panic!("Coordinate outside projection domain");
}
return if v < 0. { PI } else { 0. };
}
acos(v)
}
pub fn asqrt(v: f64) -> f64 {
if v <= 0. { 0. } else { sqrt(v) }
}
pub fn aatan2(n: f64, d: f64) -> f64 {
if fabs(n) < ATOL && fabs(d) < ATOL { 0. } else { atan2(n, d) }
}
pub fn zpoly1(z: Complex, c: &[Complex], mut n: usize) -> Complex {
let mut t;
let mut a = c[n - 1];
while n > 0 {
n -= 1;
t = a.r;
a = c[n];
a.r = a.r + z.r * t - z.i * a.i;
a.i = a.i + z.r * a.i + z.i * t;
}
t = a.r;
a.r = z.r * t - z.i * a.i;
a.i = z.r * a.i + z.i * t;
a
}
pub fn zpolyd1(z: Complex, c: &[Complex], mut n: usize, der: &mut Complex) -> Complex {
let mut t;
let mut first = true;
let mut a = c[n - 1];
let mut b = a;
while n > 0 {
n -= 1;
if first {
first = false;
} else {
t = b.r;
b.r = a.r + z.r * t - z.i * b.i;
b.i = a.i + z.r * b.i + z.i * t;
}
t = a.r;
a = c[n];
a.r = a.r + z.r * t - z.i * a.i;
a.i = a.i + z.r * a.i + z.i * t;
}
t = b.r;
b.r = a.r + z.r * (t) - z.i * b.i;
b.i = a.i + z.r * b.i + z.i * t;
t = a.r;
a.r = z.r * (t) - z.i * a.i;
a.i = z.r * a.i + z.i * t;
*der = b;
a
}