Skip to content

Commit c9917ba

Browse files
committed
feat(lox-earth): add sidereal time calculations
1 parent e9081e7 commit c9917ba

File tree

8 files changed

+461
-28
lines changed

8 files changed

+461
-28
lines changed

crates/lox-earth/src/itrf.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use crate::coordinate_transformations::{
88
PoleCoords, celestial_to_intermediate_frame_of_date_matrix, polar_motion_matrix,
99
};
1010
use crate::eop::{EopProvider, EopProviderError};
11-
use crate::rotation::earth_rotation_angle_00;
1211
use crate::tio::tio_locator;
1312
use glam::{DMat3, DVec3};
1413
use lox_bodies::{Earth, RotationalElements};
@@ -52,11 +51,12 @@ where
5251
}
5352

5453
pub fn cirf_to_tirf(seconds: f64) -> Rotation {
55-
let era = earth_rotation_angle_00(seconds / SECONDS_PER_DAY);
56-
let rate = Earth.rotation_rate(seconds);
57-
let m = DMat3::from_rotation_z(-era.to_radians());
58-
let v = DVec3::new(0.0, 0.0, rate);
59-
Rotation::new(m).with_angular_velocity(v)
54+
// let era = earth_rotation_angle_00(seconds / SECONDS_PER_DAY);
55+
// let rate = Earth.rotation_rate(seconds);
56+
// let m = DMat3::from_rotation_z(-era.to_radians());
57+
// let v = DVec3::new(0.0, 0.0, rate);
58+
// Rotation::new(m).with_angular_velocity(v)
59+
todo!()
6060
}
6161

