use crate::datum::HelmertParams;
const ARCSEC_TO_RAD: f64 = std::f64::consts::PI / (180.0 * 3600.0);
pub(crate) fn helmert_forward(params: &HelmertParams, x: f64, y: f64, z: f64) -> (f64, f64, f64) {
let s = 1.0 + params.ds * 1e-6; let rx = params.rx * ARCSEC_TO_RAD;
let ry = params.ry * ARCSEC_TO_RAD;
let rz = params.rz * ARCSEC_TO_RAD;
let xo = params.dx + s * (x - rz * y + ry * z);
let yo = params.dy + s * (rz * x + y - rx * z);
let zo = params.dz + s * (-ry * x + rx * y + z);
(xo, yo, zo)
}
pub(crate) fn helmert_inverse(params: &HelmertParams, x: f64, y: f64, z: f64) -> (f64, f64, f64) {
let inv = params.inverse();
helmert_forward(&inv, x, y, z)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::datum;
use crate::ellipsoid;
use crate::geocentric::{geocentric_to_geodetic, geodetic_to_geocentric};
#[test]
fn identity_with_zero_params() {
let params = HelmertParams::translation(0.0, 0.0, 0.0);
let (xo, yo, zo) = helmert_forward(¶ms, 1000.0, 2000.0, 3000.0);
assert_eq!(xo, 1000.0);
assert_eq!(yo, 2000.0);
assert_eq!(zo, 3000.0);
}
#[test]
fn translation_only() {
let params = HelmertParams::translation(10.0, 20.0, 30.0);
let (xo, yo, zo) = helmert_forward(¶ms, 1000.0, 2000.0, 3000.0);
assert!((xo - 1010.0).abs() < 1e-6);
assert!((yo - 2020.0).abs() < 1e-6);
assert!((zo - 3030.0).abs() < 1e-6);
}
#[test]
fn roundtrip_forward_inverse() {
let params = datum::OSGB36.helmert_to_wgs84().unwrap();
let x = 3_980_000.0;
let y = -100_000.0;
let z = 4_960_000.0;
let (x2, y2, z2) = helmert_forward(params, x, y, z);
let (x3, y3, z3) = helmert_inverse(params, x2, y2, z2);
assert!((x3 - x).abs() < 0.01, "x: {x3} vs {x}");
assert!((y3 - y).abs() < 0.01, "y: {y3} vs {y}");
assert!((z3 - z).abs() < 0.01, "z: {z3} vs {z}");
}
#[test]
fn nad27_to_wgs84_known_point() {
let nad27_params = datum::NAD27.helmert_to_wgs84().unwrap();
let lon = (-90.0_f64).to_radians();
let lat = 45.0_f64.to_radians();
let h = 0.0;
let (x, y, z) = geodetic_to_geocentric(&ellipsoid::CLARKE1866, lon, lat, h);
let (x2, y2, z2) = helmert_forward(nad27_params, x, y, z);
let (lon2, lat2, _h2) = geocentric_to_geodetic(&ellipsoid::WGS84, x2, y2, z2);
let d_lon = (lon2 - lon).to_degrees() * 3600.0; let d_lat = (lat2 - lat).to_degrees() * 3600.0;
assert!(d_lon.abs() < 10.0, "lon shift: {d_lon} arcseconds");
assert!(d_lat.abs() < 10.0, "lat shift: {d_lat} arcseconds");
}
}