use nalgebra::{Matrix3, Vector3};
use anise::constants::frames as anise_frames;
use crate::constants::AS2RAD;
use crate::time::Epoch;
use crate::utils::BraheError;
use super::almanac::{brahe_epoch_to_anise, ensure_kernel_loaded};
use super::kernels::SPKKernel;
#[inline]
#[allow(non_snake_case)]
fn j2000_to_icrf() -> Matrix3<f64> {
let dxi = -16.6170e-3 * AS2RAD; let deta = -6.8192e-3 * AS2RAD; let dalpha = -14.6e-3 * AS2RAD;
let b = Matrix3::new(
1.0 - 0.5 * (dxi * dxi + deta * deta),
dalpha,
-dxi,
-dalpha - dxi * deta,
1.0 - 0.5 * (dalpha * dalpha + deta * deta),
-deta,
dxi + dalpha * deta,
deta + dalpha * dxi,
1.0 - 0.5 * (deta * deta + dxi * dxi),
);
b.transpose() }
pub fn sun_position_de(epc: Epoch, kernel: SPKKernel) -> Result<Vector3<f64>, BraheError> {
let ctx = ensure_kernel_loaded(kernel)?;
let anise_epoch = brahe_epoch_to_anise(epc);
let r_j2000 = ctx
.translate(
anise_frames::SUN_J2000,
anise_frames::EME2000,
anise_epoch,
None,
)
.map_err(|e| BraheError::Error(format!("Failed to query Sun position: {}", e)))?;
let r_m = Vector3::new(
r_j2000.radius_km[0] * 1.0e3,
r_j2000.radius_km[1] * 1.0e3,
r_j2000.radius_km[2] * 1.0e3,
);
Ok(j2000_to_icrf() * r_m)
}
pub fn moon_position_de(epc: Epoch, kernel: SPKKernel) -> Result<Vector3<f64>, BraheError> {
let ctx = ensure_kernel_loaded(kernel)?;
let anise_epoch = brahe_epoch_to_anise(epc);
let r_j2000 = ctx
.translate(
anise_frames::IAU_MOON_FRAME,
anise_frames::EME2000,
anise_epoch,
None,
)
.map_err(|e| BraheError::Error(format!("Failed to query Moon position: {}", e)))?;
let r_m = Vector3::new(
r_j2000.radius_km[0] * 1.0e3,
r_j2000.radius_km[1] * 1.0e3,
r_j2000.radius_km[2] * 1.0e3,
);
Ok(j2000_to_icrf() * r_m)
}
pub fn jupiter_position_de(epc: Epoch, kernel: SPKKernel) -> Result<Vector3<f64>, BraheError> {
let ctx = ensure_kernel_loaded(kernel)?;
let anise_epoch = brahe_epoch_to_anise(epc);
let r_j2000 = ctx
.translate(
anise_frames::JUPITER_BARYCENTER_J2000,
anise_frames::EME2000,
anise_epoch,
None,
)
.map_err(|e| BraheError::Error(format!("Failed to query Jupiter position: {}", e)))?;
let r_m = Vector3::new(
r_j2000.radius_km[0] * 1.0e3,
r_j2000.radius_km[1] * 1.0e3,
r_j2000.radius_km[2] * 1.0e3,
);
Ok(j2000_to_icrf() * r_m)
}
pub fn mars_position_de(epc: Epoch, kernel: SPKKernel) -> Result<Vector3<f64>, BraheError> {
let ctx = ensure_kernel_loaded(kernel)?;
let anise_epoch = brahe_epoch_to_anise(epc);
let r_j2000 = ctx
.translate(
anise_frames::MARS_BARYCENTER_J2000,
anise_frames::EME2000,
anise_epoch,
None,
)
.map_err(|e| BraheError::Error(format!("Failed to query Mars position: {}", e)))?;
let r_m = Vector3::new(
r_j2000.radius_km[0] * 1.0e3,
r_j2000.radius_km[1] * 1.0e3,
r_j2000.radius_km[2] * 1.0e3,
);
Ok(j2000_to_icrf() * r_m)
}
pub fn mercury_position_de(epc: Epoch, kernel: SPKKernel) -> Result<Vector3<f64>, BraheError> {
let ctx = ensure_kernel_loaded(kernel)?;
let anise_epoch = brahe_epoch_to_anise(epc);
let r_j2000 = ctx
.translate(
anise_frames::MERCURY_J2000,
anise_frames::EME2000,
anise_epoch,
None,
)
.map_err(|e| BraheError::Error(format!("Failed to query Mercury position: {}", e)))?;
let r_m = Vector3::new(
r_j2000.radius_km[0] * 1.0e3,
r_j2000.radius_km[1] * 1.0e3,
r_j2000.radius_km[2] * 1.0e3,
);
Ok(j2000_to_icrf() * r_m)
}
pub fn neptune_position_de(epc: Epoch, kernel: SPKKernel) -> Result<Vector3<f64>, BraheError> {
let ctx = ensure_kernel_loaded(kernel)?;
let anise_epoch = brahe_epoch_to_anise(epc);
let r_j2000 = ctx
.translate(
anise_frames::NEPTUNE_BARYCENTER_J2000,
anise_frames::EME2000,
anise_epoch,
None,
)
.map_err(|e| BraheError::Error(format!("Failed to query Neptune position: {}", e)))?;
let r_m = Vector3::new(
r_j2000.radius_km[0] * 1.0e3,
r_j2000.radius_km[1] * 1.0e3,
r_j2000.radius_km[2] * 1.0e3,
);
Ok(j2000_to_icrf() * r_m)
}
pub fn saturn_position_de(epc: Epoch, kernel: SPKKernel) -> Result<Vector3<f64>, BraheError> {
let ctx = ensure_kernel_loaded(kernel)?;
let anise_epoch = brahe_epoch_to_anise(epc);
let r_j2000 = ctx
.translate(
anise_frames::SATURN_BARYCENTER_J2000,
anise_frames::EME2000,
anise_epoch,
None,
)
.map_err(|e| BraheError::Error(format!("Failed to query Saturn position: {}", e)))?;
let r_m = Vector3::new(
r_j2000.radius_km[0] * 1.0e3,
r_j2000.radius_km[1] * 1.0e3,
r_j2000.radius_km[2] * 1.0e3,
);
Ok(j2000_to_icrf() * r_m)
}
pub fn uranus_position_de(epc: Epoch, kernel: SPKKernel) -> Result<Vector3<f64>, BraheError> {
let ctx = ensure_kernel_loaded(kernel)?;
let anise_epoch = brahe_epoch_to_anise(epc);
let r_j2000 = ctx
.translate(
anise_frames::URANUS_BARYCENTER_J2000,
anise_frames::EME2000,
anise_epoch,
None,
)
.map_err(|e| BraheError::Error(format!("Failed to query Uranus position: {}", e)))?;
let r_m = Vector3::new(
r_j2000.radius_km[0] * 1.0e3,
r_j2000.radius_km[1] * 1.0e3,
r_j2000.radius_km[2] * 1.0e3,
);
Ok(j2000_to_icrf() * r_m)
}
pub fn venus_position_de(epc: Epoch, kernel: SPKKernel) -> Result<Vector3<f64>, BraheError> {
let ctx = ensure_kernel_loaded(kernel)?;
let anise_epoch = brahe_epoch_to_anise(epc);
let r_j2000 = ctx
.translate(
anise_frames::VENUS_J2000,
anise_frames::EME2000,
anise_epoch,
None,
)
.map_err(|e| BraheError::Error(format!("Failed to query Venus position: {}", e)))?;
let r_m = Vector3::new(
r_j2000.radius_km[0] * 1.0e3,
r_j2000.radius_km[1] * 1.0e3,
r_j2000.radius_km[2] * 1.0e3,
);
Ok(j2000_to_icrf() * r_m)
}
pub fn solar_system_barycenter_position_de(
epc: Epoch,
kernel: SPKKernel,
) -> Result<Vector3<f64>, BraheError> {
let ctx = ensure_kernel_loaded(kernel)?;
let anise_epoch = brahe_epoch_to_anise(epc);
let r_j2000 = ctx
.translate(
anise_frames::SSB_J2000,
anise_frames::EME2000,
anise_epoch,
None,
)
.map_err(|e| {
BraheError::Error(format!(
"Failed to query Solar System Barycenter position: {}",
e
))
})?;
let r_m = Vector3::new(
r_j2000.radius_km[0] * 1.0e3,
r_j2000.radius_km[1] * 1.0e3,
r_j2000.radius_km[2] * 1.0e3,
);
Ok(j2000_to_icrf() * r_m)
}
pub fn ssb_position_de(epc: Epoch, kernel: SPKKernel) -> Result<Vector3<f64>, BraheError> {
solar_system_barycenter_position_de(epc, kernel)
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use approx::assert_abs_diff_eq;
use rstest::rstest;
use super::*;
use crate::orbit_dynamics::ephemerides::{moon_position, sun_position};
use crate::utils::testing::setup_global_test_almanac;
#[rstest]
#[case(2025, 1, 1)]
#[case(2025, 2, 15)]
#[case(2025, 3, 30)]
#[case(2025, 5, 15)]
#[case(2025, 7, 1)]
#[case(2025, 8, 15)]
#[case(2025, 10, 1)]
#[case(2025, 11, 15)]
#[case(2025, 12, 31)]
fn test_sun_position_de(#[case] year: u32, #[case] month: u8, #[case] day: u8) {
setup_global_test_almanac();
let epc = Epoch::from_date(year, month, day, crate::time::TimeSystem::UTC);
let r_analytical = sun_position(epc);
let r_de = sun_position_de(epc, SPKKernel::DE440s).unwrap();
let dot = r_analytical.dot(&r_de) / (r_analytical.norm() * r_de.norm());
let angle = dot.acos() * (180.0 / std::f64::consts::PI);
assert_abs_diff_eq!(angle, 0.0, epsilon = 1.0e-1);
}
#[rstest]
#[case(2025, 1, 1)]
#[case(2025, 2, 15)]
#[case(2025, 3, 30)]
#[case(2025, 5, 15)]
#[case(2025, 7, 1)]
#[case(2025, 8, 15)]
#[case(2025, 10, 1)]
#[case(2025, 11, 15)]
#[case(2025, 12, 31)]
fn test_moon_position_de(#[case] year: u32, #[case] month: u8, #[case] day: u8) {
setup_global_test_almanac();
let epc = Epoch::from_date(year, month, day, crate::time::TimeSystem::UTC);
let r_analytical = moon_position(epc);
let r_de = moon_position_de(epc, SPKKernel::DE440s).unwrap();
let dot = r_analytical.dot(&r_de) / (r_analytical.norm() * r_de.norm());
let angle = dot.acos() * (180.0 / std::f64::consts::PI);
assert_abs_diff_eq!(angle, 0.0, epsilon = 1.0e-1);
}
#[rstest]
#[case(2025, 1, 1)]
#[case(2025, 2, 15)]
#[case(2025, 3, 30)]
#[case(2025, 5, 15)]
#[case(2025, 7, 1)]
#[case(2025, 8, 15)]
#[case(2025, 10, 1)]
#[case(2025, 11, 15)]
#[case(2025, 12, 31)]
fn test_jupiter_position_de(#[case] year: u32, #[case] month: u8, #[case] day: u8) {
setup_global_test_almanac();
let epc = Epoch::from_date(year, month, day, crate::time::TimeSystem::UTC);
let _r = jupiter_position_de(epc, SPKKernel::DE440s).unwrap();
}
#[rstest]
#[case(2025, 1, 1)]
#[case(2025, 2, 15)]
#[case(2025, 3, 30)]
#[case(2025, 5, 15)]
#[case(2025, 7, 1)]
#[case(2025, 8, 15)]
#[case(2025, 10, 1)]
#[case(2025, 11, 15)]
#[case(2025, 12, 31)]
fn test_mars_position_de(#[case] year: u32, #[case] month: u8, #[case] day: u8) {
setup_global_test_almanac();
let epc = Epoch::from_date(year, month, day, crate::time::TimeSystem::UTC);
let _r = mars_position_de(epc, SPKKernel::DE440s).unwrap();
}
#[rstest]
#[case(2025, 1, 1)]
#[case(2025, 2, 15)]
#[case(2025, 3, 30)]
#[case(2025, 5, 15)]
#[case(2025, 7, 1)]
#[case(2025, 8, 15)]
#[case(2025, 10, 1)]
#[case(2025, 11, 15)]
#[case(2025, 12, 31)]
fn test_mercury_position_de(#[case] year: u32, #[case] month: u8, #[case] day: u8) {
setup_global_test_almanac();
let epc = Epoch::from_date(year, month, day, crate::time::TimeSystem::UTC);
let _r = mercury_position_de(epc, SPKKernel::DE440s).unwrap();
}
#[rstest]
#[case(2025, 1, 1)]
#[case(2025, 2, 15)]
#[case(2025, 3, 30)]
#[case(2025, 5, 15)]
#[case(2025, 7, 1)]
#[case(2025, 8, 15)]
#[case(2025, 10, 1)]
#[case(2025, 11, 15)]
#[case(2025, 12, 31)]
fn test_neptune_position_de(#[case] year: u32, #[case] month: u8, #[case] day: u8) {
setup_global_test_almanac();
let epc = Epoch::from_date(year, month, day, crate::time::TimeSystem::UTC);
let _r = neptune_position_de(epc, SPKKernel::DE440s).unwrap();
}
#[rstest]
#[case(2025, 1, 1)]
#[case(2025, 2, 15)]
#[case(2025, 3, 30)]
#[case(2025, 5, 15)]
#[case(2025, 7, 1)]
#[case(2025, 8, 15)]
#[case(2025, 10, 1)]
#[case(2025, 11, 15)]
#[case(2025, 12, 31)]
fn test_saturn_position_de(#[case] year: u32, #[case] month: u8, #[case] day: u8) {
setup_global_test_almanac();
let epc = Epoch::from_date(year, month, day, crate::time::TimeSystem::UTC);
let _r = saturn_position_de(epc, SPKKernel::DE440s).unwrap();
}
#[rstest]
#[case(2025, 1, 1)]
#[case(2025, 2, 15)]
#[case(2025, 3, 30)]
#[case(2025, 5, 15)]
#[case(2025, 7, 1)]
#[case(2025, 8, 15)]
#[case(2025, 10, 1)]
#[case(2025, 11, 15)]
#[case(2025, 12, 31)]
fn test_uranus_position_de(#[case] year: u32, #[case] month: u8, #[case] day: u8) {
setup_global_test_almanac();
let epc = Epoch::from_date(year, month, day, crate::time::TimeSystem::UTC);
let _r = uranus_position_de(epc, SPKKernel::DE440s).unwrap();
}
#[rstest]
#[case(2025, 1, 1)]
#[case(2025, 2, 15)]
#[case(2025, 3, 30)]
#[case(2025, 5, 15)]
#[case(2025, 7, 1)]
#[case(2025, 8, 15)]
#[case(2025, 10, 1)]
#[case(2025, 11, 15)]
#[case(2025, 12, 31)]
fn test_venus_position_de(#[case] year: u32, #[case] month: u8, #[case] day: u8) {
setup_global_test_almanac();
let epc = Epoch::from_date(year, month, day, crate::time::TimeSystem::UTC);
let _r = venus_position_de(epc, SPKKernel::DE440s).unwrap();
}
#[rstest]
#[case(2025, 1, 1)]
#[case(2025, 2, 15)]
#[case(2025, 3, 30)]
#[case(2025, 5, 15)]
#[case(2025, 7, 1)]
#[case(2025, 8, 15)]
#[case(2025, 10, 1)]
#[case(2025, 11, 15)]
#[case(2025, 12, 31)]
fn test_solar_system_barycenter_position_de(
#[case] year: u32,
#[case] month: u8,
#[case] day: u8,
) {
setup_global_test_almanac();
let epc = Epoch::from_date(year, month, day, crate::time::TimeSystem::UTC);
let _r = solar_system_barycenter_position_de(epc, SPKKernel::DE440s).unwrap();
}
}