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}