pub mod provenance {
pub use crate::archive::atmosphere::provenance as atmosphere;
pub use crate::archive::elp::provenance as elp;
pub use crate::archive::gravity::provenance as gravity;
pub use crate::archive::nutation::provenance as nutation;
pub use crate::archive::pluto::provenance as pluto;
pub use crate::archive::time::provenance as time;
pub use crate::archive::vsop::provenance as vsop;
}
pub(crate) mod elp2000;
pub(crate) mod jpl;
#[cfg(feature = "lagrange-centers")]
pub mod lagrange;
pub mod pluto;
pub(crate) mod vsop87;
mod runtime_backend;
mod vsop87_backend;
pub use pluto::Pluto;
pub use runtime_backend::RuntimeEphemeris;
pub use vsop87::VSOP87;
pub use vsop87_backend::Vsop87Ephemeris;
use crate::coordinates::{
cartesian::{Position, Velocity},
centers::{Barycentric, Geocentric, Heliocentric},
frames::EclipticMeanJ2000,
};
use crate::qtty::{AstronomicalUnit, Day, Kilometer};
use crate::time::JulianDate;
pub type AuPerDay = crate::qtty::Per<AstronomicalUnit, Day>;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EphemerisError {
OutOfRange {
jd: f64,
start_jd: f64,
end_jd: f64,
},
InvalidSegment {
init_seconds: f64,
intlen_seconds: f64,
n_records: usize,
},
}
impl core::fmt::Display for EphemerisError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
Self::OutOfRange {
jd,
start_jd,
end_jd,
} => write!(
f,
"ephemeris epoch JD {jd} is outside covered range [{start_jd}, {end_jd}]"
),
Self::InvalidSegment {
init_seconds,
intlen_seconds,
n_records,
} => write!(
f,
"invalid ephemeris segment metadata: init={init_seconds}s intlen={intlen_seconds}s n_records={n_records}"
),
}
}
}
impl std::error::Error for EphemerisError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MajorPlanet {
Mercury,
Venus,
Mars,
Jupiter,
Saturn,
Uranus,
Neptune,
}
impl MajorPlanet {
pub const fn system_barycenter_naif_id(self) -> i32 {
match self {
Self::Mercury => 1,
Self::Venus => 2,
Self::Mars => 4,
Self::Jupiter => 5,
Self::Saturn => 6,
Self::Uranus => 7,
Self::Neptune => 8,
}
}
pub const fn center_naif_id(self) -> i32 {
match self {
Self::Mercury => 199,
Self::Venus => 299,
Self::Mars => 499,
Self::Jupiter => 599,
Self::Saturn => 699,
Self::Uranus => 799,
Self::Neptune => 899,
}
}
pub const fn naif_id(self, point: PlanetPoint) -> i32 {
match point {
PlanetPoint::Center => self.center_naif_id(),
PlanetPoint::SystemBarycenter => self.system_barycenter_naif_id(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PlanetPoint {
Center,
SystemBarycenter,
}
#[derive(Debug)]
pub enum PlanetEphemerisError {
UnsupportedPoint {
planet: MajorPlanet,
point: PlanetPoint,
},
Ephemeris(EphemerisError),
}
impl core::fmt::Display for PlanetEphemerisError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnsupportedPoint { planet, point } => write!(
f,
"embedded JPL data does not provide {:?} {:?}; load an SPK kernel set for planet-center offsets",
planet, point
),
Self::Ephemeris(err) => err.fmt(f),
}
}
}
impl std::error::Error for PlanetEphemerisError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Ephemeris(err) => Some(err),
Self::UnsupportedPoint { .. } => None,
}
}
}
impl From<EphemerisError> for PlanetEphemerisError {
fn from(value: EphemerisError) -> Self {
Self::Ephemeris(value)
}
}
pub trait Ephemeris {
#[inline]
fn try_sun_barycentric(
jd: JulianDate,
) -> Result<Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>, EphemerisError> {
Ok(Self::sun_barycentric(jd))
}
fn sun_barycentric(
jd: JulianDate,
) -> Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>;
#[inline]
fn try_earth_barycentric(
jd: JulianDate,
) -> Result<Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>, EphemerisError> {
Ok(Self::earth_barycentric(jd))
}
fn earth_barycentric(
jd: JulianDate,
) -> Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>;
#[inline]
fn try_earth_heliocentric(
jd: JulianDate,
) -> Result<Position<Heliocentric, EclipticMeanJ2000, AstronomicalUnit>, EphemerisError> {
Ok(Self::earth_heliocentric(jd))
}
fn earth_heliocentric(
jd: JulianDate,
) -> Position<Heliocentric, EclipticMeanJ2000, AstronomicalUnit>;
#[inline]
fn try_earth_barycentric_velocity(
jd: JulianDate,
) -> Result<Velocity<EclipticMeanJ2000, AuPerDay>, EphemerisError> {
Ok(Self::earth_barycentric_velocity(jd))
}
fn earth_barycentric_velocity(jd: JulianDate) -> Velocity<EclipticMeanJ2000, AuPerDay>;
#[inline]
fn try_moon_geocentric(
jd: JulianDate,
) -> Result<Position<Geocentric, EclipticMeanJ2000, Kilometer>, EphemerisError> {
Ok(Self::moon_geocentric(jd))
}
fn moon_geocentric(jd: JulianDate) -> Position<Geocentric, EclipticMeanJ2000, Kilometer>;
}
pub trait DynEphemeris {
fn try_sun_barycentric(
&self,
jd: JulianDate,
) -> Result<Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>, EphemerisError>;
fn sun_barycentric(
&self,
jd: JulianDate,
) -> Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>;
fn try_earth_barycentric(
&self,
jd: JulianDate,
) -> Result<Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>, EphemerisError>;
fn earth_barycentric(
&self,
jd: JulianDate,
) -> Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>;
fn try_earth_heliocentric(
&self,
jd: JulianDate,
) -> Result<Position<Heliocentric, EclipticMeanJ2000, AstronomicalUnit>, EphemerisError>;
fn earth_heliocentric(
&self,
jd: JulianDate,
) -> Position<Heliocentric, EclipticMeanJ2000, AstronomicalUnit>;
fn try_earth_barycentric_velocity(
&self,
jd: JulianDate,
) -> Result<Velocity<EclipticMeanJ2000, AuPerDay>, EphemerisError>;
fn earth_barycentric_velocity(&self, jd: JulianDate) -> Velocity<EclipticMeanJ2000, AuPerDay>;
fn try_moon_geocentric(
&self,
jd: JulianDate,
) -> Result<Position<Geocentric, EclipticMeanJ2000, Kilometer>, EphemerisError>;
fn moon_geocentric(&self, jd: JulianDate)
-> Position<Geocentric, EclipticMeanJ2000, Kilometer>;
}
impl<T: Ephemeris> DynEphemeris for T {
#[inline]
fn try_sun_barycentric(
&self,
jd: JulianDate,
) -> Result<Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>, EphemerisError> {
<T as Ephemeris>::try_sun_barycentric(jd)
}
#[inline]
fn sun_barycentric(
&self,
jd: JulianDate,
) -> Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> {
<T as Ephemeris>::sun_barycentric(jd)
}
#[inline]
fn try_earth_barycentric(
&self,
jd: JulianDate,
) -> Result<Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>, EphemerisError> {
<T as Ephemeris>::try_earth_barycentric(jd)
}
#[inline]
fn earth_barycentric(
&self,
jd: JulianDate,
) -> Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> {
<T as Ephemeris>::earth_barycentric(jd)
}
#[inline]
fn try_earth_heliocentric(
&self,
jd: JulianDate,
) -> Result<Position<Heliocentric, EclipticMeanJ2000, AstronomicalUnit>, EphemerisError> {
<T as Ephemeris>::try_earth_heliocentric(jd)
}
#[inline]
fn earth_heliocentric(
&self,
jd: JulianDate,
) -> Position<Heliocentric, EclipticMeanJ2000, AstronomicalUnit> {
<T as Ephemeris>::earth_heliocentric(jd)
}
#[inline]
fn try_earth_barycentric_velocity(
&self,
jd: JulianDate,
) -> Result<Velocity<EclipticMeanJ2000, AuPerDay>, EphemerisError> {
<T as Ephemeris>::try_earth_barycentric_velocity(jd)
}
#[inline]
fn earth_barycentric_velocity(&self, jd: JulianDate) -> Velocity<EclipticMeanJ2000, AuPerDay> {
<T as Ephemeris>::earth_barycentric_velocity(jd)
}
#[inline]
fn try_moon_geocentric(
&self,
jd: JulianDate,
) -> Result<Position<Geocentric, EclipticMeanJ2000, Kilometer>, EphemerisError> {
<T as Ephemeris>::try_moon_geocentric(jd)
}
#[inline]
fn moon_geocentric(
&self,
jd: JulianDate,
) -> Position<Geocentric, EclipticMeanJ2000, Kilometer> {
<T as Ephemeris>::moon_geocentric(jd)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ephemeris::Vsop87Ephemeris;
const JD_J2000: f64 = tempoch::J2000_JD_TT_DAY.value();
fn jd() -> JulianDate {
crate::time::JulianDate::new(JD_J2000)
}
#[test]
fn dyn_ephemeris_sun_barycentric_via_blanket_impl() {
let eph: &dyn DynEphemeris = &Vsop87Ephemeris;
let pos = eph.sun_barycentric(jd());
let mag =
(pos.x().value().powi(2) + pos.y().value().powi(2) + pos.z().value().powi(2)).sqrt();
assert!(mag < 0.02, "Sun-SSB offset too large: {mag} AU");
assert!(pos.x().is_finite());
}
#[test]
fn dyn_ephemeris_earth_barycentric_via_blanket_impl() {
let eph: &dyn DynEphemeris = &Vsop87Ephemeris;
let pos = eph.earth_barycentric(jd());
let mag =
(pos.x().value().powi(2) + pos.y().value().powi(2) + pos.z().value().powi(2)).sqrt();
assert!(
mag > 0.9 && mag < 1.1,
"Earth-SSB distance should be ~1 AU, got {mag}"
);
}
#[test]
fn dyn_ephemeris_earth_heliocentric_via_blanket_impl() {
let eph: &dyn DynEphemeris = &Vsop87Ephemeris;
let pos = eph.earth_heliocentric(jd());
let mag =
(pos.x().value().powi(2) + pos.y().value().powi(2) + pos.z().value().powi(2)).sqrt();
assert!(
mag > 0.98 && mag < 1.02,
"Earth heliocentric should be ~1 AU, got {mag}"
);
}
#[test]
fn dyn_ephemeris_earth_barycentric_velocity_via_blanket_impl() {
let eph: &dyn DynEphemeris = &Vsop87Ephemeris;
let vel = eph.earth_barycentric_velocity(jd());
let speed =
(vel.x().value().powi(2) + vel.y().value().powi(2) + vel.z().value().powi(2)).sqrt();
assert!(
speed > 0.01 && speed < 0.03,
"Earth speed ~0.017 AU/day, got {speed}"
);
}
#[test]
fn dyn_ephemeris_moon_geocentric_via_blanket_impl() {
let eph: &dyn DynEphemeris = &Vsop87Ephemeris;
let pos = eph.moon_geocentric(jd());
let dist =
(pos.x().value().powi(2) + pos.y().value().powi(2) + pos.z().value().powi(2)).sqrt();
assert!(
dist > 350_000.0 && dist < 420_000.0,
"Moon geocentric dist: {dist} km"
);
}
#[test]
fn static_vs_dynamic_dispatch_agree() {
let jd_val = jd();
let pos_static = <Vsop87Ephemeris as Ephemeris>::sun_barycentric(jd_val);
let pos_dyn = {
let eph: &dyn DynEphemeris = &Vsop87Ephemeris;
eph.sun_barycentric(jd_val)
};
assert!((pos_static.x().value() - pos_dyn.x().value()).abs() < 1e-15);
assert!((pos_static.y().value() - pos_dyn.y().value()).abs() < 1e-15);
assert!((pos_static.z().value() - pos_dyn.z().value()).abs() < 1e-15);
}
}