use crate::{
CoordinateSystem, Enu, Lle, Meters, Radians, Xyz,
math::{atan2, cos, sin, sqrt},
};
pub(crate) trait Wgs
where
Self: Copy,
{
const A: f64;
const B: f64;
const F: f64 = (Self::A - Self::B) / Self::A;
const E_SQ: f64 = Self::F * (2.0 - Self::F);
}
impl<T, AngularMeasure> CoordinateSystem<AngularMeasure> for T
where
AngularMeasure: From<Radians> + Copy,
Radians: From<AngularMeasure> + Copy,
T: Wgs,
{
fn lle_to_xyz(g: &Lle<Self, AngularMeasure>) -> Xyz {
let lambda: Radians = g.latitude.into();
let phi: Radians = g.longitude.into();
let lambda = lambda.as_float();
let phi = phi.as_float();
let sin_lambda = sin(lambda);
let cos_lambda = cos(lambda);
let sin_phi = sin(phi);
let cos_phi = cos(phi);
let n = Self::A / sqrt(1.0 - Self::E_SQ * sin_lambda * sin_lambda);
Xyz {
x: Meters::new((g.elevation.as_float() + n) * cos_lambda * cos_phi),
y: Meters::new((g.elevation.as_float() + n) * cos_lambda * sin_phi),
z: Meters::new((g.elevation.as_float() + (1.0 - Self::E_SQ) * n) * sin_lambda),
}
}
fn xyz_to_lle(x: &Xyz) -> Lle<Self, AngularMeasure> {
let eps = Self::E_SQ / (1.0 - Self::E_SQ);
let p = sqrt(x.x.as_float() * x.x.as_float() + x.y.as_float() * x.y.as_float());
let q = atan2(x.z.as_float() * Self::A, p * Self::B);
let sin_q = sin(q);
let cos_q = cos(q);
let sin_q_3 = sin_q * sin_q * sin_q;
let cos_q_3 = cos_q * cos_q * cos_q;
let phi = atan2(
x.z.as_float() + eps * Self::B * sin_q_3,
p - Self::E_SQ * Self::A * cos_q_3,
);
let lambda = atan2(x.y.as_float(), x.x.as_float());
let v = Self::A / sqrt(1.0 - Self::E_SQ * sin(phi) * sin(phi));
let h = Meters::new((p / cos(phi)) - v);
Lle::<Self, AngularMeasure>::new(Radians::new(phi).into(), Radians::new(lambda).into(), h)
}
fn xyz_to_enu(g: &Lle<Self, AngularMeasure>, x: &Xyz) -> Enu {
let lambda: Radians = g.latitude.into();
let phi: Radians = g.longitude.into();
let lambda = lambda.as_float();
let phi = phi.as_float();
let sin_lambda = sin(lambda);
let cos_lambda = cos(lambda);
let sin_phi = sin(phi);
let cos_phi = cos(phi);
let xref = Self::lle_to_xyz(g);
let xd = x.x.as_float() - xref.x.as_float();
let yd = x.y.as_float() - xref.y.as_float();
let zd = x.z.as_float() - xref.z.as_float();
Enu {
east: Meters::new(-sin_phi * xd + cos_phi * yd),
north: Meters::new(
-cos_phi * sin_lambda * xd - sin_lambda * sin_phi * yd + cos_lambda * zd,
),
up: Meters::new(
cos_lambda * cos_phi * xd + cos_lambda * sin_phi * yd + sin_lambda * zd,
),
}
}
fn enu_to_xyz(g: &Lle<Self, AngularMeasure>, lt: &Enu) -> Xyz {
let lambda: Radians = g.latitude.into();
let phi: Radians = g.longitude.into();
let lambda = lambda.as_float();
let phi = phi.as_float();
let sin_lambda = sin(lambda);
let cos_lambda = cos(lambda);
let sin_phi = sin(phi);
let cos_phi = cos(phi);
let n = Self::A / sqrt(1.0 - Self::E_SQ * sin_lambda * sin_lambda);
let x0 = (g.elevation.as_float() + n) * cos_lambda * cos_phi;
let y0 = (g.elevation.as_float() + n) * cos_lambda * sin_phi;
let z0 = (g.elevation.as_float() + (1.0 - Self::E_SQ) * n) * sin_lambda;
let east = lt.east.as_float();
let north = lt.north.as_float();
let up = lt.up.as_float();
let xd = -sin_phi * east - cos_phi * sin_lambda * north + cos_lambda * cos_phi * up;
let yd = cos_phi * east - sin_lambda * sin_phi * north + cos_lambda * sin_phi * up;
let zd = cos_lambda * north + sin_lambda * up;
let x = xd + x0;
let y = yd + y0;
let z = zd + z0;
Xyz {
x: Meters::new(x),
y: Meters::new(y),
z: Meters::new(z),
}
}
}