lox_frames/
dynamic.rs

1/*
2 * Copyright (c) 2025. Helge Eichhorn and the LOX contributors
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
7 */
8
9use std::str::FromStr;
10
11use lox_bodies::{DynOrigin, Origin, TryRotationalElements};
12use lox_time::{
13    Time,
14    julian_dates::JulianDate,
15    time_scales::{Tdb, TimeScale, TryToScale},
16};
17use thiserror::Error;
18
19use crate::{
20    frames::{Cirf, Icrf, Itrf, Tirf},
21    traits::{
22        NonBodyFixedFrameError, NonQuasiInertialFrameError, ReferenceFrame, TryBodyFixed,
23        TryQuasiInertial, TryRotateTo,
24    },
25    transformations::{
26        Rotation,
27        iau::{IauFrameTransformationError, icrf_to_iau},
28        iers::{cirf_to_tirf, icrf_to_cirf, tirf_to_itrf},
29    },
30};
31
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
33pub enum DynFrame {
34    #[default]
35    Icrf,
36    Cirf,
37    Tirf,
38    Itrf,
39    Iau(DynOrigin),
40}
41
42impl ReferenceFrame for DynFrame {
43    fn name(&self) -> String {
44        match self {
45            DynFrame::Icrf => Icrf.name(),
46            DynFrame::Cirf => Cirf.name(),
47            DynFrame::Tirf => Tirf.name(),
48            DynFrame::Itrf => Itrf.name(),
49            DynFrame::Iau(dyn_origin) => {
50                let body = dyn_origin.name();
51                match body {
52                    "Sun" | "Moon" => format!("IAU Body-Fixed Reference Frame for the {body}"),
53                    _ => format!("IAU Body-Fixed Reference Frame for {body}"),
54                }
55            }
56        }
57    }
58
59    fn abbreviation(&self) -> String {
60        match self {
61            DynFrame::Icrf => Icrf.abbreviation(),
62            DynFrame::Cirf => Cirf.abbreviation(),
63            DynFrame::Tirf => Tirf.abbreviation(),
64            DynFrame::Itrf => Itrf.abbreviation(),
65            DynFrame::Iau(dyn_origin) => {
66                let body = dyn_origin.name().replace([' ', '-'], "_").to_uppercase();
67                format!("IAU_{body}")
68            }
69        }
70    }
71
72    fn is_rotating(&self) -> bool {
73        match self {
74            DynFrame::Icrf | DynFrame::Cirf => false,
75            DynFrame::Tirf | DynFrame::Itrf | DynFrame::Iau(_) => true,
76        }
77    }
78}
79
80impl TryQuasiInertial for DynFrame {
81    fn try_quasi_inertial(&self) -> Result<(), NonQuasiInertialFrameError> {
82        match self {
83            DynFrame::Icrf => Ok(()),
84            _ => Err(NonQuasiInertialFrameError(self.abbreviation())),
85        }
86    }
87}
88
89impl TryBodyFixed for DynFrame {
90    fn try_body_fixed(&self) -> Result<(), NonBodyFixedFrameError> {
91        match self {
92            DynFrame::Iau(_) | DynFrame::Itrf => Ok(()),
93            _ => Err(NonBodyFixedFrameError(self.abbreviation())),
94        }
95    }
96}
97
98fn parse_iau_frame(s: &str) -> Option<DynFrame> {
99    let (prefix, origin) = s.split_once("_")?;
100    if prefix.to_lowercase() != "iau" {
101        return None;
102    }
103    let origin: DynOrigin = origin.to_lowercase().parse().ok()?;
104    let _ = origin.try_rotational_elements(0.0).ok()?;
105    Some(DynFrame::Iau(origin))
106}
107
108#[derive(Clone, Debug, Error, PartialEq, Eq)]
109#[error("no frame with name '{0}' is known")]
110pub struct UnknownFrameError(String);
111
112impl FromStr for DynFrame {
113    type Err = UnknownFrameError;
114
115    fn from_str(s: &str) -> Result<Self, Self::Err> {
116        match s {
117            "icrf" | "ICRF" => Ok(DynFrame::Icrf),
118            "cirf" | "CIRF" => Ok(DynFrame::Cirf),
119            "tirf" | "TIRF" => Ok(DynFrame::Tirf),
120            "itrf" | "ITRF" => Ok(DynFrame::Itrf),
121            _ => {
122                if let Some(frame) = parse_iau_frame(s) {
123                    Ok(frame)
124                } else {
125                    Err(UnknownFrameError(s.to_owned()))
126                }
127            }
128        }
129    }
130}
131
132impl<T, P> TryRotateTo<T, DynFrame, P> for DynFrame
133where
134    T: TimeScale + TryToScale<Tdb, P> + Copy,
135{
136    // FIXME
137    type Error = IauFrameTransformationError;
138
139    fn try_rotation(
140        &self,
141        frame: DynFrame,
142        time: Time<T>,
143        provider: Option<&P>,
144    ) -> Result<Rotation, Self::Error> {
145        // FIXME
146        let seconds_j2000 = time.seconds_since_j2000();
147        let centuries_j2000 = time.centuries_since_j2000();
148        match self {
149            DynFrame::Icrf => match frame {
150                DynFrame::Icrf => Ok(Rotation::IDENTITY),
151                DynFrame::Cirf => Ok(icrf_to_cirf(centuries_j2000)),
152                DynFrame::Tirf => {
153                    Ok(icrf_to_cirf(centuries_j2000).compose(&cirf_to_tirf(seconds_j2000)))
154                }
155                DynFrame::Itrf => Ok(icrf_to_cirf(centuries_j2000)
156                    .compose(&cirf_to_tirf(seconds_j2000))
157                    .compose(&tirf_to_itrf(centuries_j2000))),
158                DynFrame::Iau(target) => icrf_to_iau(time, target, provider),
159            },
160            DynFrame::Cirf => match frame {
161                DynFrame::Icrf => Ok(icrf_to_cirf(centuries_j2000).transpose()),
162                DynFrame::Cirf => Ok(Rotation::IDENTITY),
163                DynFrame::Tirf => Ok(cirf_to_tirf(seconds_j2000)),
164                DynFrame::Itrf => {
165                    Ok(cirf_to_tirf(seconds_j2000).compose(&tirf_to_itrf(centuries_j2000)))
166                }
167                DynFrame::Iau(_) => Ok(self
168                    .try_rotation(DynFrame::Icrf, time, provider)?
169                    .compose(&DynFrame::Icrf.try_rotation(frame, time, provider)?)),
170            },
171            DynFrame::Tirf => match frame {
172                DynFrame::Icrf => Ok(cirf_to_tirf(seconds_j2000)
173                    .transpose()
174                    .compose(&icrf_to_cirf(centuries_j2000).transpose())),
175                DynFrame::Cirf => Ok(cirf_to_tirf(seconds_j2000).transpose()),
176                DynFrame::Tirf => Ok(Rotation::IDENTITY),
177                DynFrame::Itrf => Ok(tirf_to_itrf(centuries_j2000)),
178                DynFrame::Iau(_) => Ok(self
179                    .try_rotation(DynFrame::Icrf, time, provider)?
180                    .compose(&DynFrame::Icrf.try_rotation(frame, time, provider)?)),
181            },
182            DynFrame::Itrf => match frame {
183                DynFrame::Icrf => Ok(tirf_to_itrf(centuries_j2000)
184                    .transpose()
185                    .compose(&cirf_to_tirf(seconds_j2000).transpose())
186                    .compose(&icrf_to_cirf(centuries_j2000).transpose())),
187                DynFrame::Cirf => Ok(tirf_to_itrf(centuries_j2000)
188                    .transpose()
189                    .compose(&cirf_to_tirf(seconds_j2000).transpose())),
190                DynFrame::Tirf => Ok(tirf_to_itrf(centuries_j2000).transpose()),
191                DynFrame::Itrf => Ok(Rotation::IDENTITY),
192                DynFrame::Iau(_) => Ok(self
193                    .try_rotation(DynFrame::Icrf, time, provider)?
194                    .compose(&DynFrame::Icrf.try_rotation(frame, time, provider)?)),
195            },
196            DynFrame::Iau(origin) => match frame {
197                DynFrame::Icrf => Ok(icrf_to_iau(time, *origin, provider)?.transpose()),
198                DynFrame::Cirf => Ok(self
199                    .try_rotation(DynFrame::Icrf, time, provider)?
200                    .compose(&DynFrame::Icrf.try_rotation(frame, time, provider)?)),
201                DynFrame::Tirf => Ok(self
202                    .try_rotation(DynFrame::Icrf, time, provider)?
203                    .compose(&DynFrame::Icrf.try_rotation(frame, time, provider)?)),
204                DynFrame::Itrf => Ok(self
205                    .try_rotation(DynFrame::Icrf, time, provider)?
206                    .compose(&DynFrame::Icrf.try_rotation(frame, time, provider)?)),
207                DynFrame::Iau(target) => {
208                    if *origin == target {
209                        Ok(Rotation::IDENTITY)
210                    } else {
211                        Ok(self
212                            .try_rotation(DynFrame::Icrf, time, provider)?
213                            .compose(&DynFrame::Icrf.try_rotation(frame, time, provider)?))
214                    }
215                }
216            },
217        }
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    use glam::DVec3;
226    use lox_bodies::DynOrigin;
227    use lox_math::assert_close;
228    use lox_math::is_close::IsClose;
229    use lox_time::utc::Utc;
230    use rstest::rstest;
231
232    #[rstest]
233    #[case("IAU_EARTH", Some(DynFrame::Iau(DynOrigin::Earth)))]
234    #[case("FOO_EARTH", None)]
235    #[case("IAU_RUPERT", None)]
236    #[case("IAU_SYCORAX", None)]
237    fn test_parse_iau_frame(#[case] name: &str, #[case] exp: Option<DynFrame>) {
238        let act = parse_iau_frame(name);
239        assert_eq!(act, exp)
240    }
241
242    #[rstest]
243    #[case(
244        DynFrame::Iau(DynOrigin::Earth),
245        DVec3::new(
246            -5.740_259_426_667_957e3,
247            3.121_136_072_795_472_5e3,
248            -1.863_182_656_331_802_7e3,
249        ),
250        DVec3::new(
251            -3.532_378_757_836_52,
252            -3.152_377_656_863_808,
253            5.642_296_713_889_555,
254        ),
255    )]
256    #[case(
257        DynFrame::Iau(DynOrigin::Moon),
258        DVec3::new(
259            3.777_805_761_337_502e3,
260            -5.633_812_666_439_680_5e3,
261            -3.896_880_165_980_424e2,
262        ),
263        DVec3::new(
264            2.576_901_711_027_508_3,
265            1.250_106_874_006_032_4,
266            7.100_615_382_464_156,
267        ),
268    )]
269    fn test_icrf_to_bodyfixed(#[case] frame: DynFrame, #[case] r_exp: DVec3, #[case] v_exp: DVec3) {
270        let time = Utc::from_iso("2024-07-05T09:09:18.173").unwrap().to_time();
271        let r = DVec3::new(-5530.01774359, -3487.0895338, -1850.03476185);
272        let v = DVec3::new(1.29534407, -5.02456882, 5.6391936);
273        let rot = DynFrame::Icrf.try_rotation(frame, time, None::<&()>);
274        let (r_act, v_act) = rot.unwrap().rotate_state(r, v);
275        assert_close!(r_act, r_exp, 1e-8);
276        assert_close!(v_act, v_exp, 1e-5);
277    }
278}