use core::ops::{Add, Mul, Neg, Sub};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Vec3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Vec3 {
pub const ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
#[inline]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Vec3 { x, y, z }
}
#[inline]
pub fn dot(self, o: Vec3) -> f64 {
self.x * o.x + self.y * o.y + self.z * o.z
}
#[inline]
pub fn cross(self, o: Vec3) -> Vec3 {
Vec3::new(
self.y * o.z - self.z * o.y,
self.z * o.x - self.x * o.z,
self.x * o.y - self.y * o.x,
)
}
#[inline]
pub fn norm(self) -> f64 {
self.dot(self).sqrt()
}
#[inline]
pub fn normalized(self) -> Option<Vec3> {
let n = self.norm();
(n > 0.0).then(|| self * (1.0 / n))
}
#[inline]
pub const fn to_array(self) -> [f64; 3] {
[self.x, self.y, self.z]
}
}
impl From<[f64; 3]> for Vec3 {
#[inline]
fn from([x, y, z]: [f64; 3]) -> Self {
Vec3 { x, y, z }
}
}
impl From<Vec3> for [f64; 3] {
#[inline]
fn from(v: Vec3) -> Self {
v.to_array()
}
}
impl Add for Vec3 {
type Output = Vec3;
#[inline]
fn add(self, o: Vec3) -> Vec3 {
Vec3::new(self.x + o.x, self.y + o.y, self.z + o.z)
}
}
impl Sub for Vec3 {
type Output = Vec3;
#[inline]
fn sub(self, o: Vec3) -> Vec3 {
Vec3::new(self.x - o.x, self.y - o.y, self.z - o.z)
}
}
impl Mul<f64> for Vec3 {
type Output = Vec3;
#[inline]
fn mul(self, k: f64) -> Vec3 {
Vec3::new(self.x * k, self.y * k, self.z * k)
}
}
impl Neg for Vec3 {
type Output = Vec3;
#[inline]
fn neg(self) -> Vec3 {
Vec3::new(-self.x, -self.y, -self.z)
}
}
#[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 }
}
}
#[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 Enu {
pub east: f64,
pub north: f64,
pub up: f64,
}
impl Enu {
#[inline]
pub const fn new(east: f64, north: f64, up: f64) -> Self {
Enu { east, north, up }
}
#[inline]
pub fn norm(self) -> f64 {
self.vec().norm()
}
#[inline]
pub const fn vec(self) -> Vec3 {
Vec3::new(self.east, self.north, self.up)
}
#[inline]
pub const fn to_ned(self) -> Ned {
Ned::new(self.north, self.east, -self.up)
}
#[inline]
pub fn to_aer(self) -> Aer {
Aer::from_enu(self)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Ned {
pub north: f64,
pub east: f64,
pub down: f64,
}
impl Ned {
#[inline]
pub const fn new(north: f64, east: f64, down: f64) -> Self {
Ned { north, east, down }
}
#[inline]
pub const fn to_enu(self) -> Enu {
Enu::new(self.east, self.north, -self.down)
}
#[inline]
pub fn norm(self) -> f64 {
self.to_enu().norm()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Aer {
pub azimuth_deg: f64,
pub elevation_deg: f64,
pub range_m: f64,
}
impl Aer {
#[inline]
pub const fn new(azimuth_deg: f64, elevation_deg: f64, range_m: f64) -> Self {
Aer {
azimuth_deg,
elevation_deg,
range_m,
}
}
#[inline]
pub fn direction_enu(self) -> Enu {
let (saz, caz) = self.azimuth_deg.to_radians().sin_cos();
let (sel, cel) = self.elevation_deg.to_radians().sin_cos();
Enu::new(saz * cel, caz * cel, sel)
}
#[inline]
pub fn to_enu(self) -> Enu {
let d = self.direction_enu();
Enu::new(d.east * self.range_m, d.north * self.range_m, d.up * self.range_m)
}
#[inline]
pub fn from_enu(v: Enu) -> Aer {
let range = v.norm();
let azimuth_deg = v.east.atan2(v.north).to_degrees().rem_euclid(360.0);
let elevation_deg = if range == 0.0 {
0.0
} else {
(v.up / range).asin().to_degrees()
};
Aer {
azimuth_deg,
elevation_deg,
range_m: range,
}
}
}
#[cfg(all(test, feature = "serde"))]
mod serde_tests {
use super::*;
#[test]
fn coordinate_types_round_trip_through_json() {
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
);
}
}