1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#[cfg(not(feature = "std"))]
use num_traits::Float;

#[cfg(not(feature = "std"))]
use num_traits::Euclid;

/// Model of the Earth radius and gravitational field
#[derive(Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Geopotential {
    /// Equatorial radius of the earth in km
    // aₑ
    pub ae: f64,

    /// square root of earth's gravitational parameter in earth radii³ min⁻²
    // kₑ
    pub ke: f64,

    /// un-normalised second zonal harmonic
    // J₂
    pub j2: f64,

    /// un-normalised third zonal harmonic
    // J₃
    pub j3: f64,

    /// un-normalised fourth zonal harmonic
    // J₄
    pub j4: f64,
}

/// The geopotential model recommended by the IAU
///
/// This model is recommended to propagate orbits.
pub const WGS84: Geopotential = Geopotential {
    ae: 6378.137,
    ke: 0.07436685316871385,
    j2: 0.00108262998905,
    j3: -0.00000253215306,
    j4: -0.00000161098761,
};

/// The geopotential model used in the AFSPC implementation
///
/// This model should be used if compatibility with the AFSPC implementation is needed.
pub const WGS72: Geopotential = Geopotential {
    ae: 6378.135,
    ke: 0.07436691613317342,
    j2: 0.001082616,
    j3: -0.00000253881,
    j4: -0.00000165597,
};

/// Converts an epoch to sidereal time using the IAU expression
///
/// This is the recommended method to calculate the sidereal time.
///
/// # Arguments
///
/// * `epoch` - Years since UTC 1 January 2000 12h00
pub fn iau_epoch_to_sidereal_time(epoch: f64) -> f64 {
    // c₂₀₀₀ = y₂₀₀₀ / 100
    let c2000 = epoch / 100.0;

    // θ₀ = ¹/₂₄₀ (π / 180) (- 6.2 × 10⁻⁶ c₂₀₀₀³ + 0.093104 c₂₀₀₀²
    //      + (876600 × 3600 + 8640184.812866) c₂₀₀₀ + 67310.54841) mod 2π
    ((-6.2e-6 * c2000.powi(3)
        + 0.093104 * c2000.powi(2)
        + (876600.0 * 3600.0 + 8640184.812866) * c2000
        + 67310.54841)
        * (core::f64::consts::PI / 180.0)
        / 240.0)
        .rem_euclid({
            #[cfg(feature = "std")]
            {
                2.0 * core::f64::consts::PI
            }
            #[cfg(not(feature = "std"))]
            {
                &(2.0 * core::f64::consts::PI)
            }
        })
}

/// Converts an epoch to sidereal time using the AFSPC expression
///
/// This function should be used if compatibility with the AFSPC implementation is needed.
///
/// # Arguments
///
/// * `epoch` - Years since UTC 1 January 2000 12h00
pub fn afspc_epoch_to_sidereal_time(epoch: f64) -> f64 {
    // d₁₉₇₀ = 365.25 (y₂₀₀₀ + 30) + 1
    let d1970 = (epoch + 30.0) * 365.25 + 1.0;

    // θ₀ = 1.7321343856509374 + 1.72027916940703639 × 10⁻² ⌊t₁₉₇₀ + 10⁻⁸⌋
    //      + (1.72027916940703639 × 10⁻² + 2π) (t₁₉₇₀ - ⌊t₁₉₇₀ + 10⁻⁸⌋)
    //      + 5.07551419432269442 × 10⁻¹⁵ t₁₉₇₀² mod 2π
    #[allow(clippy::excessive_precision)]
    (1.7321343856509374
        + 1.72027916940703639e-2 * (d1970 + 1.0e-8).floor()
        + (1.72027916940703639e-2 + 2.0 * core::f64::consts::PI)
            * (d1970 - (d1970 + 1.0e-8).floor())
        + d1970.powi(2) * 5.07551419432269442e-15)
        .rem_euclid({
            #[cfg(feature = "std")]
            {
                2.0 * core::f64::consts::PI
            }
            #[cfg(not(feature = "std"))]
            {
                &(2.0 * core::f64::consts::PI)
            }
        })
}