use lox_bodies::TryRotationalElements;
use lox_time::{
Time,
offsets::TryOffset,
time_scales::{ContinuousTimeScale, Tdb, Tt, Ut1},
};
use crate::{
Frame,
frames::{Cirf, Iau, Icrf, Itrf, J2000, Mod, Pef, Teme, Tirf, Tod},
iers::{IersSystem, ReferenceSystem},
rotations::{Rotation, RotationError, RotationProvider, TryRotation},
traits::{FrameKey, ReferenceFrame, frame_key},
};
pub trait RotateToIcrf<T: ContinuousTimeScale, P> {
type Error;
fn rotation_to_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error>;
fn rotation_from_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error>;
}
pub fn rotation_via_icrf<T, P, O, Tg>(
provider: &P,
origin: O,
target: Tg,
time: Time<T>,
) -> Result<Rotation, O::Error>
where
T: ContinuousTimeScale + Copy,
O: RotateToIcrf<T, P>,
Tg: RotateToIcrf<T, P, Error = O::Error>,
{
let origin_to_icrf = origin.rotation_to_icrf(provider, time)?;
let icrf_to_target = target.rotation_from_icrf(provider, time)?;
Ok(origin_to_icrf.compose(icrf_to_target))
}
impl<T, O, Tg, P> TryRotation<O, Tg, T> for P
where
T: ContinuousTimeScale + Copy,
O: ReferenceFrame + RotateToIcrf<T, P, Error = RotationError>,
Tg: ReferenceFrame + RotateToIcrf<T, P, Error = RotationError>,
{
type Error = RotationError;
fn try_rotation(&self, origin: O, target: Tg, time: Time<T>) -> Result<Rotation, Self::Error> {
let origin_key = frame_key(&origin);
let target_key = frame_key(&target);
if origin_key.is_some() && origin_key == target_key {
Ok(Rotation::IDENTITY)
} else if origin_key == Some(FrameKey::Icrf) {
target.rotation_from_icrf(self, time)
} else if target_key == Some(FrameKey::Icrf) {
origin.rotation_to_icrf(self, time)
} else {
rotation_via_icrf(self, origin, target, time)
}
}
}
impl<T, P> RotateToIcrf<T, P> for Icrf
where
T: ContinuousTimeScale + Copy,
P: RotationProvider<T>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, _provider: &P, _time: Time<T>) -> Result<Rotation, Self::Error> {
Ok(Rotation::IDENTITY)
}
fn rotation_from_icrf(&self, _provider: &P, _time: Time<T>) -> Result<Rotation, Self::Error> {
Ok(Rotation::IDENTITY)
}
}
impl<T, P> RotateToIcrf<T, P> for J2000
where
T: ContinuousTimeScale + Copy,
P: RotationProvider<T>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, provider: &P, _time: Time<T>) -> Result<Rotation, Self::Error> {
Ok(provider.j2000_to_icrf())
}
fn rotation_from_icrf(&self, provider: &P, _time: Time<T>) -> Result<Rotation, Self::Error> {
Ok(provider.icrf_to_j2000())
}
}
impl<T, P, R> RotateToIcrf<T, P> for Iau<R>
where
T: ContinuousTimeScale + Copy,
R: TryRotationalElements + Copy,
P: RotationProvider<T> + TryOffset<T, Tdb>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
provider.iau_to_icrf(time, *self)
}
fn rotation_from_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
provider.icrf_to_iau(time, *self)
}
}
impl<T, P> RotateToIcrf<T, P> for Cirf
where
T: ContinuousTimeScale + Copy,
P: RotationProvider<T> + TryOffset<T, Tdb>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
provider.cirf_to_icrf(time)
}
fn rotation_from_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
provider.icrf_to_cirf(time)
}
}
impl<T, P> RotateToIcrf<T, P> for Tirf
where
T: ContinuousTimeScale + Copy,
P: RotationProvider<T> + TryOffset<T, Tdb> + TryOffset<T, Ut1>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
Ok(provider
.tirf_to_cirf(time)?
.compose(provider.cirf_to_icrf(time)?))
}
fn rotation_from_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
Ok(provider
.icrf_to_cirf(time)?
.compose(provider.cirf_to_tirf(time)?))
}
}
impl<T, P> RotateToIcrf<T, P> for Itrf
where
T: ContinuousTimeScale + Copy,
P: RotationProvider<T> + TryOffset<T, Tt> + TryOffset<T, Tdb> + TryOffset<T, Ut1>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
provider.itrf_to_icrf(time)
}
fn rotation_from_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
provider.icrf_to_itrf(time)
}
}
impl<T, P, C> RotateToIcrf<T, P> for Mod<C>
where
T: ContinuousTimeScale + Copy,
C: IersSystem + Into<ReferenceSystem> + Copy,
P: RotationProvider<T> + TryOffset<T, Tt>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
provider.mod_to_icrf(time, self.0.into())
}
fn rotation_from_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
provider.icrf_to_mod(time, self.0.into())
}
}
impl<T, P, C> RotateToIcrf<T, P> for Tod<C>
where
T: ContinuousTimeScale + Copy,
C: IersSystem + Into<ReferenceSystem> + Copy,
P: RotationProvider<T> + TryOffset<T, Tt> + TryOffset<T, Tdb>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
let sys: ReferenceSystem = self.0.into();
Ok(provider
.tod_to_mod(time, sys)?
.compose(provider.mod_to_icrf(time, sys)?))
}
fn rotation_from_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
let sys: ReferenceSystem = self.0.into();
Ok(provider
.icrf_to_mod(time, sys)?
.compose(provider.mod_to_tod(time, sys)?))
}
}
impl<T, P, C> RotateToIcrf<T, P> for Pef<C>
where
T: ContinuousTimeScale + Copy,
C: IersSystem + Into<ReferenceSystem> + Copy,
P: RotationProvider<T> + TryOffset<T, Tt> + TryOffset<T, Tdb> + TryOffset<T, Ut1>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
let sys: ReferenceSystem = self.0.into();
Ok(provider
.pef_to_tod(time, sys)?
.compose(provider.tod_to_mod(time, sys)?)
.compose(provider.mod_to_icrf(time, sys)?))
}
fn rotation_from_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
let sys: ReferenceSystem = self.0.into();
Ok(provider
.icrf_to_mod(time, sys)?
.compose(provider.mod_to_tod(time, sys)?)
.compose(provider.tod_to_pef(time, sys)?))
}
}
impl<T, P> RotateToIcrf<T, P> for Teme
where
T: ContinuousTimeScale + Copy,
P: RotationProvider<T> + TryOffset<T, Tt> + TryOffset<T, Tdb>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
provider.teme_to_icrf(time)
}
fn rotation_from_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
provider.icrf_to_teme(time)
}
}
impl<T, P> RotateToIcrf<T, P> for Frame
where
T: ContinuousTimeScale + Copy,
P: RotationProvider<T> + TryOffset<T, Tt> + TryOffset<T, Tdb> + TryOffset<T, Ut1>,
{
type Error = RotationError;
fn rotation_to_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
match *self {
Frame::Icrf => Icrf.rotation_to_icrf(provider, time),
Frame::J2000 => J2000.rotation_to_icrf(provider, time),
Frame::Cirf => Cirf.rotation_to_icrf(provider, time),
Frame::Tirf => Tirf.rotation_to_icrf(provider, time),
Frame::Itrf => Itrf.rotation_to_icrf(provider, time),
Frame::Iau(origin) => Iau::try_new(origin)?.rotation_to_icrf(provider, time),
Frame::Mod(sys) => Mod(sys).rotation_to_icrf(provider, time),
Frame::Tod(sys) => Tod(sys).rotation_to_icrf(provider, time),
Frame::Pef(sys) => Pef(sys).rotation_to_icrf(provider, time),
Frame::Teme => Teme.rotation_to_icrf(provider, time),
}
}
fn rotation_from_icrf(&self, provider: &P, time: Time<T>) -> Result<Rotation, Self::Error> {
match *self {
Frame::Icrf => Icrf.rotation_from_icrf(provider, time),
Frame::J2000 => J2000.rotation_from_icrf(provider, time),
Frame::Cirf => Cirf.rotation_from_icrf(provider, time),
Frame::Tirf => Tirf.rotation_from_icrf(provider, time),
Frame::Itrf => Itrf.rotation_from_icrf(provider, time),
Frame::Iau(origin) => Iau::try_new(origin)?.rotation_from_icrf(provider, time),
Frame::Mod(sys) => Mod(sys).rotation_from_icrf(provider, time),
Frame::Tod(sys) => Tod(sys).rotation_from_icrf(provider, time),
Frame::Pef(sys) => Pef(sys).rotation_from_icrf(provider, time),
Frame::Teme => Teme.rotation_from_icrf(provider, time),
}
}
}
#[cfg(test)]
mod tests {
use lox_approx::assert_approx_eq;
use lox_core::glam::DMat3;
use lox_bodies::Origin;
use lox_time::time_scales::Tai;
use crate::iers::{Iau2000Model, Iers2003};
use crate::providers::DefaultRotationProvider;
use super::*;
fn epoch() -> Time<Tt> {
Time::from_two_part_julian_date(Tt, 2454195.5, 0.500754444444444)
}
fn max_abs_diff(a: DMat3, b: DMat3) -> f64 {
let d = a - b;
d.x_axis
.abs()
.max_element()
.max(d.y_axis.abs().max_element())
.max(d.z_axis.abs().max_element())
}
#[test]
fn roundtrip_icrf_itrf() {
let t = epoch();
let fwd = DefaultRotationProvider.try_rotation(Icrf, Itrf, t).unwrap();
let bwd = DefaultRotationProvider.try_rotation(Itrf, Icrf, t).unwrap();
assert_approx_eq!(fwd.m * bwd.m, DMat3::IDENTITY, atol <= 1e-14);
}
#[test]
fn rotates_between_two_non_icrf_frames() {
let t = epoch();
let tod = Tod(Iers2003(Iau2000Model::A));
let fwd = DefaultRotationProvider.try_rotation(tod, Itrf, t).unwrap();
let bwd = DefaultRotationProvider.try_rotation(Itrf, tod, t).unwrap();
assert_approx_eq!(fwd.m * bwd.m, DMat3::IDENTITY, atol <= 1e-14);
}
#[test]
fn dynamic_rotates_between_two_non_icrf_frames() {
let t = epoch();
let tod = Frame::Tod(ReferenceSystem::Iers2003(Iau2000Model::A));
let fwd = DefaultRotationProvider
.try_rotation(tod, Frame::Itrf, t)
.unwrap();
let bwd = DefaultRotationProvider
.try_rotation(Frame::Itrf, tod, t)
.unwrap();
assert_approx_eq!(fwd.m * bwd.m, DMat3::IDENTITY, atol <= 1e-14);
}
#[test]
fn roundtrip_icrf_j2000() {
let t = epoch();
let fwd = DefaultRotationProvider
.try_rotation(Icrf, J2000, t)
.unwrap();
let bwd = DefaultRotationProvider
.try_rotation(J2000, Icrf, t)
.unwrap();
assert_approx_eq!(fwd.m * bwd.m, DMat3::IDENTITY, atol <= 1e-15);
assert!(!fwd.m.abs_diff_eq(DMat3::IDENTITY, 1e-9));
assert!(fwd.m.abs_diff_eq(DMat3::IDENTITY, 1e-6));
}
#[test]
fn threads_2000b_model() {
let t = epoch();
let tod_a = DefaultRotationProvider
.try_rotation(Icrf, Tod(Iers2003(Iau2000Model::A)), t)
.unwrap();
let tod_b = DefaultRotationProvider
.try_rotation(Icrf, Tod(Iers2003(Iau2000Model::B)), t)
.unwrap();
assert!(max_abs_diff(tod_a.m, tod_b.m) > 1e-9);
}
fn tai_j2000() -> Time<Tai> {
Time::j2000(Tai)
}
#[test]
fn mixed_icrf_to_dynframe() {
let rot = DefaultRotationProvider
.try_rotation(Icrf, Frame::Icrf, tai_j2000())
.unwrap();
assert!(rot.m.abs_diff_eq(DMat3::IDENTITY, 1e-14));
}
#[test]
fn mixed_dynframe_to_icrf() {
let rot = DefaultRotationProvider
.try_rotation(Frame::Icrf, Icrf, tai_j2000())
.unwrap();
assert!(rot.m.abs_diff_eq(DMat3::IDENTITY, 1e-14));
}
#[test]
fn mixed_iau_dynorigin_and_dynframe() {
let iau_earth = Iau::try_new(Origin::Earth).unwrap();
let fwd = DefaultRotationProvider
.try_rotation(Icrf, Frame::Iau(Origin::Earth), tai_j2000())
.unwrap();
let bwd = DefaultRotationProvider
.try_rotation(iau_earth, Frame::Icrf, tai_j2000())
.unwrap();
assert!(!fwd.m.abs_diff_eq(DMat3::IDENTITY, 1e-6));
assert!((fwd.m * bwd.m).abs_diff_eq(DMat3::IDENTITY, 1e-14));
}
}