use crate::astro::constants::earth::OMEGA_E_DOT_RAD_S;
use crate::astro::frames::transforms::{
gcrs_to_itrs_matrix_with_polar_motion, mat3_vec3_mul, polar_motion_matrix, FrameTransformError,
PolarMotion,
};
use crate::astro::math::mat3::{inline_rxr, inline_tr, Mat3};
use crate::astro::time::civil::{civil_from_j2000_seconds, j2000_seconds_from_split};
use crate::astro::time::model::{Instant, InstantRepr, TimeScale};
use crate::astro::time::scales::TimeScales;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EarthOrientation {
time_scales: TimeScales,
polar_motion: PolarMotion,
gcrf_to_itrf: Mat3,
itrf_to_gcrf: Mat3,
earth_rotation_vector_itrf_rad_s: [f64; 3],
}
impl EarthOrientation {
pub fn from_time_scales(ts: &TimeScales) -> Result<Self, FrameTransformError> {
Self::from_time_scales_with_polar_motion(ts, PolarMotion::ZERO)
}
pub fn from_time_scales_with_polar_motion(
ts: &TimeScales,
polar_motion: PolarMotion,
) -> Result<Self, FrameTransformError> {
let gcrf_to_itrf = gcrs_to_itrs_matrix_with_polar_motion(ts, polar_motion)?;
let itrf_to_gcrf = inline_tr(&gcrf_to_itrf);
let polar = polar_motion_matrix(polar_motion)?;
let earth_rotation_vector_itrf_rad_s =
mat3_vec3_mul(&polar, &[0.0, 0.0, OMEGA_E_DOT_RAD_S])?;
Ok(Self {
time_scales: *ts,
polar_motion,
gcrf_to_itrf,
itrf_to_gcrf,
earth_rotation_vector_itrf_rad_s,
})
}
pub fn from_utc(
year: i32,
month: i32,
day: i32,
hour: i32,
minute: i32,
second: f64,
) -> Result<Self, FrameTransformError> {
Self::from_utc_with_polar_motion(year, month, day, hour, minute, second, PolarMotion::ZERO)
}
pub fn from_utc_with_polar_motion(
year: i32,
month: i32,
day: i32,
hour: i32,
minute: i32,
second: f64,
polar_motion: PolarMotion,
) -> Result<Self, FrameTransformError> {
let ts = TimeScales::from_utc(year, month, day, hour, minute, second)
.map_err(|_| invalid_input("utc", "time-scale conversion failed"))?;
Self::from_time_scales_with_polar_motion(&ts, polar_motion)
}
pub fn from_instant(epoch: Instant) -> Result<Self, FrameTransformError> {
Self::from_instant_with_polar_motion(epoch, PolarMotion::ZERO)
}
pub fn from_instant_with_polar_motion(
epoch: Instant,
polar_motion: PolarMotion,
) -> Result<Self, FrameTransformError> {
let ts = time_scales_from_instant(epoch)?;
Self::from_time_scales_with_polar_motion(&ts, polar_motion)
}
pub fn time_scales(&self) -> TimeScales {
self.time_scales
}
pub fn polar_motion(&self) -> PolarMotion {
self.polar_motion
}
pub fn earth_rotation_vector_itrf_rad_s(&self) -> [f64; 3] {
self.earth_rotation_vector_itrf_rad_s
}
pub fn gcrf_to_itrf_matrix(&self) -> Mat3 {
self.gcrf_to_itrf
}
pub fn itrf_to_gcrf_matrix(&self) -> Mat3 {
self.itrf_to_gcrf
}
pub fn gcrf_to_itrf_rotation_rate_matrix(&self) -> Mat3 {
let neg_skew = neg_skew_matrix(self.earth_rotation_vector_itrf_rad_s);
inline_rxr(&neg_skew, &self.gcrf_to_itrf)
}
pub fn itrf_to_gcrf_rotation_rate_matrix(&self) -> Mat3 {
let skew = skew_matrix(self.earth_rotation_vector_itrf_rad_s);
inline_rxr(&self.itrf_to_gcrf, &skew)
}
pub fn gcrf_to_itrf_position_km(
&self,
position_gcrf_km: [f64; 3],
) -> Result<[f64; 3], FrameTransformError> {
validate_vec3("position_gcrf_km", &position_gcrf_km)?;
mat3_vec3_mul(&self.gcrf_to_itrf, &position_gcrf_km)
}
pub fn itrf_to_gcrf_position_km(
&self,
position_itrf_km: [f64; 3],
) -> Result<[f64; 3], FrameTransformError> {
validate_vec3("position_itrf_km", &position_itrf_km)?;
mat3_vec3_mul(&self.itrf_to_gcrf, &position_itrf_km)
}
pub fn gcrf_to_itrf_state_km(
&self,
position_gcrf_km: [f64; 3],
velocity_gcrf_km_s: [f64; 3],
) -> Result<([f64; 3], [f64; 3]), FrameTransformError> {
validate_vec3("position_gcrf_km", &position_gcrf_km)?;
validate_vec3("velocity_gcrf_km_s", &velocity_gcrf_km_s)?;
let position_itrf_km = mat3_vec3_mul(&self.gcrf_to_itrf, &position_gcrf_km)?;
let rotated_velocity = mat3_vec3_mul(&self.gcrf_to_itrf, &velocity_gcrf_km_s)?;
let transport = cross(self.earth_rotation_vector_itrf_rad_s, position_itrf_km);
Ok((position_itrf_km, sub(rotated_velocity, transport)))
}
pub fn itrf_to_gcrf_state_km(
&self,
position_itrf_km: [f64; 3],
velocity_itrf_km_s: [f64; 3],
) -> Result<([f64; 3], [f64; 3]), FrameTransformError> {
validate_vec3("position_itrf_km", &position_itrf_km)?;
validate_vec3("velocity_itrf_km_s", &velocity_itrf_km_s)?;
let position_gcrf_km = mat3_vec3_mul(&self.itrf_to_gcrf, &position_itrf_km)?;
let transport = cross(self.earth_rotation_vector_itrf_rad_s, position_itrf_km);
let velocity_rotating = add(velocity_itrf_km_s, transport);
let velocity_gcrf_km_s = mat3_vec3_mul(&self.itrf_to_gcrf, &velocity_rotating)?;
Ok((position_gcrf_km, velocity_gcrf_km_s))
}
}
pub trait EarthOrientationProvider: Send + Sync {
fn orientation_at_tdb_seconds(
&self,
epoch_tdb_seconds: f64,
) -> Result<EarthOrientation, FrameTransformError>;
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TdbEarthOrientationProvider {
polar_motion: PolarMotion,
}
impl TdbEarthOrientationProvider {
pub const fn new() -> Self {
Self {
polar_motion: PolarMotion::ZERO,
}
}
pub const fn with_polar_motion(polar_motion: PolarMotion) -> Self {
Self { polar_motion }
}
pub fn polar_motion(&self) -> PolarMotion {
self.polar_motion
}
}
impl Default for TdbEarthOrientationProvider {
fn default() -> Self {
Self::new()
}
}
impl EarthOrientationProvider for TdbEarthOrientationProvider {
fn orientation_at_tdb_seconds(
&self,
epoch_tdb_seconds: f64,
) -> Result<EarthOrientation, FrameTransformError> {
let ts = time_scales_from_scale_j2000_seconds(TimeScale::Tdb, epoch_tdb_seconds)?;
EarthOrientation::from_time_scales_with_polar_motion(&ts, self.polar_motion)
}
}
fn time_scales_from_instant(epoch: Instant) -> Result<TimeScales, FrameTransformError> {
let seconds = match epoch.repr {
InstantRepr::JulianDate(jd) => j2000_seconds_from_split(jd.jd_whole, jd.fraction),
InstantRepr::Nanos(_) => {
return Err(invalid_input("epoch", "must be a split Julian date"));
}
};
time_scales_from_scale_j2000_seconds(epoch.scale, seconds)
}
fn time_scales_from_scale_j2000_seconds(
scale: TimeScale,
epoch_j2000_s: f64,
) -> Result<TimeScales, FrameTransformError> {
if !epoch_j2000_s.is_finite() {
return Err(invalid_input("epoch_j2000_s", "must be finite"));
}
let whole = epoch_j2000_s.floor();
if whole < i64::MIN as f64 || whole > i64::MAX as f64 {
return Err(invalid_input(
"epoch_j2000_s",
"whole seconds are out of range",
));
}
let fraction = epoch_j2000_s - whole;
let (year, month, day, hour, minute, second) = civil_from_j2000_seconds(whole as i64);
TimeScales::from_scale(
scale,
year as i32,
month as i32,
day as i32,
hour as i32,
minute as i32,
second as f64 + fraction,
)
.map_err(|_| invalid_input("epoch_j2000_s", "time-scale conversion failed"))
}
fn invalid_input(field: &'static str, reason: &'static str) -> FrameTransformError {
FrameTransformError::InvalidInput { field, reason }
}
fn validate_vec3(field: &'static str, value: &[f64; 3]) -> Result<(), FrameTransformError> {
if value.iter().all(|component| component.is_finite()) {
Ok(())
} else {
Err(invalid_input(field, "components must be finite"))
}
}
fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
fn add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
}
fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
fn skew_matrix(omega: [f64; 3]) -> Mat3 {
[
[0.0, -omega[2], omega[1]],
[omega[2], 0.0, -omega[0]],
[-omega[1], omega[0], 0.0],
]
}
fn neg_skew_matrix(omega: [f64; 3]) -> Mat3 {
[
[0.0, omega[2], -omega[1]],
[-omega[2], 0.0, omega[0]],
[omega[1], -omega[0], 0.0],
]
}