use crate::constants::AngleFormat;
use crate::time::Epoch;
use crate::utils::BraheError;
use nalgebra::{DMatrix, SMatrix};
use serde::{Deserialize, Serialize};
use std::fmt;
pub use crate::math::interpolation::{
CovarianceInterpolationMethod, InterpolationConfig, InterpolationMethod,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum TrajectoryEvictionPolicy {
#[default]
None,
KeepCount,
KeepWithinDuration,
}
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OrbitFrame {
ECI,
ECEF,
GCRF,
ITRF,
EME2000,
}
impl fmt::Display for OrbitFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OrbitFrame::ECI => write!(f, "ECI"),
OrbitFrame::ECEF => write!(f, "ECEF"),
OrbitFrame::GCRF => write!(f, "GCRF"),
OrbitFrame::ITRF => write!(f, "ITRF"),
OrbitFrame::EME2000 => write!(f, "EME2000"),
}
}
}
impl fmt::Debug for OrbitFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OrbitFrame::ECI => write!(f, "OrbitFrame(Earth-Centered Inertial)"),
OrbitFrame::ECEF => write!(f, "OrbitFrame(Earth-Centered Earth-Fixed)"),
OrbitFrame::GCRF => write!(f, "OrbitFrame(Geocentric Celestial Reference Frame)"),
OrbitFrame::ITRF => write!(f, "OrbitFrame(International Terrestrial Reference Frame)"),
OrbitFrame::EME2000 => {
write!(f, "OrbitFrame(Earth Mean Equator and Equinox of J2000.0)")
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OrbitRepresentation {
Cartesian,
Keplerian,
}
impl fmt::Display for OrbitRepresentation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OrbitRepresentation::Cartesian => write!(f, "Cartesian"),
OrbitRepresentation::Keplerian => write!(f, "Keplerian"),
}
}
}
impl fmt::Debug for OrbitRepresentation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OrbitRepresentation::Cartesian => write!(f, "OrbitRepresentation(Cartesian)"),
OrbitRepresentation::Keplerian => write!(f, "OrbitRepresentation(Keplerian)"),
}
}
}
pub trait Trajectory {
type StateVector;
fn from_data(epochs: Vec<Epoch>, states: Vec<Self::StateVector>) -> Result<Self, BraheError>
where
Self: Sized;
fn add(&mut self, epoch: Epoch, state: Self::StateVector) -> ();
fn epoch_at_idx(&self, index: usize) -> Result<Epoch, BraheError>;
fn state_at_idx(&self, index: usize) -> Result<Self::StateVector, BraheError>;
fn nearest_state(&self, epoch: &Epoch) -> Result<(Epoch, Self::StateVector), BraheError>;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn start_epoch(&self) -> Option<Epoch>;
fn end_epoch(&self) -> Option<Epoch>;
fn timespan(&self) -> Option<f64>;
fn first(&self) -> Option<(Epoch, Self::StateVector)>;
fn last(&self) -> Option<(Epoch, Self::StateVector)>;
fn clear(&mut self);
fn remove_epoch(&mut self, epoch: &Epoch) -> Result<Self::StateVector, BraheError>;
fn remove(&mut self, index: usize) -> Result<(Epoch, Self::StateVector), BraheError>;
fn get(&self, index: usize) -> Result<(Epoch, Self::StateVector), BraheError>;
fn index_before_epoch(&self, epoch: &Epoch) -> Result<usize, BraheError>;
fn index_after_epoch(&self, epoch: &Epoch) -> Result<usize, BraheError>;
fn state_before_epoch(&self, epoch: &Epoch) -> Result<(Epoch, Self::StateVector), BraheError> {
let index = self.index_before_epoch(epoch)?;
self.get(index)
}
fn state_after_epoch(&self, epoch: &Epoch) -> Result<(Epoch, Self::StateVector), BraheError> {
let index = self.index_after_epoch(epoch)?;
self.get(index)
}
fn set_eviction_policy_max_size(&mut self, max_size: usize) -> Result<(), BraheError>;
fn set_eviction_policy_max_age(&mut self, max_age: f64) -> Result<(), BraheError>;
fn get_eviction_policy(&self) -> TrajectoryEvictionPolicy;
}
pub trait InterpolatableTrajectory: Trajectory + InterpolationConfig {
fn interpolate_linear(&self, epoch: &Epoch) -> Result<Self::StateVector, BraheError>
where
Self::StateVector: Clone
+ std::ops::Mul<f64, Output = Self::StateVector>
+ std::ops::Add<Output = Self::StateVector>,
{
if self.is_empty() {
return Err(BraheError::Error(
"Cannot interpolate state from empty trajectory".to_string(),
));
}
if self.len() == 1 {
let (only_epoch, only_state) = self.first().unwrap();
if *epoch != only_epoch {
return Err(BraheError::Error(
"Cannot interpolate state: single state trajectory and epoch does not match"
.to_string(),
));
}
return Ok(only_state);
}
if let Some(start) = self.start_epoch()
&& *epoch < start
{
return Err(BraheError::OutOfBoundsError(format!(
"Cannot interpolate: epoch {} is before trajectory start {}",
epoch, start
)));
}
if let Some(end) = self.end_epoch()
&& *epoch > end
{
return Err(BraheError::OutOfBoundsError(format!(
"Cannot interpolate: epoch {} is after trajectory end {}",
epoch, end
)));
}
let idx1 = self.index_before_epoch(epoch)?;
let idx2 = self.index_after_epoch(epoch)?;
if idx1 == idx2 {
return self.state_at_idx(idx1);
}
let (epoch1, state1) = self.get(idx1)?;
let (epoch2, state2) = self.get(idx2)?;
let t = (*epoch - epoch1) / (epoch2 - epoch1);
let interpolated = state1.clone() * (1.0 - t) + state2 * t;
Ok(interpolated)
}
fn interpolate(&self, epoch: &Epoch) -> Result<Self::StateVector, BraheError>
where
Self::StateVector: Clone
+ std::ops::Mul<f64, Output = Self::StateVector>
+ std::ops::Add<Output = Self::StateVector>,
{
if let Some(start) = self.start_epoch()
&& *epoch < start
{
return Err(BraheError::OutOfBoundsError(format!(
"Cannot interpolate: epoch {} is before trajectory start {}",
epoch, start
)));
}
if let Some(end) = self.end_epoch()
&& *epoch > end
{
return Err(BraheError::OutOfBoundsError(format!(
"Cannot interpolate: epoch {} is after trajectory end {}",
epoch, end
)));
}
let idx1 = self.index_before_epoch(epoch)?;
let idx2 = self.index_after_epoch(epoch)?;
if idx1 == idx2 {
return self.state_at_idx(idx1);
}
let method = self.get_interpolation_method();
let required = method.min_points_required();
if self.len() < required {
return Err(BraheError::Error(format!(
"{:?} requires {} points, trajectory has {}",
method,
required,
self.len()
)));
}
match method {
InterpolationMethod::Linear => self.interpolate_linear(epoch),
InterpolationMethod::Lagrange { .. }
| InterpolationMethod::HermiteCubic
| InterpolationMethod::HermiteQuintic => {
self.interpolate_linear(epoch)
}
}
}
}
pub trait OrbitalTrajectory: InterpolatableTrajectory {
fn from_orbital_data(
epochs: Vec<Epoch>,
states: Vec<Self::StateVector>,
frame: OrbitFrame,
representation: OrbitRepresentation,
angle_format: Option<AngleFormat>,
covariances: Option<Vec<SMatrix<f64, 6, 6>>>,
) -> Self
where
Self: Sized;
fn to_eci(&self) -> Self
where
Self: Sized;
fn to_ecef(&self) -> Self
where
Self: Sized;
fn to_gcrf(&self) -> Self
where
Self: Sized;
fn to_eme2000(&self) -> Self
where
Self: Sized;
fn to_itrf(&self) -> Self
where
Self: Sized;
fn to_keplerian(&self, angle_format: AngleFormat) -> Self
where
Self: Sized;
}
pub trait STMStorage: Trajectory {
fn enable_stm_storage(&mut self);
fn stm_at_idx(&self, index: usize) -> Option<&DMatrix<f64>>;
fn set_stm_at(&mut self, index: usize, stm: DMatrix<f64>);
fn stm_dimensions(&self) -> (usize, usize);
fn stm_at(&self, epoch: Epoch) -> Option<DMatrix<f64>> {
let stms = self.stm_storage()?;
if self.len() == 0 {
return None;
}
for i in 0..self.len() {
if self.epoch_at_idx(i).ok()? == epoch {
return Some(stms.get(i)?.clone());
}
}
let idx_before = self.index_before_epoch(&epoch).ok()?;
let idx_after = self.index_after_epoch(&epoch).ok()?;
if idx_before == idx_after {
return Some(stms.get(idx_before)?.clone());
}
let t0 = self.epoch_at_idx(idx_before).ok()? - self.start_epoch()?;
let t1 = self.epoch_at_idx(idx_after).ok()? - self.start_epoch()?;
let t = epoch - self.start_epoch()?;
let alpha = (t - t0) / (t1 - t0);
let stm = &stms[idx_before] * (1.0 - alpha) + &stms[idx_after] * alpha;
Some(stm)
}
#[doc(hidden)]
fn stm_storage(&self) -> Option<&Vec<DMatrix<f64>>>;
#[doc(hidden)]
fn stm_storage_mut(&mut self) -> Option<&mut Vec<DMatrix<f64>>>;
}
pub trait SensitivityStorage: Trajectory {
fn enable_sensitivity_storage(&mut self, param_dim: usize);
fn sensitivity_at_idx(&self, index: usize) -> Option<&DMatrix<f64>>;
fn set_sensitivity_at(&mut self, index: usize, sensitivity: DMatrix<f64>);
fn sensitivity_dimensions(&self) -> Option<(usize, usize)>;
fn sensitivity_at(&self, epoch: Epoch) -> Option<DMatrix<f64>> {
let sens = self.sensitivity_storage()?;
if self.len() == 0 {
return None;
}
for i in 0..self.len() {
if self.epoch_at_idx(i).ok()? == epoch {
return Some(sens.get(i)?.clone());
}
}
let idx_before = self.index_before_epoch(&epoch).ok()?;
let idx_after = self.index_after_epoch(&epoch).ok()?;
if idx_before == idx_after {
return Some(sens.get(idx_before)?.clone());
}
let t0 = self.epoch_at_idx(idx_before).ok()? - self.start_epoch()?;
let t1 = self.epoch_at_idx(idx_after).ok()? - self.start_epoch()?;
let t = epoch - self.start_epoch()?;
let alpha = (t - t0) / (t1 - t0);
let sensitivity = &sens[idx_before] * (1.0 - alpha) + &sens[idx_after] * alpha;
Some(sensitivity)
}
#[doc(hidden)]
fn sensitivity_storage(&self) -> Option<&Vec<DMatrix<f64>>>;
#[doc(hidden)]
fn sensitivity_storage_mut(&mut self) -> Option<&mut Vec<DMatrix<f64>>>;
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn test_trajectory_eviction_policy_debug_none() {
let policy = TrajectoryEvictionPolicy::None;
assert_eq!(format!("{:?}", policy), "None");
}
#[test]
fn test_trajectory_eviction_policy_debug_keep_count() {
let policy = TrajectoryEvictionPolicy::KeepCount;
assert_eq!(format!("{:?}", policy), "KeepCount");
}
#[test]
fn test_trajectory_eviction_policy_debug_keep_within_duration() {
let policy = TrajectoryEvictionPolicy::KeepWithinDuration;
assert_eq!(format!("{:?}", policy), "KeepWithinDuration");
}
#[test]
fn test_orbit_frame_display_eci() {
let frame = OrbitFrame::ECI;
assert_eq!(format!("{}", frame), "ECI");
}
#[test]
fn test_orbit_frame_display_ecef() {
let frame = OrbitFrame::ECEF;
assert_eq!(format!("{}", frame), "ECEF");
}
#[test]
fn test_orbit_frame_display_gcrf() {
let frame = OrbitFrame::GCRF;
assert_eq!(format!("{}", frame), "GCRF");
}
#[test]
fn test_orbit_frame_display_itrf() {
let frame = OrbitFrame::ITRF;
assert_eq!(format!("{}", frame), "ITRF");
}
#[test]
fn test_orbit_frame_display_eme2000() {
let frame = OrbitFrame::EME2000;
assert_eq!(format!("{}", frame), "EME2000");
}
#[test]
fn test_orbit_frame_debug_eci() {
let frame = OrbitFrame::ECI;
assert_eq!(
format!("{:?}", frame),
"OrbitFrame(Earth-Centered Inertial)"
);
}
#[test]
fn test_orbit_frame_debug_ecef() {
let frame = OrbitFrame::ECEF;
assert_eq!(
format!("{:?}", frame),
"OrbitFrame(Earth-Centered Earth-Fixed)"
);
}
#[test]
fn test_orbit_frame_debug_gcrf() {
let frame = OrbitFrame::GCRF;
assert_eq!(
format!("{:?}", frame),
"OrbitFrame(Geocentric Celestial Reference Frame)"
);
}
#[test]
fn test_orbit_frame_debug_itrf() {
let frame = OrbitFrame::ITRF;
assert_eq!(
format!("{:?}", frame),
"OrbitFrame(International Terrestrial Reference Frame)"
);
}
#[test]
fn test_orbit_frame_debug_eme2000() {
let frame = OrbitFrame::EME2000;
assert_eq!(
format!("{:?}", frame),
"OrbitFrame(Earth Mean Equator and Equinox of J2000.0)"
);
}
#[test]
fn test_orbit_representation_display_cartesian() {
let rep = OrbitRepresentation::Cartesian;
assert_eq!(format!("{}", rep), "Cartesian");
}
#[test]
fn test_orbit_representation_display_keplerian() {
let rep = OrbitRepresentation::Keplerian;
assert_eq!(format!("{}", rep), "Keplerian");
}
#[test]
fn test_orbit_representation_debug_cartesian() {
let rep = OrbitRepresentation::Cartesian;
assert_eq!(format!("{:?}", rep), "OrbitRepresentation(Cartesian)");
}
#[test]
fn test_orbit_representation_debug_keplerian() {
let rep = OrbitRepresentation::Keplerian;
assert_eq!(format!("{:?}", rep), "OrbitRepresentation(Keplerian)");
}
}