1use 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 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 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}