astrograph/output/logger/eclipse/
mod.rs

1use std::{
2    collections::HashMap,
3    fs::OpenOptions,
4    io::Write,
5    sync::{Arc, RwLock},
6};
7
8use collision_check::CollisionGrid;
9use coordinates::prelude::Spherical;
10
11use crate::{output::Output, Float};
12
13/// Provides a struct that speeds up eclipse checks
14mod collision_check;
15
16#[derive(Clone, Debug, Default)]
17pub struct Logger {
18    /// List of eclipses that have been observed
19    eclipse_log: Arc<RwLock<HashMap<Arc<std::path::Path>, Vec<String>>>>,
20}
21
22/// Gets a list of eclipses that have been observed at this time
23fn get_eclipses_on_frame(
24    observations: &[(crate::body::Arc, Spherical<Float>)],
25    time: &str,
26) -> Vec<String> {
27    // TODO: Handle lunar eclipses
28    // Create an object to speed up searches similar to a hashgrid or oct-tree
29    let grid = CollisionGrid::new(observations);
30
31    let mut results = Vec::new();
32
33    for p in observations {
34        // Get name of the near body
35        let name =
36            p.0.read()
37                .map(|p| p.get_name())
38                .unwrap_or("Poisoned Body".into());
39
40        for (other, magnitude) in grid.collisions(p) {
41            // For each body this body has eclipsed, get the name of the far body
42            let other_name = other
43                .read()
44                .map(|b| b.get_name())
45                .unwrap_or("Poisoned Body".into());
46
47            results.push(format!("Time={time}, There was an eclipse between {name} and {other_name} with magnitude {magnitude:.2}"));
48        }
49    }
50
51    results
52}
53
54impl Output for Logger {
55    fn write_observations(
56        &self,
57        observations: &[(crate::body::Arc, Spherical<Float>)],
58        observatory_name: &str,
59        time: i128,
60        output_path_root: &std::path::Path,
61    ) -> Result<(), std::io::Error> {
62        let log = get_eclipses_on_frame(observations, &time.to_string());
63        let path = super::super::to_default_path(
64            output_path_root,
65            observatory_name,
66            time,
67            "-eclipses.txt",
68        );
69        if let Ok(mut hash_map) = self.eclipse_log.write() {
70            if let Some(values) = hash_map.get_mut(path.as_path()) {
71                values.extend(log);
72            } else {
73                hash_map.insert(path.into(), log);
74            }
75        }
76
77        Ok(())
78    }
79
80    fn flush(&self) -> Result<(), std::io::Error> {
81        if let Ok(hash_map) = self.eclipse_log.read() {
82            for (path, data) in hash_map.iter() {
83                // Create path to file
84                if let Some(parent) = path.parent() {
85                    std::fs::create_dir_all(parent)?;
86                }
87
88                // Create the file and write any eclipse data
89                let mut file = OpenOptions::new().create(true).append(true).open(path)?;
90                // Fill the file with the eclipses
91                file.write_all(data.join("\n").as_bytes())?;
92            }
93        }
94        Ok(())
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use coordinates::prelude::Spherical;
102
103    use crate::{body::Body, dynamic::fixed::Fixed};
104
105    #[test]
106    fn eclipse_is_logged_in_correct_format() {
107        let sun = Body::new(None, Fixed::new([0.0, 0.0, 0.0].into()));
108        let earth = Body::new(Some(sun.clone()), Fixed::new([2.0, 0.0, 0.0].into()));
109        let _moon = Body::new(Some(earth.clone()), Fixed::new([-1.0, 0.0, 0.0].into()));
110
111        Body::hydrate_all(&sun, &None);
112        let time = 0.0;
113
114        let observations: Vec<_> = earth
115            .read()
116            .unwrap()
117            .get_observations_from_here(time)
118            .into_iter()
119            .map(|(b, loc)| {
120                let loc = Spherical::from(loc);
121                println!("{loc:?}");
122                (b, loc)
123            })
124            .collect();
125
126        let log = get_eclipses_on_frame(&observations, &time.to_string());
127
128        assert_eq!(
129            log[0],
130            format!(
131                "Time={time}, There was an eclipse between {} and {} with magnitude {:.2}",
132                "0-0", "", 1.0
133            )
134        );
135    }
136}