Skip to main content

pleiades_eclipse/
types.rs

1//! Eclipse domain value types.
2
3use pleiades_types::{Instant, Longitude};
4
5/// Whether an eclipse is of the Sun (Moon between Earth and Sun, at new moon)
6/// or of the Moon (Earth between Sun and Moon, at full moon).
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum EclipseKind {
9    /// Solar eclipse: the Moon occults the Sun at a new moon near a node.
10    Solar,
11    /// Lunar eclipse: the Moon passes through Earth's shadow at a full moon near a node.
12    Lunar,
13}
14
15/// Geometric classification of a solar eclipse at its point of greatest eclipse.
16///
17/// The distinction is topocentric per the NASA canon convention (see
18/// [`crate`] validation notes): `Total`/`Annular`/`Hybrid` require the shadow
19/// axis to meet Earth; `Partial` is assigned when the axis misses the ellipsoid.
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum SolarEclipseType {
22    /// The Moon fully covers the Sun's disk at greatest eclipse.
23    Total,
24    /// The Moon is angularly smaller than the Sun, leaving a bright ring.
25    Annular,
26    /// Annular-total: annular at the path ends but total near the sub-solar point.
27    Hybrid,
28    /// The shadow axis misses Earth; only a partial phase is seen anywhere.
29    Partial,
30}
31
32/// Geometric classification of a lunar eclipse from the Moon's penetration of
33/// Earth's shadow at greatest eclipse.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum LunarEclipseType {
36    /// The Moon enters only the penumbra (partial shadow); no umbral contact.
37    Penumbral,
38    /// Part of the Moon enters the umbra (full shadow).
39    Partial,
40    /// The entire Moon is within the umbra at greatest eclipse.
41    Total,
42}
43
44/// An eclipse type tagged by kind: either a [`SolarEclipseType`] or a
45/// [`LunarEclipseType`].
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum EclipseType {
48    /// A solar eclipse and its sub-classification.
49    Solar(SolarEclipseType),
50    /// A lunar eclipse and its sub-classification.
51    Lunar(LunarEclipseType),
52}
53
54impl EclipseType {
55    /// Returns the [`EclipseKind`] (solar or lunar) this type belongs to.
56    pub fn kind(&self) -> EclipseKind {
57        match self {
58            EclipseType::Solar(_) => EclipseKind::Solar,
59            EclipseType::Lunar(_) => EclipseKind::Lunar,
60        }
61    }
62}
63
64/// Selects which eclipse kinds a search returns.
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum EclipseFilter {
67    /// Return both solar and lunar eclipses.
68    All,
69    /// Return solar eclipses only.
70    SolarOnly,
71    /// Return lunar eclipses only.
72    LunarOnly,
73}
74
75impl EclipseFilter {
76    /// Returns `true` if this filter admits the given [`EclipseKind`].
77    pub fn admits(&self, kind: EclipseKind) -> bool {
78        match self {
79            EclipseFilter::All => true,
80            EclipseFilter::SolarOnly => kind == EclipseKind::Solar,
81            EclipseFilter::LunarOnly => kind == EclipseKind::Lunar,
82        }
83    }
84}
85
86/// Which lunar node the eclipse occurs near, derived from the sign of the
87/// Moon's ecliptic latitude change through the syzygy.
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum Node {
90    /// Ascending node: the Moon's ecliptic latitude is increasing through zero.
91    North,
92    /// Descending node: the Moon's ecliptic latitude is decreasing through zero.
93    South,
94}
95
96/// A geocentric sub-shadow point on Earth. Deliberately not `ObserverLocation`:
97/// the greatest-eclipse point is a position, not an observing site.
98#[derive(Clone, Copy, Debug, PartialEq)]
99pub struct GeoLocation {
100    /// Geographic (geodetic) latitude of the point, degrees, positive north.
101    pub latitude_degrees: f64,
102    /// Geographic longitude of the point, degrees, positive east.
103    pub longitude_degrees: f64,
104}
105
106/// A single global/geocentric eclipse and its computed circumstances.
107///
108/// All quantities are geocentric except the solar magnitude and type, which
109/// follow the NASA canon's topocentric-at-greatest-eclipse convention. No
110/// per-observer (local) circumstances are provided.
111#[derive(Clone, Copy, Debug, PartialEq)]
112pub struct Eclipse {
113    /// Solar or lunar.
114    pub kind: EclipseKind,
115    /// The classified eclipse type (matches `kind`).
116    pub eclipse_type: EclipseType,
117    /// Instant of greatest eclipse, in the TDB time scale.
118    pub greatest_eclipse: Instant,
119    /// Eclipse magnitude (fraction of the eclipsed body's diameter covered at
120    /// greatest eclipse): topocentric for solar, umbral/penumbral for lunar.
121    pub magnitude: f64,
122    /// Gamma: least distance of the shadow axis from Earth's center, in
123    /// equatorial Earth radii (signed; sign follows the Moon's latitude).
124    pub gamma: f64,
125    /// Saros series number this eclipse belongs to.
126    pub saros_series: u32,
127    /// Ecliptic longitude of the eclipsed body at greatest eclipse (apparent
128    /// tropical ecliptic of date, no ayanamsa): the Sun for solar eclipses,
129    /// the Moon (Sun + 180°) for lunar eclipses.
130    pub eclipsed_longitude: Longitude,
131    /// The lunar node (ascending/descending) the eclipse occurs near.
132    pub near_node: Node,
133    /// Sub-shadow point of greatest eclipse for solar eclipses; always `None`
134    /// for lunar eclipses (which have no single surface location).
135    pub greatest_eclipse_location: Option<GeoLocation>,
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn eclipse_type_reports_its_kind() {
144        assert_eq!(
145            EclipseType::Solar(SolarEclipseType::Total).kind(),
146            EclipseKind::Solar
147        );
148        assert_eq!(
149            EclipseType::Lunar(LunarEclipseType::Penumbral).kind(),
150            EclipseKind::Lunar
151        );
152    }
153
154    #[test]
155    fn filter_admits_expected_kinds() {
156        assert!(EclipseFilter::All.admits(EclipseKind::Solar));
157        assert!(EclipseFilter::All.admits(EclipseKind::Lunar));
158        assert!(EclipseFilter::SolarOnly.admits(EclipseKind::Solar));
159        assert!(!EclipseFilter::SolarOnly.admits(EclipseKind::Lunar));
160        assert!(EclipseFilter::LunarOnly.admits(EclipseKind::Lunar));
161        assert!(!EclipseFilter::LunarOnly.admits(EclipseKind::Solar));
162    }
163}