use crate::error::ProjResult;
use crate::math::aux::{pj_auxlat_coeffs, pj_auxlat_convert, pj_auxlat_convert_sc, AuxLat};
use crate::scalar::Scalar;
const PROJ_AUTHALIC_SERIES_CUTOFF: f64 = 0.01;
fn series_valid(n: f64) -> bool {
n.abs() < PROJ_AUTHALIC_SERIES_CUTOFF
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Apa {
pub inv: [f64; 6],
pub fwd: [f64; 6],
pub series_valid: bool,
pub n: f64,
pub e: f64,
pub es: f64,
pub one_es: f64,
}
pub fn pj_qsfn(sinphi: f64, e: f64, one_es: f64) -> f64 {
const EPSILON: f64 = 1e-7;
if e >= EPSILON {
let e_sinphi = 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;
}
one_es * (sinphi / one_minus_e_sinphi_sq + e_sinphi.atanh() / e)
} else {
2.0 * sinphi
}
}
pub fn pj_qsfn_g<S: Scalar>(sinphi: S, e: f64, one_es: f64) -> S {
const EPSILON: f64 = 1e-7;
let one_es_s = S::from_f64(one_es);
if e >= EPSILON {
let e_s = S::from_f64(e);
let e_sinphi = e_s * sinphi;
let one = S::one();
let one_minus = one - e_sinphi * e_sinphi;
let half = S::from_f64(0.5);
let atanh_e_sinphi = half * ((one + e_sinphi) / (one - e_sinphi)).ln();
one_es_s * (sinphi / one_minus + atanh_e_sinphi / e_s)
} else {
S::from_f64(2.0) * sinphi
}
}
pub fn pj_authalic_lat_compute_coeffs(n: f64, e: f64, es: f64, one_es: f64) -> ProjResult<Apa> {
let valid = series_valid(n);
let mut inv = [0.0_f64; 6];
pj_auxlat_coeffs(n, AuxLat::Authalic, AuxLat::Geographic, &mut inv)?;
let mut fwd = [0.0_f64; 6];
if valid {
pj_auxlat_coeffs(n, AuxLat::Geographic, AuxLat::Authalic, &mut fwd)?;
}
Ok(Apa {
inv,
fwd,
series_valid: valid,
n,
e,
es,
one_es,
})
}
pub fn pj_authalic_lat(phi: f64, sinphi: f64, cosphi: f64, apa: &Apa, qp: f64) -> f64 {
if apa.series_valid {
pj_auxlat_convert_sc(phi, sinphi, cosphi, &apa.fwd, 6)
} else {
let q = pj_qsfn(sinphi, apa.e, apa.one_es);
let mut ratio = q / qp;
if ratio.abs() > 1.0 {
ratio = if ratio > 0.0 { 1.0 } else { -1.0 };
}
ratio.asin()
}
}
pub fn pj_authalic_lat_inverse(beta: f64, apa: &Apa, qp: f64) -> f64 {
let mut phi = pj_auxlat_convert(beta, &apa.inv, 6);
if apa.series_valid {
return phi;
}
let q = beta.sin() * qp;
let q_div_one_minus_es = q / apa.one_es;
for _ in 0..10 {
let sinphi = phi.sin();
let cosphi = phi.cos();
let one_minus_es_sin2phi = 1.0 - apa.es * (sinphi * sinphi);
let dphi = (one_minus_es_sin2phi * one_minus_es_sin2phi) / (2.0 * cosphi)
* (q_div_one_minus_es
- sinphi / one_minus_es_sin2phi
- (apa.e * sinphi).atanh() / apa.e);
if dphi.abs() >= 1e-15 {
phi += dphi;
} else {
break;
}
}
phi
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ellipsoid::Ellipsoid;
#[test]
fn q_at_equator_is_zero() {
let ell = Ellipsoid::named("WGS84").expect("wgs84");
assert_eq!(pj_qsfn(0.0, ell.e, ell.one_es), 0.0);
}
#[test]
fn qp_positive() {
let ell = Ellipsoid::named("WGS84").expect("wgs84");
let qp = pj_qsfn(1.0, ell.e, ell.one_es);
assert!(qp > 0.0);
assert!(qp.is_finite());
assert!(qp > 1.99 && qp < 2.0);
}
#[test]
fn authalic_round_trip() {
let ell = Ellipsoid::named("WGS84").expect("wgs84");
let apa = pj_authalic_lat_compute_coeffs(ell.n, ell.e, ell.es, ell.one_es).expect("apa");
let qp = pj_qsfn(1.0, ell.e, ell.one_es);
for &phi in &[0.1, 0.5, 1.0, -0.7, 0.0, 1.4, -1.3] {
let xi = pj_authalic_lat(phi, phi.sin(), phi.cos(), &apa, qp);
let back = pj_authalic_lat_inverse(xi, &apa, qp);
assert!((back - phi).abs() < 1e-12, "phi={} back={}", phi, back);
}
}
#[test]
fn closed_form_round_trip_large_n() {
let f = 1.0_f64 / 30.0;
let es = 2.0 * f - f * f;
let e = es.sqrt();
let one_es = 1.0 - es;
let n = f / (2.0 - f);
let apa = pj_authalic_lat_compute_coeffs(n, e, es, one_es).expect("apa");
assert!(!apa.series_valid);
let qp = pj_qsfn(1.0, e, one_es);
for &phi in &[0.1, 0.5, 1.0, -0.7, 1.3] {
let xi = pj_authalic_lat(phi, phi.sin(), phi.cos(), &apa, qp);
let back = pj_authalic_lat_inverse(xi, &apa, qp);
assert!((back - phi).abs() < 1e-12, "phi={} back={}", phi, back);
}
}
#[test]
fn sphere_identity() {
assert_eq!(pj_qsfn(0.3, 0.0, 1.0), 0.6);
let apa = pj_authalic_lat_compute_coeffs(0.0, 0.0, 0.0, 1.0).expect("apa");
let qp = pj_qsfn(1.0, 0.0, 1.0);
for &phi in &[0.1, 0.5, -0.7, 1.0] {
let xi = pj_authalic_lat(phi, phi.sin(), phi.cos(), &apa, qp);
assert!((xi - phi).abs() < 1e-13, "phi={} xi={}", phi, xi);
let back = pj_authalic_lat_inverse(xi, &apa, qp);
assert!((back - phi).abs() < 1e-13, "phi={} back={}", phi, back);
}
}
#[test]
fn qsfn_pole_guard() {
let ell = Ellipsoid::named("WGS84").expect("wgs84");
let qp = pj_qsfn(1.0, ell.e, ell.one_es);
assert!(qp.is_finite());
}
}