use std::{
ffi::c_char,
os::raw::{c_int, c_long, c_short},
panic::AssertUnwindSafe,
path::Path,
sync::OnceLock,
};
use ::anise::{
almanac::Almanac,
astro::Aberration,
frames::Frame as AniseFrame,
prelude::{Duration, Epoch},
};
use supernovas_ffi as sys;
use super::EphemerisProvider;
use crate::error::{Error, Result};
const KM_PER_AU: f64 = 1.495_978_707e8;
const SEC_PER_DAY: f64 = sys::NOVAS_DAY;
const NAIF_SSB: i32 = 0;
const NAIF_SUN: i32 = 10;
static ALMANAC: OnceLock<Almanac> = OnceLock::new();
pub struct AniseEphemeris {
almanac: Almanac,
}
impl AniseEphemeris {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path_str = path.as_ref().to_str().ok_or(Error::Ephemeris)?;
let almanac = Almanac::new(path_str).map_err(|_| Error::Ephemeris)?;
Ok(AniseEphemeris { almanac })
}
pub fn with(self, path: impl AsRef<Path>) -> Result<Self> {
let path_str = path.as_ref().to_str().ok_or(Error::Ephemeris)?;
let almanac = self.almanac.load(path_str).map_err(|_| Error::Ephemeris)?;
Ok(AniseEphemeris { almanac })
}
}
impl EphemerisProvider for AniseEphemeris {
fn install(self) -> Result<()> {
ALMANAC.set(self.almanac).map_err(|_| Error::Ephemeris)?;
let rc1 = unsafe { sys::set_planet_provider(Some(planet_provider)) };
let rc2 = unsafe { sys::set_planet_provider_hp(Some(planet_provider_hp)) };
let rc3 = unsafe { sys::set_ephem_provider(Some(ephem_provider)) };
if rc1 != 0 || rc2 != 0 || rc3 != 0 {
return Err(Error::Ephemeris);
}
Ok(())
}
}
fn write_state(
almanac: &Almanac,
naif: i32,
center: i32,
jd_tdb_high: f64,
jd_tdb_low: f64,
pos: *mut f64,
vel: *mut f64,
) -> bool {
let epoch = Epoch::from_jde_tdb(jd_tdb_high) + Duration::from_days(jd_tdb_low);
let target = AniseFrame::from_ephem_j2000(naif);
let observer = AniseFrame::from_ephem_j2000(center);
let state = match almanac.translate(target, observer, epoch, Aberration::NONE) {
Ok(s) => s,
Err(e) => {
crate::error::set_provider_error(format_args!(
"ANISE could not translate NAIF {naif} relative to NAIF {center}: {e}"
));
return false;
}
};
unsafe {
if !pos.is_null() {
*pos.add(0) = state.radius_km.x / KM_PER_AU;
*pos.add(1) = state.radius_km.y / KM_PER_AU;
*pos.add(2) = state.radius_km.z / KM_PER_AU;
}
if !vel.is_null() {
*vel.add(0) = state.velocity_km_s.x * SEC_PER_DAY / KM_PER_AU;
*vel.add(1) = state.velocity_km_s.y * SEC_PER_DAY / KM_PER_AU;
*vel.add(2) = state.velocity_km_s.z * SEC_PER_DAY / KM_PER_AU;
}
}
true
}
fn planet_state(
jd_tdb_high: f64,
jd_tdb_low: f64,
body: sys::novas_planet,
origin: sys::novas_origin,
pos: *mut f64,
vel: *mut f64,
) -> c_short {
let result = std::panic::catch_unwind(AssertUnwindSafe(|| -> c_short {
let Some(almanac) = ALMANAC.get() else {
crate::error::set_provider_error("ANISE almanac not installed");
return 3;
};
let naif = unsafe { sys::novas_to_dexxx_planet(body) };
if naif < 0 {
crate::error::set_provider_error(format_args!(
"no NAIF mapping for NOVAS planet id {}",
body as u32
));
return 1;
}
let center = match origin {
sys::novas_origin::NOVAS_BARYCENTER => NAIF_SSB,
sys::novas_origin::NOVAS_HELIOCENTER => NAIF_SUN,
};
if write_state(
almanac,
naif as i32,
center,
jd_tdb_high,
jd_tdb_low,
pos,
vel,
) {
0
} else {
3
}
}));
result.unwrap_or(99)
}
unsafe extern "C" fn planet_provider(
jd_tdb: f64,
body: sys::novas_planet,
origin: sys::novas_origin,
pos: *mut f64,
vel: *mut f64,
) -> c_short {
planet_state(jd_tdb, 0.0, body, origin, pos, vel)
}
unsafe extern "C" fn planet_provider_hp(
jd_tdb: *const f64,
body: sys::novas_planet,
origin: sys::novas_origin,
pos: *mut f64,
vel: *mut f64,
) -> c_short {
let (high, low) = unsafe { (*jd_tdb, *jd_tdb.add(1)) };
planet_state(high, low, body, origin, pos, vel)
}
unsafe extern "C" fn ephem_provider(
_name: *const c_char,
id: c_long,
jd_tdb_high: f64,
jd_tdb_low: f64,
origin: *mut sys::novas_origin,
pos: *mut f64,
vel: *mut f64,
) -> c_int {
let result = std::panic::catch_unwind(AssertUnwindSafe(|| -> c_int {
let Some(almanac) = ALMANAC.get() else {
crate::error::set_provider_error("ANISE almanac not installed");
return 1;
};
if id == -1 {
crate::error::set_provider_error(
"the ANISE backend does not support name-based lookup (id = -1); \
construct the EphemObject with an explicit NAIF ID",
);
return 1;
}
if !origin.is_null() {
unsafe { *origin = sys::novas_origin::NOVAS_BARYCENTER };
}
if write_state(
almanac,
id as i32,
NAIF_SSB,
jd_tdb_high,
jd_tdb_low,
pos,
vel,
) {
0
} else {
3
}
}));
result.unwrap_or(99)
}