use crate::math::{es, tauf, taupf, A, F};
pub(crate) const K0: f64 = 0.994;
pub(crate) const FALSE_EN: f64 = 2_000_000.0;
fn c_ps() -> f64 {
(1.0 - F) * (es() * es().atanh()).exp()
}
pub(crate) fn forward(north: bool, lat: f64, lon: f64) -> (f64, f64) {
let lat_p = if north { lat } else { -lat };
let rho = if lat_p == 90.0 {
0.0
} else {
let tau = lat_p.to_radians().tan();
let taup = taupf(tau);
let tprime = 1.0 / (taup + (1.0 + taup * taup).sqrt());
2.0 * A * K0 * tprime / c_ps()
};
let (slam, clam) = lon.to_radians().sin_cos();
let x = FALSE_EN + rho * slam;
let y = if north {
FALSE_EN - rho * clam
} else {
FALSE_EN + rho * clam
};
(x, y)
}
pub(crate) fn reverse(north: bool, easting: f64, northing: f64) -> (f64, f64) {
let x = easting - FALSE_EN;
let yp = if north {
FALSE_EN - northing
} else {
northing - FALSE_EN
};
let rho = x.hypot(yp);
let lat_p = if rho == 0.0 {
90.0
} else {
let tprime = rho * c_ps() / (2.0 * A * K0);
let taup = (1.0 / tprime - tprime) / 2.0;
tauf(taup).atan().to_degrees()
};
let lon = x.atan2(yp).to_degrees();
let lat = if north { lat_p } else { -lat_p };
(lat, lon)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constants_pinned() {
assert_eq!(K0, 0.994);
assert_eq!(FALSE_EN, 2_000_000.0);
}
#[test]
fn poles_project_to_the_exact_grid_center() {
assert_eq!(forward(true, 90.0, 0.0), (2_000_000.0, 2_000_000.0));
assert_eq!(forward(false, -90.0, 77.0), (2_000_000.0, 2_000_000.0));
let (lat, lon) = reverse(true, 2_000_000.0, 2_000_000.0);
assert_eq!((lat, lon), (90.0, 0.0));
}
#[test]
fn round_trip_is_tight() {
for &(north, lat, lon) in &[
(true, 87.3, -122.0),
(true, 84.001, 179.5),
(false, -85.75, 31.0),
(false, -80.001, -0.25),
] {
let (x, y) = forward(north, lat, lon);
let (lat2, lon2) = reverse(north, x, y);
let (x2, y2) = forward(north, lat2, lon2);
assert!((x - x2).abs() < 1e-7, "x drift at {lat} {lon}");
assert!((y - y2).abs() < 1e-7, "y drift at {lat} {lon}");
}
}
}