1use crate::astro::constants::earth::OMEGA_E_DOT_RAD_S;
10use crate::astro::frames::transforms::{
11 gcrs_to_itrs_matrix_with_polar_motion, mat3_vec3_mul, polar_motion_matrix, FrameTransformError,
12 PolarMotion,
13};
14use crate::astro::math::mat3::{inline_rxr, inline_tr, Mat3};
15use crate::astro::time::civil::{civil_from_j2000_seconds, j2000_seconds_from_split};
16use crate::astro::time::model::{Instant, InstantRepr, TimeScale};
17use crate::astro::time::scales::TimeScales;
18
19#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct EarthOrientation {
28 time_scales: TimeScales,
29 polar_motion: PolarMotion,
30 gcrf_to_itrf: Mat3,
31 itrf_to_gcrf: Mat3,
32 earth_rotation_vector_itrf_rad_s: [f64; 3],
33}
34
35impl EarthOrientation {
36 pub fn from_time_scales(ts: &TimeScales) -> Result<Self, FrameTransformError> {
38 Self::from_time_scales_with_polar_motion(ts, PolarMotion::ZERO)
39 }
40
41 pub fn from_time_scales_with_polar_motion(
44 ts: &TimeScales,
45 polar_motion: PolarMotion,
46 ) -> Result<Self, FrameTransformError> {
47 let gcrf_to_itrf = gcrs_to_itrs_matrix_with_polar_motion(ts, polar_motion)?;
48 let itrf_to_gcrf = inline_tr(&gcrf_to_itrf);
49 let polar = polar_motion_matrix(polar_motion)?;
50 let earth_rotation_vector_itrf_rad_s =
51 mat3_vec3_mul(&polar, &[0.0, 0.0, OMEGA_E_DOT_RAD_S])?;
52 Ok(Self {
53 time_scales: *ts,
54 polar_motion,
55 gcrf_to_itrf,
56 itrf_to_gcrf,
57 earth_rotation_vector_itrf_rad_s,
58 })
59 }
60
61 pub fn from_utc(
64 year: i32,
65 month: i32,
66 day: i32,
67 hour: i32,
68 minute: i32,
69 second: f64,
70 ) -> Result<Self, FrameTransformError> {
71 Self::from_utc_with_polar_motion(year, month, day, hour, minute, second, PolarMotion::ZERO)
72 }
73
74 pub fn from_utc_with_polar_motion(
77 year: i32,
78 month: i32,
79 day: i32,
80 hour: i32,
81 minute: i32,
82 second: f64,
83 polar_motion: PolarMotion,
84 ) -> Result<Self, FrameTransformError> {
85 let ts = TimeScales::from_utc(year, month, day, hour, minute, second)
86 .map_err(|_| invalid_input("utc", "time-scale conversion failed"))?;
87 Self::from_time_scales_with_polar_motion(&ts, polar_motion)
88 }
89
90 pub fn from_instant(epoch: Instant) -> Result<Self, FrameTransformError> {
93 Self::from_instant_with_polar_motion(epoch, PolarMotion::ZERO)
94 }
95
96 pub fn from_instant_with_polar_motion(
99 epoch: Instant,
100 polar_motion: PolarMotion,
101 ) -> Result<Self, FrameTransformError> {
102 let ts = time_scales_from_instant(epoch)?;
103 Self::from_time_scales_with_polar_motion(&ts, polar_motion)
104 }
105
106 pub fn time_scales(&self) -> TimeScales {
108 self.time_scales
109 }
110
111 pub fn polar_motion(&self) -> PolarMotion {
113 self.polar_motion
114 }
115
116 pub fn earth_rotation_vector_itrf_rad_s(&self) -> [f64; 3] {
118 self.earth_rotation_vector_itrf_rad_s
119 }
120
121 pub fn gcrf_to_itrf_matrix(&self) -> Mat3 {
123 self.gcrf_to_itrf
124 }
125
126 pub fn itrf_to_gcrf_matrix(&self) -> Mat3 {
128 self.itrf_to_gcrf
129 }
130
131 pub fn gcrf_to_itrf_rotation_rate_matrix(&self) -> Mat3 {
134 let neg_skew = neg_skew_matrix(self.earth_rotation_vector_itrf_rad_s);
135 inline_rxr(&neg_skew, &self.gcrf_to_itrf)
136 }
137
138 pub fn itrf_to_gcrf_rotation_rate_matrix(&self) -> Mat3 {
141 let skew = skew_matrix(self.earth_rotation_vector_itrf_rad_s);
142 inline_rxr(&self.itrf_to_gcrf, &skew)
143 }
144
145 pub fn gcrf_to_itrf_position_km(
147 &self,
148 position_gcrf_km: [f64; 3],
149 ) -> Result<[f64; 3], FrameTransformError> {
150 validate_vec3("position_gcrf_km", &position_gcrf_km)?;
151 mat3_vec3_mul(&self.gcrf_to_itrf, &position_gcrf_km)
152 }
153
154 pub fn itrf_to_gcrf_position_km(
156 &self,
157 position_itrf_km: [f64; 3],
158 ) -> Result<[f64; 3], FrameTransformError> {
159 validate_vec3("position_itrf_km", &position_itrf_km)?;
160 mat3_vec3_mul(&self.itrf_to_gcrf, &position_itrf_km)
161 }
162
163 pub fn gcrf_to_itrf_state_km(
168 &self,
169 position_gcrf_km: [f64; 3],
170 velocity_gcrf_km_s: [f64; 3],
171 ) -> Result<([f64; 3], [f64; 3]), FrameTransformError> {
172 validate_vec3("position_gcrf_km", &position_gcrf_km)?;
173 validate_vec3("velocity_gcrf_km_s", &velocity_gcrf_km_s)?;
174 let position_itrf_km = mat3_vec3_mul(&self.gcrf_to_itrf, &position_gcrf_km)?;
175 let rotated_velocity = mat3_vec3_mul(&self.gcrf_to_itrf, &velocity_gcrf_km_s)?;
176 let transport = cross(self.earth_rotation_vector_itrf_rad_s, position_itrf_km);
177 Ok((position_itrf_km, sub(rotated_velocity, transport)))
178 }
179
180 pub fn itrf_to_gcrf_state_km(
185 &self,
186 position_itrf_km: [f64; 3],
187 velocity_itrf_km_s: [f64; 3],
188 ) -> Result<([f64; 3], [f64; 3]), FrameTransformError> {
189 validate_vec3("position_itrf_km", &position_itrf_km)?;
190 validate_vec3("velocity_itrf_km_s", &velocity_itrf_km_s)?;
191 let position_gcrf_km = mat3_vec3_mul(&self.itrf_to_gcrf, &position_itrf_km)?;
192 let transport = cross(self.earth_rotation_vector_itrf_rad_s, position_itrf_km);
193 let velocity_rotating = add(velocity_itrf_km_s, transport);
194 let velocity_gcrf_km_s = mat3_vec3_mul(&self.itrf_to_gcrf, &velocity_rotating)?;
195 Ok((position_gcrf_km, velocity_gcrf_km_s))
196 }
197}
198
199pub trait EarthOrientationProvider: Send + Sync {
205 fn orientation_at_tdb_seconds(
207 &self,
208 epoch_tdb_seconds: f64,
209 ) -> Result<EarthOrientation, FrameTransformError>;
210}
211
212#[derive(Debug, Clone, Copy, PartialEq)]
215pub struct TdbEarthOrientationProvider {
216 polar_motion: PolarMotion,
217}
218
219impl TdbEarthOrientationProvider {
220 pub const fn new() -> Self {
222 Self {
223 polar_motion: PolarMotion::ZERO,
224 }
225 }
226
227 pub const fn with_polar_motion(polar_motion: PolarMotion) -> Self {
229 Self { polar_motion }
230 }
231
232 pub fn polar_motion(&self) -> PolarMotion {
234 self.polar_motion
235 }
236}
237
238impl Default for TdbEarthOrientationProvider {
239 fn default() -> Self {
240 Self::new()
241 }
242}
243
244impl EarthOrientationProvider for TdbEarthOrientationProvider {
245 fn orientation_at_tdb_seconds(
246 &self,
247 epoch_tdb_seconds: f64,
248 ) -> Result<EarthOrientation, FrameTransformError> {
249 let ts = time_scales_from_scale_j2000_seconds(TimeScale::Tdb, epoch_tdb_seconds)?;
250 EarthOrientation::from_time_scales_with_polar_motion(&ts, self.polar_motion)
251 }
252}
253
254fn time_scales_from_instant(epoch: Instant) -> Result<TimeScales, FrameTransformError> {
255 let seconds = match epoch.repr {
256 InstantRepr::JulianDate(jd) => j2000_seconds_from_split(jd.jd_whole, jd.fraction),
257 InstantRepr::Nanos(_) => {
258 return Err(invalid_input("epoch", "must be a split Julian date"));
259 }
260 };
261 time_scales_from_scale_j2000_seconds(epoch.scale, seconds)
262}
263
264fn time_scales_from_scale_j2000_seconds(
265 scale: TimeScale,
266 epoch_j2000_s: f64,
267) -> Result<TimeScales, FrameTransformError> {
268 if !epoch_j2000_s.is_finite() {
269 return Err(invalid_input("epoch_j2000_s", "must be finite"));
270 }
271 let whole = epoch_j2000_s.floor();
272 if whole < i64::MIN as f64 || whole > i64::MAX as f64 {
273 return Err(invalid_input(
274 "epoch_j2000_s",
275 "whole seconds are out of range",
276 ));
277 }
278 let fraction = epoch_j2000_s - whole;
279 let (year, month, day, hour, minute, second) = civil_from_j2000_seconds(whole as i64);
280 TimeScales::from_scale(
281 scale,
282 year as i32,
283 month as i32,
284 day as i32,
285 hour as i32,
286 minute as i32,
287 second as f64 + fraction,
288 )
289 .map_err(|_| invalid_input("epoch_j2000_s", "time-scale conversion failed"))
290}
291
292fn invalid_input(field: &'static str, reason: &'static str) -> FrameTransformError {
293 FrameTransformError::InvalidInput { field, reason }
294}
295
296fn validate_vec3(field: &'static str, value: &[f64; 3]) -> Result<(), FrameTransformError> {
297 if value.iter().all(|component| component.is_finite()) {
298 Ok(())
299 } else {
300 Err(invalid_input(field, "components must be finite"))
301 }
302}
303
304fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
305 [
306 a[1] * b[2] - a[2] * b[1],
307 a[2] * b[0] - a[0] * b[2],
308 a[0] * b[1] - a[1] * b[0],
309 ]
310}
311
312fn add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
313 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
314}
315
316fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
317 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
318}
319
320fn skew_matrix(omega: [f64; 3]) -> Mat3 {
321 [
322 [0.0, -omega[2], omega[1]],
323 [omega[2], 0.0, -omega[0]],
324 [-omega[1], omega[0], 0.0],
325 ]
326}
327
328fn neg_skew_matrix(omega: [f64; 3]) -> Mat3 {
329 [
330 [0.0, omega[2], -omega[1]],
331 [-omega[2], 0.0, omega[0]],
332 [omega[1], -omega[0], 0.0],
333 ]
334}