use nalgebra as na;
use crate::orbitprop;
use crate::orbitprop::PropSettings;
use crate::AstroTime;
use crate::SKResult;
type PVCovType = na::SMatrix<f64, 6, 6>;
#[derive(Clone, Debug)]
pub enum StateCov {
None,
PVCov(PVCovType),
}
#[derive(Clone, Debug)]
pub struct SatState {
pub time: AstroTime,
pub pv: na::Vector6<f64>,
pub cov: StateCov,
}
impl SatState {
pub fn from_pv(time: &AstroTime, pos: &na::Vector3<f64>, vel: &na::Vector3<f64>) -> SatState {
SatState {
time: time.clone(),
pv: na::vector![pos[0], pos[1], pos[2], vel[0], vel[1], vel[2]],
cov: StateCov::None,
}
}
pub fn pos_gcrf(&self) -> na::Vector3<f64> {
self.pv.fixed_view::<3, 1>(0, 0).into()
}
pub fn vel_gcrf(&self) -> na::Vector3<f64> {
self.pv.fixed_view::<3, 1>(3, 0).into()
}
pub fn set_cov(&mut self, cov: StateCov) {
self.cov = cov;
}
pub fn qgcrf2lvlh(&self) -> na::UnitQuaternion<f64> {
type Quat = na::UnitQuaternion<f64>;
let p = self.pos_gcrf();
let v = self.vel_gcrf();
let h = p.cross(&v);
let q1 = Quat::rotation_between(&(-1.0 * p), &na::Vector3::z_axis()).unwrap();
let q2 = Quat::rotation_between(&(-1.0 * (q1 * h)), &na::Vector3::y_axis()).unwrap();
q2 * q1
}
pub fn cov(&self) -> StateCov {
self.cov.clone()
}
pub fn set_lvlh_pos_uncertainty(&mut self, sigma_lvlh: &na::Vector3<f64>) {
let dcm = self.qgcrf2lvlh().to_rotation_matrix();
let mut pcov = na::Matrix3::<f64>::zeros();
pcov.set_diagonal(&sigma_lvlh.map(|x| x * x));
let mut m = na::Matrix6::<f64>::zeros();
m.fixed_view_mut::<3, 3>(0, 0)
.copy_from(&(dcm.transpose() * pcov * dcm));
self.cov = StateCov::PVCov(m);
}
pub fn set_lvlh_vel_uncertainty(&mut self, sigma_lvlh: &na::Vector3<f64>) {
let dcm = self.qgcrf2lvlh().to_rotation_matrix();
let mut pcov = na::Matrix3::<f64>::zeros();
pcov.set_diagonal(&sigma_lvlh.map(|x| x * x));
let mut m = na::Matrix6::<f64>::zeros();
m.fixed_view_mut::<3, 3>(3, 3)
.copy_from(&(dcm.transpose() * pcov * dcm));
self.cov = StateCov::PVCov(m);
}
pub fn set_gcrf_pos_uncertainty(&mut self, sigma_cart: &na::Vector3<f64>) {
self.cov = StateCov::PVCov({
let mut m = PVCovType::zeros();
let mut diag = na::Vector3::<f64>::zeros();
diag[0] = sigma_cart[0] * sigma_cart[0];
diag[1] = sigma_cart[1] * sigma_cart[1];
diag[2] = sigma_cart[2] * sigma_cart[2];
let mut pcov = na::Matrix3::<f64>::zeros();
pcov.set_diagonal(&diag);
m.fixed_view_mut::<3, 3>(0, 0).copy_from(&pcov);
m
})
}
pub fn set_gcrf_vel_uncertainty(&mut self, sigma_cart: &na::Vector3<f64>) {
self.cov = StateCov::PVCov({
let mut m = PVCovType::zeros();
let mut diag = na::Vector3::<f64>::zeros();
diag[0] = sigma_cart[0] * sigma_cart[0];
diag[1] = sigma_cart[1] * sigma_cart[1];
diag[2] = sigma_cart[2] * sigma_cart[2];
let mut pcov = na::Matrix3::<f64>::zeros();
pcov.set_diagonal(&diag);
m.fixed_view_mut::<3, 3>(3, 3).copy_from(&pcov);
m
})
}
pub fn propagate(
&self,
time: &AstroTime,
option_settings: Option<&PropSettings>,
) -> SKResult<SatState> {
let default = orbitprop::PropSettings::default();
let settings = option_settings.unwrap_or(&default);
match self.cov {
StateCov::None => {
let res = orbitprop::propagate(&self.pv, &self.time, time, settings, None)?;
Ok(SatState {
time: time.clone(),
pv: res.state_end,
cov: StateCov::None,
})
}
StateCov::PVCov(cov) => {
let mut state = na::SMatrix::<f64, 6, 7>::zeros();
state.fixed_view_mut::<6, 1>(0, 0).copy_from(&self.pv);
state
.fixed_view_mut::<6, 6>(0, 1)
.copy_from(&na::Matrix6::<f64>::identity());
let res = orbitprop::propagate(&state, &self.time, time, settings, None)?;
Ok(SatState {
time: time.clone(),
pv: res.state_end.fixed_view::<6, 1>(0, 0).into(),
cov: {
let phi = res.state_end.fixed_view::<6, 6>(0, 1);
StateCov::PVCov(phi * cov * phi.transpose())
},
})
}
}
}
pub fn to_string(&self) -> String {
let mut s1 = format!(
r#"Satellite State
Time: {}
GCRF Position: [{:+8.0}, {:+8.0}, {:+8.0}] m,
GCRF Velocity: [{:+8.3}, {:+8.3}, {:+8.3}] m/s"#,
self.time, self.pv[0], self.pv[1], self.pv[2], self.pv[3], self.pv[4], self.pv[5],
);
match self.cov {
StateCov::None => s1,
StateCov::PVCov(cov) => {
s1.push_str(
format!(
r#"
Covariance: {cov:+8.2e}"#
)
.as_str(),
);
s1
}
}
}
}
impl std::fmt::Display for SatState {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.to_string())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::consts;
use approx::{assert_abs_diff_eq, assert_relative_eq};
#[test]
fn test_qgcrf2lvlh() -> SKResult<()> {
let satstate = SatState::from_pv(
&AstroTime::from_datetime(2015, 3, 20, 0, 0, 0.0),
&na::vector![consts::GEO_R, 0.0, 0.0],
&na::vector![0.0, (consts::MU_EARTH / consts::GEO_R).sqrt(), 0.0],
);
let state2 = satstate.propagate(&(satstate.time + crate::Duration::Hours(3.56)), None)?;
let rz = -1.0 / state2.pos_gcrf().norm() * (state2.qgcrf2lvlh() * state2.pos_gcrf());
let h = state2.pos_gcrf().cross(&state2.vel_gcrf());
let ry = -1.0 / h.norm() * (state2.qgcrf2lvlh() * h);
let rx = 1.0 / state2.vel_gcrf().norm() * (state2.qgcrf2lvlh() * state2.vel_gcrf());
assert_relative_eq!(rz, na::Vector3::z_axis(), epsilon = 1.0e-6);
assert_relative_eq!(ry, na::Vector3::y_axis(), epsilon = 1.0e-6);
assert_relative_eq!(rx, na::Vector3::x_axis(), epsilon = 1.0e-4);
Ok(())
}
#[test]
fn test_satstate() -> SKResult<()> {
let mut satstate = SatState::from_pv(
&AstroTime::from_datetime(2015, 3, 20, 0, 0, 0.0),
&na::vector![consts::GEO_R, 0.0, 0.0],
&na::vector![0.0, (consts::MU_EARTH / consts::GEO_R).sqrt(), 0.0],
);
satstate.set_lvlh_pos_uncertainty(&na::vector![1.0, 1.0, 1.0]);
satstate.set_lvlh_vel_uncertainty(&na::vector![0.01, 0.02, 0.03]);
let state2 = satstate.propagate(&(satstate.time + 0.5), None)?;
let state0 = state2.propagate(&satstate.time, None)?;
assert_abs_diff_eq!(satstate.pos_gcrf(), state0.pos_gcrf(), epsilon = 0.1);
assert_abs_diff_eq!(satstate.vel_gcrf(), state0.vel_gcrf(), epsilon = 0.001);
let cov1 = match satstate.cov() {
StateCov::PVCov(v) => v,
StateCov::None => panic!("cov is not none"),
};
let cov2 = match state0.cov() {
StateCov::PVCov(v) => v,
StateCov::None => panic!("cov is not none"),
};
assert_abs_diff_eq!(cov1, cov2, epsilon = 0.001);
Ok(())
}
#[test]
fn test_satcov() -> SKResult<()> {
let mut satstate = SatState::from_pv(
&AstroTime::from_datetime(2015, 3, 20, 0, 0, 0.0),
&na::vector![consts::GEO_R, 0.0, 0.0],
&na::vector![0.0, (consts::MU_EARTH / consts::GEO_R).sqrt(), 0.0],
);
satstate.set_lvlh_pos_uncertainty(&na::vector![1.0, 1.0, 1.0]);
let _state2 = satstate.propagate(&(satstate.time + 1.0), None)?;
Ok(())
}
}