nyx_space/od/ground_station/
event.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 super::GroundStation;
20use crate::md::EventEvaluator;
21use crate::{errors::EventError, md::prelude::Interpolatable};
22use anise::prelude::Almanac;
23use hifitime::{Duration, Unit};
24use nalgebra::{allocator::Allocator, DefaultAllocator};
25use std::sync::Arc;
26
27impl<S: Interpolatable> EventEvaluator<S> for &GroundStation
28where
29    DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
30{
31    /// Compute the elevation in the SEZ frame. This call will panic if the frame of the input state does not match that of the ground station.
32    fn eval(&self, rx_gs_frame: &S, almanac: Arc<Almanac>) -> Result<f64, EventError> {
33        let dt = rx_gs_frame.epoch();
34        // Then, compute the rotation matrix from the body fixed frame of the ground station to its topocentric frame SEZ.
35        let tx_gs_frame = self.to_orbit(dt, &almanac).unwrap();
36
37        let from = tx_gs_frame.frame.orientation_id * 1_000 + 1;
38        let dcm_topo2fixed = tx_gs_frame
39            .dcm_from_topocentric_to_body_fixed(from)
40            .unwrap()
41            .transpose();
42
43        // Now, rotate the spacecraft in the SEZ frame to compute its elevation as seen from the ground station.
44        // We transpose the DCM so that it's the fixed to topocentric rotation.
45        let rx_sez = (dcm_topo2fixed * rx_gs_frame.orbit()).unwrap();
46        let tx_sez = (dcm_topo2fixed * tx_gs_frame).unwrap();
47        // Now, let's compute the range ρ.
48        let rho_sez = (rx_sez - tx_sez).unwrap();
49
50        // Finally, compute the elevation (math is the same as declination)
51        // Source: Vallado, section 4.4.3
52        // Only the sine is needed as per Vallado, and the formula is the same as the declination
53        // because we're in the SEZ frame.
54        Ok(rho_sez.declination_deg() - self.elevation_mask_deg)
55    }
56
57    fn eval_string(&self, state: &S, almanac: Arc<Almanac>) -> Result<String, EventError> {
58        Ok(format!(
59            "Elevation from {} is {:.6} deg on {}",
60            self.name,
61            self.eval(state, almanac)? + self.elevation_mask_deg,
62            state.epoch()
63        ))
64    }
65
66    /// Epoch precision of the election evaluator is 1 ms
67    fn epoch_precision(&self) -> Duration {
68        1 * Unit::Second
69    }
70
71    /// Angle precision of the elevation evaluator is 1 millidegree.
72    fn value_precision(&self) -> f64 {
73        1e-3
74    }
75}