1use std::collections::HashMap;
12
13use hifitime::Epoch;
14
15#[cfg(feature = "python")]
16use pyo3::prelude::*;
17use snafu::ensure;
18
19use crate::naif::daf::NAIFSummaryRecord;
20use crate::naif::pck::BPCSummaryRecord;
21use crate::naif::BPC;
22use crate::orientations::{NoOrientationsLoadedSnafu, OrientationError};
23use crate::{naif::daf::DAFError, NaifId};
24
25use super::{Almanac, MAX_LOADED_BPCS};
26
27impl Almanac {
28 pub fn from_bpc(bpc: BPC) -> Result<Almanac, OrientationError> {
29 let me = Self::default();
30 me.with_bpc(bpc)
31 }
32
33 pub fn with_bpc(&self, bpc: BPC) -> Result<Self, OrientationError> {
35 let mut me = self.clone();
37 let mut data_idx = MAX_LOADED_BPCS;
38 for (idx, item) in self.bpc_data.iter().enumerate() {
39 if item.is_none() {
40 data_idx = idx;
41 break;
42 }
43 }
44 if data_idx == MAX_LOADED_BPCS {
45 return Err(OrientationError::StructureIsFull {
46 max_slots: MAX_LOADED_BPCS,
47 });
48 }
49 me.bpc_data[data_idx] = Some(bpc);
50 Ok(me)
51 }
52
53 pub fn num_loaded_bpc(&self) -> usize {
54 let mut count = 0;
55 for maybe in &self.bpc_data {
56 if maybe.is_none() {
57 break;
58 } else {
59 count += 1;
60 }
61 }
62
63 count
64 }
65
66 pub fn bpc_summary_from_name_at_epoch(
68 &self,
69 name: &str,
70 epoch: Epoch,
71 ) -> Result<(&BPCSummaryRecord, usize, usize), OrientationError> {
72 for (no, maybe_bpc) in self
73 .bpc_data
74 .iter()
75 .take(self.num_loaded_bpc())
76 .rev()
77 .enumerate()
78 {
79 let bpc = maybe_bpc.as_ref().unwrap();
80 if let Ok((summary, idx_in_bpc)) = bpc.summary_from_name_at_epoch(name, epoch) {
81 return Ok((summary, no, idx_in_bpc));
82 }
83 }
84
85 Err(OrientationError::BPC {
87 action: "searching for BPC summary",
88 source: DAFError::SummaryNameAtEpochError {
89 kind: "BPC",
90 name: name.to_string(),
91 epoch,
92 },
93 })
94 }
95
96 pub fn bpc_summary_at_epoch(
98 &self,
99 id: i32,
100 epoch: Epoch,
101 ) -> Result<(&BPCSummaryRecord, usize, usize), OrientationError> {
102 for (no, maybe_bpc) in self
103 .bpc_data
104 .iter()
105 .take(self.num_loaded_bpc())
106 .rev()
107 .enumerate()
108 {
109 let bpc = maybe_bpc.as_ref().unwrap();
110 if let Ok((summary, idx_in_bpc)) = bpc.summary_from_id_at_epoch(id, epoch) {
111 return Ok((summary, self.num_loaded_bpc() - no - 1, idx_in_bpc));
113 }
114 }
115
116 Err(OrientationError::BPC {
118 action: "searching for BPC summary",
119 source: DAFError::SummaryIdAtEpochError {
120 kind: "BPC",
121 id,
122 epoch,
123 },
124 })
125 }
126
127 pub fn bpc_summary_from_name(
129 &self,
130 name: &str,
131 ) -> Result<(&BPCSummaryRecord, usize, usize), OrientationError> {
132 for (bpc_no, maybe_bpc) in self
133 .bpc_data
134 .iter()
135 .take(self.num_loaded_bpc())
136 .rev()
137 .enumerate()
138 {
139 let bpc = maybe_bpc.as_ref().unwrap();
140 if let Ok((summary, idx_in_bpc)) = bpc.summary_from_name(name) {
141 return Ok((summary, bpc_no, idx_in_bpc));
142 }
143 }
144
145 Err(OrientationError::BPC {
147 action: "searching for BPC summary",
148 source: DAFError::SummaryNameError {
149 kind: "BPC",
150 name: name.to_string(),
151 },
152 })
153 }
154
155 pub fn bpc_summary(
157 &self,
158 id: i32,
159 ) -> Result<(&BPCSummaryRecord, usize, usize), OrientationError> {
160 for (no, maybe_bpc) in self
161 .bpc_data
162 .iter()
163 .take(self.num_loaded_bpc())
164 .rev()
165 .enumerate()
166 {
167 let bpc = maybe_bpc.as_ref().unwrap();
168 if let Ok((summary, idx_in_bpc)) = bpc.summary_from_id(id) {
169 return Ok((summary, self.num_loaded_bpc() - no - 1, idx_in_bpc));
171 }
172 }
173
174 Err(OrientationError::BPC {
176 action: "searching for BPC summary",
177 source: DAFError::SummaryIdError { kind: "BPC", id },
178 })
179 }
180}
181
182#[cfg_attr(feature = "python", pymethods)]
183impl Almanac {
184 pub fn bpc_summaries(&self, id: NaifId) -> Result<Vec<BPCSummaryRecord>, OrientationError> {
192 let mut summaries = vec![];
193
194 for maybe_bpc in self.bpc_data.iter().take(self.num_loaded_bpc()).rev() {
195 let bpc = maybe_bpc.as_ref().unwrap();
196 if let Ok(these_summaries) = bpc.data_summaries() {
197 for summary in these_summaries {
198 if summary.id() == id {
199 summaries.push(*summary);
200 }
201 }
202 }
203 }
204
205 if summaries.is_empty() {
206 Err(OrientationError::BPC {
208 action: "searching for BPC summary",
209 source: DAFError::SummaryIdError { kind: "BPC", id },
210 })
211 } else {
212 Ok(summaries)
213 }
214 }
215
216 pub fn bpc_domain(&self, id: NaifId) -> Result<(Epoch, Epoch), OrientationError> {
221 let summaries = self.bpc_summaries(id)?;
222
223 let start = summaries
225 .iter()
226 .min_by_key(|summary| summary.start_epoch())
227 .unwrap()
228 .start_epoch();
229
230 let end = summaries
231 .iter()
232 .max_by_key(|summary| summary.end_epoch())
233 .unwrap()
234 .end_epoch();
235
236 Ok((start, end))
237 }
238
239 pub fn bpc_domains(&self) -> Result<HashMap<NaifId, (Epoch, Epoch)>, OrientationError> {
246 ensure!(self.num_loaded_bpc() > 0, NoOrientationsLoadedSnafu);
247
248 let mut domains = HashMap::new();
249 for maybe_bpc in self.bpc_data.iter().take(self.num_loaded_bpc()).rev() {
250 let bpc = maybe_bpc.as_ref().unwrap();
251 if let Ok(these_summaries) = bpc.data_summaries() {
252 for summary in these_summaries {
253 let this_id = summary.id();
254 match domains.get_mut(&this_id) {
255 Some((ref mut cur_start, ref mut cur_end)) => {
256 if *cur_start > summary.start_epoch() {
257 *cur_start = summary.start_epoch();
258 }
259 if *cur_end < summary.end_epoch() {
260 *cur_end = summary.end_epoch();
261 }
262 }
263 None => {
264 domains.insert(this_id, (summary.start_epoch(), summary.end_epoch()));
265 }
266 }
267 }
268 }
269 }
270
271 Ok(domains)
272 }
273}
274
275#[cfg(test)]
276mod ut_almanac_bpc {
277 use crate::prelude::{Almanac, Epoch};
278
279 #[test]
280 fn summaries_nothing_loaded() {
281 let almanac = Almanac::default();
282
283 let e = Epoch::now().unwrap();
284
285 assert!(
286 almanac.bpc_summary(0).is_err(),
287 "empty Almanac should report an error"
288 );
289 assert!(
290 almanac.bpc_summary_at_epoch(0, e).is_err(),
291 "empty Almanac should report an error"
292 );
293 assert!(
294 almanac.bpc_summary_from_name("invalid name").is_err(),
295 "empty Almanac should report an error"
296 );
297 assert!(
298 almanac
299 .bpc_summary_from_name_at_epoch("invalid name", e)
300 .is_err(),
301 "empty Almanac should report an error"
302 );
303 }
304}