#![forbid(unsafe_code)]
use core::fmt;
use spacelab_core::{Angle, Epoch, Tai, UtcDateTime, time::tai_to_tt};
use spacelab_eop::{EopError, EopQuality, EopSeries};
use spacelab_fa::{LeapError, delta_at};
use spacelab_frames::{Itrf, Orientation, Teme, Transform, teme_to_itrf};
pub use spacelab_core as core_types;
pub use spacelab_earth as earth;
pub use spacelab_eop as eop;
pub use spacelab_fa as fa;
pub use spacelab_frames as frames;
pub use spacelab_sgp4 as sgp4;
const NS_PER_SECOND: f64 = 1e9;
const TT_MINUS_TAI_NS: i128 = 32_184_000_000;
#[derive(Debug, Clone, PartialEq)]
pub enum EarthOrientationError {
Leap(LeapError),
Eop(EopError),
}
impl fmt::Display for EarthOrientationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Leap(e) => write!(f, "leap seconds: {e}"),
Self::Eop(e) => write!(f, "earth orientation: {e}"),
}
}
}
impl std::error::Error for EarthOrientationError {}
impl From<LeapError> for EarthOrientationError {
fn from(e: LeapError) -> Self {
Self::Leap(e)
}
}
impl From<EopError> for EarthOrientationError {
fn from(e: EopError) -> Self {
Self::Eop(e)
}
}
#[must_use = "the quality of the answer is part of the answer"]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EarthOrientation {
pub orientation: Orientation,
pub quality: EarthOrientationQuality,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EarthOrientationQuality {
Tabulated {
quality: EopQuality,
dut1_sigma_seconds: f64,
},
Absent,
}
impl EarthOrientationQuality {
#[must_use]
pub const fn tabulated(self) -> Option<EopQuality> {
match self {
Self::Tabulated { quality, .. } => Some(quality),
Self::Absent => None,
}
}
#[must_use]
pub const fn dut1_sigma_seconds(self) -> Option<f64> {
match self {
Self::Tabulated {
dut1_sigma_seconds, ..
} => Some(dut1_sigma_seconds),
Self::Absent => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum EopSource<'a> {
Series(&'a EopSeries),
NoEop,
}
pub fn earth_orientation_at(
utc: UtcDateTime,
source: EopSource<'_>,
) -> Result<EarthOrientation, EarthOrientationError> {
let naive = utc.naive_ns_since_j2000();
let tai = Epoch::<Tai>::from_ns_since_j2000(naive + i128::from(delta_at(utc)?) * 1_000_000_000);
let tt = tai_to_tt(tai);
let Some(eop) = (match source {
EopSource::Series(series) => Some(series.at_mjd(utc.modified_julian_date())?),
EopSource::NoEop => None,
}) else {
return Ok(EarthOrientation {
orientation: Orientation {
ut1: Epoch::from_ns_since_j2000(naive),
tt,
xp: Angle::from_radians(0.0),
yp: Angle::from_radians(0.0),
lod_seconds: 0.0,
},
quality: EarthOrientationQuality::Absent,
});
};
#[allow(clippy::cast_possible_truncation)] let ut1_ns = naive + (eop.dut1_seconds * NS_PER_SECOND).round() as i128;
Ok(EarthOrientation {
orientation: Orientation {
ut1: Epoch::from_ns_since_j2000(ut1_ns),
tt,
xp: eop.xp,
yp: eop.yp,
lod_seconds: eop.lod_seconds,
},
quality: EarthOrientationQuality::Tabulated {
quality: eop.quality,
dut1_sigma_seconds: eop.dut1_sigma_seconds,
},
})
}
pub fn teme_to_itrf_at(
utc: UtcDateTime,
source: EopSource<'_>,
) -> Result<(Transform<Teme, Itrf>, EarthOrientationQuality), EarthOrientationError> {
let e = earth_orientation_at(utc, source)?;
let o = e.orientation;
Ok((
teme_to_itrf(o.ut1, o.tt, o.xp, o.yp, o.lod_seconds),
e.quality,
))
}
#[must_use]
pub const fn tt_minus_tai_ns() -> i128 {
TT_MINUS_TAI_NS
}