1use std::str::FromStr;
7
8use lox_bodies::{
9 CoordinateOrigin, Earth, Origin, RotationalElements, Spheroid, TryRotationalElements,
10 TrySpheroid, UndefinedOriginPropertyError,
11};
12use lox_core::coords::Ellipsoid;
13use thiserror::Error;
14
15use crate::{
16 iers::{Iau2000Model, IersSystem, ReferenceSystem},
17 traits::{
18 BodyFixed, FrameKey, NonBodyFixedFrameError, NonQuasiInertialFrameError, QuasiInertial,
19 ReferenceEllipsoid, ReferenceFrame, TryBodyFixed, TryQuasiInertial, TryReferenceEllipsoid,
20 UndefinedReferenceEllipsoidError, frame_key,
21 },
22};
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
28pub struct Icrf;
29
30impl ReferenceFrame for Icrf {
31 fn name(&self) -> String {
32 "International Celestial Reference Frame".to_string()
33 }
34
35 fn abbreviation(&self) -> String {
36 "ICRF".to_string()
37 }
38
39 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
40 Some(FrameKey::Icrf)
41 }
42}
43
44impl QuasiInertial for Icrf {}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
50pub struct J2000;
51
52impl ReferenceFrame for J2000 {
53 fn name(&self) -> String {
54 "J2000 Mean Equator and Equinox".to_string()
55 }
56
57 fn abbreviation(&self) -> String {
58 "J2000".to_string()
59 }
60
61 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
62 Some(FrameKey::J2000)
63 }
64}
65
66impl QuasiInertial for J2000 {}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
72pub struct Cirf;
73
74impl ReferenceFrame for Cirf {
75 fn name(&self) -> String {
76 "Celestial Intermediate Reference Frame".to_string()
77 }
78
79 fn abbreviation(&self) -> String {
80 "CIRF".to_string()
81 }
82
83 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
84 Some(FrameKey::Cirf)
85 }
86}
87
88impl QuasiInertial for Cirf {}
89
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
94pub struct Tirf;
95
96impl ReferenceFrame for Tirf {
97 fn name(&self) -> String {
98 "Terrestrial Intermediate Reference Frame".to_string()
99 }
100
101 fn abbreviation(&self) -> String {
102 "TIRF".to_string()
103 }
104
105 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
106 Some(FrameKey::Tirf)
107 }
108}
109
110impl BodyFixed for Tirf {
111 type Origin = Earth;
112
113 fn origin(&self) -> Self::Origin {
114 Earth
115 }
116}
117
118impl ReferenceEllipsoid for Tirf {
120 fn reference_ellipsoid(&self) -> Ellipsoid {
121 Ellipsoid::GRS80
122 }
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
129pub struct Itrf;
130
131impl ReferenceFrame for Itrf {
132 fn name(&self) -> String {
133 "International Terrestrial Reference Frame".to_string()
134 }
135
136 fn abbreviation(&self) -> String {
137 "ITRF".to_string()
138 }
139
140 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
141 Some(FrameKey::Itrf)
142 }
143}
144
145impl BodyFixed for Itrf {
146 type Origin = Earth;
147
148 fn origin(&self) -> Self::Origin {
149 Earth
150 }
151}
152
153impl ReferenceEllipsoid for Itrf {
154 fn reference_ellipsoid(&self) -> Ellipsoid {
155 Ellipsoid::GRS80
156 }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
162pub struct Mod<T: IersSystem>(pub T);
163
164impl<T> ReferenceFrame for Mod<T>
165where
166 T: IersSystem + Into<ReferenceSystem> + Copy,
167{
168 fn name(&self) -> String {
169 format!("{} Mean of Date Frame", self.0.name())
170 }
171
172 fn abbreviation(&self) -> String {
173 format!("MOD({})", self.0.abbreviation())
174 }
175
176 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
177 Some(FrameKey::Mod(self.0.into()))
178 }
179}
180
181impl<T> QuasiInertial for Mod<T> where T: IersSystem + Into<ReferenceSystem> + Copy {}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186pub struct Tod<T: IersSystem>(pub T);
187
188impl<T> ReferenceFrame for Tod<T>
189where
190 T: IersSystem + Into<ReferenceSystem> + Copy,
191{
192 fn name(&self) -> String {
193 format!("{} True of Date Frame", self.0.name())
194 }
195
196 fn abbreviation(&self) -> String {
197 format!("TOD({})", self.0.abbreviation())
198 }
199
200 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
201 Some(FrameKey::Tod(self.0.into()))
202 }
203}
204
205impl<T> QuasiInertial for Tod<T> where T: IersSystem + Into<ReferenceSystem> + Copy {}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
210pub struct Pef<T: IersSystem>(pub T);
211
212impl<T> ReferenceFrame for Pef<T>
213where
214 T: IersSystem + Into<ReferenceSystem> + Copy,
215{
216 fn name(&self) -> String {
217 format!("{} Pseudo-Earth Fixed Frame", self.0.name())
218 }
219
220 fn abbreviation(&self) -> String {
221 format!("PEF({})", self.0.abbreviation())
222 }
223
224 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
225 Some(FrameKey::Pef(self.0.into()))
226 }
227}
228
229impl<T> BodyFixed for Pef<T>
230where
231 T: IersSystem + Into<ReferenceSystem> + Copy,
232{
233 type Origin = Earth;
234
235 fn origin(&self) -> Self::Origin {
236 Earth
237 }
238}
239
240impl<T> ReferenceEllipsoid for Pef<T>
242where
243 T: IersSystem + Into<ReferenceSystem> + Copy,
244{
245 fn reference_ellipsoid(&self) -> Ellipsoid {
246 Ellipsoid::GRS80
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
253#[cfg_attr(feature = "serde", serde(into = "&'static str", try_from = "String"))]
254pub struct Teme;
255
256impl ReferenceFrame for Teme {
257 fn name(&self) -> String {
258 "True Equator Mean Equinox".to_owned()
259 }
260
261 fn abbreviation(&self) -> String {
262 "TEME".to_owned()
263 }
264
265 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
266 Some(FrameKey::Teme)
267 }
268}
269
270impl QuasiInertial for Teme {}
271
272macro_rules! impl_frame_serde {
275 ($ty:ident, $abbrev:literal) => {
276 #[cfg(feature = "serde")]
277 impl From<$ty> for &'static str {
278 fn from(_: $ty) -> Self {
279 $abbrev
280 }
281 }
282
283 #[cfg(feature = "serde")]
284 impl TryFrom<String> for $ty {
285 type Error = String;
286 fn try_from(s: String) -> Result<Self, Self::Error> {
287 if s == $abbrev {
288 Ok($ty)
289 } else {
290 Err(format!("expected \"{}\", got \"{}\"", $abbrev, s))
291 }
292 }
293 }
294 };
295}
296
297impl_frame_serde!(Icrf, "ICRF");
298impl_frame_serde!(J2000, "J2000");
299impl_frame_serde!(Cirf, "CIRF");
300impl_frame_serde!(Tirf, "TIRF");
301impl_frame_serde!(Itrf, "ITRF");
302impl_frame_serde!(Teme, "TEME");
303
304#[derive(Clone, Copy, Debug, PartialEq, Eq)]
306#[cfg_attr(feature = "serde", derive(serde::Serialize))]
307pub struct Iau<T: TryRotationalElements>(T);
308
309#[cfg(feature = "serde")]
312impl<'de, T> serde::Deserialize<'de> for Iau<T>
313where
314 T: TryRotationalElements + serde::Deserialize<'de>,
315{
316 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
317 where
318 D: serde::Deserializer<'de>,
319 {
320 let body = T::deserialize(deserializer)?;
321 Iau::try_new(body).map_err(serde::de::Error::custom)
322 }
323}
324
325impl<T> Iau<T>
326where
327 T: RotationalElements,
328{
329 pub fn new(body: T) -> Self {
331 Self(body)
332 }
333}
334
335impl<T> Iau<T>
336where
337 T: TryRotationalElements,
338{
339 pub fn try_new(body: T) -> Result<Self, UndefinedOriginPropertyError> {
341 let _ = body.try_right_ascension(0.0)?;
342 Ok(Self(body))
343 }
344
345 pub fn body(&self) -> T
347 where
348 T: Copy,
349 {
350 self.0
351 }
352
353 pub fn rotational_elements(&self, j2000: f64) -> (f64, f64, f64) {
356 self.0
359 .try_rotational_elements(j2000)
360 .expect("Iau frame wraps a body with defined rotational elements")
361 }
362
363 pub fn rotational_element_rates(&self, j2000: f64) -> (f64, f64, f64) {
365 self.0
366 .try_rotational_element_rates(j2000)
367 .expect("Iau frame wraps a body with defined rotational elements")
368 }
369}
370
371impl<T: TryRotationalElements + Copy> BodyFixed for Iau<T> {
372 type Origin = T;
373
374 fn origin(&self) -> Self::Origin {
375 self.0
376 }
377}
378
379impl<T: RotationalElements + Spheroid + Copy> ReferenceEllipsoid for Iau<T> {
380 fn reference_ellipsoid(&self) -> Ellipsoid {
381 self.0.ellipsoid()
382 }
383}
384
385pub(crate) fn iau_name(body: &str) -> String {
387 match body {
388 "Sun" | "Moon" => format!("IAU Body-Fixed Reference Frame for the {body}"),
389 _ => format!("IAU Body-Fixed Reference Frame for {body}"),
390 }
391}
392
393pub(crate) fn iau_abbreviation(body: &str) -> String {
395 format!("IAU_{}", body.replace([' ', '-'], "_").to_uppercase())
396}
397
398impl<T> ReferenceFrame for Iau<T>
399where
400 T: TryRotationalElements + CoordinateOrigin,
401{
402 fn name(&self) -> String {
403 iau_name(self.0.name())
404 }
405
406 fn abbreviation(&self) -> String {
407 iau_abbreviation(self.0.name())
408 }
409
410 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
411 Some(FrameKey::Iau(self.0.id()))
412 }
413}
414
415#[cfg(all(test, feature = "serde"))]
416mod serde_tests {
417 use lox_bodies::Origin;
418
419 use super::Iau;
420
421 #[test]
422 fn deserialize_valid_body() {
423 let json = serde_json::to_string(&Origin::Earth).unwrap();
424 let frame: Iau<Origin> = serde_json::from_str(&json).unwrap();
425 assert_eq!(frame.body(), Origin::Earth);
426 }
427
428 #[test]
429 fn deserialize_rejects_undefined_elements() {
430 let json = serde_json::to_string(&Origin::Sycorax).unwrap();
433 let result: Result<Iau<Origin>, _> = serde_json::from_str(&json);
434 assert!(result.is_err());
435 }
436}
437
438#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
445#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
446pub enum Frame {
447 #[default]
449 Icrf,
450 J2000,
452 Cirf,
454 Tirf,
456 Itrf,
458 Iau(Origin),
460 Mod(ReferenceSystem),
462 Tod(ReferenceSystem),
464 Pef(ReferenceSystem),
466 Teme,
468}
469
470impl ReferenceFrame for Frame {
471 fn name(&self) -> String {
472 match self {
473 Frame::Icrf => Icrf.name(),
474 Frame::J2000 => J2000.name(),
475 Frame::Cirf => Cirf.name(),
476 Frame::Tirf => Tirf.name(),
477 Frame::Itrf => Itrf.name(),
478 Frame::Iau(dynamic_origin) => iau_name(dynamic_origin.name()),
479 Frame::Mod(sys) => Mod(*sys).name(),
480 Frame::Tod(sys) => Tod(*sys).name(),
481 Frame::Pef(sys) => Pef(*sys).name(),
482 Frame::Teme => Teme.name(),
483 }
484 }
485
486 fn abbreviation(&self) -> String {
487 match self {
488 Frame::Icrf => Icrf.abbreviation(),
489 Frame::J2000 => J2000.abbreviation(),
490 Frame::Cirf => Cirf.abbreviation(),
491 Frame::Tirf => Tirf.abbreviation(),
492 Frame::Itrf => Itrf.abbreviation(),
493 Frame::Iau(dynamic_origin) => iau_abbreviation(dynamic_origin.name()),
494 Frame::Mod(sys) => Mod(*sys).abbreviation(),
495 Frame::Tod(sys) => Tod(*sys).abbreviation(),
496 Frame::Pef(sys) => Pef(*sys).abbreviation(),
497 Frame::Teme => Teme.abbreviation(),
498 }
499 }
500
501 fn frame_key(&self, _: crate::traits::private::Internal) -> Option<FrameKey> {
502 match self {
503 Frame::Icrf => frame_key(&Icrf),
504 Frame::J2000 => frame_key(&J2000),
505 Frame::Cirf => frame_key(&Cirf),
506 Frame::Tirf => frame_key(&Tirf),
507 Frame::Itrf => frame_key(&Itrf),
508 Frame::Iau(dynamic_origin) => Some(FrameKey::Iau(dynamic_origin.id())),
509 Frame::Mod(sys) => frame_key(&Mod(*sys)),
510 Frame::Tod(sys) => frame_key(&Tod(*sys)),
511
512 Frame::Pef(sys) => frame_key(&Pef(*sys)),
513 Frame::Teme => frame_key(&Teme),
514 }
515 }
516}
517
518impl TryQuasiInertial for Frame {
519 fn try_quasi_inertial(&self) -> Result<(), NonQuasiInertialFrameError> {
520 match self {
521 Frame::Icrf
522 | Frame::J2000
523 | Frame::Cirf
524 | Frame::Mod(_)
525 | Frame::Tod(_)
526 | Frame::Teme => Ok(()),
527 _ => Err(NonQuasiInertialFrameError(self.abbreviation())),
528 }
529 }
530}
531
532impl TryBodyFixed for Frame {
533 type Origin = Origin;
534
535 fn try_body_fixed(&self) -> Result<(), NonBodyFixedFrameError> {
536 match self {
537 Frame::Iau(_) | Frame::Itrf | Frame::Tirf | Frame::Pef(_) => Ok(()),
538 _ => Err(NonBodyFixedFrameError(self.abbreviation())),
539 }
540 }
541
542 fn try_origin(&self) -> Result<Self::Origin, NonBodyFixedFrameError> {
543 match self {
544 Frame::Iau(origin) => Ok(*origin),
545 Frame::Itrf => Ok(Itrf.origin().into()),
546 Frame::Tirf => Ok(Tirf.origin().into()),
547 Frame::Pef(sys) => Ok(Pef(*sys).origin().into()),
548 _ => Err(NonBodyFixedFrameError(self.abbreviation())),
549 }
550 }
551}
552
553impl TryReferenceEllipsoid for Frame {
554 fn try_reference_ellipsoid(&self) -> Result<Ellipsoid, UndefinedReferenceEllipsoidError> {
555 match self {
556 Frame::Iau(origin) => Ok(origin.try_ellipsoid()?),
557 Frame::Itrf => Ok(Itrf.reference_ellipsoid()),
558 Frame::Tirf => Ok(Tirf.reference_ellipsoid()),
559 Frame::Pef(sys) => Ok(Pef(*sys).reference_ellipsoid()),
560 _ => Err(UndefinedReferenceEllipsoidError::NotBodyFixed(
561 NonBodyFixedFrameError(self.abbreviation()),
562 )),
563 }
564 }
565}
566
567impl From<Icrf> for Frame {
570 fn from(_: Icrf) -> Self {
571 Frame::Icrf
572 }
573}
574
575impl From<J2000> for Frame {
576 fn from(_: J2000) -> Self {
577 Frame::J2000
578 }
579}
580
581impl From<Cirf> for Frame {
582 fn from(_: Cirf) -> Self {
583 Frame::Cirf
584 }
585}
586
587impl From<Tirf> for Frame {
588 fn from(_: Tirf) -> Self {
589 Frame::Tirf
590 }
591}
592
593impl From<Itrf> for Frame {
594 fn from(_: Itrf) -> Self {
595 Frame::Itrf
596 }
597}
598
599impl From<Teme> for Frame {
600 fn from(_: Teme) -> Self {
601 Frame::Teme
602 }
603}
604
605impl<T: IersSystem + Into<ReferenceSystem>> From<Mod<T>> for Frame {
608 fn from(frame: Mod<T>) -> Self {
609 Frame::Mod(frame.0.into())
610 }
611}
612
613impl<T: IersSystem + Into<ReferenceSystem>> From<Tod<T>> for Frame {
614 fn from(frame: Tod<T>) -> Self {
615 Frame::Tod(frame.0.into())
616 }
617}
618
619impl<T: IersSystem + Into<ReferenceSystem>> From<Pef<T>> for Frame {
620 fn from(frame: Pef<T>) -> Self {
621 Frame::Pef(frame.0.into())
622 }
623}
624
625impl<T: TryRotationalElements + Copy + Into<Origin>> From<Iau<T>> for Frame {
628 fn from(frame: Iau<T>) -> Self {
629 Frame::Iau(frame.body().into())
630 }
631}
632
633fn parse_iau_frame(s: &str) -> Option<Frame> {
634 let (prefix, origin) = s.split_once("_")?;
635 if prefix.to_lowercase() != "iau" {
636 return None;
637 }
638 let origin: Origin = origin.to_lowercase().parse().ok()?;
639 let _ = origin.try_rotational_elements(0.0).ok()?;
640 Some(Frame::Iau(origin))
641}
642
643fn parse_reference_system(s: &str) -> Option<ReferenceSystem> {
644 match s.to_uppercase().as_str() {
645 "IERS1996" => Some(ReferenceSystem::Iers1996),
646 "IERS2003" | "IERS2003A" => Some(ReferenceSystem::Iers2003(Iau2000Model::A)),
647 "IERS2003B" => Some(ReferenceSystem::Iers2003(Iau2000Model::B)),
648 "IERS2010" => Some(ReferenceSystem::Iers2010),
649 _ => None,
650 }
651}
652
653fn parse_equinox_frame(s: &str) -> Option<Frame> {
655 let s_stripped = s.strip_suffix(')')?;
656 let (frame, system) = s_stripped.split_once('(')?;
657 let sys = parse_reference_system(system)?;
658 match frame.to_uppercase().as_str() {
659 "MOD" => Some(Frame::Mod(sys)),
660 "TOD" => Some(Frame::Tod(sys)),
661 "PEF" => Some(Frame::Pef(sys)),
662 _ => None,
663 }
664}
665
666#[derive(Clone, Debug, Error, PartialEq, Eq)]
668#[error("no frame with name '{0}' is known")]
669pub struct UnknownFrameError(String);
670
671impl FromStr for Frame {
672 type Err = UnknownFrameError;
673
674 fn from_str(s: &str) -> Result<Self, Self::Err> {
675 match s.to_uppercase().as_str() {
676 "ICRF" => Ok(Frame::Icrf),
677 "J2000" | "EME2000" => Ok(Frame::J2000),
678 "CIRF" => Ok(Frame::Cirf),
679 "TIRF" => Ok(Frame::Tirf),
680 "ITRF" => Ok(Frame::Itrf),
681 "TEME" => Ok(Frame::Teme),
682 "MOD" => Ok(Frame::Mod(ReferenceSystem::Iers1996)),
683 "TOD" => Ok(Frame::Tod(ReferenceSystem::Iers1996)),
684 "PEF" => Ok(Frame::Pef(ReferenceSystem::Iers1996)),
685 _ => {
686 if let Some(frame) = parse_equinox_frame(s) {
687 Ok(frame)
688 } else if let Some(frame) = parse_iau_frame(s) {
689 Ok(frame)
690 } else {
691 Err(UnknownFrameError(s.to_owned()))
692 }
693 }
694 }
695 }
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701
702 use crate::iers::{Iers1996, Iers2003, Iers2010};
703 use crate::rotations::TryRotation;
704 use crate::traits::frame_key;
705 use crate::{Iau, providers::DefaultRotationProvider};
706
707 use lox_approx::assert_approx_eq;
708 use lox_bodies::{Earth, Origin};
709 use lox_core::glam::DVec3;
710 use lox_time::utc::Utc;
711 use rstest::rstest;
712
713 #[test]
714 fn zst_frame_names_and_abbreviations() {
715 assert_eq!(Icrf.abbreviation(), "ICRF");
716 assert_eq!(J2000.abbreviation(), "J2000");
717 assert_eq!(Cirf.abbreviation(), "CIRF");
718 assert_eq!(Tirf.abbreviation(), "TIRF");
719 assert_eq!(Itrf.abbreviation(), "ITRF");
720 assert_eq!(Teme.abbreviation(), "TEME");
721 assert!(Icrf.name().contains("Celestial"));
722 assert!(J2000.name().contains("J2000"));
723 assert!(Cirf.name().contains("Celestial Intermediate"));
724 assert!(Tirf.name().contains("Terrestrial Intermediate"));
725 assert!(Itrf.name().contains("Terrestrial Reference"));
726 assert!(Teme.name().contains("True Equator"));
727 }
728
729 #[test]
730 fn iau_frame_naming() {
731 let sun = Iau::try_new(Origin::Sun).unwrap();
733 let earth = Iau::try_new(Origin::Earth).unwrap();
734 assert!(sun.name().contains("for the Sun"));
735 assert!(earth.name().contains("for Earth"));
736 assert_eq!(earth.abbreviation(), "IAU_EARTH");
737 }
738
739 #[test]
740 fn custom_frame_has_no_key() {
741 struct Custom;
742 impl ReferenceFrame for Custom {
743 fn name(&self) -> String {
744 "Custom".to_owned()
745 }
746 fn abbreviation(&self) -> String {
747 "CUS".to_owned()
748 }
749 }
750 assert_eq!(frame_key(&Custom), None);
751 }
752 #[rstest]
753 #[case::valid("IAU_EARTH", Some(Frame::Iau(Origin::Earth)))]
754 #[case::invalid_prefix("FOO_EARTH", None)]
755 #[case::unkown_body("IAU_RUPERT", None)]
756 #[case::undefined_rotation("IAU_SYCORAX", None)]
757 fn test_parse_iau_frame(#[case] name: &str, #[case] exp: Option<Frame>) {
758 let act = parse_iau_frame(name);
759 assert_eq!(act, exp)
760 }
761
762 #[rstest]
763 #[case(
764 Frame::Iau(Origin::Earth),
765 DVec3::new(
766 -5.740_259_426_667_957e3,
767 3.121_136_072_795_472_5e3,
768 -1.863_182_656_331_802_7e3,
769 ),
770 DVec3::new(
771 -3.532_378_757_836_52,
772 -3.152_377_656_863_808,
773 5.642_296_713_889_555,
774 ),
775 )]
776 #[case(
777 Frame::Iau(Origin::Moon),
778 DVec3::new(
779 3.777_805_761_337_502e3,
780 -5.633_812_666_439_680_5e3,
781 -3.896_880_165_980_424e2,
782 ),
783 DVec3::new(
784 2.576_901_711_027_508_3,
785 1.250_106_874_006_032_4,
786 7.100_615_382_464_156,
787 ),
788 )]
789 fn test_icrf_to_bodyfixed(#[case] frame: Frame, #[case] r_exp: DVec3, #[case] v_exp: DVec3) {
790 let time = Utc::from_iso("2024-07-05T09:09:18.173")
791 .unwrap()
792 .to_dynamic_time();
793 let r = DVec3::new(-5530.01774359, -3487.0895338, -1850.03476185);
794 let v = DVec3::new(1.29534407, -5.02456882, 5.6391936);
795 let rot = DefaultRotationProvider
796 .try_rotation(Frame::Icrf, frame, time)
797 .unwrap();
798 let (r_act, v_act) = rot.rotate_state(r, v);
799 assert_approx_eq!(r_act, r_exp, rtol <= 1e-8);
800 assert_approx_eq!(v_act, v_exp, rtol <= 1e-5);
801 }
802
803 #[rstest]
804 #[case("MOD", Frame::Mod(ReferenceSystem::Iers1996))]
805 #[case("mod", Frame::Mod(ReferenceSystem::Iers1996))]
806 #[case("TOD", Frame::Tod(ReferenceSystem::Iers1996))]
807 #[case("tod", Frame::Tod(ReferenceSystem::Iers1996))]
808 #[case("PEF", Frame::Pef(ReferenceSystem::Iers1996))]
809 #[case("pef", Frame::Pef(ReferenceSystem::Iers1996))]
810 #[case("MOD(IERS1996)", Frame::Mod(ReferenceSystem::Iers1996))]
811 #[case(
812 "MOD(IERS2003)",
813 Frame::Mod(ReferenceSystem::Iers2003(Iau2000Model::A))
814 )]
815 #[case(
816 "mod(iers2003)",
817 Frame::Mod(ReferenceSystem::Iers2003(Iau2000Model::A))
818 )]
819 #[case(
820 "TOD(IERS2003)",
821 Frame::Tod(ReferenceSystem::Iers2003(Iau2000Model::A))
822 )]
823 #[case(
824 "PEF(IERS2003)",
825 Frame::Pef(ReferenceSystem::Iers2003(Iau2000Model::A))
826 )]
827 #[case("MOD(IERS2010)", Frame::Mod(ReferenceSystem::Iers2010))]
828 #[case("TOD(IERS2010)", Frame::Tod(ReferenceSystem::Iers2010))]
829 #[case("PEF(IERS2010)", Frame::Pef(ReferenceSystem::Iers2010))]
830 fn test_parse_equinox_frames(#[case] name: &str, #[case] exp: Frame) {
831 let act: Frame = name.parse().unwrap();
832 assert_eq!(act, exp);
833 }
834
835 #[test]
836 fn test_frame_key() {
837 assert_eq!(frame_key(&Icrf), frame_key(&Frame::Icrf));
838 assert_eq!(frame_key(&J2000), frame_key(&Frame::J2000));
839 assert_eq!(frame_key(&Cirf), frame_key(&Frame::Cirf));
840 assert_eq!(frame_key(&Tirf), frame_key(&Frame::Tirf));
841 assert_eq!(frame_key(&Itrf), frame_key(&Frame::Itrf));
842 assert_eq!(
843 frame_key(&Iau::new(Earth)),
844 frame_key(&Frame::Iau(Origin::Earth))
845 );
846 let mod_b = ReferenceSystem::Iers2003(Iau2000Model::B);
848 assert_eq!(frame_key(&Mod(mod_b)), frame_key(&Frame::Mod(mod_b)));
849 assert_eq!(frame_key(&Teme), frame_key(&Frame::Teme));
850 assert_ne!(
851 frame_key(&Frame::Mod(ReferenceSystem::Iers2003(Iau2000Model::A))),
852 frame_key(&Frame::Mod(mod_b))
853 );
854 }
855
856 #[rstest]
857 #[case("J2000", Frame::J2000)]
858 #[case("j2000", Frame::J2000)]
859 #[case("EME2000", Frame::J2000)]
860 fn test_parse_j2000(#[case] name: &str, #[case] exp: Frame) {
861 let act: Frame = name.parse().unwrap();
862 assert_eq!(act, exp);
863 }
864
865 #[test]
866 fn test_j2000_quasi_inertial() {
867 assert!(Frame::J2000.try_quasi_inertial().is_ok());
868 }
869
870 #[rstest]
873 #[case(Frame::Icrf, true)]
874 #[case(Frame::J2000, true)]
875 #[case(Frame::Cirf, true)]
876 #[case(Frame::Teme, true)]
877 #[case(Frame::Mod(ReferenceSystem::Iers1996), true)]
878 #[case(Frame::Mod(ReferenceSystem::Iers2010), true)]
879 #[case(Frame::Tod(ReferenceSystem::Iers1996), true)]
880 #[case(Frame::Tod(ReferenceSystem::Iers2010), true)]
881 #[case(Frame::Tirf, false)]
882 #[case(Frame::Itrf, false)]
883 #[case(Frame::Pef(ReferenceSystem::Iers1996), false)]
884 #[case(Frame::Pef(ReferenceSystem::Iers2010), false)]
885 #[case(Frame::Iau(Origin::Earth), false)]
886 fn test_quasi_inertial_classification(#[case] frame: Frame, #[case] exp: bool) {
887 assert_eq!(frame.try_quasi_inertial().is_ok(), exp);
888 assert!(!(frame.try_quasi_inertial().is_ok() && frame.try_body_fixed().is_ok()));
890 }
891
892 #[rstest]
893 #[case(Frame::Tirf, true)]
894 #[case(Frame::Itrf, true)]
895 #[case(Frame::Pef(ReferenceSystem::Iers1996), true)]
896 #[case(Frame::Pef(ReferenceSystem::Iers2010), true)]
897 #[case(Frame::Iau(Origin::Earth), true)]
898 #[case(Frame::Iau(Origin::Moon), true)]
899 #[case(Frame::Icrf, false)]
900 #[case(Frame::J2000, false)]
901 #[case(Frame::Cirf, false)]
902 #[case(Frame::Teme, false)]
903 #[case(Frame::Mod(ReferenceSystem::Iers1996), false)]
904 #[case(Frame::Tod(ReferenceSystem::Iers1996), false)]
905 fn test_body_fixed_classification(#[case] frame: Frame, #[case] exp: bool) {
906 assert_eq!(frame.try_body_fixed().is_ok(), exp);
907 assert_eq!(frame.try_origin().is_ok(), exp);
908 }
909
910 #[rstest]
912 #[case(Frame::Itrf)]
913 #[case(Frame::Tirf)]
914 #[case(Frame::Pef(ReferenceSystem::Iers2010))]
915 fn test_terrestrial_frames_share_origin_and_datum(#[case] frame: Frame) {
916 assert_eq!(frame.try_origin().unwrap(), Origin::Earth);
917 assert_eq!(frame.try_reference_ellipsoid().unwrap(), Ellipsoid::GRS80);
918 }
919
920 fn assert_quasi_inertial_agrees(frame: impl QuasiInertial + Copy + Into<Frame>) {
925 let dynamic: Frame = frame.into();
926 assert!(
927 dynamic.try_quasi_inertial().is_ok(),
928 "{} is quasi-inertial as a ZST but not as a Frame",
929 dynamic.abbreviation()
930 );
931 }
932
933 fn assert_body_fixed_agrees<F>(frame: F)
934 where
935 F: BodyFixed + Copy + Into<Frame>,
936 F::Origin: Into<Origin>,
937 {
938 let dynamic: Frame = frame.into();
939 assert!(
940 dynamic.try_body_fixed().is_ok(),
941 "{} is body-fixed as a ZST but not as a Frame",
942 dynamic.abbreviation()
943 );
944 assert_eq!(dynamic.try_origin().unwrap(), frame.origin().into());
945 }
946
947 fn assert_reference_ellipsoid_agrees(frame: impl ReferenceEllipsoid + Copy + Into<Frame>) {
948 let dynamic: Frame = frame.into();
949 assert_eq!(
950 dynamic.try_reference_ellipsoid().unwrap(),
951 frame.reference_ellipsoid()
952 );
953 }
954
955 #[test]
956 fn test_quasi_inertial_zsts_agree_with_frame() {
957 assert_quasi_inertial_agrees(Icrf);
958 assert_quasi_inertial_agrees(J2000);
959 assert_quasi_inertial_agrees(Cirf);
960 assert_quasi_inertial_agrees(Teme);
961 assert_quasi_inertial_agrees(Mod(Iers1996));
962 assert_quasi_inertial_agrees(Mod(Iers2003::default()));
963 assert_quasi_inertial_agrees(Mod(Iers2010));
964 assert_quasi_inertial_agrees(Tod(Iers1996));
965 assert_quasi_inertial_agrees(Tod(Iers2003::default()));
966 assert_quasi_inertial_agrees(Tod(Iers2010));
967 }
968
969 #[test]
970 fn test_body_fixed_zsts_agree_with_frame() {
971 assert_body_fixed_agrees(Itrf);
972 assert_body_fixed_agrees(Tirf);
973 assert_body_fixed_agrees(Pef(Iers1996));
974 assert_body_fixed_agrees(Pef(Iers2003::default()));
975 assert_body_fixed_agrees(Pef(Iers2010));
976 assert_body_fixed_agrees(Iau::new(Earth));
977 }
978
979 #[test]
980 fn test_reference_ellipsoid_zsts_agree_with_frame() {
981 assert_reference_ellipsoid_agrees(Itrf);
982 assert_reference_ellipsoid_agrees(Tirf);
983 assert_reference_ellipsoid_agrees(Pef(Iers1996));
984 assert_reference_ellipsoid_agrees(Pef(Iers2010));
985 assert_reference_ellipsoid_agrees(Iau::new(Earth));
986 }
987
988 #[test]
989 fn test_from_simple_frames() {
990 assert_eq!(Frame::from(Icrf), Frame::Icrf);
991 assert_eq!(Frame::from(J2000), Frame::J2000);
992 assert_eq!(Frame::from(Cirf), Frame::Cirf);
993 assert_eq!(Frame::from(Tirf), Frame::Tirf);
994 assert_eq!(Frame::from(Itrf), Frame::Itrf);
995 assert_eq!(Frame::from(Teme), Frame::Teme);
996 }
997
998 #[test]
999 fn test_from_parameterized_frames() {
1000 assert_eq!(
1001 Frame::from(Mod(Iers1996)),
1002 Frame::Mod(ReferenceSystem::Iers1996)
1003 );
1004 assert_eq!(
1005 Frame::from(Tod(Iers2003::default())),
1006 Frame::Tod(ReferenceSystem::Iers2003(Iau2000Model::A))
1007 );
1008 assert_eq!(
1009 Frame::from(Pef(Iers2010)),
1010 Frame::Pef(ReferenceSystem::Iers2010)
1011 );
1012 }
1013
1014 #[test]
1015 fn test_from_iau_frame() {
1016 assert_eq!(Frame::from(Iau::new(Earth)), Frame::Iau(Origin::Earth));
1017 }
1018
1019 #[rstest]
1020 #[case(Frame::Icrf)]
1021 #[case(Frame::J2000)]
1022 #[case(Frame::Cirf)]
1023 #[case(Frame::Tirf)]
1024 #[case(Frame::Itrf)]
1025 #[case(Frame::Teme)]
1026 #[case(Frame::Mod(ReferenceSystem::Iers1996))]
1027 #[case(Frame::Mod(ReferenceSystem::Iers2003(Iau2000Model::A)))]
1028 #[case(Frame::Mod(ReferenceSystem::Iers2003(Iau2000Model::B)))]
1029 #[case(Frame::Mod(ReferenceSystem::Iers2010))]
1030 #[case(Frame::Tod(ReferenceSystem::Iers1996))]
1031 #[case(Frame::Tod(ReferenceSystem::Iers2003(Iau2000Model::A)))]
1032 #[case(Frame::Tod(ReferenceSystem::Iers2003(Iau2000Model::B)))]
1033 #[case(Frame::Tod(ReferenceSystem::Iers2010))]
1034 #[case(Frame::Pef(ReferenceSystem::Iers1996))]
1035 #[case(Frame::Pef(ReferenceSystem::Iers2003(Iau2000Model::A)))]
1036 #[case(Frame::Pef(ReferenceSystem::Iers2003(Iau2000Model::B)))]
1037 #[case(Frame::Pef(ReferenceSystem::Iers2010))]
1038 #[case(Frame::Iau(Origin::Earth))]
1039 fn test_abbreviation_round_trip(#[case] frame: Frame) {
1040 let abbr = frame.abbreviation();
1041 let parsed: Frame = abbr
1042 .parse()
1043 .unwrap_or_else(|e| panic!("failed to parse abbreviation '{}': {}", abbr, e));
1044 assert_eq!(parsed, frame);
1045 }
1046}