6262
pub fn tirf_to_itrf(centuries: f64) -> Rotation {

crates/lox-earth/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod ephemeris;
1313
pub mod fundamental;
1414
pub mod itrf;
1515
pub mod nutation;
16+
pub mod precession;
1617
pub mod rotation;
1718
#[allow(dead_code)]
1819
pub mod tides;

crates/lox-earth/src/precession.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-FileCopyrightText: 2025 Helge Eichhorn <[email protected]>
2+
//
3+
// SPDX-License-Identifier: MPL-2.0
4+
5+
use lox_core::units::Angle;
6+
use lox_time::{Time, julian_dates::JulianDate, time_scales::Tt};
7+
8+
use crate::nutation::Nutation;
9+
10+
const PRECOR: Angle = Angle::arcseconds(-0.29965);
11+
const OBLCOR: Angle = Angle::arcseconds(-0.02524);
12+
13+
pub type PrecessionCorrections = Nutation;
14+
15+
pub fn precession_rate_iau2000(time: Time<Tt>) -> PrecessionCorrections {
16+
let t = time.centuries_since_j2000();
17+
18+
PrecessionCorrections {
19+
longitude: t * PRECOR,
20+
obliquity: t * OBLCOR,
21+
}
22+
}
23+
24+
#[cfg(test)]
25+
mod tests {
26+
use lox_core::units::AngleUnits;
27+
use lox_test_utils::assert_approx_eq;
28+
29+
use super::*;
30+
31+
#[test]
32+
fn test_precession_rate_iau2000() {
33+
let time = Time::from_two_part_julian_date(Tt, 2400000.5, 53736.0);
34+
let exp = PrecessionCorrections {
35+
longitude: -8.716_465_172_668_348e-8.rad(),
36+
obliquity: -7.342_018_386_722_813e-9.rad(),
37+
};
38+
let act = precession_rate_iau2000(time);
39+
assert_approx_eq!(act, exp, atol <= 1e-22);
40+
}
41+
}

crates/lox-earth/src/rotation.rs

Lines changed: 224 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,243 @@
66
//! Module rotation_angle exposes functions for calculating the Earth Rotation Angle (ERA).
77
88
use std::f64::consts::TAU;
9+
use std::iter::zip;
910

10-
use lox_core::types::units::Days;
11+
use fast_polynomial::poly_array;
1112
use lox_core::units::{Angle, AngleUnits};
12-
use lox_time::{Time, julian_dates::JulianDate, time_scales::TimeScale};
13+
use lox_test_utils::ApproxEq;
14+
use lox_time::time_scales::{Tdb, Tt, Ut1};
15+
use lox_time::{Time, julian_dates::JulianDate};
1316

14-
pub trait EarthRotation: JulianDate {
15-
fn earth_rotation_angle_iau2000(&self) -> Angle;
16-
fn equation_of_the_equinoxes(&self);
17+
use crate::ecliptic::MeanObliquity;
18+
use crate::fundamental::iers03::{
19+
d_iers03, earth_l_iers03, f_iers03, l_iers03, lp_iers03, omega_iers03, pa_iers03,
20+
venus_l_iers03,
21+
};
22+
use crate::nutation::Nutation;
23+
use crate::precession::precession_rate_iau2000;
24+
25+
mod complementary_terms;
26+
27+
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, ApproxEq)]
28+
pub struct EarthRotationAngle(pub Angle);
29+
30+
impl EarthRotationAngle {
31+
pub fn iau2000(time: Time<Ut1>) -> Self {
32+
let d = time.days_since_j2000();
33+
let f = d.rem_euclid(1.0); // fractional part of t
34+
Self(
35+
(TAU * (f + 0.7790572732640 + 0.00273781191135448 * d))
36+
.rad()
37+
.mod_two_pi(),
38+
)
39+
}
40+
}
41+
42+
pub type Era = EarthRotationAngle;
43+
44+
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, ApproxEq)]
45+
pub struct GreenwichMeanSiderealTime(pub Angle);
46+
47+
pub type Gmst = GreenwichMeanSiderealTime;
48+
49+
impl GreenwichMeanSiderealTime {
50+
pub fn iau2000(tt: Time<Tt>, ut1: Time<Ut1>) -> Self {
51+
let t = tt.centuries_since_j2000();
52+
Self(
53+
EarthRotationAngle::iau2000(ut1).0
54+
+ Angle::arcseconds(poly_array(
55+
t,
56+
&[0.014506, 4612.15739966, 1.39667721, -0.00009344, 0.00001882],
57+
))
58+
.mod_two_pi(),
59+
)
60+
}
61+
62+
pub fn iau2006(tt: Time<Tt>, ut1: Time<Ut1>) -> Self {
63+
let t = tt.centuries_since_j2000();
64+
Self(
65+
EarthRotationAngle::iau2000(ut1).0
66+
+ Angle::arcseconds(poly_array(
67+
t,
68+
&[
69+
0.014506,
70+
4612.156534,
71+
1.3915817,
72+
-0.00000044,
73+
-0.000029956,
74+
-0.0000000368,
75+
],
76+
))
77+
.mod_two_pi(),
78+
)
79+
}
1780
}
1881

19-
// impl<T> EarthRotation for Time<T> where T: TimeScale {}
82+
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, ApproxEq)]
83+
pub struct GreenwichApparentSiderealTime(pub Angle);
2084

