1use std::collections::HashMap;
12
13use hifitime::Epoch;
14
15#[cfg(feature = "python")]
16use pyo3::prelude::*;
17use snafu::ensure;
18
19use crate::ephemerides::NoEphemerisLoadedSnafu;
20use crate::naif::daf::DAFError;
21use crate::naif::daf::NAIFSummaryRecord;
22use crate::naif::spk::summary::SPKSummaryRecord;
23use crate::naif::SPK;
24use crate::{ephemerides::EphemerisError, NaifId};
25use log::error;
26
27use super::{Almanac, MAX_LOADED_SPKS};
28
29impl Almanac {
30 pub fn from_spk(spk: SPK) -> Result<Almanac, EphemerisError> {
31 let me = Self::default();
32 me.with_spk(spk)
33 }
34
35 pub fn with_spk(&self, spk: SPK) -> Result<Self, EphemerisError> {
38 let mut me = self.clone();
40 let mut data_idx = MAX_LOADED_SPKS;
42 for (idx, item) in self.spk_data.iter().enumerate() {
43 if item.is_none() {
44 data_idx = idx;
45 break;
46 }
47 }
48 if data_idx == MAX_LOADED_SPKS {
49 return Err(EphemerisError::StructureIsFull {
50 max_slots: MAX_LOADED_SPKS,
51 });
52 }
53 me.spk_data[data_idx] = Some(spk);
54 Ok(me)
55 }
56}
57
58impl Almanac {
59 pub fn num_loaded_spk(&self) -> usize {
60 let mut count = 0;
61 for maybe in &self.spk_data {
62 if maybe.is_none() {
63 break;
64 } else {
65 count += 1;
66 }
67 }
68
69 count
70 }
71
72 pub fn spk_summary_from_name_at_epoch(
74 &self,
75 name: &str,
76 epoch: Epoch,
77 ) -> Result<(&SPKSummaryRecord, usize, usize), EphemerisError> {
78 for (spk_no, maybe_spk) in self
79 .spk_data
80 .iter()
81 .take(self.num_loaded_spk())
82 .rev()
83 .enumerate()
84 {
85 let spk = maybe_spk.as_ref().unwrap();
86 if let Ok((summary, idx_in_spk)) = spk.summary_from_name_at_epoch(name, epoch) {
87 return Ok((summary, spk_no, idx_in_spk));
88 }
89 }
90
91 error!("Almanac: No summary {name} valid at epoch {epoch}");
93 Err(EphemerisError::SPK {
94 action: "searching for SPK summary",
95 source: DAFError::SummaryNameAtEpochError {
96 kind: "SPK",
97 name: name.to_string(),
98 epoch,
99 },
100 })
101 }
102
103 pub fn spk_summary_at_epoch(
105 &self,
106 id: i32,
107 epoch: Epoch,
108 ) -> Result<(&SPKSummaryRecord, usize, usize), EphemerisError> {
109 for (spk_no, maybe_spk) in self
110 .spk_data
111 .iter()
112 .take(self.num_loaded_spk())
113 .rev()
114 .enumerate()
115 {
116 let spk = maybe_spk.as_ref().unwrap();
117 if let Ok((summary, idx_in_spk)) = spk.summary_from_id_at_epoch(id, epoch) {
118 return Ok((summary, self.num_loaded_spk() - spk_no - 1, idx_in_spk));
120 }
121 }
122
123 error!("Almanac: No summary {id} valid at epoch {epoch}");
124 Err(EphemerisError::SPK {
126 action: "searching for SPK summary",
127 source: DAFError::SummaryIdAtEpochError {
128 kind: "SPK",
129 id,
130 epoch,
131 },
132 })
133 }
134
135 pub fn spk_summary_from_name(
137 &self,
138 name: &str,
139 ) -> Result<(&SPKSummaryRecord, usize, usize), EphemerisError> {
140 for (spk_no, maybe_spk) in self
141 .spk_data
142 .iter()
143 .take(self.num_loaded_spk())
144 .rev()
145 .enumerate()
146 {
147 let spk = maybe_spk.as_ref().unwrap();
148 if let Ok((summary, idx_in_spk)) = spk.summary_from_name(name) {
149 return Ok((summary, spk_no, idx_in_spk));
150 }
151 }
152
153 error!("Almanac: No summary {name} valid");
155
156 Err(EphemerisError::SPK {
157 action: "searching for SPK summary",
158 source: DAFError::SummaryNameError {
159 kind: "SPK",
160 name: name.to_string(),
161 },
162 })
163 }
164
165 pub fn spk_summary(
167 &self,
168 id: i32,
169 ) -> Result<(&SPKSummaryRecord, usize, usize), EphemerisError> {
170 for (spk_no, maybe_spk) in self
171 .spk_data
172 .iter()
173 .take(self.num_loaded_spk())
174 .rev()
175 .enumerate()
176 {
177 let spk = maybe_spk.as_ref().unwrap();
178 if let Ok((summary, idx_in_spk)) = spk.summary_from_id(id) {
179 return Ok((summary, self.num_loaded_spk() - spk_no - 1, idx_in_spk));
181 }
182 }
183
184 error!("Almanac: No summary {id} valid");
185 Err(EphemerisError::SPK {
187 action: "searching for SPK summary",
188 source: DAFError::SummaryIdError { kind: "SPK", id },
189 })
190 }
191}
192
193#[cfg_attr(feature = "python", pymethods)]
194impl Almanac {
195 pub fn spk_summaries(&self, id: NaifId) -> Result<Vec<SPKSummaryRecord>, EphemerisError> {
203 let mut summaries = vec![];
204 for maybe_spk in self.spk_data.iter().take(self.num_loaded_spk()).rev() {
205 let spk = maybe_spk.as_ref().unwrap();
206 if let Ok(these_summaries) = spk.data_summaries() {
207 for summary in these_summaries {
208 if summary.id() == id {
209 summaries.push(*summary);
210 }
211 }
212 }
213 }
214
215 if summaries.is_empty() {
216 error!("Almanac: No summary {id} valid");
217 Err(EphemerisError::SPK {
219 action: "searching for SPK summary",
220 source: DAFError::SummaryIdError { kind: "SPK", id },
221 })
222 } else {
223 Ok(summaries)
224 }
225 }
226
227 pub fn spk_domain(&self, id: NaifId) -> Result<(Epoch, Epoch), EphemerisError> {
232 let summaries = self.spk_summaries(id)?;
233
234 let start = summaries
236 .iter()
237 .min_by_key(|summary| summary.start_epoch())
238 .unwrap()
239 .start_epoch();
240
241 let end = summaries
242 .iter()
243 .max_by_key(|summary| summary.end_epoch())
244 .unwrap()
245 .end_epoch();
246
247 Ok((start, end))
248 }
249
250 pub fn spk_domains(&self) -> Result<HashMap<NaifId, (Epoch, Epoch)>, EphemerisError> {
257 ensure!(self.num_loaded_spk() > 0, NoEphemerisLoadedSnafu);
258
259 let mut domains = HashMap::new();
260 for maybe_spk in self.spk_data.iter().take(self.num_loaded_spk()).rev() {
261 let spk = maybe_spk.as_ref().unwrap();
262 if let Ok(these_summaries) = spk.data_summaries() {
263 for summary in these_summaries {
264 let this_id = summary.id();
265 match domains.get_mut(&this_id) {
266 Some((ref mut cur_start, ref mut cur_end)) => {
267 if *cur_start > summary.start_epoch() {
268 *cur_start = summary.start_epoch();
269 }
270 if *cur_end < summary.end_epoch() {
271 *cur_end = summary.end_epoch();
272 }
273 }
274 None => {
275 domains.insert(this_id, (summary.start_epoch(), summary.end_epoch()));
276 }
277 }
278 }
279 }
280 }
281
282 Ok(domains)
283 }
284}
285
286#[cfg(test)]
287mod ut_almanac_spk {
288 use crate::{
289 constants::frames::{EARTH_J2000, MOON_J2000},
290 prelude::{Almanac, Epoch},
291 };
292
293 #[test]
294 fn summaries_nothing_loaded() {
295 let almanac = Almanac::default();
296 let e = Epoch::now().unwrap();
297
298 assert!(
299 almanac.spk_summary(0).is_err(),
300 "empty Almanac should report an error"
301 );
302 assert!(
303 almanac.spk_summary_at_epoch(0, e).is_err(),
304 "empty Almanac should report an error"
305 );
306 assert!(
307 almanac.spk_summary_from_name("invalid name").is_err(),
308 "empty Almanac should report an error"
309 );
310 assert!(
311 almanac
312 .spk_summary_from_name_at_epoch("invalid name", e)
313 .is_err(),
314 "empty Almanac should report an error"
315 );
316 }
317
318 #[test]
319 fn queries_nothing_loaded() {
320 let almanac = Almanac::default();
321 let e = Epoch::now().unwrap();
322
323 assert!(
324 almanac.try_find_ephemeris_root().is_err(),
325 "empty Almanac should report an error"
326 );
327
328 assert!(
329 almanac.ephemeris_path_to_root(MOON_J2000, e).is_err(),
330 "empty Almanac should report an error"
331 );
332
333 assert!(
334 almanac
335 .common_ephemeris_path(MOON_J2000, EARTH_J2000, e)
336 .is_err(),
337 "empty Almanac should report an error"
338 );
339 }
340}