use std::collections::HashMap;
use std::path::Path;
use nalgebra::{Point3, Vector3};
use super::errors::{JplephemError, Result};
use super::names::target_id;
use super::spk::{jd_to_seconds, SPK};
pub const AU_KM: f64 = 149_597_870.700;
pub const S_PER_DAY: f64 = 86400.0;
#[derive(Debug, Clone)]
pub struct PlanetState {
pub position: Point3<f64>,
pub velocity: Vector3<f64>,
}
pub struct SpiceKernel {
spk: SPK,
chains: HashMap<i32, Vec<(i32, i32)>>,
}
impl SpiceKernel {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let spk = SPK::open(path)?;
let chains = Self::build_chains(&spk);
Ok(SpiceKernel { spk, chains })
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
let spk = SPK::from_bytes(data)?;
let chains = Self::build_chains(&spk);
Ok(SpiceKernel { spk, chains })
}
fn build_chains(spk: &SPK) -> HashMap<i32, Vec<(i32, i32)>> {
let mut adj: HashMap<i32, Vec<(i32, i32)>> = HashMap::new();
for seg in &spk.segments {
adj.entry(seg.center)
.or_default()
.push((seg.center, seg.target));
}
let mut chains = HashMap::new();
let mut queue = std::collections::VecDeque::new();
let mut parent: HashMap<i32, (i32, i32)> = HashMap::new();
queue.push_back(0i32);
parent.insert(0, (0, 0));
while let Some(node) = queue.pop_front() {
if let Some(edges) = adj.get(&node) {
for &(center, target) in edges {
if let std::collections::hash_map::Entry::Vacant(e) = parent.entry(target) {
e.insert((center, target));
queue.push_back(target);
}
}
}
}
for &target_id in parent.keys() {
if target_id == 0 {
continue;
}
let mut chain = Vec::new();
let mut current = target_id;
while current != 0 {
if let Some(&(center, target)) = parent.get(¤t) {
if center == 0 && target == 0 {
break;
}
chain.push((center, target));
current = center;
} else {
break;
}
}
chain.reverse();
chains.insert(target_id, chain);
}
chains
}
pub fn get(&self, name: &str) -> Result<VectorFunction> {
let id = self.resolve_name(name)?;
let chain = self.chains.get(&id).ok_or_else(|| {
JplephemError::Other(format!(
"No path from SSB to body {id} ('{name}') in this kernel"
))
})?;
Ok(VectorFunction {
target_id: id,
target_name: name.to_string(),
chain: chain.clone(),
})
}
fn resolve_name(&self, name: &str) -> Result<i32> {
if let Ok(id) = name.parse::<i32>() {
return Ok(id);
}
target_id(name).ok_or_else(|| JplephemError::Other(format!("Unknown body name: '{name}'")))
}
pub fn compute_chain_pub(
&mut self,
chain: &[(i32, i32)],
tdb_seconds: f64,
) -> Result<(Vector3<f64>, Vector3<f64>)> {
let mut total_pos = Vector3::new(0.0, 0.0, 0.0);
let mut total_vel = Vector3::new(0.0, 0.0, 0.0);
for &(center, target) in chain {
let seg = self.spk.get_segment_mut(center, target)?;
let (pos, vel) = seg.compute_and_differentiate(tdb_seconds, 0.0)?;
total_pos += pos;
total_vel += vel;
}
Ok((total_pos, total_vel))
}
pub fn compute_at_jd(&mut self, name: &str, jd_tdb: f64) -> Result<PlanetState> {
let vf = self.get(name)?;
let tdb_seconds = jd_to_seconds(jd_tdb);
let (pos_km, vel_km_s) = self.compute_chain_pub(&vf.chain, tdb_seconds)?;
Ok(PlanetState {
position: Point3::new(pos_km.x / AU_KM, pos_km.y / AU_KM, pos_km.z / AU_KM),
velocity: Vector3::new(
vel_km_s.x * S_PER_DAY / AU_KM,
vel_km_s.y * S_PER_DAY / AU_KM,
vel_km_s.z * S_PER_DAY / AU_KM,
),
})
}
pub fn compute_km_jd(
&mut self,
name: &str,
jd_tdb: f64,
) -> Result<(Vector3<f64>, Vector3<f64>)> {
let vf = self.get(name)?;
let tdb_seconds = jd_to_seconds(jd_tdb);
self.compute_chain_pub(&vf.chain, tdb_seconds)
}
pub fn spk(&self) -> &SPK {
&self.spk
}
pub fn spk_mut(&mut self) -> &mut SPK {
&mut self.spk
}
}
#[derive(Debug, Clone)]
pub struct VectorFunction {
pub target_id: i32,
pub target_name: String,
pub chain: Vec<(i32, i32)>,
}
impl std::fmt::Display for VectorFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"VectorFunction({} [{}], {} segments)",
self.target_name,
self.target_id,
self.chain.len()
)
}
}