use std::fmt;
use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
use nalgebra::Vector3;
use crate::{
coords::{AzimuthElevation, Ellipsoid, LLHDegrees, LLHRadians, NED, WGS84},
math,
};
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Default)]
pub struct ECEF(Vector3<f64>);
impl ECEF {
#[must_use]
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self(Vector3::new(x, y, z))
}
#[must_use]
pub fn as_array(&self) -> &[f64; 3] {
&self.0.data.0[0]
}
#[must_use]
pub fn as_array_mut(&mut self) -> &mut [f64; 3] {
&mut self.0.data.0[0]
}
#[must_use]
pub fn as_vector(&self) -> &Vector3<f64> {
&self.0
}
#[must_use]
pub fn as_vector_mut(&mut self) -> &mut Vector3<f64> {
&mut self.0
}
#[must_use]
pub fn x(&self) -> f64 {
self.0.x
}
#[must_use]
pub fn y(&self) -> f64 {
self.0.y
}
#[must_use]
pub fn z(&self) -> f64 {
self.0.z
}
#[must_use]
pub fn to_llh(&self) -> LLHRadians {
let p = (self.x() * self.x() + self.y() * self.y()).sqrt();
let longitude = if p == 0.0 {
0.0
} else {
self.y().atan2(self.x())
};
if p < WGS84::A * 1e-16 {
let latitude = std::f64::consts::FRAC_PI_2.copysign(self.z());
let height = self.z().abs() - WGS84::B;
return LLHRadians::new(latitude, longitude, height);
}
let p_norm = p / WGS84::A;
let e_c = (1. - WGS84::E * WGS84::E).sqrt();
let z = self.z().abs() * e_c / WGS84::A;
let mut s = z;
let mut c = e_c * p_norm;
let mut prev_c = -1.0;
let mut prev_s = -1.0;
let mut a_n;
let mut b_n;
let mut d_n;
let mut f_n;
for _ in 0..10 {
a_n = (s * s + c * c).sqrt();
d_n = z * a_n * a_n * a_n + WGS84::E * WGS84::E * s * s * s;
f_n = p_norm * a_n * a_n * a_n - WGS84::E * WGS84::E * c * c * c;
b_n = 1.5 * WGS84::E * s * c * c * (a_n * (p_norm * s - z * c) - WGS84::E * s * c);
s = d_n * f_n - b_n * s;
c = f_n * f_n - b_n * c;
if s > c {
c /= s;
s = 1.0;
} else {
s /= c;
c = 1.0;
}
if (s - prev_s).abs() < 1e-16 && (c - prev_c).abs() < 1e-16 {
break;
}
prev_s = s;
prev_c = c;
}
a_n = (s * s + c * c).sqrt();
let latitude = 1.0_f64.copysign(self.z()) * (s / (e_c * c)).atan();
let height = (p * e_c * c + self.z().abs() * s - WGS84::A * e_c * a_n)
/ (e_c * e_c * c * c + s * s).sqrt();
LLHRadians::new(latitude, longitude, height)
}
#[must_use]
pub fn azel_of(&self, point: &ECEF) -> AzimuthElevation {
let ned = self.ned_to(point);
let azimuth = ned.e().atan2(ned.n());
let azimuth = if azimuth < 0.0 {
azimuth + 2.0 * std::f64::consts::PI
} else {
azimuth
};
let elevation = (-ned.d() / ned.as_vector().norm()).asin();
AzimuthElevation::new(azimuth, elevation)
}
#[must_use]
pub fn ned_to(&self, point: &ECEF) -> NED {
let temp_vector = point - self;
temp_vector.ned_vector_at(self)
}
#[must_use]
pub fn ned_vector_at(&self, point: &ECEF) -> NED {
let m = math::ecef2ned_matrix(point.to_llh());
(m * self.as_vector()).into()
}
}
impl From<[f64; 3]> for ECEF {
fn from(array: [f64; 3]) -> Self {
Self::new(array[0], array[1], array[2])
}
}
impl From<&[f64; 3]> for ECEF {
fn from(array: &[f64; 3]) -> Self {
Self::new(array[0], array[1], array[2])
}
}
impl From<Vector3<f64>> for ECEF {
fn from(vector: Vector3<f64>) -> Self {
Self(vector)
}
}
impl From<(f64, f64, f64)> for ECEF {
fn from((x, y, z): (f64, f64, f64)) -> Self {
Self::new(x, y, z)
}
}
impl From<LLHRadians> for ECEF {
fn from(value: LLHRadians) -> Self {
value.to_ecef()
}
}
impl From<LLHDegrees> for ECEF {
fn from(value: LLHDegrees) -> Self {
value.to_ecef()
}
}
impl AsRef<[f64; 3]> for ECEF {
fn as_ref(&self) -> &[f64; 3] {
self.as_array()
}
}
impl AsRef<Vector3<f64>> for ECEF {
fn as_ref(&self) -> &Vector3<f64> {
self.as_vector()
}
}
impl AsMut<[f64; 3]> for ECEF {
fn as_mut(&mut self) -> &mut [f64; 3] {
self.as_array_mut()
}
}
impl AsMut<Vector3<f64>> for ECEF {
fn as_mut(&mut self) -> &mut Vector3<f64> {
self.as_vector_mut()
}
}
impl Add for ECEF {
type Output = Self;
fn add(self, rhs: ECEF) -> Self {
Self(self.0 + rhs.0)
}
}
impl Add<&ECEF> for ECEF {
type Output = Self;
fn add(self, rhs: &Self) -> Self {
self + *rhs
}
}
impl Add<&ECEF> for &ECEF {
type Output = ECEF;
fn add(self, rhs: &ECEF) -> ECEF {
*self + *rhs
}
}
impl AddAssign for ECEF {
fn add_assign(&mut self, rhs: Self) {
*self += &rhs;
}
}
impl AddAssign<&ECEF> for ECEF {
fn add_assign(&mut self, rhs: &Self) {
self.0[0] += rhs.x();
self.0[1] += rhs.y();
self.0[2] += rhs.z();
}
}
impl Sub for ECEF {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
ECEF(self.0 - rhs.0)
}
}
impl Sub<&ECEF> for ECEF {
type Output = Self;
fn sub(self, rhs: &Self) -> Self {
self - *rhs
}
}
impl Sub<&ECEF> for &ECEF {
type Output = ECEF;
fn sub(self, rhs: &ECEF) -> ECEF {
*self - *rhs
}
}
impl SubAssign for ECEF {
fn sub_assign(&mut self, rhs: Self) {
*self -= &rhs;
}
}
impl SubAssign<&ECEF> for ECEF {
fn sub_assign(&mut self, rhs: &Self) {
self.0[0] -= rhs.x();
self.0[1] -= rhs.y();
self.0[2] -= rhs.z();
}
}
impl Mul<ECEF> for f64 {
type Output = ECEF;
fn mul(self, rhs: ECEF) -> ECEF {
ECEF(self * rhs.0)
}
}
impl Mul<&ECEF> for f64 {
type Output = ECEF;
fn mul(self, rhs: &ECEF) -> ECEF {
self * *rhs
}
}
impl MulAssign<f64> for ECEF {
fn mul_assign(&mut self, rhs: f64) {
*self *= &rhs;
}
}
impl MulAssign<&f64> for ECEF {
fn mul_assign(&mut self, rhs: &f64) {
self.0[0] *= *rhs;
self.0[1] *= *rhs;
self.0[2] *= *rhs;
}
}
impl fmt::Display for ECEF {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ECEF {{ x: {}, y: {}, z: {} }}",
self.x(),
self.y(),
self.z()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_ecef() {
let test = ECEF::new(-1.5, 2.78, 3.0);
assert_eq!(format!("{test}"), "ECEF { x: -1.5, y: 2.78, z: 3 }");
}
#[expect(clippy::float_cmp)]
#[test]
fn ecef_ops() {
let a = ECEF::new(1.0, 2.0, 3.0);
let b = ECEF::new(4.0, 5.0, 6.0);
let result = a + b;
assert_eq!(5.0, result.x());
assert_eq!(7.0, result.y());
assert_eq!(9.0, result.z());
let result = a + a + a;
assert_eq!(3.0, result.x());
assert_eq!(6.0, result.y());
assert_eq!(9.0, result.z());
let result = a - b;
assert_eq!(-3.0, result.x());
assert_eq!(-3.0, result.y());
assert_eq!(-3.0, result.z());
let result = 2.0 * a;
assert_eq!(2.0, result.x());
assert_eq!(4.0, result.y());
assert_eq!(6.0, result.z());
let mut result = a;
result += b;
assert_eq!(5.0, result.x());
assert_eq!(7.0, result.y());
assert_eq!(9.0, result.z());
let mut result = a;
result -= b;
assert_eq!(-3.0, result.x());
assert_eq!(-3.0, result.y());
assert_eq!(-3.0, result.z());
let mut result = a;
result *= 2.0;
assert_eq!(2.0, result.x());
assert_eq!(4.0, result.y());
assert_eq!(6.0, result.z());
}
}