deep_time/sidereal/mod.rs
1//! Sidereal rotation and time calculations for celestial bodies.
2//!
3//! [`Sidereal`] struct with ready-to-use `EARTH`, `MARS`, `MOON` constants.
4//! Computes rotation angle, LMST/LAST, GMST/GAST.
5//!
6//! With the `"sidereal-earth"` feature enabled a rust implementation of the
7//! ERFA Earth Equation of the Origins / Equinoxes are both available as well.
8
9#[cfg(feature = "sidereal-earth")]
10pub mod earth_eo_ee;
11
12use crate::Real;
13use core::f64::consts::TAU;
14
15#[cfg(feature = "sidereal-earth")]
16use earth_eo_ee::*;
17
18/// Represents the rotational state of a celestial body and provides
19/// methods to compute the orientation of its prime meridian at any
20/// given time.
21///
22/// The rotation angle of the prime meridian is the basis for
23/// calculating local sidereal time. Local sidereal time is required
24/// to compute the hour angle of a celestial object (HA = LST − RA),
25/// to determine when an object will cross the local meridian,
26/// to convert between horizon coordinates (altitude/azimuth) and
27/// equatorial coordinates, and to calculate accurate pointing
28/// directions for telescopes and spacecraft antennas.
29///
30/// The struct implements the modern CIO-based rotation model and
31/// works for any rotating body (Earth, Mars, the Moon, etc.) by
32/// supplying the appropriate rotation rate and reference values.
33///
34/// ## Fields
35///
36/// * `rate_rad_per_sec` — Mean sidereal rotation rate in radians per SI second.
37/// * `ref_epoch` — Reference epoch (MJD) at which `ref_angle_rad` is defined.
38/// * `ref_angle_rad` — Rotation angle of the prime meridian at `ref_epoch`.
39/// * `longitude_rad` — Observer longitude on the body (radians, east positive).
40/// `0.0` corresponds to the body's prime meridian.
41/// * `correction_rad` — General-purpose additive correction in radians.
42///
43/// ## Examples
44///
45/// Basic usage with Earth constants:
46///
47/// ```rust
48/// use deep_time::Sidereal;
49///
50/// let mut earth = Sidereal::EARTH;
51/// earth.longitude_rad = 0.0; // Greenwich
52///
53/// let mjd = 60000.0;
54/// let era = earth.rotation_angle(mjd);
55///
56/// // Local Mean Sidereal Time using the mean Equation of the Origins
57/// // (requires the "sidereal-earth" feature)
58/// # #[cfg(feature = "sidereal-earth")] {
59/// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
60/// let lmst = earth.local_sidereal_time_mean(mjd, eo_mean);
61/// # }
62/// ```
63///
64/// Realistic usage with DUT1 correction (UT1 time scale):
65///
66/// ```rust
67/// // This advanced example requires the "eop" feature for EopData
68/// // and "sidereal-earth" for the EO calculations.
69/// # #[cfg(all(feature = "eop", feature = "sidereal-earth"))] {
70/// use deep_time::Dt;
71/// use deep_time::Sidereal;
72/// use deep_time::eop::{EopData, EopFormat, Separator};
73///
74/// let eop = EopData::from_text_file(
75/// "finals.all.iau2000.txt",
76/// EopFormat::Finals2000A,
77/// Separator::Whitespace,
78/// ).unwrap();
79///
80/// let mjd_utc = 56879.0;
81/// let dut1 = Dt::mjd_to_eop_offset_f(mjd_utc, &eop).unwrap();
82/// let mjd_ut1 = mjd_utc + dut1 / 86400.0;
83///
84/// let earth = Sidereal::EARTH;
85///
86/// let era = earth.rotation_angle(mjd_ut1);
87///
88/// let eo_mean = earth.earth_eo_mean(mjd_ut1 + 32.184 / 86400.0);
89/// let gmst = earth.sidereal_angle_mean(mjd_ut1, eo_mean);
90///
91/// // Local Mean Sidereal Time
92/// let lmst = earth.local_sidereal_time_mean(mjd_ut1, eo_mean);
93/// # }
94/// ```
95#[derive(Debug, Clone, Copy)]
96pub struct Sidereal {
97 /// Mean sidereal rotation rate in **radians per SI second**.
98 pub rate_rad_per_sec: Real,
99 /// Reference epoch.
100 pub ref_epoch: Real,
101 /// Rotation angle of the prime meridian (radians) at `ref_epoch`.
102 pub ref_angle_rad: Real,
103 /// Longitude of the observer on the body (radians, east positive).
104 /// `0.0` = body's prime meridian.
105 pub longitude_rad: Real,
106 /// General scalar correction in radians.
107 pub correction_rad: Real,
108}
109
110impl Sidereal {
111 /// Pre-configured `Sidereal` for Earth using IAU 2000/2006 conventions.
112 ///
113 /// This uses:
114 /// - The conventional mean sidereal rotation rate of Earth.
115 /// - J2000.0 as the reference epoch (`ref_epoch = 51544.5`).
116 /// - The Earth Rotation Angle (ERA) at J2000.0 as `ref_angle_rad`.
117 ///
118 /// You can still customize fields after construction (e.g. `longitude_rad`
119 /// or `correction_rad`).
120 pub const EARTH: Self = Self {
121 rate_rad_per_sec: (1.00273781191135448 * core::f64::consts::TAU) / 86400.0,
122 ref_epoch: 51544.5,
123 ref_angle_rad: 0.7790572732640 * core::f64::consts::TAU,
124 longitude_rad: 0.0,
125 correction_rad: 0.0,
126 };
127
128 /// Pre-configured `Sidereal` for Mars.
129 ///
130 /// Uses a simplified mean sidereal rotation rate and J2000.0 as the
131 /// reference epoch. `ref_angle_rad` is set to zero (no specific
132 /// reference angle is defined).
133 ///
134 /// You can customize fields (especially `longitude_rad`) after construction.
135 pub const MARS: Self = Self {
136 rate_rad_per_sec: core::f64::consts::TAU / 88642.663,
137 ref_epoch: 51544.5,
138 ref_angle_rad: 0.0,
139 longitude_rad: 0.0,
140 correction_rad: 0.0,
141 };
142
143 /// Pre-configured `Sidereal` for the Moon.
144 ///
145 /// Uses a simplified mean sidereal rotation rate and J2000.0 as the
146 /// reference epoch. `ref_angle_rad` is set to zero (no specific
147 /// reference angle is defined).
148 ///
149 /// You can customize fields (especially `longitude_rad`) after construction.
150 pub const MOON: Self = Self {
151 rate_rad_per_sec: core::f64::consts::TAU / 2_360_591.424,
152 ref_epoch: 51544.5,
153 ref_angle_rad: 0.0,
154 longitude_rad: 0.0,
155 correction_rad: 0.0,
156 };
157
158 // Normalize to [0, 2π)
159 #[inline]
160 const fn normalize_angle(angle: Real) -> Real {
161 ((angle % TAU) + TAU) % TAU
162 }
163
164 /// Returns the instantaneous rotation angle of the body's prime meridian
165 /// (in radians) at the given instant, normalized to `[0, 2π)`.
166 ///
167 /// For Earth this is the pure Earth Rotation Angle (ERA) in the
168 /// Celestial Intermediate Origin (CIO) frame. It does **not** include
169 /// observer longitude or the Equation of the Origins.
170 ///
171 /// Matches Astropy's `Time.earth_rotation_angle(longitude=None)`
172 /// (or with `longitude=0`).
173 ///
174 /// ## Examples
175 ///
176 /// ```rust
177 /// use deep_time::Sidereal;
178 ///
179 /// let era = Sidereal::EARTH.rotation_angle(57753.5);
180 /// ```
181 pub const fn rotation_angle(&self, mjd: Real) -> Real {
182 // elapsed time in seconds between ref_epoch (MJD) and the given mjd
183 let elapsed_days = mjd - self.ref_epoch;
184 let elapsed_sec = elapsed_days * 86400.0;
185
186 let angle = self.ref_angle_rad + self.rate_rad_per_sec * elapsed_sec + self.correction_rad;
187
188 Self::normalize_angle(angle)
189 }
190
191 /// Returns the rotation angle of the prime meridian at the observer's
192 /// longitude, normalized to `[0, 2π)`.
193 ///
194 /// This is equivalent to `rotation_angle(mjd) + self.longitude_rad`.
195 /// It gives the angle between the Celestial Intermediate Origin (CIO)
196 /// and the observer’s local meridian.
197 ///
198 /// This value is commonly used when computing the local hour angle
199 /// of a celestial object:
200 ///
201 /// ```text
202 /// HA = local_rotation_angle(mjd) - RA
203 /// ```
204 ///
205 /// ## Examples
206 ///
207 /// ```rust
208 /// use deep_time::Sidereal;
209 ///
210 /// let mut earth = Sidereal::EARTH;
211 /// earth.longitude_rad = 0.0; // Greenwich
212 ///
213 /// let mjd = 60000.0;
214 /// let local_era = earth.local_rotation_angle(mjd);
215 /// ```
216 #[inline]
217 pub const fn local_rotation_angle(&self, mjd: Real) -> Real {
218 Self::normalize_angle(self.rotation_angle(mjd) + self.longitude_rad)
219 }
220
221 /// Returns the sidereal angle of the body's prime meridian in radians,
222 /// normalized to `[0, 2π)`.
223 ///
224 /// This computes Greenwich Mean Sidereal Time (GMST) when an appropriate
225 /// Equation of the Origins value is supplied.
226 ///
227 /// ## Parameters
228 ///
229 /// - `eo_rad`: The Equation of the Origins value to subtract from the
230 /// Earth Rotation Angle (ERA).
231 /// - Pass `0.0` to get the pure CIO-based rotation angle (ERA).
232 /// - Pass the **mean** Equation of the Origins (e.g. from
233 /// [`earth_eo_mean`](Self::earth_eo_mean)) to obtain GMST.
234 ///
235 /// ## Details
236 ///
237 /// - When `eo_rad = 0.0`, the result is the modern Earth Rotation Angle (ERA)
238 /// relative to the Celestial Intermediate Origin (CIO).
239 ///
240 /// - When `eo_rad` is the mean Equation of the Origins (i.e. the value that
241 /// satisfies `GMST = ERA − eo_rad`), the result is Greenwich Mean Sidereal
242 /// Time (GMST) referred to the mean equinox. This is the traditional
243 /// equinox-based mean sidereal time.
244 ///
245 /// ## Examples
246 ///
247 /// ```rust
248 /// use deep_time::Sidereal;
249 ///
250 /// let earth = Sidereal::EARTH;
251 /// let mjd = 60000.0;
252 ///
253 /// // Pure CIO-based rotation angle (Earth Rotation Angle)
254 /// let era = earth.sidereal_angle_mean(mjd, 0.0);
255 ///
256 /// // Traditional mean sidereal time using the mean Equation of the Origins
257 /// // (requires "sidereal-earth" feature)
258 /// # #[cfg(feature = "sidereal-earth")] {
259 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
260 /// let gmst = earth.sidereal_angle_mean(mjd, eo_mean);
261 /// # }
262 /// ```
263 #[inline]
264 pub const fn sidereal_angle_mean(&self, mjd: Real, eo_rad: Real) -> Real {
265 let angle = self.rotation_angle(mjd) - eo_rad;
266 Self::normalize_angle(angle)
267 }
268
269 /// Returns the local sidereal angle at the observer's longitude in radians,
270 /// normalized to `[0, 2π)`.
271 ///
272 /// This computes **Local Mean Sidereal Time (LMST)** when an appropriate
273 /// Equation of the Origins value is supplied.
274 ///
275 /// ## Parameters
276 ///
277 /// - `eo_rad`: The Equation of the Origins value to subtract from the
278 /// Earth Rotation Angle (ERA).
279 /// - Pass `0.0` to get the pure local Earth Rotation Angle (CIO-based).
280 /// - Pass the **mean** Equation of the Origins (e.g. from
281 /// [`earth_eo_mean`](Self::earth_eo_mean)) to obtain Local Mean
282 /// Sidereal Time (LMST).
283 ///
284 /// ## Details
285 ///
286 /// - When `eo_rad = 0.0`, the result is the local Earth Rotation Angle
287 /// relative to the Celestial Intermediate Origin (CIO) at the observer’s
288 /// longitude.
289 ///
290 /// - When `eo_rad` is the mean Equation of the Origins, the result is
291 /// **Local Mean Sidereal Time (LMST)** referred to the mean equinox.
292 ///
293 /// This value is commonly used when calculating the local hour angle of a
294 /// celestial object:
295 ///
296 /// ```text
297 /// HA = local_sidereal_angle_mean(mjd, eo) − RA
298 /// ```
299 ///
300 /// ## Examples
301 ///
302 /// ```rust
303 /// use deep_time::Sidereal;
304 ///
305 /// let mut earth = Sidereal::EARTH;
306 /// earth.longitude_rad = 0.0; // Greenwich
307 ///
308 /// let mjd = 60000.0;
309 ///
310 /// // Pure local Earth Rotation Angle (CIO-based)
311 /// let local_era = earth.local_sidereal_angle_mean(mjd, 0.0);
312 ///
313 /// // Local Mean Sidereal Time using the mean Equation of the Origins
314 /// // (requires "sidereal-earth" feature)
315 /// # #[cfg(feature = "sidereal-earth")] {
316 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
317 /// let lmst = earth.local_sidereal_angle_mean(mjd, eo_mean);
318 /// # }
319 /// ```
320 #[inline]
321 pub const fn local_sidereal_angle_mean(&self, mjd: Real, eo_rad: Real) -> Real {
322 let angle = self.rotation_angle(mjd) + self.longitude_rad - eo_rad;
323 Self::normalize_angle(angle)
324 }
325
326 /// Returns sidereal time at the body's prime meridian as seconds since
327 /// sidereal midnight, wrapped to the range `[0, 86400)`.
328 ///
329 /// This is the time equivalent of [`sidereal_angle_mean`].
330 ///
331 /// ## Parameters
332 ///
333 /// - `eo_rad`: The Equation of the Origins value to use.
334 /// - Pass `0.0` to get the time equivalent of the pure Earth Rotation Angle (ERA).
335 /// - Pass the **mean** Equation of the Origins (e.g. from
336 /// [`earth_eo_mean`](Self::earth_eo_mean)) to obtain Greenwich Mean
337 /// Sidereal Time (GMST).
338 ///
339 /// ## Details
340 ///
341 /// - When `eo_rad = 0.0`, the result is the time equivalent of the modern
342 /// Earth Rotation Angle (ERA).
343 ///
344 /// - When `eo_rad` is the mean Equation of the Origins, the result is
345 /// **Greenwich Mean Sidereal Time (GMST)** referred to the mean equinox.
346 ///
347 /// As of Astropy 7.x, this is consistent with
348 /// `Time.sidereal_time("mean").to_value("sec")` (when no longitude is
349 /// specified) when using matching UT1 time and the mean Equation of the Origins.
350 ///
351 /// ## Examples
352 ///
353 /// ```rust
354 /// use deep_time::Sidereal;
355 ///
356 /// let earth = Sidereal::EARTH;
357 /// let mjd = 60000.0;
358 ///
359 /// // Time equivalent of pure Earth Rotation Angle
360 /// let era_seconds = earth.sidereal_time_mean(mjd, 0.0);
361 ///
362 /// // Greenwich Mean Sidereal Time in seconds
363 /// // (requires "sidereal-earth" feature)
364 /// # #[cfg(feature = "sidereal-earth")] {
365 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
366 /// let gmst_seconds = earth.sidereal_time_mean(mjd, eo_mean);
367 /// # }
368 /// ```
369 pub const fn sidereal_time_mean(&self, mjd: Real, eo_rad: Real) -> Real {
370 let angle = self.sidereal_angle_mean(mjd, eo_rad);
371 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
372 fraction * 86400.0
373 }
374
375 /// Returns local sidereal time at the observer's longitude as seconds since
376 /// sidereal midnight, wrapped to the range `[0, 86400)`.
377 ///
378 /// This is the time equivalent of [`local_sidereal_angle_mean`].
379 ///
380 /// ## Parameters
381 ///
382 /// - `eo_rad`: The Equation of the Origins value to use.
383 /// - Pass `0.0` to get the time equivalent of the local Earth Rotation Angle (CIO-based).
384 /// - Pass the **mean** Equation of the Origins (e.g. from
385 /// [`earth_eo_mean`](Self::earth_eo_mean)) to obtain **Local Mean Sidereal Time (LMST)**.
386 ///
387 /// ## Details
388 ///
389 /// - When `eo_rad = 0.0`, the result is the time equivalent of the local
390 /// Earth Rotation Angle relative to the Celestial Intermediate Origin (CIO)
391 /// at the observer’s longitude.
392 ///
393 /// - When `eo_rad` is the mean Equation of the Origins, the result is
394 /// **Local Mean Sidereal Time (LMST)** referred to the mean equinox.
395 ///
396 /// As of Astropy 7.x, this is consistent with
397 /// `Time.sidereal_time("mean", longitude=...).to_value("sec")` when using
398 /// matching UT1 time and the mean Equation of the Origins.
399 ///
400 /// ## Examples
401 ///
402 /// ```rust
403 /// use deep_time::Sidereal;
404 ///
405 /// let mut earth = Sidereal::EARTH;
406 /// earth.longitude_rad = 0.0; // Greenwich
407 ///
408 /// let mjd = 60000.0;
409 ///
410 /// // Time equivalent of local Earth Rotation Angle
411 /// let local_era_seconds = earth.local_sidereal_time_mean(mjd, 0.0);
412 ///
413 /// // Local Mean Sidereal Time in seconds
414 /// // (requires "sidereal-earth" feature)
415 /// # #[cfg(feature = "sidereal-earth")] {
416 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
417 /// let lmst_seconds = earth.local_sidereal_time_mean(mjd, eo_mean);
418 /// # }
419 /// ```
420 pub const fn local_sidereal_time_mean(&self, mjd: Real, eo_rad: Real) -> Real {
421 let angle = self.local_sidereal_angle_mean(mjd, eo_rad);
422 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
423 fraction * 86400.0
424 }
425
426 /// Returns the apparent sidereal angle of the body's prime meridian
427 /// in radians, normalized to `[0, 2π)`.
428 ///
429 /// This computes **Greenwich Apparent Sidereal Time (GAST)** when the
430 /// apparent Equation of the Origins is supplied.
431 ///
432 /// ## Parameters
433 ///
434 /// - `eo_rad`: The **apparent** Equation of the Origins
435 /// (e.g. from [`earth_eo_apparent`](Self::earth_eo_apparent)).
436 /// When supplied, the result is Greenwich Apparent Sidereal Time (GAST)
437 /// referred to the true equinox.
438 ///
439 /// ## Details
440 ///
441 /// This function implements the direct relationship:
442 ///
443 /// ```text
444 /// GAST = ERA − EO_apparent
445 /// ```
446 ///
447 /// As of Astropy 7.x, this is consistent with
448 /// `Time.sidereal_time("apparent").rad` (when no longitude is specified)
449 /// when using matching UT1 time and the apparent Equation of the Origins.
450 ///
451 /// ## Examples
452 ///
453 /// ```rust
454 /// use deep_time::Sidereal;
455 ///
456 /// let earth = Sidereal::EARTH;
457 /// let mjd = 60000.0;
458 ///
459 /// // Greenwich Apparent Sidereal Time
460 /// // (requires "sidereal-earth" feature)
461 /// # #[cfg(feature = "sidereal-earth")] {
462 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
463 /// let gast = earth.sidereal_angle_apparent(mjd, eo_app);
464 /// # }
465 /// ```
466 pub const fn sidereal_angle_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
467 let angle = self.rotation_angle(mjd) - eo_rad;
468 Self::normalize_angle(angle)
469 }
470
471 /// Returns the local apparent sidereal angle at the observer's longitude
472 /// in radians, normalized to `[0, 2π)`.
473 ///
474 /// This computes **Local Apparent Sidereal Time (LAST)** when the
475 /// apparent Equation of the Origins is supplied.
476 ///
477 /// ## Parameters
478 ///
479 /// - `eo_rad`: The **apparent** Equation of the Origins
480 /// (e.g. from [`earth_eo_apparent`](Self::earth_eo_apparent)).
481 /// When supplied, the result is Local Apparent Sidereal Time (LAST)
482 /// at the observer’s longitude, referred to the true equinox.
483 ///
484 /// ## Details
485 ///
486 /// This function implements the direct relationship:
487 ///
488 /// ```text
489 /// LAST = ERA + longitude − EO_apparent
490 /// ```
491 ///
492 /// As of Astropy 7.x, this is consistent with
493 /// `Time.sidereal_time("apparent", longitude=...).rad` when using
494 /// matching UT1 time and the apparent Equation of the Origins.
495 ///
496 /// ## Examples
497 ///
498 /// ```rust
499 /// use deep_time::Sidereal;
500 ///
501 /// let mut earth = Sidereal::EARTH;
502 /// earth.longitude_rad = 0.0; // Greenwich
503 ///
504 /// let mjd = 60000.0;
505 ///
506 /// // Local Apparent Sidereal Time
507 /// // (requires "sidereal-earth" feature)
508 /// # #[cfg(feature = "sidereal-earth")] {
509 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
510 /// let last = earth.local_sidereal_angle_apparent(mjd, eo_app);
511 /// # }
512 /// ```
513 pub const fn local_sidereal_angle_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
514 let angle = self.rotation_angle(mjd) + self.longitude_rad - eo_rad;
515 Self::normalize_angle(angle)
516 }
517
518 /// Returns apparent sidereal time at the body's prime meridian as seconds
519 /// since sidereal midnight, wrapped to the range `[0, 86400)`.
520 ///
521 /// This is the time equivalent of [`sidereal_angle_apparent`].
522 ///
523 /// When the **apparent** Equation of the Origins is supplied, this function
524 /// returns **Greenwich Apparent Sidereal Time (GAST)**.
525 ///
526 /// ## Parameters
527 ///
528 /// - `eo_rad`: The **apparent** Equation of the Origins
529 /// (e.g. from [`earth_eo_apparent`](Self::earth_eo_apparent)).
530 /// When supplied, the result is Greenwich Apparent Sidereal Time (GAST)
531 /// in seconds since sidereal midnight.
532 ///
533 /// ## Details
534 ///
535 /// This function computes:
536 ///
537 /// ```text
538 /// GAST (seconds) = (ERA − EO_apparent) in fractional days × 86400
539 /// ```
540 ///
541 /// As of Astropy 7.x, this is consistent with
542 /// `Time.sidereal_time("apparent").to_value("sec")` (Greenwich) when using
543 /// matching UT1 time and the apparent Equation of the Origins.
544 ///
545 /// ## Examples
546 ///
547 /// ```rust
548 /// use deep_time::Sidereal;
549 ///
550 /// let earth = Sidereal::EARTH;
551 /// let mjd = 60000.0;
552 ///
553 /// // Greenwich Apparent Sidereal Time in seconds
554 /// // (requires "sidereal-earth" feature)
555 /// # #[cfg(feature = "sidereal-earth")] {
556 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
557 /// let gast_seconds = earth.sidereal_time_apparent(mjd, eo_app);
558 /// # }
559 /// ```
560 pub const fn sidereal_time_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
561 let angle = self.sidereal_angle_apparent(mjd, eo_rad);
562 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
563 fraction * 86400.0
564 }
565
566 /// Returns local apparent sidereal time at the observer's longitude as
567 /// seconds since sidereal midnight, wrapped to the range `[0, 86400)`.
568 ///
569 /// This is the time equivalent of [`local_sidereal_angle_apparent`].
570 ///
571 /// When the **apparent** Equation of the Origins is supplied, this function
572 /// returns **Local Apparent Sidereal Time (LAST)**.
573 ///
574 /// ## Parameters
575 ///
576 /// - `eo_rad`: The **apparent** Equation of the Origins
577 /// (e.g. from [`earth_eo_apparent`](Self::earth_eo_apparent)).
578 /// When supplied, the result is Local Apparent Sidereal Time (LAST)
579 /// at the observer’s longitude, in seconds since sidereal midnight.
580 ///
581 /// ## Details
582 ///
583 /// This function computes:
584 ///
585 /// ```text
586 /// LAST (seconds) = (ERA + longitude − EO_apparent) in fractional days × 86400
587 /// ```
588 ///
589 /// As of Astropy 7.x, this is consistent with
590 /// `Time.sidereal_time("apparent", longitude=...).to_value("sec")` when using
591 /// matching UT1 time and the apparent Equation of the Origins.
592 ///
593 /// ## Examples
594 ///
595 /// ```rust
596 /// use deep_time::Sidereal;
597 ///
598 /// let mut earth = Sidereal::EARTH;
599 /// earth.longitude_rad = 0.0; // Greenwich
600 ///
601 /// let mjd = 60000.0;
602 ///
603 /// // Local Apparent Sidereal Time in seconds
604 /// // (requires "sidereal-earth" feature)
605 /// # #[cfg(feature = "sidereal-earth")] {
606 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
607 /// let last_seconds = earth.local_sidereal_time_apparent(mjd, eo_app);
608 /// # }
609 /// ```
610 pub const fn local_sidereal_time_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
611 let angle = self.local_sidereal_angle_apparent(mjd, eo_rad);
612 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
613 fraction * 86400.0
614 }
615
616 /// Returns the apparent Equation of the Origins (radians) at the given MJD.
617 ///
618 /// This returns the value computed by ERFA’s `eo06a`. It is the modern
619 /// CIO-based quantity used to derive **Greenwich Apparent Sidereal Time (GAST)**
620 /// from the Earth Rotation Angle (ERA).
621 ///
622 /// When you subtract this value from the ERA, you get GAST:
623 ///
624 /// ```text
625 /// GAST = ERA − earth_eo_apparent(...)
626 /// ```
627 ///
628 /// This method is equivalent to calling `erfa.eo06a(tt.jd1, tt.jd2)` in Astropy.
629 ///
630 /// You should pass the value returned by this function to the apparent
631 /// sidereal time functions (`sidereal_angle_apparent`, `local_sidereal_angle_apparent`,
632 /// `sidereal_time_apparent`, and `local_sidereal_time_apparent`).
633 ///
634 /// ## Examples
635 ///
636 /// ```rust
637 /// use deep_time::Sidereal;
638 ///
639 /// let earth = Sidereal::EARTH;
640 /// let mjd_tt = 60000.0 + 32.184 / 86400.0;
641 ///
642 /// let eo_app = earth.earth_eo_apparent(mjd_tt);
643 /// let gast = earth.sidereal_angle_apparent(mjd_tt, eo_app);
644 /// ```
645 #[cfg(feature = "sidereal-earth")]
646 #[inline]
647 pub const fn earth_eo_apparent(&self, tt_mjd: Real) -> Real {
648 // Convert MJD → two-part Julian Date
649 let date1 = 2400000.5 + tt_mjd;
650 earth_eo(date1, 0.0)
651 }
652
653 /// Returns the mean Equation of the Origins (radians) at the given MJD.
654 ///
655 /// This returns the value that should be subtracted from the Earth Rotation
656 /// Angle (ERA) to obtain **Greenwich Mean Sidereal Time (GMST)**:
657 ///
658 /// ```text
659 /// GMST = ERA − earth_eo_mean(...)
660 /// ```
661 ///
662 /// Internally, this is computed as:
663 ///
664 /// ```text
665 /// earth_eo_mean = earth_eo_apparent() + earth_ee()
666 /// ```
667 ///
668 /// This is equivalent to computing `era - gmst` in Astropy:
669 ///
670 /// ```python
671 /// era = ut1.earth_rotation_angle(...).rad
672 /// gmst = ut1.sidereal_time("mean", ...).rad
673 /// eo_mean = era - gmst
674 /// ```
675 ///
676 /// You should pass the value returned by this function to the mean
677 /// sidereal time functions (`sidereal_angle_mean`, `local_sidereal_angle_mean`,
678 /// `sidereal_time_mean`, and `local_sidereal_time_mean`).
679 ///
680 /// ## Examples
681 ///
682 /// ```rust
683 /// use deep_time::Sidereal;
684 ///
685 /// let earth = Sidereal::EARTH;
686 /// let mjd_tt = 60000.0 + 32.184 / 86400.0;
687 ///
688 /// let eo_mean = earth.earth_eo_mean(mjd_tt);
689 /// let gmst = earth.sidereal_angle_mean(mjd_tt, eo_mean);
690 /// ```
691 #[cfg(feature = "sidereal-earth")]
692 #[inline]
693 pub const fn earth_eo_mean(&self, tt_mjd: Real) -> Real {
694 // Convert MJD → two-part Julian Date
695 let date1 = 2400000.5 + tt_mjd;
696 earth_eo(date1, 0.0) + earth_ee(date1, 0.0)
697 }
698
699 /// Returns the Equation of the Equinoxes (radians) at the given MJD.
700 ///
701 /// This returns the value computed by ERFA’s `ee06a`. The Equation of the
702 /// Equinoxes represents the nutation contribution to sidereal time and is
703 /// defined as:
704 ///
705 /// ```text
706 /// EE = GAST − GMST
707 /// ```
708 ///
709 /// It is equivalent to computing `gast - gmst` in Astropy:
710 ///
711 /// ```python
712 /// gast = ut1.sidereal_time("apparent", ...).rad
713 /// gmst = ut1.sidereal_time("mean", ...).rad
714 /// ee = gast - gmst
715 /// ```
716 ///
717 /// This value is used internally when converting between mean and apparent
718 /// sidereal time (for example, when the mean functions are given the apparent
719 /// EO + EE).
720 ///
721 /// ## Examples
722 ///
723 /// ```rust
724 /// use deep_time::Sidereal;
725 ///
726 /// let earth = Sidereal::EARTH;
727 /// let mjd_tt = 60000.0 + 32.184 / 86400.0;
728 ///
729 /// let ee = earth.earth_ee(mjd_tt);
730 /// ```
731 #[cfg(feature = "sidereal-earth")]
732 #[inline]
733 pub const fn earth_ee(&self, tt_mjd: Real) -> Real {
734 // Convert MJD → two-part Julian Date
735 let date1 = 2400000.5 + tt_mjd;
736 earth_ee(date1, 0.0)
737 }
738}