1use std::str::FromStr;
6
7use lox_bodies::{DynOrigin, Origin, TryRotationalElements};
8use thiserror::Error;
9
10use crate::{
11 frames::{Cirf, Iau, Icrf, Itrf, J2000, Mod, Pef, Teme, Tirf, Tod},
12 iers::{Iau2000Model, IersSystem, ReferenceSystem},
13 traits::{
14 NonBodyFixedFrameError, NonQuasiInertialFrameError, ReferenceFrame, TryBodyFixed,
15 TryQuasiInertial, frame_id,
16 },
17};
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub enum DynFrame {
23 #[default]
25 Icrf,
26 J2000,
28 Cirf,
30 Tirf,
32 Itrf,
34 Iau(DynOrigin),
36 Mod(ReferenceSystem),
38 Tod(ReferenceSystem),
40 Pef(ReferenceSystem),
42 Teme,
44}
45
46impl ReferenceFrame for DynFrame {
47 fn name(&self) -> String {
48 match self {
49 DynFrame::Icrf => Icrf.name(),
50 DynFrame::J2000 => J2000.name(),
51 DynFrame::Cirf => Cirf.name(),
52 DynFrame::Tirf => Tirf.name(),
53 DynFrame::Itrf => Itrf.name(),
54 DynFrame::Iau(dyn_origin) => {
55 let body = dyn_origin.name();
56 match body {
57 "Sun" | "Moon" => format!("IAU Body-Fixed Reference Frame for the {body}"),
58 _ => format!("IAU Body-Fixed Reference Frame for {body}"),
59 }
60 }
61 DynFrame::Mod(sys) => Mod(*sys).name(),
62 DynFrame::Tod(sys) => Tod(*sys).name(),
63 DynFrame::Pef(sys) => Pef(*sys).name(),
64 DynFrame::Teme => Teme.name(),
65 }
66 }
67
68 fn abbreviation(&self) -> String {
69 match self {
70 DynFrame::Icrf => Icrf.abbreviation(),
71 DynFrame::J2000 => J2000.abbreviation(),
72 DynFrame::Cirf => Cirf.abbreviation(),
73 DynFrame::Tirf => Tirf.abbreviation(),
74 DynFrame::Itrf => Itrf.abbreviation(),
75 DynFrame::Iau(dyn_origin) => {
76 let body = dyn_origin.name().replace([' ', '-'], "_").to_uppercase();
77 format!("IAU_{body}")
78 }
79 DynFrame::Mod(sys) => Mod(*sys).abbreviation(),
80 DynFrame::Tod(sys) => Tod(*sys).abbreviation(),
81 DynFrame::Pef(sys) => Pef(*sys).abbreviation(),
82 DynFrame::Teme => Teme.abbreviation(),
83 }
84 }
85
86 fn frame_id(&self, _: crate::traits::private::Internal) -> Option<usize> {
87 match self {
88 DynFrame::Icrf => frame_id(&Icrf),
89 DynFrame::J2000 => frame_id(&J2000),
90 DynFrame::Cirf => frame_id(&Cirf),
91 DynFrame::Tirf => frame_id(&Tirf),
92 DynFrame::Itrf => frame_id(&Itrf),
93 DynFrame::Iau(dyn_origin) => Some(1000 + dyn_origin.id().0 as usize),
94 DynFrame::Mod(sys) => frame_id(&Mod(*sys)),
95 DynFrame::Tod(sys) => frame_id(&Tod(*sys)),
96
97 DynFrame::Pef(sys) => frame_id(&Pef(*sys)),
98 DynFrame::Teme => frame_id(&Teme),
99 }
100 }
101}
102
103impl TryQuasiInertial for DynFrame {
104 fn try_quasi_inertial(&self) -> Result<(), NonQuasiInertialFrameError> {
105 match self {
106 DynFrame::Icrf
107 | DynFrame::J2000
108 | DynFrame::Cirf
109 | DynFrame::Mod(_)
110 | DynFrame::Tod(_) => Ok(()),
111 _ => Err(NonQuasiInertialFrameError(self.abbreviation())),
112 }
113 }
114}
115
116impl TryBodyFixed for DynFrame {
117 fn try_body_fixed(&self) -> Result<(), NonBodyFixedFrameError> {
118 match self {
119 DynFrame::Iau(_) | DynFrame::Itrf | DynFrame::Tirf | DynFrame::Pef(_) => Ok(()),
120 _ => Err(NonBodyFixedFrameError(self.abbreviation())),
121 }
122 }
123}
124
125impl From<Icrf> for DynFrame {
128 fn from(_: Icrf) -> Self {
129 DynFrame::Icrf
130 }
131}
132
133impl From<J2000> for DynFrame {
134 fn from(_: J2000) -> Self {
135 DynFrame::J2000
136 }
137}
138
139impl From<Cirf> for DynFrame {
140 fn from(_: Cirf) -> Self {
141 DynFrame::Cirf
142 }
143}
144
145impl From<Tirf> for DynFrame {
146 fn from(_: Tirf) -> Self {
147 DynFrame::Tirf
148 }
149}
150
151impl From<Itrf> for DynFrame {
152 fn from(_: Itrf) -> Self {
153 DynFrame::Itrf
154 }
155}
156
157impl From<Teme> for DynFrame {
158 fn from(_: Teme) -> Self {
159 DynFrame::Teme
160 }
161}
162
163impl<T: IersSystem + Into<ReferenceSystem>> From<Mod<T>> for DynFrame {
166 fn from(frame: Mod<T>) -> Self {
167 DynFrame::Mod(frame.0.into())
168 }
169}
170
171impl<T: IersSystem + Into<ReferenceSystem>> From<Tod<T>> for DynFrame {
172 fn from(frame: Tod<T>) -> Self {
173 DynFrame::Tod(frame.0.into())
174 }
175}
176
177impl<T: IersSystem + Into<ReferenceSystem>> From<Pef<T>> for DynFrame {
178 fn from(frame: Pef<T>) -> Self {
179 DynFrame::Pef(frame.0.into())
180 }
181}
182
183impl<T: TryRotationalElements + Copy + Into<DynOrigin>> From<Iau<T>> for DynFrame {
186 fn from(frame: Iau<T>) -> Self {
187 DynFrame::Iau(frame.body().into())
188 }
189}
190
191fn parse_iau_frame(s: &str) -> Option<DynFrame> {
192 let (prefix, origin) = s.split_once("_")?;
193 if prefix.to_lowercase() != "iau" {
194 return None;
195 }
196 let origin: DynOrigin = origin.to_lowercase().parse().ok()?;
197 let _ = origin.try_rotational_elements(0.0).ok()?;
198 Some(DynFrame::Iau(origin))
199}
200
201fn parse_reference_system(s: &str) -> Option<ReferenceSystem> {
202 match s.to_uppercase().as_str() {
203 "IERS1996" => Some(ReferenceSystem::Iers1996),
204 "IERS2003" => Some(ReferenceSystem::Iers2003(Iau2000Model::A)),
205 "IERS2010" => Some(ReferenceSystem::Iers2010),
206 _ => None,
207 }
208}
209
210fn parse_equinox_frame(s: &str) -> Option<DynFrame> {
212 let s_stripped = s.strip_suffix(')')?;
213 let (frame, system) = s_stripped.split_once('(')?;
214 let sys = parse_reference_system(system)?;
215 match frame.to_uppercase().as_str() {
216 "MOD" => Some(DynFrame::Mod(sys)),
217 "TOD" => Some(DynFrame::Tod(sys)),
218 "PEF" => Some(DynFrame::Pef(sys)),
219 _ => None,
220 }
221}
222
223#[derive(Clone, Debug, Error, PartialEq, Eq)]
225#[error("no frame with name '{0}' is known")]
226pub struct UnknownFrameError(String);
227
228impl FromStr for DynFrame {
229 type Err = UnknownFrameError;
230
231 fn from_str(s: &str) -> Result<Self, Self::Err> {
232 match s.to_uppercase().as_str() {
233 "ICRF" => Ok(DynFrame::Icrf),
234 "J2000" | "EME2000" => Ok(DynFrame::J2000),
235 "CIRF" => Ok(DynFrame::Cirf),
236 "TIRF" => Ok(DynFrame::Tirf),
237 "ITRF" => Ok(DynFrame::Itrf),
238 "TEME" => Ok(DynFrame::Teme),
239 "MOD" => Ok(DynFrame::Mod(ReferenceSystem::Iers1996)),
240 "TOD" => Ok(DynFrame::Tod(ReferenceSystem::Iers1996)),
241 "PEF" => Ok(DynFrame::Pef(ReferenceSystem::Iers1996)),
242 _ => {
243 if let Some(frame) = parse_equinox_frame(s) {
244 Ok(frame)
245 } else if let Some(frame) = parse_iau_frame(s) {
246 Ok(frame)
247 } else {
248 Err(UnknownFrameError(s.to_owned()))
249 }
250 }
251 }
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 use crate::rotations::TryRotation;
260 use crate::{Iau, providers::DefaultRotationProvider};
261
262 use glam::DVec3;
263 use lox_bodies::{DynOrigin, Earth};
264 use lox_test_utils::assert_approx_eq;
265 use lox_time::utc::Utc;
266 use rstest::rstest;
267
268 #[rstest]
269 #[case::valid("IAU_EARTH", Some(DynFrame::Iau(DynOrigin::Earth)))]
270 #[case::invalid_prefix("FOO_EARTH", None)]
271 #[case::unkown_body("IAU_RUPERT", None)]
272 #[case::undefined_rotation("IAU_SYCORAX", None)]
273 fn test_parse_iau_frame(#[case] name: &str, #[case] exp: Option<DynFrame>) {
274 let act = parse_iau_frame(name);
275 assert_eq!(act, exp)
276 }
277
278 #[rstest]
279 #[case(
280 DynFrame::Iau(DynOrigin::Earth),
281 DVec3::new(
282 -5.740_259_426_667_957e3,
283 3.121_136_072_795_472_5e3,
284 -1.863_182_656_331_802_7e3,
285 ),
286 DVec3::new(
287 -3.532_378_757_836_52,
288 -3.152_377_656_863_808,
289 5.642_296_713_889_555,
290 ),
291 )]
292 #[case(
293 DynFrame::Iau(DynOrigin::Moon),
294 DVec3::new(
295 3.777_805_761_337_502e3,
296 -5.633_812_666_439_680_5e3,
297 -3.896_880_165_980_424e2,
298 ),
299 DVec3::new(
300 2.576_901_711_027_508_3,
301 1.250_106_874_006_032_4,
302 7.100_615_382_464_156,
303 ),
304 )]
305 fn test_icrf_to_bodyfixed(#[case] frame: DynFrame, #[case] r_exp: DVec3, #[case] v_exp: DVec3) {
306 let time = Utc::from_iso("2024-07-05T09:09:18.173")
307 .unwrap()
308 .to_dyn_time();
309 let r = DVec3::new(-5530.01774359, -3487.0895338, -1850.03476185);
310 let v = DVec3::new(1.29534407, -5.02456882, 5.6391936);
311 let rot = DefaultRotationProvider
312 .try_rotation(DynFrame::Icrf, frame, time)
313 .unwrap();
314 let (r_act, v_act) = rot.rotate_state(r, v);
315 assert_approx_eq!(r_act, r_exp, rtol <= 1e-8);
316 assert_approx_eq!(v_act, v_exp, rtol <= 1e-5);
317 }
318
319 #[rstest]
320 #[case("MOD", DynFrame::Mod(ReferenceSystem::Iers1996))]
321 #[case("mod", DynFrame::Mod(ReferenceSystem::Iers1996))]
322 #[case("TOD", DynFrame::Tod(ReferenceSystem::Iers1996))]
323 #[case("tod", DynFrame::Tod(ReferenceSystem::Iers1996))]
324 #[case("PEF", DynFrame::Pef(ReferenceSystem::Iers1996))]
325 #[case("pef", DynFrame::Pef(ReferenceSystem::Iers1996))]
326 #[case("MOD(IERS1996)", DynFrame::Mod(ReferenceSystem::Iers1996))]
327 #[case(
328 "MOD(IERS2003)",
329 DynFrame::Mod(ReferenceSystem::Iers2003(Iau2000Model::A))
330 )]
331 #[case(
332 "mod(iers2003)",
333 DynFrame::Mod(ReferenceSystem::Iers2003(Iau2000Model::A))
334 )]
335 #[case(
336 "TOD(IERS2003)",
337 DynFrame::Tod(ReferenceSystem::Iers2003(Iau2000Model::A))
338 )]
339 #[case(
340 "PEF(IERS2003)",
341 DynFrame::Pef(ReferenceSystem::Iers2003(Iau2000Model::A))
342 )]
343 #[case("MOD(IERS2010)", DynFrame::Mod(ReferenceSystem::Iers2010))]
344 #[case("TOD(IERS2010)", DynFrame::Tod(ReferenceSystem::Iers2010))]
345 #[case("PEF(IERS2010)", DynFrame::Pef(ReferenceSystem::Iers2010))]
346 fn test_parse_equinox_frames(#[case] name: &str, #[case] exp: DynFrame) {
347 let act: DynFrame = name.parse().unwrap();
348 assert_eq!(act, exp);
349 }
350
351 #[test]
352 fn test_frame_id() {
353 assert_eq!(frame_id(&Icrf), frame_id(&DynFrame::Icrf));
354 assert_eq!(frame_id(&J2000), frame_id(&DynFrame::J2000));
355 assert_eq!(frame_id(&Cirf), frame_id(&DynFrame::Cirf));
356 assert_eq!(frame_id(&Tirf), frame_id(&DynFrame::Tirf));
357 assert_eq!(frame_id(&Itrf), frame_id(&DynFrame::Itrf));
358 assert_eq!(
359 frame_id(&Iau::new(Earth)),
360 frame_id(&DynFrame::Iau(DynOrigin::Earth))
361 );
362 }
363
364 #[rstest]
365 #[case("J2000", DynFrame::J2000)]
366 #[case("j2000", DynFrame::J2000)]
367 #[case("EME2000", DynFrame::J2000)]
368 fn test_parse_j2000(#[case] name: &str, #[case] exp: DynFrame) {
369 let act: DynFrame = name.parse().unwrap();
370 assert_eq!(act, exp);
371 }
372
373 #[test]
374 fn test_j2000_quasi_inertial() {
375 assert!(DynFrame::J2000.try_quasi_inertial().is_ok());
376 }
377
378 #[test]
379 fn test_from_simple_frames() {
380 assert_eq!(DynFrame::from(Icrf), DynFrame::Icrf);
381 assert_eq!(DynFrame::from(J2000), DynFrame::J2000);
382 assert_eq!(DynFrame::from(Cirf), DynFrame::Cirf);
383 assert_eq!(DynFrame::from(Tirf), DynFrame::Tirf);
384 assert_eq!(DynFrame::from(Itrf), DynFrame::Itrf);
385 assert_eq!(DynFrame::from(Teme), DynFrame::Teme);
386 }
387
388 #[test]
389 fn test_from_parameterized_frames() {
390 use crate::iers::{Iers1996, Iers2003, Iers2010};
391
392 assert_eq!(
393 DynFrame::from(Mod(Iers1996)),
394 DynFrame::Mod(ReferenceSystem::Iers1996)
395 );
396 assert_eq!(
397 DynFrame::from(Tod(Iers2003::default())),
398 DynFrame::Tod(ReferenceSystem::Iers2003(Iau2000Model::A))
399 );
400 assert_eq!(
401 DynFrame::from(Pef(Iers2010)),
402 DynFrame::Pef(ReferenceSystem::Iers2010)
403 );
404 }
405
406 #[test]
407 fn test_from_iau_frame() {
408 assert_eq!(
409 DynFrame::from(Iau::new(Earth)),
410 DynFrame::Iau(DynOrigin::Earth)
411 );
412 }
413
414 #[rstest]
415 #[case(DynFrame::Icrf)]
416 #[case(DynFrame::J2000)]
417 #[case(DynFrame::Cirf)]
418 #[case(DynFrame::Tirf)]
419 #[case(DynFrame::Itrf)]
420 #[case(DynFrame::Teme)]
421 #[case(DynFrame::Mod(ReferenceSystem::Iers1996))]
422 #[case(DynFrame::Mod(ReferenceSystem::Iers2003(Iau2000Model::A)))]
423 #[case(DynFrame::Mod(ReferenceSystem::Iers2010))]
424 #[case(DynFrame::Tod(ReferenceSystem::Iers1996))]
425 #[case(DynFrame::Tod(ReferenceSystem::Iers2003(Iau2000Model::A)))]
426 #[case(DynFrame::Tod(ReferenceSystem::Iers2010))]
427 #[case(DynFrame::Pef(ReferenceSystem::Iers1996))]
428 #[case(DynFrame::Pef(ReferenceSystem::Iers2003(Iau2000Model::A)))]
429 #[case(DynFrame::Pef(ReferenceSystem::Iers2010))]
430 #[case(DynFrame::Iau(DynOrigin::Earth))]
431 fn test_abbreviation_round_trip(#[case] frame: DynFrame) {
432 let abbr = frame.abbreviation();
433 let parsed: DynFrame = abbr
434 .parse()
435 .unwrap_or_else(|e| panic!("failed to parse abbreviation '{}': {}", abbr, e));
436 assert_eq!(parsed, frame);
437 }
438}