use crate::consts::{M_FORTPI, M_HALFPI};
use crate::coord::Lp;
use crate::error::{ProjError, ProjResult};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Gauss {
c: f64,
k: f64,
e: f64,
ratexp: f64,
}
fn srat(esinp: f64, ratexp: f64) -> f64 {
((1.0 - esinp) / (1.0 + esinp)).powf(ratexp)
}
pub fn pj_gauss_ini(e: f64, phi0: f64) -> ProjResult<(Gauss, f64, f64)> {
let es = e * e;
let sphi = phi0.sin();
let mut cphi = phi0.cos();
cphi *= cphi;
let rc = (1.0 - es).sqrt() / (1.0 - es * sphi * sphi);
let c = (1.0 + es * cphi * cphi / (1.0 - es)).sqrt();
if c == 0.0 {
return Err(ProjError::IllegalArgValue);
}
let chi = (sphi / c).asin();
let ratexp = 0.5 * c * e;
let srat_val = srat(e * sphi, ratexp);
if srat_val == 0.0 {
return Err(ProjError::IllegalArgValue);
}
let k = if 0.5 * phi0 + M_FORTPI < 1e-10 {
1.0 / srat_val
} else {
(0.5 * chi + M_FORTPI).tan() / ((0.5 * phi0 + M_FORTPI).tan().powf(c) * srat_val)
};
Ok((Gauss { c, k, e, ratexp }, chi, rc))
}
pub fn pj_gauss(elp: Lp, g: &Gauss) -> Lp {
let phi = 2.0
* (g.k * (0.5 * elp.phi + M_FORTPI).tan().powf(g.c) * srat(g.e * elp.phi.sin(), g.ratexp))
.atan()
- M_HALFPI;
let lam = g.c * elp.lam;
Lp::new(lam, phi)
}
pub fn pj_inv_gauss(slp: Lp, g: &Gauss) -> ProjResult<Lp> {
const MAX_ITER: usize = 20;
const DEL_TOL: f64 = 1e-14;
let lam = slp.lam / g.c;
let num = ((0.5 * slp.phi + M_FORTPI).tan() / g.k).powf(1.0 / g.c);
let mut phi_in = slp.phi;
let mut phi_out = phi_in;
let mut converged = false;
let mut iter = MAX_ITER;
while iter > 0 {
phi_out = 2.0 * (num * srat(g.e * phi_in.sin(), -0.5 * g.e)).atan() - M_HALFPI;
if (phi_out - phi_in).abs() < DEL_TOL {
converged = true;
break;
}
phi_in = phi_out;
iter -= 1;
}
if !converged {
return Err(ProjError::NoConvergence);
}
Ok(Lp::new(lam, phi_out))
}
#[cfg(test)]
mod tests {
use super::*;
fn wgs84_e() -> f64 {
let es = 0.0066943799901413165_f64;
es.sqrt()
}
#[test]
fn round_trip_forward_inverse() {
let e = wgs84_e();
let (g, _chi, _rc) = pj_gauss_ini(e, 0.5).expect("ini");
let cases = [
(0.1_f64, 0.4_f64),
(-0.2, 0.6),
(0.0, 0.5),
(0.3, 0.55),
(-0.1, 0.45),
];
for (lam, phi) in cases {
let s = pj_gauss(Lp::new(lam, phi), &g);
let back = pj_inv_gauss(s, &g).expect("inv");
assert!(
(back.lam - lam).abs() < 1e-12 && (back.phi - phi).abs() < 1e-12,
"lam={} phi={} back=({},{})",
lam,
phi,
back.lam,
back.phi
);
}
}
#[test]
fn ini_outputs_are_sane() {
let e = wgs84_e();
let (_g, chi, rc) = pj_gauss_ini(e, 0.5).expect("ini");
assert!(rc > 0.0, "rc should be positive, got {}", rc);
assert!(chi.is_finite(), "chi should be finite, got {}", chi);
}
}