use crate::md::StateParameter;
use crate::md::trajectory::INTERPOLATION_SAMPLES;
use crate::od::DynamicsError;
use crate::{cosmic::State, md::prelude::Interpolatable};
use anise::analysis::prelude::OrbitalElement;
use anise::errors::PhysicsError;
use anise::math::interpolation::{InterpolationError, hermite_eval};
use anise::prelude::Orbit;
use anise::{astro::Location, prelude::Frame};
use core::error::Error;
use core::fmt;
use core::ops::Add;
use hifitime::Epoch;
use hyperdual::{OHyperdual, hyperspace_from_vector};
use nalgebra::{Const, DimName, Matrix6, OMatrix, OVector, Vector3, Vector6};
use num_traits::Float;
pub mod ground_dynamics;
pub mod sensitivity;
pub mod solution;
pub mod trk_device;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct GroundAsset {
pub latitude_deg: f64,
pub longitude_deg: f64,
pub height_km: f64,
pub latitude_rate_deg_s: f64,
pub longitude_rate_deg_s: f64,
pub height_rate_km_s: f64,
pub epoch: Epoch,
pub frame: Frame,
pub stm: Option<OMatrix<f64, Const<6>, Const<6>>>,
}
impl GroundAsset {
pub fn from_fixed(
latitude_deg: f64,
longitude_deg: f64,
height_km: f64,
epoch: Epoch,
frame: Frame,
) -> Self {
Self {
latitude_deg,
longitude_deg,
height_km,
epoch,
frame,
..Default::default()
}
}
pub fn with_velocity_sez_m_s(
mut self,
vel_s_m_s: f64,
vel_e_m_s: f64,
vel_z_m_s: f64,
) -> Result<Self, Box<dyn Error>> {
let (lat_rate_deg_s, long_rate_deg_s, alt_rate_km_s) = latlongalt_rate(
self.orbit(),
Vector3::new(vel_s_m_s, vel_e_m_s, vel_z_m_s) * 1e-3,
)?;
self.latitude_rate_deg_s = lat_rate_deg_s;
self.longitude_rate_deg_s = long_rate_deg_s;
self.height_rate_km_s = alt_rate_km_s;
Ok(self)
}
pub fn to_location(&self) -> Location {
Location {
latitude_deg: self.latitude_deg,
longitude_deg: self.longitude_deg,
height_km: self.height_km,
frame: self.frame.into(),
..Default::default()
}
}
pub fn velocity_sez_m_s(&self) -> Result<OVector<f64, Const<3>>, PhysicsError> {
let rx = Orbit::try_latlongalt(
self.latitude_deg,
self.longitude_deg,
self.height_km,
self.epoch,
self.frame,
)?;
velocity_sez_from_latlongalt_rate(
rx,
self.latitude_rate_deg_s,
self.longitude_rate_deg_s,
self.height_rate_km_s,
)
.map(|v| v * 1e3)
}
pub fn geodetic_to_cartesian_jacobian(&self) -> Result<Matrix6<f64>, PhysicsError> {
let lat_deg = self.latitude_deg;
let lon_deg = self.longitude_deg;
let alt_km = self.height_km;
let a = self.frame.mean_equatorial_radius_km()?;
let b = self.frame.shape.unwrap().polar_radius_km;
let e2 = (a * a - b * b) / (a * a);
let state_vec = Vector6::new(
lat_deg,
lon_deg,
alt_km,
self.latitude_rate_deg_s,
self.longitude_rate_deg_s,
self.height_rate_km_s,
);
let hyper_state: Vector6<OHyperdual<f64, Const<7>>> = hyperspace_from_vector(&state_vec);
let deg_to_rad = std::f64::consts::PI / 180.0;
let lat = hyper_state[0] * deg_to_rad;
let lon = hyper_state[1] * deg_to_rad;
let alt = hyper_state[2];
let lat_rate = hyper_state[3] * deg_to_rad;
let lon_rate = hyper_state[4] * deg_to_rad;
let alt_rate = hyper_state[5];
let sin_lat = lat.sin();
let cos_lat = lat.cos();
let sin_lon = lon.sin();
let cos_lon = lon.cos();
let one = OHyperdual::<f64, Const<7>>::from_real(1.0);
let e2_dual = OHyperdual::<f64, Const<7>>::from_real(e2);
let n = OHyperdual::from_real(a) / (one - e2_dual * sin_lat * sin_lat).sqrt();
let x = (n + alt) * cos_lat * cos_lon;
let y = (n + alt) * cos_lat * sin_lon;
let z = (n * OHyperdual::from_real(1.0 - e2) + alt) * sin_lat;
let term1 = n * OHyperdual::from_real(1.0 - e2) / (one - e2_dual * sin_lat * sin_lat);
let dx_dlat = -(term1 + alt) * sin_lat * cos_lon;
let dy_dlat = -(term1 + alt) * sin_lat * sin_lon;
let dz_dlat = term1 * cos_lat + alt * cos_lat;
let dx_dlon = -(n + alt) * cos_lat * sin_lon;
let dy_dlon = (n + alt) * cos_lat * cos_lon;
let dz_dlon = OHyperdual::from_real(0.0);
let dx_dalt = cos_lat * cos_lon;
let dy_dalt = cos_lat * sin_lon;
let dz_dalt = sin_lat;
let vx = dx_dlat * lat_rate + dx_dlon * lon_rate + dx_dalt * alt_rate;
let vy = dy_dlat * lat_rate + dy_dlon * lon_rate + dy_dalt * alt_rate;
let vz = dz_dlat * lat_rate + dz_dlon * lon_rate + dz_dalt * alt_rate;
let mut jacobian = Matrix6::zeros();
let cartesian_state = [x, y, z, vx, vy, vz];
for i in 0..6 {
for j in 1..7 {
jacobian[(i, j - 1)] = cartesian_state[i][j];
}
}
Ok(jacobian)
}
pub fn great_circle_distance_km(&self, other: &Self) -> Result<f64, PhysicsError> {
let lat1 = self.latitude_deg.to_radians();
let lon1 = self.longitude_deg.to_radians();
let lat2 = other.latitude_deg.to_radians();
let lon2 = other.longitude_deg.to_radians();
let dlat = lat2 - lat1;
let dlon = lon2 - lon1;
let a = (dlat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (dlon / 2.0).sin().powi(2);
let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
let radius_km = self.frame.mean_equatorial_radius_km()?;
Ok(radius_km * c)
}
}
impl Default for GroundAsset {
fn default() -> Self {
Self {
frame: Frame::from_ephem_j2000(399),
latitude_deg: 0.,
longitude_deg: 0.,
height_km: 0.,
latitude_rate_deg_s: 0.,
longitude_rate_deg_s: 0.,
height_rate_km_s: 0.0,
epoch: Epoch::from_tdb_seconds(0.0),
stm: None,
}
}
}
impl fmt::Display for GroundAsset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"lat.: {:.3} deg\tlong.: {:.3} deg\talt.: {:.3} km (speed: {:.3} m/s)",
self.latitude_deg,
self.longitude_deg,
self.height_km,
match self.velocity_sez_m_s() {
Ok(vel_m_s) => vel_m_s.norm(),
Err(_) => f64::NAN,
}
)
}
}
impl fmt::LowerExp for GroundAsset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"lat.: {:.e} deg\tlong.: {:.e} deg\talt.: {:.e} km (speed: {:.e} m/s)",
self.latitude_deg,
self.longitude_deg,
self.height_km,
match self.velocity_sez_m_s() {
Ok(vel_m_s) => vel_m_s.norm(),
Err(_) => f64::NAN,
}
)
}
}
impl State for GroundAsset {
type Size = Const<6>;
type VecLength = Const<{ 6 + 36 }>;
fn to_vector(&self) -> nalgebra::OVector<f64, Self::VecLength> {
let mut vector = OVector::<f64, Const<42>>::zeros();
vector[0] = self.latitude_deg;
vector[1] = self.longitude_deg;
vector[2] = self.height_km;
vector[3] = self.latitude_rate_deg_s;
vector[4] = self.longitude_rate_deg_s;
vector[5] = self.height_rate_km_s;
if let Some(stm) = self.stm {
let stm_slice = stm.as_slice();
for i in 0..36 {
vector[6 + i] = stm_slice[i];
}
}
vector
}
fn orbit(&self) -> Orbit {
Orbit::try_latlongalt(
self.latitude_deg,
self.longitude_deg,
self.height_km,
self.epoch,
self.frame,
)
.expect("Ground asset frame does not allow init from lat/long/alt")
}
fn epoch(&self) -> Epoch {
self.epoch
}
fn set_epoch(&mut self, epoch: Epoch) {
self.epoch = epoch;
}
fn set(&mut self, epoch: Epoch, vector: &OVector<f64, Const<42>>) {
let asset_state =
OVector::<f64, Self::Size>::from_column_slice(&vector.as_slice()[..Self::Size::dim()]);
if self.stm.is_some() {
let sc_full_stm = OMatrix::<f64, Self::Size, Self::Size>::from_column_slice(
&vector.as_slice()[Self::Size::dim()..],
);
self.stm = Some(sc_full_stm);
}
self.latitude_deg = asset_state[0];
self.longitude_deg = asset_state[1];
self.height_km = asset_state[2];
self.latitude_rate_deg_s = asset_state[3];
self.longitude_rate_deg_s = asset_state[4];
self.height_rate_km_s = asset_state[5];
self.epoch = epoch;
}
fn reset_stm(&mut self) {
self.stm = Some(OMatrix::<f64, Const<6>, Const<6>>::identity());
}
fn unset_stm(&mut self) {
self.stm = None;
}
fn stm(&self) -> Result<OMatrix<f64, Self::Size, Self::Size>, DynamicsError> {
match self.stm {
Some(stm) => Ok(stm),
None => Err(DynamicsError::StateTransitionMatrixUnset),
}
}
fn with_stm(mut self) -> Self {
self.stm = Some(OMatrix::<f64, Const<6>, Const<6>>::identity());
self
}
fn add(self, other: OVector<f64, Self::Size>) -> Self {
self + other
}
fn value(&self, param: StateParameter) -> Result<f64, crate::errors::StateError> {
match param {
StateParameter::Element(OrbitalElement::Latitude) => Ok(self.latitude_deg),
StateParameter::Element(OrbitalElement::Longitude) => Ok(self.longitude_deg),
StateParameter::Element(OrbitalElement::Height) => Ok(self.height_km),
_ => Err(crate::errors::StateError::Unavailable { param }),
}
}
}
impl Interpolatable for GroundAsset {
fn interpolate(mut self, epoch: Epoch, states: &[Self]) -> Result<Self, InterpolationError> {
let mut epochs_tdb = [0.0; INTERPOLATION_SAMPLES];
let mut xs = [0.0; INTERPOLATION_SAMPLES];
let mut ys = [0.0; INTERPOLATION_SAMPLES];
let mut zs = [0.0; INTERPOLATION_SAMPLES];
let mut vxs = [0.0; INTERPOLATION_SAMPLES];
let mut vys = [0.0; INTERPOLATION_SAMPLES];
let mut vzs = [0.0; INTERPOLATION_SAMPLES];
for (cno, state) in states.iter().enumerate() {
xs[cno] = state.latitude_deg;
ys[cno] = state.longitude_deg;
zs[cno] = state.height_km;
vxs[cno] = state.latitude_rate_deg_s;
vys[cno] = state.longitude_rate_deg_s;
vzs[cno] = state.height_rate_km_s;
epochs_tdb[cno] = state.epoch.to_et_seconds();
}
let n = states.len();
let (latitude_deg, latitude_vel_deg_s) =
hermite_eval(&epochs_tdb[..n], &xs[..n], &vxs[..n], epoch.to_et_seconds())?;
let (longitude_deg, longitude_vel_deg_s) =
hermite_eval(&epochs_tdb[..n], &ys[..n], &vys[..n], epoch.to_et_seconds())?;
let (height_km, height_vel_km_s) =
hermite_eval(&epochs_tdb[..n], &zs[..n], &vzs[..n], epoch.to_et_seconds())?;
self.latitude_deg = latitude_deg;
self.longitude_deg = longitude_deg;
self.height_km = height_km;
self.latitude_rate_deg_s = latitude_vel_deg_s;
self.longitude_rate_deg_s = longitude_vel_deg_s;
self.height_rate_km_s = height_vel_km_s;
self.epoch = epoch;
Ok(self)
}
fn frame(&self) -> Frame {
self.frame
}
fn set_frame(&mut self, frame: Frame) {
self.frame = frame;
}
fn export_params() -> Vec<StateParameter> {
vec![
StateParameter::Element(OrbitalElement::Latitude),
StateParameter::Element(OrbitalElement::Longitude),
StateParameter::Element(OrbitalElement::Height),
]
}
}
impl Add<OVector<f64, Const<6>>> for GroundAsset {
type Output = Self;
fn add(mut self, asset_state: OVector<f64, Const<6>>) -> Self {
self.latitude_deg += asset_state[0];
self.longitude_deg += asset_state[1];
self.height_km += asset_state[2];
self.latitude_rate_deg_s += asset_state[3];
self.longitude_rate_deg_s += asset_state[4];
self.height_rate_km_s += asset_state[5];
self
}
}
pub fn latlongalt_rate(
orbit: Orbit,
velocity_sez_km_s: Vector3<f64>,
) -> Result<(f64, f64, f64), PhysicsError> {
let (lat_deg, _long_deg, alt_km) = orbit.latlongalt()?;
let lat_rad = lat_deg.to_radians();
let v_south = velocity_sez_km_s.x;
let v_east = velocity_sez_km_s.y;
let v_zenith = velocity_sez_km_s.z;
let a_km = orbit.frame.mean_equatorial_radius_km()?;
let b_km = orbit.frame.shape.unwrap().polar_radius_km;
let e2 = (a_km.powi(2) - b_km.powi(2)) / a_km.powi(2);
let sin_lat = lat_rad.sin();
let n = a_km / (1.0 - e2 * sin_lat.powi(2)).sqrt();
let m = a_km * (1.0 - e2) / (1.0 - e2 * sin_lat.powi(2)).powf(1.5);
let alt_rate_km_s = v_zenith;
let lat_rate_rad_s = -v_south / (m + alt_km);
let lat_rate_deg_s = lat_rate_rad_s.to_degrees();
let cos_lat = lat_rad.cos();
let long_rate_rad_s = if cos_lat.abs() > 1e-10 {
v_east / ((n + alt_km) * cos_lat)
} else {
0.0 };
let long_rate_deg_s = long_rate_rad_s.to_degrees();
Ok((lat_rate_deg_s, long_rate_deg_s, alt_rate_km_s))
}
pub fn velocity_sez_from_latlongalt_rate(
orbit: Orbit,
lat_rate_deg_s: f64,
long_rate_deg_s: f64,
alt_rate_km_s: f64,
) -> Result<Vector3<f64>, PhysicsError> {
let (lat_deg, _long_deg, alt_km) = orbit.latlongalt()?;
let lat_rad = lat_deg.to_radians();
let a_km = orbit.frame.mean_equatorial_radius_km()?;
let b_km = orbit.frame.shape.unwrap().polar_radius_km;
let e2 = (a_km.powi(2) - b_km.powi(2)) / a_km.powi(2);
let sin_lat = lat_rad.sin();
let m = a_km * (1.0 - e2) / (1.0 - e2 * sin_lat.powi(2)).powf(1.5);
let n = a_km / (1.0 - e2 * sin_lat.powi(2)).sqrt();
let lat_rate_rad_s = lat_rate_deg_s.to_radians();
let long_rate_rad_s = long_rate_deg_s.to_radians();
let v_south = -lat_rate_rad_s * (m + alt_km); let v_east = long_rate_rad_s * (n + alt_km) * lat_rad.cos();
let v_zenith = alt_rate_km_s;
Ok(Vector3::new(v_south, v_east, v_zenith))
}