Skip to main content

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::analysis::prelude::Event;
21use anise::astro::Aberration;
22use anise::astro::Occultation;
23use anise::constants::frames::{EARTH_J2000, MOON_J2000, SUN_J2000};
24use anise::errors::AlmanacResult;
25use serde::{Deserialize, Serialize};
26use serde_dhall::StaticType;
27
28pub use super::{Frame, Orbit, Spacecraft};
29use std::fmt;
30
31#[cfg(feature = "python")]
32use pyo3::prelude::*;
33
34#[cfg_attr(feature = "python", pyclass(from_py_object, get_all, set_all))]
35#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, StaticType)]
36pub struct ShadowModel {
37    pub light_source: Frame,
38    pub shadow_bodies: Vec<Frame>,
39    /// Light-time correction for the Sun position at emission time of photons.
40    pub correction: Option<Aberration>,
41}
42
43impl fmt::Display for ShadowModel {
44    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45        let shadow_bodies: Vec<String> = self
46            .shadow_bodies
47            .iter()
48            .map(|b| format!("{b:x}"))
49            .collect();
50
51        write!(
52            f,
53            "light-source: {:x}, shadows casted by: {} (correction: {:?})",
54            self.light_source,
55            shadow_bodies.join(", "),
56            self.correction
57        )
58    }
59}
60
61impl ShadowModel {
62    /// Creates a new typical eclipse locator.
63    /// The light source is the Sun, and the shadow bodies are the Earth and the Moon.
64    pub fn cislunar(almanac: &Almanac) -> Self {
65        let eme2k = almanac.frame_info(EARTH_J2000).unwrap();
66        let moon_j2k = almanac.frame_info(MOON_J2000).unwrap();
67        Self {
68            light_source: almanac.frame_info(SUN_J2000).unwrap(),
69            shadow_bodies: vec![eme2k, moon_j2k],
70            correction: None,
71        }
72    }
73
74    /// Compute the visibility/eclipse between an observer and an observed state
75    pub fn compute(&self, observer: Orbit, almanac: &Almanac) -> AlmanacResult<Occultation> {
76        let mut state = Occultation {
77            epoch: observer.epoch,
78            back_frame: SUN_J2000,
79            front_frame: observer.frame,
80            percentage: 0.0,
81        };
82        for eclipsing_body in &self.shadow_bodies {
83            let this_state = almanac.solar_eclipsing(*eclipsing_body, observer, self.correction)?;
84            if this_state.percentage > state.percentage {
85                state = this_state;
86            }
87        }
88        Ok(state)
89    }
90
91    /// Creates an umbra 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_umbra_events(&self) -> Vec<Event> {
94        self.shadow_bodies
95            .iter()
96            .copied()
97            .map(Event::total_eclipse)
98            .collect()
99    }
100
101    /// Creates a penumbra event from this eclipse locator
102    // Evaluation of the event, returns 0.0 for umbra, 1.0 for visibility (no shadow) and some value in between for penumbra
103    pub fn to_penumbra_events(&self) -> Vec<Event> {
104        self.shadow_bodies
105            .iter()
106            .copied()
107            .map(Event::eclipse)
108            .collect()
109    }
110}
111
112#[cfg(test)]
113mod ut_shadow_mdl {
114    use super::{Aberration, EARTH_J2000, MOON_J2000, SUN_J2000, ShadowModel};
115    #[test]
116    fn ut_shadow_mdl_dhall_no_corr() {
117        let mdl_no_corr = ShadowModel {
118            light_source: SUN_J2000,
119            shadow_bodies: vec![EARTH_J2000, MOON_J2000],
120            correction: None,
121        };
122        let as_dhall = serde_dhall::serialize(&mdl_no_corr)
123            .static_type_annotation()
124            .to_string()
125            .unwrap();
126        println!("{as_dhall}");
127
128        let from_dhall: ShadowModel = serde_dhall::from_str(&as_dhall).parse().unwrap();
129        assert_eq!(from_dhall, mdl_no_corr);
130    }
131
132    #[test]
133    fn ut_shadow_mdl_dhall() {
134        let mdl_no_corr = ShadowModel {
135            light_source: SUN_J2000,
136            shadow_bodies: vec![EARTH_J2000, MOON_J2000],
137            correction: Aberration::LT,
138        };
139        let as_dhall = serde_dhall::serialize(&mdl_no_corr)
140            .static_type_annotation()
141            .to_string()
142            .unwrap();
143        println!("{as_dhall}");
144
145        let from_dhall: ShadowModel = serde_dhall::from_str(&as_dhall).parse().unwrap();
146        assert_eq!(from_dhall, mdl_no_corr);
147    }
148}