pub fn pj_sinhpsi2tanphi(taup: f64, e: f64) -> f64 {
const NUMIT: i32 = 5;
let rooteps = f64::EPSILON.sqrt();
let tol = rooteps / 10.0;
let tmax = 2.0 / rooteps;
let e2m = 1.0 - e * e;
let stol = tol * taup.abs().max(1.0);
let mut tau = if taup.abs() > 70.0 {
taup * (e * e.atanh()).exp()
} else {
taup / e2m
};
#[allow(clippy::neg_cmp_op_on_partial_ord)]
if !(tau.abs() < tmax) {
return tau;
}
let mut i = NUMIT;
while i > 0 {
let tau1 = (1.0 + tau * tau).sqrt();
let sig = (e * (e * tau / tau1).atanh()).sinh();
let taupa = (1.0 + sig * sig).sqrt() * tau - sig * tau1;
let dtau = ((taup - taupa) * (1.0 + e2m * tau * tau))
/ (e2m * tau1 * (1.0 + taupa * taupa).sqrt());
tau += dtau;
#[allow(clippy::neg_cmp_op_on_partial_ord)]
if !(dtau.abs() >= stol) {
break;
}
i -= 1;
}
tau
}
pub fn pj_phi2(ts0: f64, e: f64) -> f64 {
pj_sinhpsi2tanphi((1.0 / ts0 - ts0) / 2.0, e).atan()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::tsfn::pj_tsfn;
const WGS84_E: f64 = 0.0818191908426215;
#[test]
fn wgs84_eccentricity_matches_es_sqrt() {
let es = 0.0066943799901413165_f64;
let e = es.sqrt();
assert!((e - WGS84_E).abs() < 1e-15, "e = {e}");
}
#[test]
fn sinhpsi2tanphi_zero_maps_to_zero() {
let e = WGS84_E;
let tau = pj_sinhpsi2tanphi(0.0, e);
assert!(tau.abs() < 1e-15, "expected ~0, got {tau}");
}
#[test]
fn phi2_at_equator_is_zero() {
let e = WGS84_E;
let ts = pj_tsfn(0.0, 0.0_f64.sin(), e);
assert!((ts - 1.0).abs() < 1e-15, "expected ts ~1, got {ts}");
let phi = pj_phi2(ts, e);
assert!(phi.abs() < 1e-15, "expected phi ~0, got {phi}");
}
#[test]
fn round_trip_selected_latitudes() {
let e = WGS84_E;
let phis = [-1.4_f64, -1.0, -0.5, -0.1, 0.0, 0.1, 0.5, 1.0, 1.4];
for &phi in &phis {
let ts = pj_tsfn(phi, phi.sin(), e);
let recovered = pj_phi2(ts, e);
assert!(
(recovered - phi).abs() < 1e-12,
"round trip failed: phi = {phi}, recovered = {recovered}, ts = {ts}"
);
}
}
#[test]
fn round_trip_dense_sweep() {
let e = WGS84_E;
let mut steps = 0_u32;
let mut phi = -1.45_f64;
while phi <= 1.45 {
let ts = pj_tsfn(phi, phi.sin(), e);
let recovered = pj_phi2(ts, e);
assert!(
(recovered - phi).abs() < 1e-11,
"dense round trip failed: phi = {phi}, recovered = {recovered}, ts = {ts}"
);
steps += 1;
phi += 0.01;
}
assert!(
steps > 250,
"expected a dense sweep, only ran {steps} steps"
);
}
}