use crate::math::{SMatrix3, SVector6};
use nalgebra::Vector3;
pub fn rotation_rtn_to_eci(x_eci: SVector6) -> SMatrix3 {
let r = x_eci.fixed_rows::<3>(0);
let v = x_eci.fixed_rows::<3>(3);
let r_norm = r.norm();
let h = r.cross(&v); let h_norm = h.norm();
let r_hat = r / r_norm;
let n_hat = h / h_norm;
let t_hat = n_hat.cross(&r_hat);
SMatrix3::from_columns(&[r_hat, t_hat, n_hat])
}
pub fn rotation_eci_to_rtn(x_eci: SVector6) -> SMatrix3 {
rotation_rtn_to_eci(x_eci).transpose()
}
pub fn state_eci_to_rtn(x_chief: SVector6, x_deputy: SVector6) -> SVector6 {
let rc = x_chief.fixed_rows::<3>(0);
let vc = x_chief.fixed_rows::<3>(3);
let r_eci_to_rtn = rotation_eci_to_rtn(x_chief);
let rho_eci = x_deputy.fixed_rows::<3>(0) - x_chief.fixed_rows::<3>(0);
let rho_dot_eci = x_deputy.fixed_rows::<3>(3) - x_chief.fixed_rows::<3>(3);
let f_dot = (rc.cross(&vc)).norm() / (rc.norm().powi(2));
let omega = Vector3::new(0.0, 0.0, f_dot);
let rho_rtn = r_eci_to_rtn * rho_eci;
let rho_dot_rtn = r_eci_to_rtn * rho_dot_eci - omega.cross(&rho_rtn);
SVector6::new(
rho_rtn[0],
rho_rtn[1],
rho_rtn[2],
rho_dot_rtn[0],
rho_dot_rtn[1],
rho_dot_rtn[2],
)
}
pub fn state_rtn_to_eci(x_chief: SVector6, x_rel_rtn: SVector6) -> SVector6 {
let rc = x_chief.fixed_rows::<3>(0);
let vc = x_chief.fixed_rows::<3>(3);
let r_rtn_to_eci = rotation_rtn_to_eci(x_chief);
let rho_rtn = x_rel_rtn.fixed_rows::<3>(0);
let rho_dot_rtn = x_rel_rtn.fixed_rows::<3>(3);
let f_dot = (rc.cross(&vc)).norm() / (rc.norm().powi(2));
let omega = Vector3::new(0.0, 0.0, f_dot);
let r_deputy = rc + r_rtn_to_eci * rho_rtn;
let v_deputy = r_rtn_to_eci * (rho_dot_rtn + omega.cross(&rho_rtn)) + vc;
SVector6::new(
r_deputy[0],
r_deputy[1],
r_deputy[2],
v_deputy[0],
v_deputy[1],
v_deputy[2],
)
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use crate::AngleFormat;
use crate::R_EARTH;
use crate::coordinates::state_koe_to_eci;
use crate::orbits::perigee_velocity;
use crate::utils::testing::setup_global_test_eop;
fn get_test_state() -> SVector6 {
let sma = R_EARTH + 700e3; SVector6::new(sma, 0.0, 0.0, 0.0, perigee_velocity(sma, 0.0), 0.0)
}
#[test]
fn test_rotation_rtn_to_eci() {
let x_eci = get_test_state();
let p_eci = x_eci.fixed_rows::<3>(0);
let r_rtn = rotation_rtn_to_eci(x_eci);
let r_eci = r_rtn * Vector3::new(1.0, 0.0, 0.0) * p_eci.norm();
assert!((r_eci - p_eci).norm() < 1e-6);
}
#[test]
fn test_rotation_eci_to_rtn_inverse() {
let x_eci = get_test_state();
let r_rtn_to_eci = rotation_rtn_to_eci(x_eci);
let r_eci_to_rtn = rotation_eci_to_rtn(x_eci);
let identity = r_rtn_to_eci * r_eci_to_rtn;
assert!((identity - SMatrix3::identity()).norm() < 1e-10);
}
#[test]
fn test_state_eci_to_rtn_and_back() {
let x_chief = get_test_state();
let x_deputy = get_test_state() + SVector6::new(100.0, 200.0, 300.0, 0.1, 0.2, 0.3);
let x_rel_rtn = state_eci_to_rtn(x_chief, x_deputy);
let x_deputy_reconstructed = state_rtn_to_eci(x_chief, x_rel_rtn);
assert!((x_deputy - x_deputy_reconstructed).norm() < 1e-6);
}
#[test]
fn test_state_eci_to_rtn_and_back_non_aligned() {
setup_global_test_eop();
let oe_chief = SVector6::new(R_EARTH + 700e3, 0.001, 97.8, 15.0, 30.0, 45.0);
let oe_deputy = SVector6::new(R_EARTH + 701e3, 0.0015, 97.85, 15.05, 30.05, 45.05);
let x_chief = state_koe_to_eci(oe_chief, AngleFormat::Degrees);
let x_deputy = state_koe_to_eci(oe_deputy, AngleFormat::Degrees);
let x_rel_rtn = state_eci_to_rtn(x_chief, x_deputy);
let x_deputy_reconstructed = state_rtn_to_eci(x_chief, x_rel_rtn);
let pos_err =
(x_deputy.fixed_rows::<3>(0) - x_deputy_reconstructed.fixed_rows::<3>(0)).norm();
let vel_err =
(x_deputy.fixed_rows::<3>(3) - x_deputy_reconstructed.fixed_rows::<3>(3)).norm();
assert!(pos_err < 1e-8, "Position round-trip error: {pos_err} m");
assert!(vel_err < 1e-8, "Velocity round-trip error: {vel_err} m/s");
}
#[test]
fn test_state_rtn_to_eci_and_back_non_aligned() {
setup_global_test_eop();
let oe_chief = SVector6::new(R_EARTH + 700e3, 0.001, 97.8, 15.0, 30.0, 45.0);
let x_chief = state_koe_to_eci(oe_chief, AngleFormat::Degrees);
let x_rel_rtn = SVector6::new(1000.0, 500.0, -300.0, 0.1, -0.05, 0.02);
let x_deputy = state_rtn_to_eci(x_chief, x_rel_rtn);
let x_rel_rtn_recovered = state_eci_to_rtn(x_chief, x_deputy);
let pos_err =
(x_rel_rtn.fixed_rows::<3>(0) - x_rel_rtn_recovered.fixed_rows::<3>(0)).norm();
let vel_err =
(x_rel_rtn.fixed_rows::<3>(3) - x_rel_rtn_recovered.fixed_rows::<3>(3)).norm();
assert!(pos_err < 1e-8, "Position round-trip error: {pos_err} m");
assert!(vel_err < 1e-8, "Velocity round-trip error: {vel_err} m/s");
}
}