use crate::vector::Vec3;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Geodetic {
pub lat_deg: f64,
pub lon_deg: f64,
pub height_m: f64,
}
impl Geodetic {
#[inline]
pub const fn new(lat_deg: f64, lon_deg: f64, height_m: f64) -> Self {
Geodetic {
lat_deg,
lon_deg,
height_m,
}
}
#[inline]
pub fn lat_rad(self) -> f64 {
self.lat_deg.to_radians()
}
#[inline]
pub fn lon_rad(self) -> f64 {
self.lon_deg.to_radians()
}
#[inline]
pub const fn with_height(self, height_m: f64) -> Self {
Geodetic { height_m, ..self }
}
pub fn normalized(self) -> Geodetic {
let mut lon = self.lon_deg.rem_euclid(360.0);
if lon > 180.0 {
lon -= 360.0;
}
Geodetic::new(self.lat_deg.clamp(-90.0, 90.0), lon, self.height_m)
}
pub fn antipode(self) -> Geodetic {
Geodetic::new(-self.lat_deg, self.lon_deg + 180.0, self.height_m).normalized()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Ecef {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Ecef {
#[inline]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Ecef { x, y, z }
}
#[inline]
pub const fn vec(self) -> Vec3 {
Vec3::new(self.x, self.y, self.z)
}
#[inline]
pub fn distance_to(self, o: Ecef) -> f64 {
(self.vec() - o.vec()).norm()
}
#[inline]
pub fn offset(self, d: Vec3) -> Ecef {
Ecef::from(self.vec() + d)
}
#[inline]
pub const fn to_array(self) -> [f64; 3] {
[self.x, self.y, self.z]
}
}
impl From<Vec3> for Ecef {
#[inline]
fn from(v: Vec3) -> Self {
Ecef::new(v.x, v.y, v.z)
}
}
impl From<[f64; 3]> for Ecef {
#[inline]
fn from([x, y, z]: [f64; 3]) -> Self {
Ecef::new(x, y, z)
}
}
impl From<Ecef> for [f64; 3] {
#[inline]
fn from(e: Ecef) -> Self {
e.to_array()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Eci {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Eci {
#[inline]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Eci { x, y, z }
}
#[inline]
pub const fn vec(self) -> Vec3 {
Vec3::new(self.x, self.y, self.z)
}
#[inline]
pub const fn to_array(self) -> [f64; 3] {
[self.x, self.y, self.z]
}
}
impl From<Vec3> for Eci {
#[inline]
fn from(v: Vec3) -> Self {
Eci::new(v.x, v.y, v.z)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::close;
#[test]
fn geodetic_normalisation_and_antipode() {
let g = Geodetic::new(95.0, 190.0, 5.0).normalized();
assert_eq!(g, Geodetic::new(90.0, -170.0, 5.0));
assert_eq!(Geodetic::new(0.0, 180.0, 0.0).normalized().lon_deg, 180.0);
assert_eq!(Geodetic::new(0.0, -180.0, 0.0).normalized().lon_deg, 180.0);
assert_eq!(
Geodetic::new(10.0, 20.0, 1.0).antipode(),
Geodetic::new(-10.0, -160.0, 1.0)
);
assert_eq!(
Geodetic::new(10.0, 20.0, 1.0).antipode().antipode(),
Geodetic::new(10.0, 20.0, 1.0)
);
assert_eq!(Geodetic::new(1.0, 2.0, 3.0).with_height(9.0).height_m, 9.0);
assert!(close(
Geodetic::new(90.0, 180.0, 0.0).lat_rad(),
core::f64::consts::FRAC_PI_2,
1e-15
));
assert!(close(
Geodetic::new(90.0, 180.0, 0.0).lon_rad(),
core::f64::consts::PI,
1e-15
));
}
#[test]
fn ecef_and_eci_helpers() {
let e = Ecef::from([1.0, 2.0, 3.0]);
assert_eq!(<[f64; 3]>::from(e), [1.0, 2.0, 3.0]);
assert_eq!(e.offset(Vec3::new(1.0, 1.0, 1.0)), Ecef::new(2.0, 3.0, 4.0));
assert!(close(e.distance_to(Ecef::new(1.0, 2.0, 0.0)), 3.0, 1e-12));
let i = Eci::from(Vec3::new(1.0, 2.0, 3.0));
assert_eq!(i.vec(), Vec3::new(1.0, 2.0, 3.0));
assert_eq!(i.to_array(), [1.0, 2.0, 3.0]);
}
#[cfg(feature = "serde")]
#[test]
fn coordinate_types_round_trip_through_json() {
use crate::Aer;
let g = Geodetic::new(34.05, -118.24, 100.0);
let json = serde_json::to_string(&g).unwrap();
assert_eq!(json, r#"{"lat_deg":34.05,"lon_deg":-118.24,"height_m":100.0}"#);
assert_eq!(serde_json::from_str::<Geodetic>(&json).unwrap(), g);
let aer = Aer::new(90.0, 5.0, 1000.0);
assert_eq!(
serde_json::from_str::<Aer>(&serde_json::to_string(&aer).unwrap()).unwrap(),
aer
);
}
}