diff --git a/src/bin/anise/main.rs b/src/bin/anise/main.rs index 5945bb17..90d68875 100644 --- a/src/bin/anise/main.rs +++ b/src/bin/anise/main.rs @@ -6,6 +6,7 @@ use anise::cli::inspect::{BpcRow, SpkRow}; use anise::cli::CliErrors; use anise::file_mmap; use anise::naif::daf::{FileRecord, NAIFRecord, NAIFSummaryRecord}; +use anise::naif::kpl::parser::convert_tpc; use anise::prelude::*; use anise::structure::dataset::{DataSet, DataSetType}; use anise::structure::metadata::Metadata; @@ -18,6 +19,7 @@ use zerocopy::FromBytes; const LOG_VAR: &str = "ANISE_LOG"; +#[cfg(feature = "std")] fn main() -> Result<(), CliErrors> { if var(LOG_VAR).is_err() { set_var(LOG_VAR, "INFO"); @@ -223,19 +225,11 @@ fn main() -> Result<(), CliErrors> { gmfile, outfile, } => { - if cfg!(feature = "std") { - use anise::naif::kpl::parser::convert_tpc; + let dataset = convert_tpc(pckfile, gmfile).map_err(CliErrors::AniseError)?; - let dataset = convert_tpc(pckfile, gmfile).map_err(CliErrors::AniseError)?; + dataset.save_as(outfile, false)?; - dataset.save_as(outfile, false)?; - - Ok(()) - } else { - Err(CliErrors::ArgumentError( - "KPL parsing requires std feature".to_string(), - )) - } + Ok(()) } } } diff --git a/src/context/bpc.rs b/src/context/bpc.rs new file mode 100644 index 00000000..b81c00f1 --- /dev/null +++ b/src/context/bpc.rs @@ -0,0 +1,146 @@ +/* + * ANISE Toolkit + * Copyright (C) 2021-2022 Christopher Rabotin et al. (cf. AUTHORS.md) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * Documentation: https://nyxspace.com/ + */ + +use hifitime::Epoch; + +use crate::errors::AniseError; +use crate::naif::pck::BPCSummaryRecord; +use crate::naif::BPC; +use log::error; + +use super::{Context, MAX_LOADED_BPCS}; + +impl<'a: 'b, 'b> Context<'a> { + /// Loads a Binary Planetary Constants kernel. + pub fn load_bpc(&self, bpc: &'b BPC) -> Result, AniseError> { + // This is just a bunch of pointers so it doesn't use much memory. + let mut me = self.clone(); + let mut data_idx = MAX_LOADED_BPCS; + for (idx, item) in self.bpc_data.iter().enumerate() { + if item.is_none() { + data_idx = idx; + break; + } + } + if data_idx == MAX_LOADED_BPCS { + return Err(AniseError::StructureIsFull); + } + me.bpc_data[data_idx] = Some(bpc); + Ok(me) + } + + pub fn num_loaded_bpc(&self) -> usize { + let mut count = 0; + for maybe in self.bpc_data { + if maybe.is_none() { + break; + } else { + count += 1; + } + } + + count + } + + /// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch and the BPC where this name was found to be valid at that epoch. + pub fn bpc_summary_from_name_at_epoch( + &self, + name: &str, + epoch: Epoch, + ) -> Result<(&BPCSummaryRecord, usize, usize), AniseError> { + for (no, maybe_bpc) in self + .bpc_data + .iter() + .take(self.num_loaded_bpc()) + .rev() + .enumerate() + { + let bpc = maybe_bpc.unwrap(); + if let Ok((summary, idx_in_bpc)) = bpc.summary_from_name_at_epoch(name, epoch) { + return Ok((summary, no, idx_in_bpc)); + } + } + + // If we're reached this point, there is no relevant summary at this epoch. + error!("Context: No summary {name} valid at epoch {epoch}"); + Err(AniseError::MissingInterpolationData(epoch)) + } + + /// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch + pub fn bpc_summary_at_epoch( + &self, + id: i32, + epoch: Epoch, + ) -> Result<(&BPCSummaryRecord, usize, usize), AniseError> { + // TODO: Consider a return type here + for (no, maybe_bpc) in self + .bpc_data + .iter() + .take(self.num_loaded_bpc()) + .rev() + .enumerate() + { + let bpc = maybe_bpc.unwrap(); + if let Ok((summary, idx_in_bpc)) = bpc.summary_from_id_at_epoch(id, epoch) { + // NOTE: We're iterating backward, so the correct BPC number is "total loaded" minus "current iteration". + return Ok((summary, self.num_loaded_bpc() - no - 1, idx_in_bpc)); + } + } + + error!("Context: No summary {id} valid at epoch {epoch}"); + // If we're reached this point, there is no relevant summary at this epoch. + Err(AniseError::MissingInterpolationData(epoch)) + } + + /// Returns the summary given the name of the summary record. + pub fn bpc_summary_from_name( + &self, + name: &str, + ) -> Result<(&BPCSummaryRecord, usize, usize), AniseError> { + for (bpc_no, maybe_bpc) in self + .bpc_data + .iter() + .take(self.num_loaded_bpc()) + .rev() + .enumerate() + { + let bpc = maybe_bpc.unwrap(); + if let Ok((summary, idx_in_bpc)) = bpc.summary_from_name(name) { + return Ok((summary, bpc_no, idx_in_bpc)); + } + } + + // If we're reached this point, there is no relevant summary at this epoch. + error!("Context: No summary {name} valid"); + Err(AniseError::NoInterpolationData) + } + + /// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch + pub fn bpc_summary(&self, id: i32) -> Result<(&BPCSummaryRecord, usize, usize), AniseError> { + // TODO: Consider a return type here + for (no, maybe_bpc) in self + .bpc_data + .iter() + .take(self.num_loaded_bpc()) + .rev() + .enumerate() + { + let bpc = maybe_bpc.unwrap(); + if let Ok((summary, idx_in_bpc)) = bpc.summary_from_id(id) { + // NOTE: We're iterating backward, so the correct BPC number is "total loaded" minus "current iteration". + return Ok((summary, self.num_loaded_bpc() - no - 1, idx_in_bpc)); + } + } + + error!("Context: No summary {id} valid"); + // If we're reached this point, there is no relevant summary + Err(AniseError::NoInterpolationData) + } +} diff --git a/src/context/mod.rs b/src/context/mod.rs index f9bf4fe3..a3aef258 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -8,16 +8,11 @@ * Documentation: https://nyxspace.com/ */ -use hifitime::Epoch; - -use crate::errors::AniseError; -use crate::naif::spk::summary::SPKSummaryRecord; use crate::naif::{BPC, SPK}; use crate::structure::dataset::DataSet; use crate::structure::planetocentric::PlanetaryData; use crate::structure::spacecraft::SpacecraftData; use core::fmt; -use log::error; // TODO: Switch these to build constants so that it's configurable when building the library. pub const MAX_LOADED_SPKS: usize = 32; @@ -25,6 +20,9 @@ pub const MAX_LOADED_BPCS: usize = 8; pub const MAX_SPACECRAFT_DATA: usize = 16; pub const MAX_PLANETARY_DATA: usize = 64; +pub mod bpc; +pub mod spk; + /// A SPICE context contains all of the loaded SPICE data. /// /// # Limitations @@ -41,163 +39,6 @@ pub struct Context<'a> { pub spacecraft_data: DataSet<'a, SpacecraftData<'a>, MAX_SPACECRAFT_DATA>, } -impl<'a: 'b, 'b> Context<'a> { - pub fn from_spk(spk: &'a SPK) -> Result, AniseError> { - let me = Self::default(); - me.load_spk(spk) - } - - /// Loads a new SPK file into a new context. - /// This new context is needed to satisfy the unloading of files. In fact, to unload a file, simply let the newly loaded context drop out of scope and Rust will clean it up. - pub fn load_spk(&self, spk: &'b SPK) -> Result, AniseError> { - // This is just a bunch of pointers so it doesn't use much memory. - let mut me = self.clone(); - // Parse as SPK and place into the SPK list if there is room - let mut data_idx = MAX_LOADED_SPKS; - for (idx, item) in self.spk_data.iter().enumerate() { - if item.is_none() { - data_idx = idx; - break; - } - } - if data_idx == MAX_LOADED_SPKS { - return Err(AniseError::StructureIsFull); - } - me.spk_data[data_idx] = Some(spk); - Ok(me) - } - - /// Loads a Binary Planetary Constants kernel. - pub fn load_bpc(&self, bpc: &'b BPC) -> Result, AniseError> { - // This is just a bunch of pointers so it doesn't use much memory. - let mut me = self.clone(); - // Parse as SPK and place into the SPK list if there is room - let mut data_idx = MAX_LOADED_SPKS; - for (idx, item) in self.bpc_data.iter().enumerate() { - if item.is_none() { - data_idx = idx; - break; - } - } - if data_idx == MAX_LOADED_SPKS { - return Err(AniseError::StructureIsFull); - } - me.bpc_data[data_idx] = Some(bpc); - Ok(me) - } - - pub fn num_loaded_spk(&self) -> usize { - let mut count = 0; - for maybe in self.spk_data { - if maybe.is_none() { - break; - } else { - count += 1; - } - } - - count - } - - /// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch and the SPK where this name was found to be valid at that epoch. - pub fn spk_summary_from_name_at_epoch( - &self, - name: &str, - epoch: Epoch, - ) -> Result<(&SPKSummaryRecord, usize, usize), AniseError> { - for (spk_no, maybe_spk) in self - .spk_data - .iter() - .take(self.num_loaded_spk()) - .rev() - .enumerate() - { - let spk = maybe_spk.unwrap(); - if let Ok((summary, idx_in_spk)) = spk.summary_from_name_at_epoch(name, epoch) { - return Ok((summary, spk_no, idx_in_spk)); - } - } - - // If we're reached this point, there is no relevant summary at this epoch. - error!("Context: No summary {name} valid at epoch {epoch}"); - Err(AniseError::MissingInterpolationData(epoch)) - } - - /// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch - pub fn spk_summary_from_id_at_epoch( - &self, - id: i32, - epoch: Epoch, - ) -> Result<(&SPKSummaryRecord, usize, usize), AniseError> { - // TODO: Consider a return type here - for (spk_no, maybe_spk) in self - .spk_data - .iter() - .take(self.num_loaded_spk()) - .rev() - .enumerate() - { - let spk = maybe_spk.unwrap(); - if let Ok((summary, idx_in_spk)) = spk.summary_from_id_at_epoch(id, epoch) { - // NOTE: We're iterating backward, so the correct SPK number is "total loaded" minus "current iteration". - return Ok((summary, self.num_loaded_spk() - spk_no - 1, idx_in_spk)); - } - } - - error!("Context: No summary {id} valid at epoch {epoch}"); - // If we're reached this point, there is no relevant summary at this epoch. - Err(AniseError::MissingInterpolationData(epoch)) - } - - /// Returns the summary given the name of the summary record. - pub fn spk_summary_from_name( - &self, - name: &str, - ) -> Result<(&SPKSummaryRecord, usize, usize), AniseError> { - for (spk_no, maybe_spk) in self - .spk_data - .iter() - .take(self.num_loaded_spk()) - .rev() - .enumerate() - { - let spk = maybe_spk.unwrap(); - if let Ok((summary, idx_in_spk)) = spk.summary_from_name(name) { - return Ok((summary, spk_no, idx_in_spk)); - } - } - - // If we're reached this point, there is no relevant summary at this epoch. - error!("Context: No summary {name} valid"); - Err(AniseError::NoInterpolationData) - } - - /// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch - pub fn spk_summary_from_id( - &self, - id: i32, - ) -> Result<(&SPKSummaryRecord, usize, usize), AniseError> { - // TODO: Consider a return type here - for (spk_no, maybe_spk) in self - .spk_data - .iter() - .take(self.num_loaded_spk()) - .rev() - .enumerate() - { - let spk = maybe_spk.unwrap(); - if let Ok((summary, idx_in_spk)) = spk.summary_from_id(id) { - // NOTE: We're iterating backward, so the correct SPK number is "total loaded" minus "current iteration". - return Ok((summary, self.num_loaded_spk() - spk_no - 1, idx_in_spk)); - } - } - - error!("Context: No summary {id} valid"); - // If we're reached this point, there is no relevant summary - Err(AniseError::NoInterpolationData) - } -} - impl<'a> fmt::Display for Context<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "Context: #SPK = {}", self.num_loaded_spk()) diff --git a/src/context/spk.rs b/src/context/spk.rs new file mode 100644 index 00000000..6df3d696 --- /dev/null +++ b/src/context/spk.rs @@ -0,0 +1,154 @@ +/* + * ANISE Toolkit + * Copyright (C) 2021-2022 Christopher Rabotin et al. (cf. AUTHORS.md) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * Documentation: https://nyxspace.com/ + */ + +use hifitime::Epoch; + +use crate::errors::AniseError; +use crate::naif::spk::summary::SPKSummaryRecord; +use crate::naif::SPK; +use log::error; + +// TODO: Switch these to build constants so that it's configurable when building the library. +use super::{Context, MAX_LOADED_SPKS}; + +impl<'a: 'b, 'b> Context<'a> { + pub fn from_spk(spk: &'a SPK) -> Result, AniseError> { + let me = Self::default(); + me.load_spk(spk) + } + + /// Loads a new SPK file into a new context. + /// This new context is needed to satisfy the unloading of files. In fact, to unload a file, simply let the newly loaded context drop out of scope and Rust will clean it up. + pub fn load_spk(&self, spk: &'b SPK) -> Result, AniseError> { + // This is just a bunch of pointers so it doesn't use much memory. + let mut me = self.clone(); + // Parse as SPK and place into the SPK list if there is room + let mut data_idx = MAX_LOADED_SPKS; + for (idx, item) in self.spk_data.iter().enumerate() { + if item.is_none() { + data_idx = idx; + break; + } + } + if data_idx == MAX_LOADED_SPKS { + return Err(AniseError::StructureIsFull); + } + me.spk_data[data_idx] = Some(spk); + Ok(me) + } + + pub fn num_loaded_spk(&self) -> usize { + let mut count = 0; + for maybe in self.spk_data { + if maybe.is_none() { + break; + } else { + count += 1; + } + } + + count + } + + /// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch and the SPK where this name was found to be valid at that epoch. + pub fn spk_summary_from_name_at_epoch( + &self, + name: &str, + epoch: Epoch, + ) -> Result<(&SPKSummaryRecord, usize, usize), AniseError> { + for (spk_no, maybe_spk) in self + .spk_data + .iter() + .take(self.num_loaded_spk()) + .rev() + .enumerate() + { + let spk = maybe_spk.unwrap(); + if let Ok((summary, idx_in_spk)) = spk.summary_from_name_at_epoch(name, epoch) { + return Ok((summary, spk_no, idx_in_spk)); + } + } + + // If we're reached this point, there is no relevant summary at this epoch. + error!("Context: No summary {name} valid at epoch {epoch}"); + Err(AniseError::MissingInterpolationData(epoch)) + } + + /// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch + pub fn spk_summary_at_epoch( + &self, + id: i32, + epoch: Epoch, + ) -> Result<(&SPKSummaryRecord, usize, usize), AniseError> { + // TODO: Consider a return type here + for (spk_no, maybe_spk) in self + .spk_data + .iter() + .take(self.num_loaded_spk()) + .rev() + .enumerate() + { + let spk = maybe_spk.unwrap(); + if let Ok((summary, idx_in_spk)) = spk.summary_from_id_at_epoch(id, epoch) { + // NOTE: We're iterating backward, so the correct SPK number is "total loaded" minus "current iteration". + return Ok((summary, self.num_loaded_spk() - spk_no - 1, idx_in_spk)); + } + } + + error!("Context: No summary {id} valid at epoch {epoch}"); + // If we're reached this point, there is no relevant summary at this epoch. + Err(AniseError::MissingInterpolationData(epoch)) + } + + /// Returns the summary given the name of the summary record. + pub fn spk_summary_from_name( + &self, + name: &str, + ) -> Result<(&SPKSummaryRecord, usize, usize), AniseError> { + for (spk_no, maybe_spk) in self + .spk_data + .iter() + .take(self.num_loaded_spk()) + .rev() + .enumerate() + { + let spk = maybe_spk.unwrap(); + if let Ok((summary, idx_in_spk)) = spk.summary_from_name(name) { + return Ok((summary, spk_no, idx_in_spk)); + } + } + + // If we're reached this point, there is no relevant summary at this epoch. + error!("Context: No summary {name} valid"); + Err(AniseError::NoInterpolationData) + } + + /// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch + pub fn spk_summary(&self, id: i32) -> Result<(&SPKSummaryRecord, usize, usize), AniseError> { + // TODO: Consider a return type here + for (spk_no, maybe_spk) in self + .spk_data + .iter() + .take(self.num_loaded_spk()) + .rev() + .enumerate() + { + let spk = maybe_spk.unwrap(); + if let Ok((summary, idx_in_spk)) = spk.summary_from_id(id) { + // NOTE: We're iterating backward, so the correct SPK number is "total loaded" minus "current iteration". + return Ok((summary, self.num_loaded_spk() - spk_no - 1, idx_in_spk)); + } + } + + error!("Context: No summary {id} valid"); + // If we're reached this point, there is no relevant summary + Err(AniseError::NoInterpolationData) + } +} diff --git a/src/ephemerides/paths.rs b/src/ephemerides/paths.rs index c710f940..36c71654 100644 --- a/src/ephemerides/paths.rs +++ b/src/ephemerides/paths.rs @@ -72,9 +72,7 @@ impl<'a> Context<'a> { } // Grab the summary data, which we use to find the paths - let summary = self - .spk_summary_from_id_at_epoch(source.ephemeris_id, epoch)? - .0; + let summary = self.spk_summary_at_epoch(source.ephemeris_id, epoch)?.0; let mut center_id = summary.center_id; @@ -87,7 +85,7 @@ impl<'a> Context<'a> { } for _ in 0..MAX_TREE_DEPTH { - match self.spk_summary_from_id_at_epoch(center_id, epoch) { + match self.spk_summary_at_epoch(center_id, epoch) { Ok((summary, _, _)) => { center_id = summary.center_id; of_path[of_path_len] = Some(center_id); diff --git a/src/ephemerides/translate_to_parent.rs b/src/ephemerides/translate_to_parent.rs index 0327ca7e..bfb5148f 100644 --- a/src/ephemerides/translate_to_parent.rs +++ b/src/ephemerides/translate_to_parent.rs @@ -45,7 +45,7 @@ impl<'a> Context<'a> { // First, let's find the SPK summary for this frame. let (summary, spk_no, idx_in_spk) = - self.spk_summary_from_id_at_epoch(source.ephemeris_id, epoch)?; + self.spk_summary_at_epoch(source.ephemeris_id, epoch)?; let new_frame = source.with_ephem(summary.center_id); diff --git a/src/orientations/mod.rs b/src/orientations/mod.rs deleted file mode 100644 index da61aa3c..00000000 --- a/src/orientations/mod.rs +++ /dev/null @@ -1,93 +0,0 @@ -use hifitime::{Duration, Epoch, TimeUnits}; - -use crate::{ - prelude::AniseError, - structure::{ - orientation::{orient_data::OrientationData, Orientation}, - spline::Evenness, - }, -}; - -impl<'a> Orientation<'a> { - pub const fn is_high_precision(&self) -> bool { - matches!(self.orientation_data, OrientationData::HighPrecision { .. }) - } - - pub fn check_integrity(&self) -> Result<(), AniseError> { - match &self.orientation_data { - OrientationData::PlanetaryConstant { .. } => { - // Planetary constant information won't be decodable unless its integrity is valid. - Ok(()) - } - OrientationData::HighPrecision { - ref_epoch: _, - backward: _, - interpolation_kind: _, - splines, - } => splines.check_integrity(), - } - } - - /// Returns the starting epoch of this ephemeris. It is guaranteed that start_epoch <= end_epoch. - /// - /// # Note - /// + If the ephemeris is stored in chronological order, then the start epoch is the same as the first epoch. - /// + If the ephemeris is stored in anti-chronological order, then the start epoch is the last epoch. - pub fn start_epoch(&self) -> Epoch { - if !self.is_high_precision() { - Epoch::from_tdb_duration(Duration::MIN) - } else { - if self.first_epoch() > self.last_epoch() { - self.last_epoch().unwrap() - } else { - self.first_epoch().unwrap() - } - } - } - - pub fn end_epoch(&self) -> Epoch { - if !self.is_high_precision() { - Epoch::from_tdb_duration(Duration::MAX) - } else { - if self.first_epoch() > self.last_epoch() { - self.first_epoch().unwrap() - } else { - self.last_epoch().unwrap() - } - } - } - - /// Returns the first epoch in the data, which will be the chronological "end" epoch if the ephemeris is generated backward - fn first_epoch(&self) -> Option { - match self.orientation_data { - OrientationData::PlanetaryConstant { .. } => None, - OrientationData::HighPrecision { - ref_epoch, - backward: _, - interpolation_kind: _, - splines: _, - } => Some(ref_epoch), - } - } - - /// Returns the last epoch in the data, which will be the chronological "start" epoch if the ephemeris is generated backward - fn last_epoch(&self) -> Option { - match self.orientation_data { - OrientationData::PlanetaryConstant { .. } => None, - OrientationData::HighPrecision { - ref_epoch, - backward: _, - interpolation_kind: _, - splines, - } => match splines.metadata.evenness { - Evenness::Even { duration_ns } => { - // Grab the number of splines - Some(ref_epoch + ((splines.len() as f64) * (duration_ns as i64).nanoseconds())) - } - Evenness::Uneven { indexes: _ } => { - todo!() - } - }, - } - } -}