1use crate::eop::{EopProvider, EopProviderError};
6use lox_frames::iers::polar_motion::PoleCoords;
7use lox_frames::iers::{Corrections, ReferenceSystem};
8use lox_frames::rotations::RotationProvider;
9use lox_time::Time;
10use lox_time::deltas::TimeDelta;
11use lox_time::offsets::{OffsetProvider, TryOffset};
12use lox_time::time_scales::{Tai, TimeScale};
13use lox_time::utc::Utc;
14use lox_time::utc::leap_seconds::{DefaultLeapSecondsProvider, LeapSecondsProvider};
15use lox_time::utc::transformations::ToUtc;
16
17impl LeapSecondsProvider for EopProvider {
18 fn delta_tai_utc(&self, tai: Time<Tai>) -> TimeDelta {
19 self.get_lsk().map_or_else(
20 || DefaultLeapSecondsProvider.delta_tai_utc(tai),
21 |lsk| lsk.delta_tai_utc(tai),
22 )
23 }
24
25 fn delta_utc_tai(&self, utc: Utc) -> TimeDelta {
26 self.get_lsk().map_or_else(
27 || DefaultLeapSecondsProvider.delta_utc_tai(utc),
28 |lsk| lsk.delta_utc_tai(utc),
29 )
30 }
31
32 fn is_leap_second_date(&self, date: lox_time::calendar_dates::Date) -> bool {
33 self.get_lsk().map_or_else(
34 || DefaultLeapSecondsProvider.is_leap_second_date(date),
35 |lsk| lsk.is_leap_second_date(date),
36 )
37 }
38
39 fn is_leap_second(&self, tai: Time<Tai>) -> bool {
40 self.get_lsk().map_or_else(
41 || DefaultLeapSecondsProvider.is_leap_second(tai),
42 |lsk| lsk.is_leap_second(tai),
43 )
44 }
45}
46
47impl OffsetProvider for EopProvider {
48 type Error = EopProviderError;
49
50 fn tai_to_ut1(&self, delta: TimeDelta) -> Result<TimeDelta, Self::Error> {
51 self.delta_ut1_tai(delta)
52 }
53
54 fn ut1_to_tai(&self, delta: TimeDelta) -> Result<TimeDelta, Self::Error> {
55 self.delta_tai_ut1(delta)
56 }
57}
58
59impl<T> RotationProvider<T> for EopProvider
60where
61 T: TimeScale + Copy,
62 Self: TryOffset<T, Tai>,
63{
64 type EopError = EopProviderError;
65
66 fn corrections(
67 &self,
68 time: Time<T>,
69 sys: ReferenceSystem,
70 ) -> Result<Corrections, EopProviderError> {
71 let utc = time
72 .try_to_scale(Tai, self)
73 .map_err(|err| EopProviderError::Offset(err.to_string()))?
74 .to_utc();
75 match sys {
76 ReferenceSystem::Iers1996 => self.nutation_precession_iau1980(utc),
77 ReferenceSystem::Iers2003(_) | ReferenceSystem::Iers2010 => {
78 self.nutation_precession_iau2000(utc)
79 }
80 }
81 }
82
83 fn pole_coords(&self, time: Time<T>) -> Result<PoleCoords, EopProviderError> {
84 let utc = time
85 .try_to_scale(Tai, self)
86 .map_err(|err| EopProviderError::Offset(err.to_string()))?
87 .to_utc();
88 self.polar_motion(utc)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 use std::sync::OnceLock;
97
98 use crate::eop::EopParser;
99
100 use lox_io::spice::lsk::LeapSecondsKernel;
101 use lox_test_utils::assert_approx_eq;
102 use lox_test_utils::data_file;
103 use lox_time::Time;
104 use lox_time::deltas::ToDelta;
105 use lox_time::offsets::TryOffset;
106 use lox_time::subsecond::Subsecond;
107 use lox_time::time;
108 use lox_time::time_scales::DynTimeScale;
109 use lox_time::time_scales::{Tai, Ut1};
110 use lox_time::utc::transformations::ToUtc;
111 use lox_time::{DynTime, calendar_dates::Date, time_of_day::TimeOfDay};
112 use rstest::{fixture, rstest};
113
114 #[rstest]
115 #[case(536414400, -36.40775963091942)]
116 #[case(536415400, -36.40776991477994)]
117 #[case(536416400, -36.407780212136835)]
118 #[case(536417400, -36.40779052210695)]
119 #[case(536418400, -36.407800844763194)]
120 #[case(536419400, -36.407811180178484)]
121 #[case(536420400, -36.40782152842571)]
122 #[case(536421400, -36.40783188957778)]
123 #[case(536422400, -36.4078422637076)]
124 #[case(536423400, -36.407852650888074)]
125 #[case(536424400, -36.4078630511921)]
126 #[case(536425400, -36.4078734646926)]
127 #[case(536426400, -36.40788389146246)]
128 #[case(536427400, -36.407894331574596)]
129 #[case(536428400, -36.407904785101906)]
130 #[case(536429400, -36.40791525211729)]
131 #[case(536430400, -36.40792573269367)]
132 #[case(536431400, -36.407936226903935)]
133 #[case(536432400, -36.40794673482099)]
134 #[case(536433400, -36.40795725651775)]
135 #[case(536434400, -36.407967792067105)]
136 #[case(536435400, -36.40797834154198)]
137 #[case(536436400, -36.40798890501525)]
138 #[case(536437400, -36.407999482559845)]
139 #[case(536438400, -36.40801007424866)]
140 #[case(536439400, -36.4080206801546)]
141 #[case(536440400, -36.40803130035057)]
142 #[case(536441400, -36.40804193490947)]
143 #[case(536442400, -36.408052583904215)]
144 #[case(536443400, -36.408063247407696)]
145 #[case(536444400, -36.40807392549283)]
146 #[case(536445400, -36.408084618232515)]
147 #[case(536446400, -36.40809532569965)]
148 #[case(536447400, -36.40810604796715)]
149 #[case(536448400, -36.408116785107914)]
150 #[case(536449400, -36.408127537194844)]
151 #[case(536450400, -36.408138304300856)]
152 #[case(536451400, -36.40814908649884)]
153 #[case(536452400, -36.40815988386171)]
154 #[case(536453400, -36.40817069646236)]
155 #[case(536454400, -36.40818152437371)]
156 #[case(536455400, -36.408192367668654)]
157 #[case(536456400, -36.4082032264201)]
158 #[case(536457400, -36.408214100700945)]
159 #[case(536458400, -36.4082249905841)]
160 #[case(536459400, -36.40823589614247)]
161 #[case(536460400, -36.40824681744896)]
162 #[case(536461400, -36.408257754576475)]
163 #[case(536462400, -36.40826870759791)]
164 #[case(536463400, -36.40827967658618)]
165 #[case(536464400, -36.408290661614195)]
166 #[case(536465400, -36.40830166275484)]
167 #[case(536466400, -36.40831268008103)]
168 #[case(536467400, -36.40832371366567)]
169 #[case(536468400, -36.40833476358167)]
170 #[case(536469400, -36.408345829901926)]
171 #[case(536470400, -36.40835691269934)]
172 #[case(536471400, -36.40836801204682)]
173 #[case(536472400, -36.40837912801728)]
174 #[case(536473400, -36.40839026068361)]
175 #[case(536474400, -36.40840141011872)]
176 #[case(536475400, -36.40841257639551)]
177 #[case(536476400, -36.4084237595869)]
178 #[case(536477400, -36.40843495976578)]
179 #[case(536478400, -36.408446177005054)]
180 #[case(536479400, -36.40845741137764)]
181 #[case(536480400, -36.40846866295642)]
182 #[case(536481400, -36.40847993181432)]
183 #[case(536482400, -36.40849121802424)]
184 #[case(536483400, -36.408502521659074)]
185 #[case(536484400, -36.40851384279173)]
186 #[case(536485400, -36.40852518149512)]
187 #[case(536486400, -36.40853653784214)]
188 #[case(536487400, -36.4085479119057)]
189 #[case(536488400, -36.40855930375871)]
190 #[case(536489400, -36.408570713474056)]
191 #[case(536490400, -36.40858214112466)]
192 #[case(536491400, -36.40859358678342)]
193 #[case(536492400, -36.408605050523235)]
194 #[case(536493400, -36.408616532417014)]
195 #[case(536494400, -36.40862803253767)]
196 #[case(536495400, -36.40863955095809)]
197 #[case(536496400, -36.408651087751196)]
198 #[case(536497400, -36.40866264298989)]
199 #[case(536498400, -36.40867421674706)]
200 #[case(536499400, -36.40868580909562)]
201 #[case(536500400, -36.40869742010849)]
202 fn test_delta_ut1_tai_orekit(
203 #[case] seconds: i64,
204 #[case] expected: f64,
205 provider: &EopProvider,
206 ) {
207 let tai = Time::new(Tai, seconds, Subsecond::default());
208 let ut1 = Time::new(Ut1, seconds, Subsecond::default());
209 let actual = provider
210 .delta_ut1_tai(tai.to_delta())
211 .unwrap()
212 .to_seconds()
213 .to_f64();
214 assert_approx_eq!(actual, expected, rtol <= 1e-6);
215 let actual = provider
216 .delta_tai_ut1(ut1.to_delta())
217 .unwrap()
218 .to_seconds()
219 .to_f64();
220 assert_approx_eq!(actual, -expected, rtol <= 1e-6);
221 }
222
223 #[rstest]
224 #[case(time!(Tai, 1973, 1, 1).unwrap())]
225 #[case(time!(Tai, 2100, 1, 1).unwrap())]
226 fn test_delta_ut1_tai_extrapolation(#[case] time: Time<Tai>, provider: &EopProvider) {
227 let act = provider.delta_ut1_tai(time.to_delta()).unwrap_err();
228 assert!(matches!(act, EopProviderError::ExtrapolatedValue(_)));
229 let ut1 = time.with_scale(Ut1);
230 let act = provider.delta_tai_ut1(ut1.to_delta()).unwrap_err();
231 assert!(matches!(act, EopProviderError::ExtrapolatedValue(_)));
232 }
233
234 const UT1_TOL: f64 = 1e-2;
235
236 #[rstest]
242 #[case::tai_ut1("TAI", "UT1", -36.949521832072996)]
243 #[case::tcb_ut1("TCB", "UT1", -92.61803559995818)]
244 #[case::tcg_ut1("TCG", "UT1", -70.1891114139689)]
245 #[case::tdb_ut1("TDB", "UT1", -69.13340440689674)]
246 #[case::tt_ut1("TT", "UT1", -69.13352209269237)]
247 #[case::ut1_tai("UT1", "TAI", 36.949521532869305)]
248 #[case::ut1_tcb("UT1", "TCB", 92.61803631703046)]
249 #[case::ut1_tcg("UT1", "TCG", 70.18911089451464)]
250 #[case::ut1_tdb("UT1", "TDB", 69.13340387022173)]
251 #[case::ut1_tt("UT1", "TT", 69.13352153286931)]
252 #[case::ut1_ut1("UT1", "UT1", 0.0)]
253 fn test_dyn_time_scale_ut1(
254 #[case] scale1: &str,
255 #[case] scale2: &str,
256 #[case] exp: f64,
257 provider: &EopProvider,
258 ) {
259 let scale1: DynTimeScale = scale1.parse().unwrap();
260 let scale2: DynTimeScale = scale2.parse().unwrap();
261 let date = Date::new(2024, 12, 30).unwrap();
262 let time = TimeOfDay::from_hms(10, 27, 13.145).unwrap();
263 let dt = DynTime::from_date_and_time(scale1, date, time)
264 .unwrap()
265 .to_delta();
266 let act = provider
267 .try_offset(scale1, scale2, dt)
268 .unwrap()
269 .to_seconds()
270 .to_f64();
271 assert_approx_eq!(act, exp, rtol <= UT1_TOL);
272 }
273
274 #[test]
275 fn test_ut1_to_utc() {
276 let tai = time!(Tai, 2024, 5, 17, 12, 13, 14.0).unwrap();
277 let exp = tai.to_utc();
278 let ut1 = tai.try_to_scale(Ut1, provider()).unwrap();
279 let act = ut1.try_to_scale(Tai, provider()).unwrap().to_utc();
280 assert_eq!(act, exp);
281 }
282
283 #[fixture]
284 fn provider() -> &'static EopProvider {
285 static PROVIDER: OnceLock<EopProvider> = OnceLock::new();
286 PROVIDER.get_or_init(|| {
287 EopParser::new()
288 .from_paths(
289 data_file("iers/finals.all.csv"),
290 data_file("iers/finals2000A.all.csv"),
291 )
292 .with_leap_seconds_kernel(
293 LeapSecondsKernel::from_file(data_file("spice/naif0012.tls")).unwrap(),
294 )
295 .parse()
296 .unwrap()
297 })
298 }
299}