1use std::str::FromStr;
6
7use lox_bodies::{DynOrigin, Origin, TryRotationalElements, UndefinedOriginPropertyError};
8use lox_time::{Time, time_scales::DynTimeScale};
9use thiserror::Error;
10
11use crate::{
12 Iau,
13 frames::{Cirf, Icrf, Itrf, Tirf},
14 providers::DefaultTransformProvider,
15 traits::{
16 NonBodyFixedFrameError, NonQuasiInertialFrameError, ReferenceFrame, TryBodyFixed,
17 TryQuasiInertial,
18 },
19 transformations::{Rotation, TryTransform},
20};
21
22#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
23pub enum DynFrame {
24 #[default]
25 Icrf,
26 Cirf,
27 Tirf,
28 Itrf,
29 Iau(DynOrigin),
30}
31
32impl ReferenceFrame for DynFrame {
33 fn name(&self) -> String {
34 match self {
35 DynFrame::Icrf => Icrf.name(),
36 DynFrame::Cirf => Cirf.name(),
37 DynFrame::Tirf => Tirf.name(),
38 DynFrame::Itrf => Itrf.name(),
39 DynFrame::Iau(dyn_origin) => {
40 let body = dyn_origin.name();
41 match body {
42 "Sun" | "Moon" => format!("IAU Body-Fixed Reference Frame for the {body}"),
43 _ => format!("IAU Body-Fixed Reference Frame for {body}"),
44 }
45 }
46 }
47 }
48
49 fn abbreviation(&self) -> String {
50 match self {
51 DynFrame::Icrf => Icrf.abbreviation(),
52 DynFrame::Cirf => Cirf.abbreviation(),
53 DynFrame::Tirf => Tirf.abbreviation(),
54 DynFrame::Itrf => Itrf.abbreviation(),
55 DynFrame::Iau(dyn_origin) => {
56 let body = dyn_origin.name().replace([' ', '-'], "_").to_uppercase();
57 format!("IAU_{body}")
58 }
59 }
60 }
61
62 fn is_rotating(&self) -> bool {
63 match self {
64 DynFrame::Icrf | DynFrame::Cirf => false,
65 DynFrame::Tirf | DynFrame::Itrf | DynFrame::Iau(_) => true,
66 }
67 }
68}
69
70impl TryQuasiInertial for DynFrame {
71 fn try_quasi_inertial(&self) -> Result<(), NonQuasiInertialFrameError> {
72 match self {
73 DynFrame::Icrf => Ok(()),
74 _ => Err(NonQuasiInertialFrameError(self.abbreviation())),
75 }
76 }
77}
78
79impl TryBodyFixed for DynFrame {
80 fn try_body_fixed(&self) -> Result<(), NonBodyFixedFrameError> {
81 match self {
82 DynFrame::Iau(_) | DynFrame::Itrf => Ok(()),
83 _ => Err(NonBodyFixedFrameError(self.abbreviation())),
84 }
85 }
86}
87
88fn parse_iau_frame(s: &str) -> Option<DynFrame> {
89 let (prefix, origin) = s.split_once("_")?;
90 if prefix.to_lowercase() != "iau" {
91 return None;
92 }
93 let origin: DynOrigin = origin.to_lowercase().parse().ok()?;
94 let _ = origin.try_rotational_elements(0.0).ok()?;
95 Some(DynFrame::Iau(origin))
96}
97
98#[derive(Clone, Debug, Error, PartialEq, Eq)]
99#[error("no frame with name '{0}' is known")]
100pub struct UnknownFrameError(String);
101
102impl FromStr for DynFrame {
103 type Err = UnknownFrameError;
104
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 match s {
107 "icrf" | "ICRF" => Ok(DynFrame::Icrf),
108 "cirf" | "CIRF" => Ok(DynFrame::Cirf),
109 "tirf" | "TIRF" => Ok(DynFrame::Tirf),
110 "itrf" | "ITRF" => Ok(DynFrame::Itrf),
111 _ => {
112 if let Some(frame) = parse_iau_frame(s) {
113 Ok(frame)
114 } else {
115 Err(UnknownFrameError(s.to_owned()))
116 }
117 }
118 }
119 }
120}
121
122#[derive(Debug, Error)]
123pub enum DynTransformError {
124 #[error("transformations between {0} and {1} require an EOP provider")]
125 MissingEopProvider(String, String),
126 #[error(transparent)]
127 MissingUt1Provider(#[from] lox_time::offsets::MissingEopProviderError),
128 #[error(transparent)]
129 UndefinedRotationalElements(#[from] UndefinedOriginPropertyError),
130}
131
132impl TryTransform<DynFrame, DynFrame, DynTimeScale> for DefaultTransformProvider {
133 type Error = DynTransformError;
134
135 fn try_transform(
136 &self,
137 origin: DynFrame,
138 target: DynFrame,
139 time: Time<DynTimeScale>,
140 ) -> Result<Rotation, Self::Error> {
141 match (origin, target) {
142 (DynFrame::Icrf, DynFrame::Icrf) => Ok(Rotation::IDENTITY),
143 (DynFrame::Icrf, DynFrame::Iau(target)) => {
144 Ok(self.try_transform(Icrf, Iau::try_new(target)?, time)?)
145 }
146 (DynFrame::Cirf, DynFrame::Cirf) => Ok(Rotation::IDENTITY),
147 (DynFrame::Tirf, DynFrame::Tirf) => Ok(Rotation::IDENTITY),
148 (DynFrame::Iau(origin), DynFrame::Icrf) => {
149 Ok(self.try_transform(Iau::try_new(origin)?, Icrf, time)?)
150 }
151 (DynFrame::Iau(origin), DynFrame::Iau(target)) => {
152 let origin = Iau::try_new(origin)?;
153 let target = Iau::try_new(target)?;
154 Ok(self
155 .try_transform(origin, Icrf, time)?
156 .compose(self.try_transform(Icrf, target, time)?))
157 }
158 (origin, target) => Err(DynTransformError::MissingEopProvider(
159 origin.abbreviation(),
160 target.abbreviation(),
161 )),
162 }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 use glam::DVec3;
171 use lox_bodies::DynOrigin;
172 use lox_test_utils::assert_approx_eq;
173 use lox_time::utc::Utc;
174 use rstest::rstest;
175
176 #[rstest]
177 #[case::valid("IAU_EARTH", Some(DynFrame::Iau(DynOrigin::Earth)))]
178 #[case::invalid_prefix("FOO_EARTH", None)]
179 #[case::unkown_body("IAU_RUPERT", None)]
180 #[case::undefined_rotation("IAU_SYCORAX", None)]
181 fn test_parse_iau_frame(#[case] name: &str, #[case] exp: Option<DynFrame>) {
182 let act = parse_iau_frame(name);
183 assert_eq!(act, exp)
184 }
185
186 #[rstest]
187 #[case(
188 DynFrame::Iau(DynOrigin::Earth),
189 DVec3::new(
190 -5.740_259_426_667_957e3,
191 3.121_136_072_795_472_5e3,
192 -1.863_182_656_331_802_7e3,
193 ),
194 DVec3::new(
195 -3.532_378_757_836_52,
196 -3.152_377_656_863_808,
197 5.642_296_713_889_555,
198 ),
199 )]
200 #[case(
201 DynFrame::Iau(DynOrigin::Moon),
202 DVec3::new(
203 3.777_805_761_337_502e3,
204 -5.633_812_666_439_680_5e3,
205 -3.896_880_165_980_424e2,
206 ),
207 DVec3::new(
208 2.576_901_711_027_508_3,
209 1.250_106_874_006_032_4,
210 7.100_615_382_464_156,
211 ),
212 )]
213 fn test_icrf_to_bodyfixed(#[case] frame: DynFrame, #[case] r_exp: DVec3, #[case] v_exp: DVec3) {
214 let time = Utc::from_iso("2024-07-05T09:09:18.173")
215 .unwrap()
216 .to_dyn_time();
217 let r = DVec3::new(-5530.01774359, -3487.0895338, -1850.03476185);
218 let v = DVec3::new(1.29534407, -5.02456882, 5.6391936);
219 let rot = DefaultTransformProvider
220 .try_transform(DynFrame::Icrf, frame, time)
221 .unwrap();
222 let (r_act, v_act) = rot.rotate_state(r, v);
223 assert_approx_eq!(r_act, r_exp, rtol <= 1e-8);
224 assert_approx_eq!(v_act, v_exp, rtol <= 1e-5);
225 }
226}