use download_jpl_file::{EphemFilePath, EphemFileSource};
use hifitime::Epoch;
use horizon::{horizon_data::HorizonData, horizon_ids::HorizonID};
use naif::{
naif_data::NaifData,
naif_ids::{
planet_bary::PlanetaryBary, satellite_mass::SatelliteMassCenter,
solar_system_bary::SolarSystemBary, NaifIds,
},
};
use nalgebra::Vector3;
use crate::outfit_errors::OutfitError;
pub mod download_jpl_file;
pub mod horizon;
pub mod naif;
#[derive(Debug, Clone)]
pub enum JPLEphem {
HorizonFile(HorizonData),
NaifFile(NaifData),
}
impl JPLEphem {
pub fn new(source: impl Into<EphemFileSource>) -> Result<Self, OutfitError> {
let file_path = EphemFilePath::get_ephemeris_file(&source.into())?;
match file_path {
EphemFilePath::JPLHorizon(..) => {
let horizon_data = HorizonData::read_horizon_file(&file_path);
Ok(JPLEphem::HorizonFile(horizon_data))
}
EphemFilePath::Naif(..) => {
let naif_data = NaifData::read_naif_file(&file_path);
Ok(JPLEphem::NaifFile(naif_data))
}
}
}
pub fn earth_ephemeris(
&self,
ephem_time: &Epoch,
compute_velocity: bool,
) -> (Vector3<f64>, Option<Vector3<f64>>) {
match self {
JPLEphem::HorizonFile(horizon_data) => {
let ephem_res = horizon_data
.ephemeris(
HorizonID::Earth,
HorizonID::Sun,
ephem_time.to_mjd_tt_days(),
compute_velocity,
false,
)
.to_au();
(ephem_res.position, ephem_res.velocity)
}
JPLEphem::NaifFile(naif_data) => {
let ephem_res = naif_data
.ephemeris(
NaifIds::PB(PlanetaryBary::EarthMoon),
NaifIds::SSB(SolarSystemBary::SSB),
ephem_time.to_et_seconds(),
)
.to_au();
(ephem_res.position, ephem_res.velocity.map(|v| v / 86400.0)) }
}
}
pub fn try_into_horizon(self) -> Result<HorizonData, OutfitError> {
match self {
JPLEphem::HorizonFile(horizon_data) => Ok(horizon_data),
_ => Err(OutfitError::InvalidJPLEphemFileSource(
"Expected a JPL Horizon source".to_string(),
)),
}
}
pub fn try_into_naif(self) -> Result<NaifData, OutfitError> {
match self {
JPLEphem::NaifFile(naif_data) => Ok(naif_data),
_ => Err(OutfitError::InvalidJPLEphemFileSource(
"Expected a NAIF source".to_string(),
)),
}
}
pub fn body_ephemeris(
&self,
body: NaifIds,
epoch: &Epoch,
) -> Result<(Vector3<f64>, Vector3<f64>), OutfitError> {
match self {
JPLEphem::HorizonFile(horizon_data) => {
let horizon_id = naif_to_horizon_id(body)?;
let ephem_res = horizon_data
.ephemeris(
horizon_id,
HorizonID::Sun,
epoch.to_mjd_tt_days(),
true,
false,
)
.to_au();
let vel = ephem_res.velocity.unwrap_or_else(Vector3::zeros) * 86400.0; Ok((ephem_res.position, vel))
}
JPLEphem::NaifFile(naif_data) => {
let et = epoch.to_et_seconds();
let body_res = naif_data
.ephemeris(body, NaifIds::SSB(SolarSystemBary::SSB), et)
.to_au();
let sun_res = naif_data
.ephemeris(
NaifIds::SSB(SolarSystemBary::Sun),
NaifIds::SSB(SolarSystemBary::SSB),
et,
)
.to_au();
let pos = body_res.position - sun_res.position;
let vel = (body_res.velocity.unwrap_or_else(Vector3::zeros)
- sun_res.velocity.unwrap_or_else(Vector3::zeros))
/ 86400.0;
Ok((pos, vel))
}
}
}
}
impl TryFrom<&str> for JPLEphem {
type Error = OutfitError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let source = EphemFileSource::try_from(s)?;
JPLEphem::new(source)
}
}
impl TryFrom<String> for JPLEphem {
type Error = OutfitError;
fn try_from(s: String) -> Result<Self, Self::Error> {
JPLEphem::try_from(s.as_str())
}
}
fn naif_to_horizon_id(body: NaifIds) -> Result<HorizonID, OutfitError> {
match body {
NaifIds::SSB(SolarSystemBary::Sun) => Ok(HorizonID::Sun),
NaifIds::SSB(SolarSystemBary::SSB) => Err(OutfitError::EphemerisBodyNotSupported(
"Solar System Barycenter is not a physical body".to_string(),
)),
NaifIds::PB(PlanetaryBary::Mercury) => Ok(HorizonID::Mercury),
NaifIds::PB(PlanetaryBary::Venus) => Ok(HorizonID::Venus),
NaifIds::PB(PlanetaryBary::EarthMoon) => Ok(HorizonID::Earth),
NaifIds::PB(PlanetaryBary::Mars) => Ok(HorizonID::Mars),
NaifIds::PB(PlanetaryBary::Jupiter) => Ok(HorizonID::Jupiter),
NaifIds::PB(PlanetaryBary::Saturn) => Ok(HorizonID::Saturn),
NaifIds::PB(PlanetaryBary::Uranus) => Ok(HorizonID::Uranus),
NaifIds::PB(PlanetaryBary::Neptune) => Ok(HorizonID::Neptune),
NaifIds::PB(PlanetaryBary::Pluto) => Ok(HorizonID::Pluto),
NaifIds::SMC(SatelliteMassCenter::Moon) => Ok(HorizonID::Moon),
other => Err(OutfitError::EphemerisBodyNotSupported(format!(
"{other} is not available in the Horizon backend"
))),
}
}