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}