nyx_space/cosmic/
eclipse.rs

1/*
2    Nyx, blazing fast astrodynamics
3    Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19use anise::almanac::Almanac;
20use anise::astro::Occultation;
21use anise::constants::frames::{EARTH_J2000, MOON_J2000, SUN_J2000};
22use anise::errors::AlmanacResult;
23use snafu::ResultExt;
24
25pub use super::{Frame, Orbit, Spacecraft};
26use crate::errors::{EventAlmanacSnafu, EventError};
27use crate::md::EventEvaluator;
28use crate::time::{Duration, Unit};
29use std::fmt;
30use std::sync::Arc;
31
32#[derive(Clone)]
33pub struct EclipseLocator {
34    pub light_source: Frame,
35    pub shadow_bodies: Vec<Frame>,
36}
37
38impl fmt::Display for EclipseLocator {
39    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40        let shadow_bodies: Vec<String> = self
41            .shadow_bodies
42            .iter()
43            .map(|b| format!("{b:x}"))
44            .collect();
45        write!(
46            f,
47            "light-source: {:x}, shadows casted by: {}",
48            self.light_source,
49            shadow_bodies.join(", ")
50        )
51    }
52}
53
54impl EclipseLocator {
55    /// Creates a new typical eclipse locator.
56    /// The light source is the Sun, and the shadow bodies are the Earth and the Moon.
57    pub fn cislunar(almanac: Arc<Almanac>) -> Self {
58        let eme2k = almanac.frame_from_uid(EARTH_J2000).unwrap();
59        let moon_j2k = almanac.frame_from_uid(MOON_J2000).unwrap();
60        Self {
61            light_source: almanac.frame_from_uid(SUN_J2000).unwrap(),
62            shadow_bodies: vec![eme2k, moon_j2k],
63        }
64    }
65
66    /// Compute the visibility/eclipse between an observer and an observed state
67    pub fn compute(&self, observer: Orbit, almanac: Arc<Almanac>) -> AlmanacResult<Occultation> {
68        let mut state = Occultation {
69            epoch: observer.epoch,
70            back_frame: SUN_J2000,
71            front_frame: observer.frame,
72            percentage: 0.0,
73        };
74        for eclipsing_body in &self.shadow_bodies {
75            let this_state = almanac.solar_eclipsing(*eclipsing_body, observer, None)?;
76            if this_state.percentage > state.percentage {
77                state = this_state;
78            }
79        }
80        Ok(state)
81    }
82
83    /// Creates an umbra event from this eclipse locator.
84    /// Evaluation of the event, returns 0.0 for umbra, 1.0 for visibility (no shadow) and some value in between for penumbra
85    pub fn to_umbra_event(&self) -> UmbraEvent {
86        UmbraEvent {
87            e_loc: self.clone(),
88        }
89    }
90
91    /// Creates a penumbra event from this eclipse locator
92    // Evaluation of the event, returns 0.0 for umbra, 1.0 for visibility (no shadow) and some value in between for penumbra
93    pub fn to_penumbra_event(&self) -> PenumbraEvent {
94        PenumbraEvent {
95            e_loc: self.clone(),
96        }
97    }
98}
99
100/// An event to find the darkest eclipse state (more than 98% shadow)
101pub struct UmbraEvent {
102    e_loc: EclipseLocator,
103}
104
105impl fmt::Display for UmbraEvent {
106    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
107        write!(f, "umbra event {}", self.e_loc)
108    }
109}
110
111impl EventEvaluator<Spacecraft> for UmbraEvent {
112    // Evaluation of the event
113    fn eval(&self, sc: &Spacecraft, almanac: Arc<Almanac>) -> Result<f64, EventError> {
114        let occult = self
115            .e_loc
116            .compute(sc.orbit, almanac)
117            .context(EventAlmanacSnafu)?
118            .factor();
119
120        Ok((occult - 1.0).abs())
121        // match self
122        //     .e_loc
123        //     .compute(sc.orbit, almanac)
124        //     .context(EventAlmanacSnafu)?
125        // {
126        //     EclipseState::Umbra => Ok(0.0),
127        //     EclipseState::Visibilis => Ok(1.0),
128        //     EclipseState::Penumbra(val) => Ok(val),
129        // }
130    }
131
132    /// Stop searching when the time has converged to less than 0.1 seconds
133    fn epoch_precision(&self) -> Duration {
134        0.1 * Unit::Second
135    }
136    /// Finds the darkest part of an eclipse within 2% of penumbra (i.e. 98% in shadow)
137    fn value_precision(&self) -> f64 {
138        0.02
139    }
140    fn eval_string(&self, state: &Spacecraft, almanac: Arc<Almanac>) -> Result<String, EventError> {
141        Ok(format!(
142            "{}",
143            self.e_loc
144                .compute(state.orbit, almanac)
145                .context(EventAlmanacSnafu)?
146        ))
147    }
148}
149
150/// An event to find the start of a penumbra
151pub struct PenumbraEvent {
152    e_loc: EclipseLocator,
153}
154
155impl fmt::Display for PenumbraEvent {
156    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157        write!(f, "penumbra event {}", self.e_loc)
158    }
159}
160
161impl EventEvaluator<Spacecraft> for PenumbraEvent {
162    fn eval(&self, sc: &Spacecraft, almanac: Arc<Almanac>) -> Result<f64, EventError> {
163        let occult = self
164            .e_loc
165            .compute(sc.orbit, almanac)
166            .context(EventAlmanacSnafu)?
167            .factor();
168
169        Ok((occult - 1.0).abs())
170    }
171
172    /// Stop searching when the time has converged to less than 0.1 seconds
173    fn epoch_precision(&self) -> Duration {
174        0.1 * Unit::Second
175    }
176    /// Finds the slightest penumbra within 2% (i.e. 98% in visibility)
177    fn value_precision(&self) -> f64 {
178        0.02
179    }
180
181    fn eval_string(&self, state: &Spacecraft, almanac: Arc<Almanac>) -> Result<String, EventError> {
182        Ok(format!(
183            "{}",
184            self.e_loc
185                .compute(state.orbit, almanac)
186                .context(EventAlmanacSnafu)?
187        ))
188    }
189}