use crate::astro::dynamics::frames::RTN;
use crate::astro::dynamics::OrbitState;
use affn::cartesian::Displacement;
use affn::frames::GCRS;
use qtty::unit::Kilometer;
#[derive(Debug, Clone)]
pub struct RtnDiff {
pub jd_tt: f64,
pub r_m: f64,
pub t_m: f64,
pub n_m: f64,
}
impl RtnDiff {
pub fn position_rtn_km(&self) -> Displacement<RTN, Kilometer> {
Displacement::new(self.r_m * 1e-3, self.t_m * 1e-3, self.n_m * 1e-3)
}
}
#[derive(Debug, Clone)]
pub struct RtnSummary {
pub n: usize,
pub rms_3d_m: f64,
pub rms_r_m: f64,
pub rms_t_m: f64,
pub rms_n_m: f64,
}
pub fn rtn_diff(estimated: &[OrbitState], reference: &[OrbitState]) -> Vec<RtnDiff> {
let n = estimated.len().min(reference.len());
let mut out = Vec::with_capacity(n);
for i in 0..n {
let e = estimated[i];
let r = reference[i];
let d_gcrs: Displacement<GCRS, Kilometer> = e.position - r.position;
let frame = crate::astro::dynamics::frames::LocalOrbitalFrame::<RTN>::try_from_state(&r)
.expect("RTN frame from reference state");
let d_rtn = frame.to_local(d_gcrs);
out.push(RtnDiff {
jd_tt: e.epoch.to::<tempoch::JD>().value(),
r_m: d_rtn.x().value() * 1000.0,
t_m: d_rtn.y().value() * 1000.0,
n_m: d_rtn.z().value() * 1000.0,
});
}
out
}
pub fn rtn_summary(diffs: &[RtnDiff]) -> RtnSummary {
let n = diffs.len();
if n == 0 {
return RtnSummary {
n: 0,
rms_3d_m: 0.0,
rms_r_m: 0.0,
rms_t_m: 0.0,
rms_n_m: 0.0,
};
}
let mut sr = 0.0;
let mut st = 0.0;
let mut sn = 0.0;
for d in diffs {
sr += d.r_m * d.r_m;
st += d.t_m * d.t_m;
sn += d.n_m * d.n_m;
}
let rms_r = (sr / n as f64).sqrt();
let rms_t = (st / n as f64).sqrt();
let rms_n = (sn / n as f64).sqrt();
let rms_3d = (rms_r * rms_r + rms_t * rms_t + rms_n * rms_n).sqrt();
RtnSummary {
n,
rms_3d_m: rms_3d,
rms_r_m: rms_r,
rms_t_m: rms_t,
rms_n_m: rms_n,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::astro::dynamics::{Position, Velocity};
use crate::time::JulianDate;
#[test]
fn rtn_zero_for_identical_orbits() {
let s = OrbitState::new(
JulianDate::new(2_451_545.0).to_j2000s(),
Position::new(7000.0, 0.0, 0.0),
Velocity::new(0.0, 7.5, 0.0),
);
let d = rtn_diff(&[s], &[s]);
assert_eq!(d.len(), 1);
assert!(d[0].r_m.abs() < 1e-9);
let summary = rtn_summary(&d);
assert!(summary.rms_3d_m < 1e-9);
assert_eq!(summary.n, 1);
}
#[test]
fn radial_offset_only_appears_in_r_component() {
let r = OrbitState::new(
JulianDate::new(2_451_545.0).to_j2000s(),
Position::new(7000.0, 0.0, 0.0),
Velocity::new(0.0, 7.5, 0.0),
);
let e = OrbitState::new(
JulianDate::new(2_451_545.0).to_j2000s(),
Position::new(7000.001, 0.0, 0.0),
Velocity::new(0.0, 7.5, 0.0),
);
let d = rtn_diff(&[e], &[r]);
assert!((d[0].r_m - 1.0).abs() < 1e-9);
assert!(d[0].t_m.abs() < 1e-9);
assert!(d[0].n_m.abs() < 1e-9);
}
}