use std::marker::PhantomData;
use std::sync::Arc;
use crate::coordinates::centers::ReferenceCenter;
use crate::coordinates::frames::ICRS;
use crate::pod::providers::EphemerisProvider;
use affn::cartesian::{Position, Velocity};
use qtty::unit::Kilometer;
use qtty::{KmPerSecond, Quantity};
use tempoch::{Time, TDB};
use crate::formats::spice::{SpiceError, SpkKernel};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpiceState<C: ReferenceCenter<Params = ()>> {
pub position: Position<C, ICRS, Kilometer>,
pub velocity_km_s: Velocity<ICRS, KmPerSecond>,
}
pub struct SpiceEphemerisProvider<C: ReferenceCenter<Params = ()>> {
kernel: Arc<SpkKernel>,
center_naif_id: i32,
_center: PhantomData<fn() -> C>,
}
impl<C: ReferenceCenter<Params = ()>> Clone for SpiceEphemerisProvider<C> {
fn clone(&self) -> Self {
Self {
kernel: Arc::clone(&self.kernel),
center_naif_id: self.center_naif_id,
_center: PhantomData,
}
}
}
impl<C: ReferenceCenter<Params = ()>> std::fmt::Debug for SpiceEphemerisProvider<C> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SpiceEphemerisProvider")
.field("center_name", &C::center_name())
.field("center_naif_id", &self.center_naif_id)
.field("kernel", &self.kernel)
.finish()
}
}
impl<C: ReferenceCenter<Params = ()>> SpiceEphemerisProvider<C> {
pub fn new(kernel: SpkKernel, center_naif_id: i32) -> Self {
Self {
kernel: Arc::new(kernel),
center_naif_id,
_center: PhantomData,
}
}
pub fn from_arc(kernel: Arc<SpkKernel>, center_naif_id: i32) -> Self {
Self {
kernel,
center_naif_id,
_center: PhantomData,
}
}
pub fn center_naif_id(&self) -> i32 {
self.center_naif_id
}
pub fn kernel(&self) -> &SpkKernel {
&self.kernel
}
pub fn state_at(
&self,
body_naif_id: i32,
epoch: Time<TDB>,
) -> Result<SpiceState<C>, SpiceError> {
EphemerisProvider::state_at(self, body_naif_id, epoch)
}
}
impl<C: ReferenceCenter<Params = ()>> EphemerisProvider for SpiceEphemerisProvider<C> {
type State = SpiceState<C>;
type Error = SpiceError;
fn state(&self, body_naif_id: i32, epoch_seconds_tdb: f64) -> Result<Self::State, Self::Error> {
let s = self
.kernel
.state(body_naif_id, self.center_naif_id, epoch_seconds_tdb)?;
let position = Position::<C, ICRS, Kilometer>::new(
Quantity::<Kilometer>::new(s[0]),
Quantity::<Kilometer>::new(s[1]),
Quantity::<Kilometer>::new(s[2]),
);
Ok(SpiceState {
position,
velocity_km_s: Velocity::<ICRS, KmPerSecond>::new(
Quantity::<KmPerSecond>::new(s[3]),
Quantity::<KmPerSecond>::new(s[4]),
Quantity::<KmPerSecond>::new(s[5]),
),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::coordinates::centers::{Barycentric, Geocentric};
fn synthetic_kernel() -> SpkKernel {
let mut buf = vec![0u8; 3 * 1024];
buf[0..8].copy_from_slice(b"DAF/SPK ");
buf[8..12].copy_from_slice(&2i32.to_le_bytes());
buf[12..16].copy_from_slice(&6i32.to_le_bytes());
buf[76..80].copy_from_slice(&2i32.to_le_bytes());
let rec = &mut buf[1024..2048];
rec[16..24].copy_from_slice(&2.0_f64.to_le_bytes());
let seg1_start_word: i32 = 257;
let seg1_end_word: i32 = seg1_start_word + 8;
let seg2_start_word: i32 = seg1_end_word + 1;
let seg2_end_word: i32 = seg2_start_word + 8;
let s1 = &mut rec[24..64];
s1[0..8].copy_from_slice(&0.0_f64.to_le_bytes());
s1[8..16].copy_from_slice(&1.0_f64.to_le_bytes());
s1[16..20].copy_from_slice(&399i32.to_le_bytes());
s1[20..24].copy_from_slice(&0i32.to_le_bytes());
s1[24..28].copy_from_slice(&1i32.to_le_bytes());
s1[28..32].copy_from_slice(&2i32.to_le_bytes());
s1[32..36].copy_from_slice(&seg1_start_word.to_le_bytes());
s1[36..40].copy_from_slice(&seg1_end_word.to_le_bytes());
let s2 = &mut rec[64..104];
s2[0..8].copy_from_slice(&0.0_f64.to_le_bytes());
s2[8..16].copy_from_slice(&1.0_f64.to_le_bytes());
s2[16..20].copy_from_slice(&301i32.to_le_bytes());
s2[20..24].copy_from_slice(&399i32.to_le_bytes());
s2[24..28].copy_from_slice(&1i32.to_le_bytes());
s2[28..32].copy_from_slice(&2i32.to_le_bytes());
s2[32..36].copy_from_slice(&seg2_start_word.to_le_bytes());
s2[36..40].copy_from_slice(&seg2_end_word.to_le_bytes());
let wf = |word: i32, v: f64, b: &mut [u8]| {
let off = (word as usize - 1) * 8;
b[off..off + 8].copy_from_slice(&v.to_le_bytes());
};
wf(seg1_start_word, 0.5, &mut buf);
wf(seg1_start_word + 1, 0.5, &mut buf);
wf(seg1_start_word + 2, 1.0e8, &mut buf);
wf(seg1_start_word + 3, 0.0, &mut buf);
wf(seg1_start_word + 4, 0.0, &mut buf);
wf(seg1_end_word - 3, 0.0, &mut buf);
wf(seg1_end_word - 2, 1.0, &mut buf);
wf(seg1_end_word - 1, 5.0, &mut buf);
wf(seg1_end_word, 1.0, &mut buf);
wf(seg2_start_word, 0.5, &mut buf);
wf(seg2_start_word + 1, 0.5, &mut buf);
wf(seg2_start_word + 2, 0.0, &mut buf);
wf(seg2_start_word + 3, 3.84e5, &mut buf);
wf(seg2_start_word + 4, 0.0, &mut buf);
wf(seg2_end_word - 3, 0.0, &mut buf);
wf(seg2_end_word - 2, 1.0, &mut buf);
wf(seg2_end_word - 1, 5.0, &mut buf);
wf(seg2_end_word, 1.0, &mut buf);
SpkKernel::from_bytes(buf).unwrap()
}
#[test]
fn provider_returns_typed_barycentric_position_for_earth() {
let kernel = synthetic_kernel();
let provider: SpiceEphemerisProvider<Barycentric> = SpiceEphemerisProvider::new(kernel, 0);
let state = provider.state(399, 0.5).unwrap();
assert!((state.position.x().value() - 1.0e8).abs() < 1e-6);
assert!(state.position.y().value().abs() < 1e-6);
}
#[test]
fn provider_returns_typed_geocentric_position_for_moon() {
let kernel = synthetic_kernel();
let provider: SpiceEphemerisProvider<Geocentric> = SpiceEphemerisProvider::new(kernel, 399);
let state = provider.state(301, 0.5).unwrap();
assert!((state.position.y().value() - 3.84e5).abs() < 1e-6);
assert!(state.position.x().value().abs() < 1e-6);
}
#[test]
fn cloned_provider_shares_kernel() {
let kernel = synthetic_kernel();
let p1: SpiceEphemerisProvider<Barycentric> = SpiceEphemerisProvider::new(kernel, 0);
let p2 = p1.clone();
let s1 = p1.state(399, 0.5).unwrap();
let s2 = p2.state(399, 0.5).unwrap();
assert_eq!(s1.position.x().value(), s2.position.x().value());
assert_eq!(p1.center_naif_id(), 0);
assert_eq!(p2.center_naif_id(), 0);
}
#[test]
fn debug_repr_lists_center_name() {
let kernel = synthetic_kernel();
let provider: SpiceEphemerisProvider<Barycentric> = SpiceEphemerisProvider::new(kernel, 0);
let dbg = format!("{provider:?}");
assert!(dbg.contains("Barycentric"));
assert!(dbg.contains("center_naif_id: 0"));
}
}