use std::f64::consts::PI;
pub const WGS84_SEMI_MAJOR_AXIS: f64 = 6_378_137.0;
pub const WGS84_SEMI_MINOR_AXIS: f64 = 6_356_752.314_245;
pub const WGS84_FLATTENING: f64 = 1.0 / 298.257_223_563;
pub const WGS84_E2: f64 = 2.0 * WGS84_FLATTENING - WGS84_FLATTENING * WGS84_FLATTENING;
#[inline]
pub fn deg_to_rad(deg: f64) -> f64 {
deg * PI / 180.0
}
#[inline]
pub fn rad_to_deg(rad: f64) -> f64 {
rad * 180.0 / PI
}
pub fn geodetic_to_ecef(lon_deg: f64, lat_deg: f64, height: f64) -> [f64; 3] {
let lon = deg_to_rad(lon_deg);
let lat = deg_to_rad(lat_deg);
let sin_lat = lat.sin();
let cos_lat = lat.cos();
let sin_lon = lon.sin();
let cos_lon = lon.cos();
let n = WGS84_SEMI_MAJOR_AXIS / (1.0 - WGS84_E2 * sin_lat * sin_lat).sqrt();
let x = (n + height) * cos_lat * cos_lon;
let y = (n + height) * cos_lat * sin_lon;
let z = (n * (1.0 - WGS84_E2) + height) * sin_lat;
[x, y, z]
}
pub fn ecef_to_geodetic(x: f64, y: f64, z: f64) -> (f64, f64, f64) {
let lon = y.atan2(x);
let p = (x * x + y * y).sqrt();
let mut lat = (z / p).atan();
for _ in 0..5 {
let sin_lat = lat.sin();
let n = WGS84_SEMI_MAJOR_AXIS / (1.0 - WGS84_E2 * sin_lat * sin_lat).sqrt();
lat = (z + WGS84_E2 * n * sin_lat).atan2(p);
}
let sin_lat = lat.sin();
let cos_lat = lat.cos();
let n = WGS84_SEMI_MAJOR_AXIS / (1.0 - WGS84_E2 * sin_lat * sin_lat).sqrt();
let height = if cos_lat.abs() > 1e-10 {
p / cos_lat - n
} else {
z.abs() / sin_lat.abs() - n * (1.0 - WGS84_E2)
};
(rad_to_deg(lon), rad_to_deg(lat), height)
}
#[inline]
pub fn ecef_to_ellipsoid_scaled(ecef: &[f64; 3]) -> [f64; 3] {
[
ecef[0] / WGS84_SEMI_MAJOR_AXIS,
ecef[1] / WGS84_SEMI_MAJOR_AXIS,
ecef[2] / WGS84_SEMI_MINOR_AXIS,
]
}
#[inline]
pub fn vec3_magnitude(v: &[f64; 3]) -> f64 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
#[inline]
pub fn vec3_normalize(v: &[f64; 3]) -> [f64; 3] {
let mag = vec3_magnitude(v);
if mag < 1e-10 {
return [0.0, 0.0, 1.0];
}
[v[0] / mag, v[1] / mag, v[2] / mag]
}
#[inline]
pub fn vec3_distance(a: &[f64; 3], b: &[f64; 3]) -> f64 {
let dx = a[0] - b[0];
let dy = a[1] - b[1];
let dz = a[2] - b[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f64 = 1.0;
#[test]
fn test_deg_rad_conversion() {
assert!((deg_to_rad(180.0) - PI).abs() < 1e-10);
assert!((rad_to_deg(PI) - 180.0).abs() < 1e-10);
assert!((deg_to_rad(90.0) - PI / 2.0).abs() < 1e-10);
}
#[test]
fn test_geodetic_to_ecef_equator() {
let [x, y, z] = geodetic_to_ecef(0.0, 0.0, 0.0);
assert!((x - WGS84_SEMI_MAJOR_AXIS).abs() < EPSILON);
assert!(y.abs() < EPSILON);
assert!(z.abs() < EPSILON);
}
#[test]
fn test_geodetic_to_ecef_north_pole() {
let [x, y, z] = geodetic_to_ecef(0.0, 90.0, 0.0);
assert!(x.abs() < EPSILON);
assert!(y.abs() < EPSILON);
assert!((z - WGS84_SEMI_MINOR_AXIS).abs() < EPSILON);
}
#[test]
fn test_geodetic_to_ecef_90_longitude() {
let [x, y, z] = geodetic_to_ecef(90.0, 0.0, 0.0);
assert!(x.abs() < EPSILON);
assert!((y - WGS84_SEMI_MAJOR_AXIS).abs() < EPSILON);
assert!(z.abs() < EPSILON);
}
#[test]
fn test_geodetic_to_ecef_with_height() {
let height = 1000.0;
let [x, _y, _z] = geodetic_to_ecef(0.0, 0.0, height);
assert!((x - (WGS84_SEMI_MAJOR_AXIS + height)).abs() < EPSILON);
}
#[test]
fn test_ecef_geodetic_roundtrip() {
let test_cases = [
(0.0, 0.0, 0.0),
(90.0, 0.0, 0.0),
(-90.0, 0.0, 0.0),
(0.0, 45.0, 0.0),
(0.0, -45.0, 0.0),
(139.7, 35.7, 100.0), (-122.4, 37.8, 50.0), ];
for (lon, lat, h) in test_cases {
let ecef = geodetic_to_ecef(lon, lat, h);
let (lon2, lat2, h2) = ecef_to_geodetic(ecef[0], ecef[1], ecef[2]);
assert!(
(lon - lon2).abs() < 1e-6,
"Longitude mismatch: {lon} vs {lon2}"
);
assert!(
(lat - lat2).abs() < 1e-6,
"Latitude mismatch: {lat} vs {lat2}"
);
assert!((h - h2).abs() < 1e-3, "Height mismatch: {h} vs {h2}");
}
}
#[test]
fn test_ellipsoid_scaled() {
let ecef = [WGS84_SEMI_MAJOR_AXIS, 0.0, 0.0];
let scaled = ecef_to_ellipsoid_scaled(&ecef);
assert!((scaled[0] - 1.0).abs() < 1e-10);
assert!(scaled[1].abs() < 1e-10);
assert!(scaled[2].abs() < 1e-10);
}
#[test]
fn test_vec3_operations() {
let v = [3.0, 4.0, 0.0];
assert!((vec3_magnitude(&v) - 5.0).abs() < 1e-10);
let n = vec3_normalize(&v);
assert!((n[0] - 0.6).abs() < 1e-10);
assert!((n[1] - 0.8).abs() < 1e-10);
assert!(n[2].abs() < 1e-10);
let a = [0.0, 0.0, 0.0];
let b = [3.0, 4.0, 0.0];
assert!((vec3_distance(&a, &b) - 5.0).abs() < 1e-10);
}
}