//! ECI (TEME) to ECEF coordinate transform. //! //! Rotates from the True Equator Mean Equinox (TEME) frame used by //! SGP4 to the Earth-Centered Earth-Fixed (ECEF) frame using the //! Greenwich Mean Sidereal Time (GMST) rotation. use serde::{Deserialize, Serialize}; use crate::julian::gmst; use crate::sgp4::EciPosition; /// Position in the Earth-Centered Earth-Fixed (ECEF) frame. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct EcefPosition { /// X position (km). pub x: f64, /// Y position (km). pub y: f64, /// Z position (km). pub z: f64, } /// Rotate an ECI (TEME) position to ECEF using the GMST at the given Julian Date. /// /// The TEME-to-ECEF rotation is a simple Z-axis rotation by -GMST: /// ```text /// ecef_x = eci_x * cos(gmst) + eci_y * sin(gmst) /// ecef_y = -eci_x * sin(gmst) + eci_y * cos(gmst) /// ecef_z = eci_z /// ``` pub fn eci_to_ecef(eci: &EciPosition, jd: f64) -> EcefPosition { let theta = gmst(jd); let cos_theta = theta.cos(); let sin_theta = theta.sin(); EcefPosition { x: eci.x * cos_theta + eci.y * sin_theta, y: -eci.x * sin_theta + eci.y * cos_theta, z: eci.z, } } /// Rotate an ECEF position to ECI (TEME) at the given Julian Date. /// /// Inverse of [`eci_to_ecef`]. pub fn ecef_to_eci(ecef: &EcefPosition, jd: f64) -> EciPosition { let theta = gmst(jd); let cos_theta = theta.cos(); let sin_theta = theta.sin(); EciPosition { x: ecef.x * cos_theta - ecef.y * sin_theta, y: ecef.x * sin_theta + ecef.y * cos_theta, z: ecef.z, vx: 0.0, vy: 0.0, vz: 0.0, } } #[cfg(test)] mod tests { use super::*; #[test] fn test_ecef_roundtrip() { let eci = EciPosition { x: 6500.0, y: 1000.0, z: 3000.0, vx: 0.0, vy: 0.0, vz: 0.0, }; let jd = 2451545.0; // J2000.0 let ecef = eci_to_ecef(&eci, jd); let eci2 = ecef_to_eci(&ecef, jd); assert!((eci.x - eci2.x).abs() < 1e-9, "x: {} vs {}", eci.x, eci2.x); assert!((eci.y - eci2.y).abs() < 1e-9, "y: {} vs {}", eci.y, eci2.y); assert!((eci.z - eci2.z).abs() < 1e-9, "z: {} vs {}", eci.z, eci2.z); } #[test] fn test_ecef_z_unchanged() { let eci = EciPosition { x: 1000.0, y: 2000.0, z: 5000.0, vx: 0.0, vy: 0.0, vz: 0.0, }; let ecef = eci_to_ecef(&eci, 2451545.0); assert!((eci.z - ecef.z).abs() < 1e-12); } #[test] fn test_ecef_preserves_magnitude() { let eci = EciPosition { x: 6500.0, y: 1000.0, z: 3000.0, vx: 0.0, vy: 0.0, vz: 0.0, }; let ecef = eci_to_ecef(&eci, 2460000.0); let r_eci = (eci.x * eci.x + eci.y * eci.y + eci.z * eci.z).sqrt(); let r_ecef = (ecef.x * ecef.x + ecef.y * ecef.y + ecef.z * ecef.z).sqrt(); assert!((r_eci - r_ecef).abs() < 1e-9); } }