use std::{os::raw::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;
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)) };
if rc1 != 0 || rc2 != 0 {
return Err(Error::Ephemeris);
}
Ok(())
}
}
fn naif_id(body: sys::novas_planet) -> Option<i32> {
let id = unsafe { sys::novas_to_naif_planet(body) };
if id < 0 { None } else { Some(id as i32) }
}
fn origin_naif_id(origin: sys::novas_origin) -> i32 {
use sys::novas_origin::*;
match origin {
NOVAS_BARYCENTER => 0,
NOVAS_HELIOCENTER => 10,
}
}
unsafe extern "C" fn planet_provider(
jd_tdb: f64,
body: sys::novas_planet,
origin: sys::novas_origin,
position: *mut f64,
velocity: *mut f64,
) -> c_short {
provider_impl(jd_tdb, 0.0, body, origin, position, velocity)
}
unsafe extern "C" fn planet_provider_hp(
jd_tdb: *const f64,
body: sys::novas_planet,
origin: sys::novas_origin,
position: *mut f64,
velocity: *mut f64,
) -> c_short {
let (high, low) = unsafe { (*jd_tdb, *jd_tdb.add(1)) };
provider_impl(high, low, body, origin, position, velocity)
}
fn provider_impl(
jd_high: f64,
jd_low: f64,
body: sys::novas_planet,
origin: sys::novas_origin,
position: *mut f64,
velocity: *mut f64,
) -> c_short {
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
let Some(almanac) = ALMANAC.get() else {
return 1;
};
let Some(target_id) = naif_id(body) else {
return 2;
};
let observer_id = origin_naif_id(origin);
let epoch = Epoch::from_jde_tdb(jd_high) + Duration::from_days(jd_low);
let target = AniseFrame::from_ephem_j2000(target_id);
let observer = AniseFrame::from_ephem_j2000(observer_id);
let state = match almanac.translate(target, observer, epoch, Aberration::NONE) {
Ok(s) => s,
Err(_) => return 3,
};
unsafe {
*position.add(0) = state.radius_km.x / KM_PER_AU;
*position.add(1) = state.radius_km.y / KM_PER_AU;
*position.add(2) = state.radius_km.z / KM_PER_AU;
*velocity.add(0) = state.velocity_km_s.x * SEC_PER_DAY / KM_PER_AU;
*velocity.add(1) = state.velocity_km_s.y * SEC_PER_DAY / KM_PER_AU;
*velocity.add(2) = state.velocity_km_s.z * SEC_PER_DAY / KM_PER_AU;
}
0
}));
result.unwrap_or(99)
}