Skip to main content

lox_frames/
frames.rs

1// SPDX-FileCopyrightText: 2025 Helge Eichhorn <git@helgeeichhorn.de>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5use lox_bodies::{Origin, RotationalElements, TryRotationalElements, UndefinedOriginPropertyError};
6
7use crate::{
8    iers::IersSystem,
9    traits::{BodyFixed, QuasiInertial, ReferenceFrame},
10};
11
12const ICRF_ID: usize = 0;
13const CIRF_ID: usize = 1;
14const TIRF_ID: usize = 2;
15const ITRF_ID: usize = 3;
16const J2000_ID: usize = 4;
17
18const MOD_ID: usize = 11;
19const TOD_ID: usize = 12;
20const PEF_ID: usize = 13;
21
22/// International Celestial Reference Frame.
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
26pub struct Icrf;
27
28impl ReferenceFrame for Icrf {
29    fn name(&self) -> String {
30        "International Celestial Reference Frame".to_string()
31    }
32
33    fn abbreviation(&self) -> String {
34        "ICRF".to_string()
35    }
36
37    fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
38        Some(ICRF_ID)
39    }
40}
41
42impl QuasiInertial for Icrf {}
43
44/// J2000 Mean Equator and Equinox frame.
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
48pub struct J2000;
49
50impl ReferenceFrame for J2000 {
51    fn name(&self) -> String {
52        "J2000 Mean Equator and Equinox".to_string()
53    }
54
55    fn abbreviation(&self) -> String {
56        "J2000".to_string()
57    }
58
59    fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
60        Some(J2000_ID)
61    }
62}
63
64impl QuasiInertial for J2000 {}
65
66/// Celestial Intermediate Reference Frame.
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
69#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
70pub struct Cirf;
71
72impl ReferenceFrame for Cirf {
73    fn name(&self) -> String {
74        "Celestial Intermediate Reference Frame".to_string()
75    }
76
77    fn abbreviation(&self) -> String {
78        "CIRF".to_string()
79    }
80
81    fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
82        Some(CIRF_ID)
83    }
84}
85
86/// Terrestrial Intermediate Reference Frame.
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
90pub struct Tirf;
91
92impl ReferenceFrame for Tirf {
93    fn name(&self) -> String {
94        "Terrestrial Intermediate Reference Frame".to_string()
95    }
96
97    fn abbreviation(&self) -> String {
98        "TIRF".to_string()
99    }
100
101    fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
102        Some(TIRF_ID)
103    }
104}
105
106/// International Terrestrial Reference Frame.
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
109#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
110pub struct Itrf;
111
112impl ReferenceFrame for Itrf {
113    fn name(&self) -> String {
114        "International Terrestrial Reference Frame".to_string()
115    }
116
117    fn abbreviation(&self) -> String {
118        "ITRF".to_string()
119    }
120
121    fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
122        Some(ITRF_ID)
123    }
124}
125
126/// Mean of Date frame, parameterised by IERS convention.
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
129pub struct Mod<T: IersSystem>(pub T);
130
131impl<T> ReferenceFrame for Mod<T>
132where
133    T: IersSystem,
134{
135    fn name(&self) -> String {
136        format!("{} Mean of Date Frame", self.0.name())
137    }
138
139    fn abbreviation(&self) -> String {
140        format!("MOD({})", self.0.name())
141    }
142
143    fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
144        Some(MOD_ID * 10 + self.0.id())
145    }
146}
147
148/// True of Date frame, parameterised by IERS convention.
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
151pub struct Tod<T: IersSystem>(pub T);
152
153impl<T> ReferenceFrame for Tod<T>
154where
155    T: IersSystem,
156{
157    fn name(&self) -> String {
158        format!("{} True of Date Frame", self.0.name())
159    }
160
161    fn abbreviation(&self) -> String {
162        format!("TOD({})", self.0.name())
163    }
164
165    fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
166        Some(TOD_ID * 10 + self.0.id())
167    }
168}
169
170/// Pseudo-Earth Fixed frame, parameterised by IERS convention.
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173pub struct Pef<T: IersSystem>(pub T);
174
175impl<T> ReferenceFrame for Pef<T>
176where
177    T: IersSystem,
178{
179    fn name(&self) -> String {
180        format!("{} Pseudo-Earth Fixed Frame", self.0.name())
181    }
182
183    fn abbreviation(&self) -> String {
184        format!("PEF({})", self.0.name())
185    }
186
187    fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
188        Some(PEF_ID * 10 + self.0.id())
189    }
190}
191
192/// True Equator Mean Equinox frame.
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
195#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
196pub struct Teme;
197
198impl ReferenceFrame for Teme {
199    fn name(&self) -> String {
200        "True Equator Mean Equinox".to_owned()
201    }
202
203    fn abbreviation(&self) -> String {
204        "TEME".to_owned()
205    }
206
207    fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
208        Some(7)
209    }
210}
211
212impl BodyFixed for Itrf {}
213
214// -- serde: serialize frame ZSTs as their abbreviation --
215
216macro_rules! impl_frame_serde {
217    ($ty:ident, $abbrev:literal) => {
218        #[cfg(feature = "serde")]
219        impl From<$ty> for &'static str {
220            fn from(_: $ty) -> Self {
221                $abbrev
222            }
223        }
224
225        #[cfg(feature = "serde")]
226        impl TryFrom<String> for $ty {
227            type Error = String;
228            fn try_from(s: String) -> Result<Self, Self::Error> {
229                if s == $abbrev {
230                    Ok($ty)
231                } else {
232                    Err(format!("expected \"{}\", got \"{}\"", $abbrev, s))
233                }
234            }
235        }
236    };
237}
238
239impl_frame_serde!(Icrf, "ICRF");
240impl_frame_serde!(J2000, "J2000");
241impl_frame_serde!(Cirf, "CIRF");
242impl_frame_serde!(Tirf, "TIRF");
243impl_frame_serde!(Itrf, "ITRF");
244impl_frame_serde!(Teme, "TEME");
245
246/// IAU body-fixed reference frame derived from rotational elements.
247#[derive(Clone, Copy, Debug, PartialEq, Eq)]
248#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
249pub struct Iau<T: TryRotationalElements>(T);
250
251impl<T> Iau<T>
252where
253    T: RotationalElements,
254{
255    /// Creates an IAU frame for a body with known rotational elements.
256    pub fn new(body: T) -> Self {
257        Self(body)
258    }
259}
260
261impl<T> Iau<T>
262where
263    T: TryRotationalElements,
264{
265    /// Creates an IAU frame, returning an error if rotational elements are undefined.
266    pub fn try_new(body: T) -> Result<Self, UndefinedOriginPropertyError> {
267        let _ = body.try_right_ascension(0.0)?;
268        Ok(Self(body))
269    }
270
271    /// Returns the underlying body.
272    pub fn body(&self) -> T
273    where
274        T: Copy,
275    {
276        self.0
277    }
278
279    /// Returns the rotational elements (right ascension, declination, prime meridian) at
280    /// the given Julian centuries since J2000.
281    pub fn rotational_elements(&self, j2000: f64) -> (f64, f64, f64) {
282        self.0.try_rotational_elements(j2000).unwrap()
283    }
284
285    /// Returns the time derivatives of the rotational elements.
286    pub fn rotational_element_rates(&self, j2000: f64) -> (f64, f64, f64) {
287        self.0.try_rotational_element_rates(j2000).unwrap()
288    }
289}
290
291impl<T: TryRotationalElements> BodyFixed for Iau<T> {}
292
293impl<T> ReferenceFrame for Iau<T>
294where
295    T: TryRotationalElements + Origin,
296{
297    fn name(&self) -> String {
298        let body = self.0.name();
299        match body {
300            "Sun" | "Moon" => format!("IAU Body-Fixed Reference Frame for the {body}"),
301            _ => format!("IAU Body-Fixed Reference Frame for {body}"),
302        }
303    }
304
305    fn abbreviation(&self) -> String {
306        let body = self.0.name().replace([' ', '-'], "_").to_uppercase();
307        format!("IAU_{body}")
308    }
309
310    fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
311        Some(1000 + self.0.id().0 as usize)
312    }
313}