21-
pub fn earth_rotation_angle_00(days_since_j2000_ut1: Days) -> Angle {
22-
let f = days_since_j2000_ut1.rem_euclid(1.0); // fractional part of t
23-
let era = (TAU * (f + 0.7790572732640 + 0.00273781191135448 * days_since_j2000_ut1)).rad();
24-
era.mod_two_pi()
85+
pub type Gast = GreenwichApparentSiderealTime;
86+
87+
impl GreenwichApparentSiderealTime {
88+
pub fn iau2000a(tt: Time<Tt>, ut1: Time<Ut1>) -> Self {
89+
Self(
90+
(GreenwichMeanSiderealTime::iau2000(tt, ut1).0
91+
+ EquationOfTheEquinoxes::iau2000a(tt).0)
92+
.mod_two_pi(),
93+
)
94+
}
95+
96+
pub fn iau2000b(tt: Time<Tt>, ut1: Time<Ut1>) -> Self {
97+
Self(
98+
(GreenwichMeanSiderealTime::iau2000(tt, ut1).0
99+
+ EquationOfTheEquinoxes::iau2000b(tt).0)
100+
.mod_two_pi(),
101+
)
102+
}
103+
}
104+
105+
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, ApproxEq)]
106+
pub struct EquationOfTheEquinoxes(pub Angle);
107+
108+
impl EquationOfTheEquinoxes {
109+
pub fn iau2000a(time: Time<Tt>) -> Self {
110+
let Nutation {
111+
obliquity: depspr, ..
112+
} = precession_rate_iau2000(time);
113+
let epsa = MeanObliquity::iau1980(time).0 + depspr;
114+
let Nutation {
115+
longitude: dpsi, ..
116+
} = Nutation::iau2000a(time.with_scale(Tdb));
117+
Self::iau2000(time, epsa, dpsi)
118+
}
119+
120+
pub fn iau2000b(time: Time<Tt>) -> Self {
121+
let Nutation {
122+
obliquity: depspr, ..
123+
} = precession_rate_iau2000(time);
124+
let epsa = MeanObliquity::iau1980(time).0 + depspr;
125+
let Nutation {
126+
longitude: dpsi, ..
127+
} = Nutation::iau2000b(time.with_scale(Tdb));
128+
Self::iau2000(time, epsa, dpsi)
129+
}
130+
131+
pub fn iau2000(time: Time<Tt>, epsa: Angle, dpsi: Angle) -> Self {
132+
Self(epsa.cos() * dpsi + Self::complimentary_terms_iau2000(time))
133+
}
134+
135+
fn complimentary_terms_iau2000(time: Time<Tt>) -> Angle {
136+
let t = time.centuries_since_j2000();
137+
138+
let fa = [
139+
l_iers03(t).as_f64(),
140+
lp_iers03(t).as_f64(),
141+
f_iers03(t).as_f64(),
142+
d_iers03(t).as_f64(),
143+
omega_iers03(t).as_f64(),
144+
venus_l_iers03(t).as_f64(),
145+
earth_l_iers03(t).as_f64(),
146+
pa_iers03(t).as_f64(),
147+
];
148+
149+
let s0 = complementary_terms::E0.iter().rev().fold(0.0, |s0, term| {
150+
let a = zip(&term.nfa, &fa).fold(0.0, |a, (&nfa, &fa)| a + nfa as f64 * fa);
151+
let (sa, ca) = a.sin_cos();
152+
s0 + term.s * sa + term.c * ca
153+
});
154+
155+
let s1 = complementary_terms::E1.iter().rev().fold(0.0, |s1, term| {
156+
let a = zip(&term.nfa, &fa).fold(0.0, |a, (&nfa, &fa)| a + nfa as f64 * fa);
157+
let (sa, ca) = a.sin_cos();
158+
s1 + term.s * sa + term.c * ca
159+
});
160+
161+
Angle::arcseconds(s0 + s1 * t)
162+
}
25163
}
26164

