use crate::position::Ecef;
use crate::vector::{fma, Vec3};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Helmert7 {
pub tx: f64,
pub ty: f64,
pub tz: f64,
pub rx: f64,
pub ry: f64,
pub rz: f64,
pub scale: f64,
}
const ARCSEC: f64 = core::f64::consts::PI / (180.0 * 3600.0);
impl Helmert7 {
#[inline]
pub const fn new(tx: f64, ty: f64, tz: f64, rx: f64, ry: f64, rz: f64, scale: f64) -> Self {
Helmert7 {
tx,
ty,
tz,
rx,
ry,
rz,
scale,
}
}
#[inline]
pub fn position_vector(
tx: f64,
ty: f64,
tz: f64,
rx_arcsec: f64,
ry_arcsec: f64,
rz_arcsec: f64,
ds_ppm: f64,
) -> Self {
Helmert7::new(
tx,
ty,
tz,
rx_arcsec * ARCSEC,
ry_arcsec * ARCSEC,
rz_arcsec * ARCSEC,
ds_ppm * 1e-6,
)
}
#[inline]
pub fn coordinate_frame(
tx: f64,
ty: f64,
tz: f64,
rx_arcsec: f64,
ry_arcsec: f64,
rz_arcsec: f64,
ds_ppm: f64,
) -> Self {
Helmert7::position_vector(tx, ty, tz, -rx_arcsec, -ry_arcsec, -rz_arcsec, ds_ppm)
}
#[inline]
pub fn apply(&self, p: Ecef) -> Ecef {
let m = 1.0 + self.scale;
Ecef::new(
fma(m, fma(self.ry, p.z, fma(self.rz, -p.y, p.x)), self.tx),
fma(m, fma(self.rx, -p.z, fma(self.rz, p.x, p.y)), self.ty),
fma(m, fma(self.rx, p.y, fma(self.ry, -p.x, p.z)), self.tz),
)
}
pub fn apply_inverse(&self, p: Ecef) -> Ecef {
let m = 1.0 + self.scale;
let q = Vec3::new((p.x - self.tx) / m, (p.y - self.ty) / m, (p.z - self.tz) / m);
let (rx, ry, rz) = (self.rx, self.ry, self.rz);
let det = 1.0 + rx * rx + ry * ry + rz * rz;
let x = (q.x * (1.0 + rx * rx) + q.y * (rz + rx * ry) + q.z * (rx * rz - ry)) / det;
let y = (q.x * (rx * ry - rz) + q.y * (1.0 + ry * ry) + q.z * (rx + ry * rz)) / det;
let z = (q.x * (rx * rz + ry) + q.y * (ry * rz - rx) + q.z * (1.0 + rz * rz)) / det;
Ecef::new(x, y, z)
}
#[inline]
pub fn inverse(&self) -> Self {
Helmert7::new(-self.tx, -self.ty, -self.tz, -self.rx, -self.ry, -self.rz, -self.scale)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_and_translation() {
let p = Ecef::new(1000.0, 2000.0, 3000.0);
assert_eq!(Helmert7::default().apply(p), p);
let t = Helmert7::new(10.0, -5.0, 2.0, 0.0, 0.0, 0.0, 0.0);
assert_eq!(t.apply(p), Ecef::new(1010.0, 1995.0, 3002.0));
}
#[test]
fn exact_inverse_round_trips_and_first_order_is_close() {
let h = Helmert7::position_vector(-446.448, 125.157, -542.060, -0.1502, -0.2470, -0.8421, 20.4894);
let p = Ecef::new(3_874_938.0, 116_218.0, 5_047_168.0);
let exact = h.apply_inverse(h.apply(p));
assert!(exact.distance_to(p) < 1e-6, "{exact:?}");
let approx = h.inverse().apply(h.apply(p));
assert!(approx.distance_to(p) < 0.02, "{approx:?}");
}
#[test]
fn conventions_differ_by_rotation_sign() {
let a = Helmert7::position_vector(0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 0.0);
let b = Helmert7::coordinate_frame(0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 0.0);
assert_eq!(a.rx, -b.rx);
assert!((a.rz - 3.0 * ARCSEC).abs() < 1e-18);
}
}