1use std::str::FromStr;
6
7use lox_bodies::{DynOrigin, Origin, TryRotationalElements};
8use thiserror::Error;
9
10use crate::{
11 frames::{Cirf, Icrf, Itrf, J2000, Mod, Pef, Teme, Tirf, Tod},
12 iers::{Iau2000Model, ReferenceSystem},
13 traits::{
14 NonBodyFixedFrameError, NonQuasiInertialFrameError, ReferenceFrame, TryBodyFixed,
15 TryQuasiInertial, frame_id,
16 },
17};
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub enum DynFrame {
22 #[default]
23 Icrf,
24 J2000,
25 Cirf,
26 Tirf,
27 Itrf,
28 Iau(DynOrigin),
29 Mod(ReferenceSystem),
30 Tod(ReferenceSystem),
31 Pef(ReferenceSystem),
32 Teme,
33}
34
35impl ReferenceFrame for DynFrame {
36 fn name(&self) -> String {
37 match self {
38 DynFrame::Icrf => Icrf.name(),
39 DynFrame::J2000 => J2000.name(),
40 DynFrame::Cirf => Cirf.name(),
41 DynFrame::Tirf => Tirf.name(),
42 DynFrame::Itrf => Itrf.name(),
43 DynFrame::Iau(dyn_origin) => {
44 let body = dyn_origin.name();
45 match body {
46 "Sun" | "Moon" => format!("IAU Body-Fixed Reference Frame for the {body}"),
47 _ => format!("IAU Body-Fixed Reference Frame for {body}"),
48 }
49 }
50 DynFrame::Mod(sys) => Mod(*sys).name(),
51 DynFrame::Tod(sys) => Tod(*sys).name(),
52 DynFrame::Pef(sys) => Pef(*sys).name(),
53 DynFrame::Teme => Teme.name(),
54 }
55 }
56
57 fn abbreviation(&self) -> String {
58 match self {
59 DynFrame::Icrf => Icrf.abbreviation(),
60 DynFrame::J2000 => J2000.abbreviation(),
61 DynFrame::Cirf => Cirf.abbreviation(),
62 DynFrame::Tirf => Tirf.abbreviation(),
63 DynFrame::Itrf => Itrf.abbreviation(),
64 DynFrame::Iau(dyn_origin) => {
65 let body = dyn_origin.name().replace([' ', '-'], "_").to_uppercase();
66 format!("IAU_{body}")
67 }
68 DynFrame::Mod(sys) => Mod(*sys).abbreviation(),
69 DynFrame::Tod(sys) => Tod(*sys).abbreviation(),
70 DynFrame::Pef(sys) => Pef(*sys).abbreviation(),
71 DynFrame::Teme => Teme.abbreviation(),
72 }
73 }
74
75 fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
76 match self {
77 DynFrame::Icrf => frame_id(&Icrf),
78 DynFrame::J2000 => frame_id(&J2000),
79 DynFrame::Cirf => frame_id(&Cirf),
80 DynFrame::Tirf => frame_id(&Tirf),
81 DynFrame::Itrf => frame_id(&Itrf),
82 DynFrame::Iau(dyn_origin) => Some(1000 + dyn_origin.id().0 as usize),
83 DynFrame::Mod(sys) => frame_id(&Mod(*sys)),
84 DynFrame::Tod(sys) => frame_id(&Tod(*sys)),
85
86 DynFrame::Pef(sys) => frame_id(&Pef(*sys)),
87 DynFrame::Teme => frame_id(&Teme),
88 }
89 }
90}
91
92impl TryQuasiInertial for DynFrame {
93 fn try_quasi_inertial(&self) -> Result<(), NonQuasiInertialFrameError> {
94 match self {
95 DynFrame::Icrf
96 | DynFrame::J2000
97 | DynFrame::Cirf
98 | DynFrame::Mod(_)
99 | DynFrame::Tod(_) => Ok(()),
100 _ => Err(NonQuasiInertialFrameError(self.abbreviation())),
101 }
102 }
103}
104
105impl TryBodyFixed for DynFrame {
106 fn try_body_fixed(&self) -> Result<(), NonBodyFixedFrameError> {
107 match self {
108 DynFrame::Iau(_) | DynFrame::Itrf | DynFrame::Tirf | DynFrame::Pef(_) => Ok(()),
109 _ => Err(NonBodyFixedFrameError(self.abbreviation())),
110 }
111 }
112}
113
114fn parse_iau_frame(s: &str) -> Option<DynFrame> {
115 let (prefix, origin) = s.split_once("_")?;
116 if prefix.to_lowercase() != "iau" {
117 return None;
118 }
119 let origin: DynOrigin = origin.to_lowercase().parse().ok()?;
120 let _ = origin.try_rotational_elements(0.0).ok()?;
121 Some(DynFrame::Iau(origin))
122}
123
124fn parse_reference_system(s: &str) -> Option<ReferenceSystem> {
125 match s.to_uppercase().as_str() {
126 "IERS1996" => Some(ReferenceSystem::Iers1996),
127 "IERS2003" => Some(ReferenceSystem::Iers2003(Iau2000Model::A)),
128 "IERS2010" => Some(ReferenceSystem::Iers2010),
129 _ => None,
130 }
131}
132
133fn parse_equinox_frame(s: &str) -> Option<DynFrame> {
135 let s_stripped = s.strip_suffix(')')?;
136 let (frame, system) = s_stripped.split_once('(')?;
137 let sys = parse_reference_system(system)?;
138 match frame.to_uppercase().as_str() {
139 "MOD" => Some(DynFrame::Mod(sys)),
140 "TOD" => Some(DynFrame::Tod(sys)),
141 "PEF" => Some(DynFrame::Pef(sys)),
142 _ => None,
143 }
144}
145
146#[derive(Clone, Debug, Error, PartialEq, Eq)]
147#[error("no frame with name '{0}' is known")]
148pub struct UnknownFrameError(String);
149
150impl FromStr for DynFrame {
151 type Err = UnknownFrameError;
152
153 fn from_str(s: &str) -> Result<Self, Self::Err> {
154 match s.to_uppercase().as_str() {
155 "ICRF" => Ok(DynFrame::Icrf),
156 "J2000" | "EME2000" => Ok(DynFrame::J2000),
157 "CIRF" => Ok(DynFrame::Cirf),
158 "TIRF" => Ok(DynFrame::Tirf),
159 "ITRF" => Ok(DynFrame::Itrf),
160 "TEME" => Ok(DynFrame::Teme),
161 "MOD" => Ok(DynFrame::Mod(ReferenceSystem::Iers1996)),
162 "TOD" => Ok(DynFrame::Tod(ReferenceSystem::Iers1996)),
163 "PEF" => Ok(DynFrame::Pef(ReferenceSystem::Iers1996)),
164 _ => {
165 if let Some(frame) = parse_equinox_frame(s) {
166 Ok(frame)
167 } else if let Some(frame) = parse_iau_frame(s) {
168 Ok(frame)
169 } else {
170 Err(UnknownFrameError(s.to_owned()))
171 }
172 }
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 use crate::rotations::TryRotation;
182 use crate::{Iau, providers::DefaultRotationProvider};
183
184 use glam::DVec3;
185 use lox_bodies::{DynOrigin, Earth};
186 use lox_test_utils::assert_approx_eq;
187 use lox_time::utc::Utc;
188 use rstest::rstest;
189
190 #[rstest]
191 #[case::valid("IAU_EARTH", Some(DynFrame::Iau(DynOrigin::Earth)))]
192 #[case::invalid_prefix("FOO_EARTH", None)]
193 #[case::unkown_body("IAU_RUPERT", None)]
194 #[case::undefined_rotation("IAU_SYCORAX", None)]
195 fn test_parse_iau_frame(#[case] name: &str, #[case] exp: Option<DynFrame>) {
196 let act = parse_iau_frame(name);
197 assert_eq!(act, exp)
198 }
199
200 #[rstest]
201 #[case(
202 DynFrame::Iau(DynOrigin::Earth),
203 DVec3::new(
204 -5.740_259_426_667_957e3,
205 3.121_136_072_795_472_5e3,
206 -1.863_182_656_331_802_7e3,
207 ),
208 DVec3::new(
209 -3.532_378_757_836_52,
210 -3.152_377_656_863_808,
211 5.642_296_713_889_555,
212 ),
213 )]
214 #[case(
215 DynFrame::Iau(DynOrigin::Moon),
216 DVec3::new(
217 3.777_805_761_337_502e3,
218 -5.633_812_666_439_680_5e3,
219 -3.896_880_165_980_424e2,
220 ),
221 DVec3::new(
222 2.576_901_711_027_508_3,
223 1.250_106_874_006_032_4,
224 7.100_615_382_464_156,
225 ),
226 )]
227 fn test_icrf_to_bodyfixed(#[case] frame: DynFrame, #[case] r_exp: DVec3, #[case] v_exp: DVec3) {
228 let time = Utc::from_iso("2024-07-05T09:09:18.173")
229 .unwrap()
230 .to_dyn_time();
231 let r = DVec3::new(-5530.01774359, -3487.0895338, -1850.03476185);
232 let v = DVec3::new(1.29534407, -5.02456882, 5.6391936);
233 let rot = DefaultRotationProvider
234 .try_rotation(DynFrame::Icrf, frame, time)
235 .unwrap();
236 let (r_act, v_act) = rot.rotate_state(r, v);
237 assert_approx_eq!(r_act, r_exp, rtol <= 1e-8);
238 assert_approx_eq!(v_act, v_exp, rtol <= 1e-5);
239 }
240
241 #[rstest]
242 #[case("MOD", DynFrame::Mod(ReferenceSystem::Iers1996))]
243 #[case("mod", DynFrame::Mod(ReferenceSystem::Iers1996))]
244 #[case("TOD", DynFrame::Tod(ReferenceSystem::Iers1996))]
245 #[case("tod", DynFrame::Tod(ReferenceSystem::Iers1996))]
246 #[case("PEF", DynFrame::Pef(ReferenceSystem::Iers1996))]
247 #[case("pef", DynFrame::Pef(ReferenceSystem::Iers1996))]
248 #[case("MOD(IERS1996)", DynFrame::Mod(ReferenceSystem::Iers1996))]
249 #[case(
250 "MOD(IERS2003)",
251 DynFrame::Mod(ReferenceSystem::Iers2003(Iau2000Model::A))
252 )]
253 #[case(
254 "mod(iers2003)",
255 DynFrame::Mod(ReferenceSystem::Iers2003(Iau2000Model::A))
256 )]
257 #[case(
258 "TOD(IERS2003)",
259 DynFrame::Tod(ReferenceSystem::Iers2003(Iau2000Model::A))
260 )]
261 #[case(
262 "PEF(IERS2003)",
263 DynFrame::Pef(ReferenceSystem::Iers2003(Iau2000Model::A))
264 )]
265 #[case("MOD(IERS2010)", DynFrame::Mod(ReferenceSystem::Iers2010))]
266 #[case("TOD(IERS2010)", DynFrame::Tod(ReferenceSystem::Iers2010))]
267 #[case("PEF(IERS2010)", DynFrame::Pef(ReferenceSystem::Iers2010))]
268 fn test_parse_equinox_frames(#[case] name: &str, #[case] exp: DynFrame) {
269 let act: DynFrame = name.parse().unwrap();
270 assert_eq!(act, exp);
271 }
272
273 #[test]
274 fn test_frame_id() {
275 assert_eq!(frame_id(&Icrf), frame_id(&DynFrame::Icrf));
276 assert_eq!(frame_id(&J2000), frame_id(&DynFrame::J2000));
277 assert_eq!(frame_id(&Cirf), frame_id(&DynFrame::Cirf));
278 assert_eq!(frame_id(&Tirf), frame_id(&DynFrame::Tirf));
279 assert_eq!(frame_id(&Itrf), frame_id(&DynFrame::Itrf));
280 assert_eq!(
281 frame_id(&Iau::new(Earth)),
282 frame_id(&DynFrame::Iau(DynOrigin::Earth))
283 );
284 }
285
286 #[rstest]
287 #[case("J2000", DynFrame::J2000)]
288 #[case("j2000", DynFrame::J2000)]
289 #[case("EME2000", DynFrame::J2000)]
290 fn test_parse_j2000(#[case] name: &str, #[case] exp: DynFrame) {
291 let act: DynFrame = name.parse().unwrap();
292 assert_eq!(act, exp);
293 }
294
295 #[test]
296 fn test_j2000_quasi_inertial() {
297 assert!(DynFrame::J2000.try_quasi_inertial().is_ok());
298 }
299
300 #[rstest]
301 #[case(DynFrame::Icrf)]
302 #[case(DynFrame::J2000)]
303 #[case(DynFrame::Cirf)]
304 #[case(DynFrame::Tirf)]
305 #[case(DynFrame::Itrf)]
306 #[case(DynFrame::Teme)]
307 #[case(DynFrame::Mod(ReferenceSystem::Iers1996))]
308 #[case(DynFrame::Mod(ReferenceSystem::Iers2003(Iau2000Model::A)))]
309 #[case(DynFrame::Mod(ReferenceSystem::Iers2010))]
310 #[case(DynFrame::Tod(ReferenceSystem::Iers1996))]
311 #[case(DynFrame::Tod(ReferenceSystem::Iers2003(Iau2000Model::A)))]
312 #[case(DynFrame::Tod(ReferenceSystem::Iers2010))]
313 #[case(DynFrame::Pef(ReferenceSystem::Iers1996))]
314 #[case(DynFrame::Pef(ReferenceSystem::Iers2003(Iau2000Model::A)))]
315 #[case(DynFrame::Pef(ReferenceSystem::Iers2010))]
316 #[case(DynFrame::Iau(DynOrigin::Earth))]
317 fn test_abbreviation_round_trip(#[case] frame: DynFrame) {
318 let abbr = frame.abbreviation();
319 let parsed: DynFrame = abbr
320 .parse()
321 .unwrap_or_else(|e| panic!("failed to parse abbreviation '{}': {}", abbr, e));
322 assert_eq!(parsed, frame);
323 }
324}