27165
#[cfg(test)]
28166
mod tests {
29167
use lox_test_utils::assert_approx_eq;
30168

31-
use rstest::rstest;
32-
33169
use super::*;
34170

35-
#[rstest]
36-
#[case::before_j2000(-123.45, 6.227104062035152.rad())]
37-
#[case::j2000(0.0, 4.894961212823756.rad())]
38-
#[case::after_j2000(123.45, 3.562818363612361.rad())]
39-
fn test_rotation_angle_00(#[case] days_since_j2000_ut1: Days, #[case] expected: Angle) {
40-
let actual = earth_rotation_angle_00(days_since_j2000_ut1);
41-
assert_approx_eq!(expected, actual, rtol <= 1e-9);
171+
#[test]
172+
fn test_earth_rotation_angle_iau2000() {
173+
let time = Time::from_two_part_julian_date(Ut1, 2400000.5, 54388.0);
174+
let exp = EarthRotationAngle(Angle::new(0.402_283_724_002_815_8));
175+
let act = EarthRotationAngle::iau2000(time);
176+
assert_approx_eq!(act, exp, rtol <= 1e-12);
177+
}
178+
179+
#[test]
180+
fn test_greenwich_apparent_sidereal_time_iau2000a() {
181+
let tt = Time::from_two_part_julian_date(Tt, 2400000.5, 53736.0);
182+
let ut1 = Time::from_two_part_julian_date(Ut1, 2400000.5, 53736.0);
183+
let exp = GreenwichApparentSiderealTime(Angle::new(1.754_166_138_018_281_4));
184+
let act = Gast::iau2000a(tt, ut1);
185+
assert_approx_eq!(act, exp, rtol <= 1e-12);
186+
}
187+
188+
#[test]
189+
fn test_greenwich_apparent_sidereal_time_iau2000b() {
190+
let tt = Time::from_two_part_julian_date(Tt, 2400000.5, 53736.0);
191+
let ut1 = Time::from_two_part_julian_date(Ut1, 2400000.5, 53736.0);
192+
let exp = GreenwichApparentSiderealTime(Angle::new(1.754_166_136_510_680_7));
193+
let act = Gast::iau2000b(tt, ut1);
194+
assert_approx_eq!(act, exp, rtol <= 1e-12);
195+
}
196+
197+
#[test]
198+
fn test_greenwich_mean_sidereal_time_iau2000() {
199+
let tt = Time::from_two_part_julian_date(Tt, 2400000.5, 53736.0);
200+
let ut1 = Time::from_two_part_julian_date(Ut1, 2400000.5, 53736.0);
201+
let exp = GreenwichMeanSiderealTime(Angle::new(1.754_174_972_210_740_7));
202+
let act = Gmst::iau2000(tt, ut1);
203+
assert_approx_eq!(act, exp, rtol <= 1e-12);
204+
}
205+
206+
#[test]
207+
fn test_greenwich_mean_sidereal_time_iau2006() {
208+
let tt = Time::from_two_part_julian_date(Tt, 2400000.5, 53736.0);
209+
let ut1 = Time::from_two_part_julian_date(Ut1, 2400000.5, 53736.0);
210+
let exp = GreenwichMeanSiderealTime(Angle::new(1.754_174_971_870_091_2));
211+
let act = Gmst::iau2006(tt, ut1);
212+
assert_approx_eq!(act, exp, rtol <= 1e-12);
213+
}
214+
215+
#[test]
216+
fn test_equation_of_the_equinoxes_iau2000a() {
217+
let time = Time::from_two_part_julian_date(Tt, 2400000.5, 53736.0);
218+
let exp = EquationOfTheEquinoxes(Angle::new(-8.834_192_459_222_587e-6));
219+
let act = EquationOfTheEquinoxes::iau2000a(time);
220+
assert_approx_eq!(act, exp, atol <= 1e-18);
221+
}
222+
223+
#[test]
224+
fn test_equation_of_the_equinoxes_iau2000b() {
225+
let time = Time::from_two_part_julian_date(Tt, 2400000.5, 53736.0);
226+
let exp = EquationOfTheEquinoxes(Angle::new(-8.835_700_060_003_032e-6));
227+
let act = EquationOfTheEquinoxes::iau2000b(time);
228+
assert_approx_eq!(act, exp, atol <= 1e-18);
229+
}
230+
231+
#[test]
232+
fn test_equation_of_the_equinoxes_iau2000() {
233+
let epsa = Angle::new(0.409_078_976_335_651);
234+
let dpsi = Angle::new(-9.630_909_107_115_582e-6);
235+
let time = Time::from_two_part_julian_date(Tt, 2400000.5, 53736.0);
236+
let exp = EquationOfTheEquinoxes(Angle::new(-8.834_193_235_367_966e-6));
237+
let act = EquationOfTheEquinoxes::iau2000(time, epsa, dpsi);
238+
assert_approx_eq!(act, exp, atol <= 1e-20);
239+
}
240+
241+
#[test]
242+
fn test_equation_of_the_equinoxes_complimentary_terms() {
243+
let time = Time::from_two_part_julian_date(Tt, 2400000.5, 53736.0);
244+
let exp = Angle::new(2.046_085_004_885_125e-9);
245+
let act = EquationOfTheEquinoxes::complimentary_terms_iau2000(time);
246+
assert_approx_eq!(act, exp, atol <= 1e-20);
42247
}
43248
}

0 commit comments

Comments
 (0)