astroceleste_engine/ephemeris/
kernels.rs1use crate::constants::{AU_KM, DAY_S, T0};
4use crate::frames::Vec3;
5use crate::time::Time;
6
7use super::spk::{Segment, Spk, SpkError};
8
9pub const SSB: i32 = 0;
11
12#[derive(Debug)]
16pub struct Kernel {
17 pub name: String,
19 spk: Spk,
20 pub start_jd: f64,
22 pub end_jd: f64,
24}
25
26impl Kernel {
27 pub fn new(name: impl Into<String>, spk: Spk) -> Result<Self, SpkError> {
30 let segments = spk.segments();
31 if segments.is_empty() {
32 return Err(SpkError::Format("kernel has no segments".into()));
33 }
34 let (mut start_et, mut end_et) = (f64::NEG_INFINITY, f64::INFINITY);
35 let mut targets: Vec<i32> = segments.iter().map(|s| s.target).collect();
36 targets.sort_unstable();
37 targets.dedup();
38 for target in targets {
39 let (start, end) = body_span(segments, target);
40 start_et = start_et.max(start);
41 end_et = end_et.min(end);
42 }
43 if start_et.is_nan() || end_et.is_nan() || start_et > end_et {
44 return Err(SpkError::Format(
45 "the kernel's bodies share no common time span".into(),
46 ));
47 }
48 let (start_jd, end_jd) = (start_et / DAY_S + T0, end_et / DAY_S + T0);
49 Ok(Kernel {
50 name: name.into(),
51 spk,
52 start_jd,
53 end_jd,
54 })
55 }
56
57 pub fn covers(&self, jd: f64) -> bool {
59 self.start_jd <= jd && jd <= self.end_jd
60 }
61
62 pub fn contains(&self, code: i32) -> bool {
64 self.spk
65 .segments()
66 .iter()
67 .any(|s| s.target == code || s.center == code)
68 }
69
70 fn segment_for(&self, target: i32, et: f64) -> Option<&Segment> {
74 let mut segments = self.spk.segments().iter().filter(|s| s.target == target);
75 let first = segments.next()?;
76 if first.covers(et) {
77 return Some(first);
78 }
79 segments.find(|s| s.covers(et)).or(Some(first))
80 }
81
82 #[doc(hidden)] pub fn barycentric(&self, code: i32, t: &Time) -> Result<(Vec3, Vec3), SpkError> {
87 const MAX_CHAIN: usize = 8;
88 let et = (t.whole - T0 + t.tdb_fraction) * DAY_S;
89 let mut chain: [Option<&Segment>; MAX_CHAIN] = [None; MAX_CHAIN];
90 let mut len = 0;
91 let mut current = code;
92 while current != SSB {
93 if len == MAX_CHAIN {
94 return Err(SpkError::Format(format!("segment chain for {code} loops")));
95 }
96 let segment = self.segment_for(current, et).ok_or(SpkError::NoSegment {
97 target: current,
98 center: SSB,
99 })?;
100 chain[len] = Some(segment);
101 len += 1;
102 current = segment.center;
103 }
104 let mut position = [0.0; 3];
105 let mut velocity = [0.0; 3];
106 for segment in chain[..len].iter().rev().flatten() {
107 let (p, v) = self
108 .spk
109 .segment_state_split(segment, t.whole, t.tdb_fraction)?;
110 for k in 0..3 {
111 position[k] += p[k] / AU_KM;
112 velocity[k] += v[k] * DAY_S / AU_KM;
113 }
114 }
115 Ok((position, velocity))
116 }
117}
118
119fn body_span(segments: &[Segment], target: i32) -> (f64, f64) {
121 let mut spans: Vec<(f64, f64)> = segments
122 .iter()
123 .filter(|s| s.target == target)
124 .map(|s| (s.start_et, s.end_et))
125 .collect();
126 spans.sort_by(|a, b| a.0.total_cmp(&b.0));
127 let mut best = (f64::NAN, f64::NAN);
128 let mut run = spans[0];
129 for &(start, end) in &spans[1..] {
130 if start <= run.1 {
131 run.1 = run.1.max(end);
132 } else {
133 best = longer(best, run);
134 run = (start, end);
135 }
136 }
137 longer(best, run)
138}
139
140fn longer(a: (f64, f64), b: (f64, f64)) -> (f64, f64) {
141 if a.1 - a.0 >= b.1 - b.0 {
142 a
143 } else {
144 b
145 }
146}
147
148#[derive(Debug, Default)]
150pub struct KernelSet {
151 kernels: Vec<Kernel>,
152}
153
154impl KernelSet {
155 pub fn new() -> Self {
157 Self::default()
158 }
159
160 pub fn push(&mut self, kernel: Kernel) {
162 self.kernels.push(kernel);
163 }
164
165 pub fn kernels(&self) -> &[Kernel] {
167 &self.kernels
168 }
169
170 pub fn is_empty(&self) -> bool {
172 self.kernels.is_empty()
173 }
174
175 pub fn for_jd(&self, jd: f64) -> Option<&Kernel> {
177 self.kernels.iter().find(|k| k.covers(jd))
178 }
179
180 pub fn coverage(&self) -> Option<(f64, f64)> {
182 if self.kernels.is_empty() {
183 return None;
184 }
185 let start = self
186 .kernels
187 .iter()
188 .map(|k| k.start_jd)
189 .fold(f64::INFINITY, f64::min);
190 let end = self
191 .kernels
192 .iter()
193 .map(|k| k.end_jd)
194 .fold(f64::NEG_INFINITY, f64::max);
195 Some((start, end))
196 }
197}