Skip to main content

lox_frames/
iers.rs

1// SPDX-FileCopyrightText: 2025 Helge Eichhorn <git@helgeeichhorn.de>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5use std::{
6    fmt::Display,
7    ops::{Add, AddAssign},
8};
9
10use lox_core::glam::{DMat3, DVec3};
11use lox_units::Angle;
12
13use crate::iers::{cip::CipCoords, ecliptic::MeanObliquity, nutation::Nutation};
14
15/// Celestial Intermediate Origin locator.
16pub mod cio;
17/// Celestial Intermediate Pole coordinates.
18pub mod cip;
19/// Earth rotation angle and equation of the equinoxes.
20pub mod earth_rotation;
21/// Obliquity of the ecliptic.
22pub mod ecliptic;
23/// Fundamental (Delaunay) arguments.
24pub mod fundamental;
25/// Nutation models.
26pub mod nutation;
27/// Polar motion coordinates and matrices.
28pub mod polar_motion;
29/// Precession matrices and frame bias.
30pub mod precession;
31/// Terrestrial Intermediate Origin locator.
32pub mod tio;
33
34mod sealed {
35    pub trait Sealed {}
36    impl Sealed for super::Iers1996 {}
37    impl Sealed for super::Iers2003 {}
38    impl Sealed for super::Iers2010 {}
39    impl Sealed for super::ReferenceSystem {}
40}
41
42/// Sealed trait for IERS convention systems.
43pub trait IersSystem: sealed::Sealed {
44    /// Returns the numeric identifier for this convention.
45    fn id(&self) -> usize;
46    /// Returns the convention name (e.g. "IERS1996").
47    fn name(&self) -> String;
48    /// Returns a round-trippable tag, encoding the nutation model where it
49    /// differs from the default (e.g. "IERS2003B").
50    fn abbreviation(&self) -> String {
51        self.name()
52    }
53}
54
55/// IERS 1996 conventions.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58pub struct Iers1996;
59
60impl IersSystem for Iers1996 {
61    fn id(&self) -> usize {
62        0
63    }
64
65    fn name(&self) -> String {
66        "IERS1996".to_owned()
67    }
68}
69
70impl Display for Iers1996 {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        self.name().fmt(f)
73    }
74}
75
76/// IAU 2000 nutation model variant.
77#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub enum Iau2000Model {
80    /// Full IAU 2000A model.
81    #[default]
82    A = 1,
83    /// Truncated IAU 2000B model.
84    B = 2,
85}
86
87impl Display for Iau2000Model {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        match self {
90            Iau2000Model::A => "IAU2000A".fmt(f),
91            Iau2000Model::B => "IAU2000B".fmt(f),
92        }
93    }
94}
95
96/// IERS 2003 conventions, parameterised by IAU 2000 nutation model.
97#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
98#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
99pub struct Iers2003(pub Iau2000Model);
100
101impl IersSystem for Iers2003 {
102    fn id(&self) -> usize {
103        self.0 as usize
104    }
105
106    fn name(&self) -> String {
107        "IERS2003".to_owned()
108    }
109
110    fn abbreviation(&self) -> String {
111        match self.0 {
112            Iau2000Model::A => "IERS2003".to_owned(),
113            Iau2000Model::B => "IERS2003B".to_owned(),
114        }
115    }
116}
117
118impl Display for Iers2003 {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        self.name().fmt(f)
121    }
122}
123
124/// IERS 2010 conventions.
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127pub struct Iers2010;
128
129impl IersSystem for Iers2010 {
130    fn id(&self) -> usize {
131        3
132    }
133
134    fn name(&self) -> String {
135        "IERS2010".to_owned()
136    }
137}
138
139impl Display for Iers2010 {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        self.name().fmt(f)
142    }
143}
144
145/// Dynamic dispatch enum for IERS convention systems.
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
148pub enum ReferenceSystem {
149    /// IERS 1996 conventions.
150    Iers1996,
151    /// IERS 2003 conventions with the given IAU 2000 model.
152    Iers2003(Iau2000Model),
153    /// IERS 2010 conventions.
154    Iers2010,
155}
156
157impl IersSystem for ReferenceSystem {
158    fn id(&self) -> usize {
159        match self {
160            ReferenceSystem::Iers1996 => Iers1996.id(),
161            ReferenceSystem::Iers2003(iau2000) => Iers2003(*iau2000).id(),
162            ReferenceSystem::Iers2010 => Iers2010.id(),
163        }
164    }
165
166    fn name(&self) -> String {
167        match self {
168            ReferenceSystem::Iers1996 => Iers1996.to_string(),
169            ReferenceSystem::Iers2003(model) => Iers2003(*model).to_string(),
170            ReferenceSystem::Iers2010 => Iers2010.to_string(),
171        }
172    }
173
174    fn abbreviation(&self) -> String {
175        match self {
176            ReferenceSystem::Iers1996 => Iers1996.abbreviation(),
177            ReferenceSystem::Iers2003(model) => Iers2003(*model).abbreviation(),
178            ReferenceSystem::Iers2010 => Iers2010.abbreviation(),
179        }
180    }
181}
182
183impl Display for ReferenceSystem {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        self.name().fmt(f)
186    }
187}
188
189impl From<Iers1996> for ReferenceSystem {
190    fn from(_: Iers1996) -> Self {
191        ReferenceSystem::Iers1996
192    }
193}
194
195impl From<Iers2003> for ReferenceSystem {
196    fn from(sys: Iers2003) -> Self {
197        ReferenceSystem::Iers2003(sys.0)
198    }
199}
200
201impl From<Iers2010> for ReferenceSystem {
202    fn from(_: Iers2010) -> Self {
203        ReferenceSystem::Iers2010
204    }
205}
206
207/// Earth orientation parameter corrections (δψ/δX, δε/δY).
208#[derive(Debug, Clone, Copy, Default)]
209#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
210pub struct Corrections(pub Angle, pub Angle);
211
212impl Corrections {
213    /// Returns `true` if both correction angles are zero.
214    pub fn is_zero(&self) -> bool {
215        self.0.is_zero() && self.1.is_zero()
216    }
217}
218
219impl Add<Corrections> for Nutation {
220    type Output = Self;
221
222    fn add(self, rhs: Corrections) -> Self::Output {
223        Self {
224            dpsi: self.dpsi + rhs.0,
225            deps: self.deps + rhs.1,
226        }
227    }
228}
229
230impl AddAssign<Corrections> for Nutation {
231    fn add_assign(&mut self, rhs: Corrections) {
232        self.dpsi += rhs.0;
233        self.deps += rhs.1;
234    }
235}
236
237impl Add<Corrections> for CipCoords {
238    type Output = Self;
239
240    fn add(self, rhs: Corrections) -> Self::Output {
241        Self {
242            x: self.x + rhs.0,
243            y: self.y + rhs.1,
244        }
245    }
246}
247
248impl AddAssign<Corrections> for CipCoords {
249    fn add_assign(&mut self, rhs: Corrections) {
250        self.x += rhs.0;
251        self.y += rhs.1;
252    }
253}
254
255impl ReferenceSystem {
256    /// Converts EOP corrections between ecliptic and equatorial representations.
257    pub fn ecliptic_corrections(
258        &self,
259        corr: Corrections,
260        nut: Nutation,
261        epsa: MeanObliquity,
262        rpb: DMat3,
263    ) -> Corrections {
264        match self {
265            ReferenceSystem::Iers1996 => corr,
266            ReferenceSystem::Iers2003(_) | ReferenceSystem::Iers2010 => {
267                let Corrections(dx, dy) = corr;
268                let rbpn = nut.nutation_matrix(epsa) * rpb;
269                let v1 = DVec3::new(dx.as_f64(), dy.as_f64(), 0.0);
270                let v2 = rbpn * v1;
271                Corrections(Angle::new(v2.x / epsa.0.sin()), Angle::new(v2.y))
272            }
273        }
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use rstest::rstest;
280
281    use super::*;
282
283    #[rstest]
284    #[case(Iers1996, 0)]
285    #[case(Iers2003(Iau2000Model::A), 1)]
286    #[case(Iers2003(Iau2000Model::B), 2)]
287    #[case(Iers2010, 3)]
288    #[case(ReferenceSystem::Iers1996, 0)]
289    #[case(ReferenceSystem::Iers2003(Iau2000Model::A), 1)]
290    #[case(ReferenceSystem::Iers2003(Iau2000Model::B), 2)]
291    #[case(ReferenceSystem::Iers2010, 3)]
292    fn test_iers_convention_id<T: IersSystem>(#[case] iers: T, #[case] exp: usize) {
293        let act = iers.id();
294        assert_eq!(act, exp);
295    }
296}