Skip to content

Commit

Permalink
Move ground station to its own module for maintainability
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristopherRabotin committed Nov 23, 2024
1 parent 3aa4fb8 commit 52a4163
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 273 deletions.
3 changes: 2 additions & 1 deletion data/tests/config/one_ground_station.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ doppler_noise_km_s:
light_time_correction: false
measurement_types:
- Range
- Doppler
- Doppler
integration_time: 1 min
96 changes: 96 additions & 0 deletions src/od/ground_station/builtin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
Nyx, blazing fast astrodynamics
Copyright (C) 2018-onwards Christopher Rabotin <[email protected]>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use super::*;

impl GroundStation {
pub fn dss65_madrid(
elevation_mask: f64,
range_noise_km: StochasticNoise,
doppler_noise_km_s: StochasticNoise,
iau_earth: Frame,
) -> Self {
let mut measurement_types = IndexSet::new();
measurement_types.insert(MeasurementType::Range);
measurement_types.insert(MeasurementType::Doppler);
Self {
name: "Madrid".to_string(),
elevation_mask_deg: elevation_mask,
latitude_deg: 40.427_222,
longitude_deg: 4.250_556,
height_km: 0.834_939,
frame: iau_earth,
measurement_types,
integration_time: None,
light_time_correction: false,
timestamp_noise_s: None,
range_noise_km: Some(range_noise_km),
doppler_noise_km_s: Some(doppler_noise_km_s),
}
}

pub fn dss34_canberra(
elevation_mask: f64,
range_noise_km: StochasticNoise,
doppler_noise_km_s: StochasticNoise,
iau_earth: Frame,
) -> Self {
let mut measurement_types = IndexSet::new();
measurement_types.insert(MeasurementType::Range);
measurement_types.insert(MeasurementType::Doppler);
Self {
name: "Canberra".to_string(),
elevation_mask_deg: elevation_mask,
latitude_deg: -35.398_333,
longitude_deg: 148.981_944,
height_km: 0.691_750,
frame: iau_earth,
measurement_types,
integration_time: None,
light_time_correction: false,
timestamp_noise_s: None,
range_noise_km: Some(range_noise_km),
doppler_noise_km_s: Some(doppler_noise_km_s),
}
}

pub fn dss13_goldstone(
elevation_mask: f64,
range_noise_km: StochasticNoise,
doppler_noise_km_s: StochasticNoise,
iau_earth: Frame,
) -> Self {
let mut measurement_types = IndexSet::new();
measurement_types.insert(MeasurementType::Range);
measurement_types.insert(MeasurementType::Doppler);
Self {
name: "Goldstone".to_string(),
elevation_mask_deg: elevation_mask,
latitude_deg: 35.247_164,
longitude_deg: 243.205,
height_km: 1.071_149_04,
frame: iau_earth,
measurement_types,
integration_time: None,
light_time_correction: false,
timestamp_noise_s: None,
range_noise_km: Some(range_noise_km),
doppler_noise_km_s: Some(doppler_noise_km_s),
}
}
}
75 changes: 75 additions & 0 deletions src/od/ground_station/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Nyx, blazing fast astrodynamics
Copyright (C) 2018-onwards Christopher Rabotin <[email protected]>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use super::GroundStation;
use crate::md::EventEvaluator;
use crate::{errors::EventError, md::prelude::Interpolatable};
use anise::prelude::Almanac;
use hifitime::{Duration, Unit};
use nalgebra::{allocator::Allocator, DefaultAllocator};
use std::sync::Arc;

impl<S: Interpolatable> EventEvaluator<S> for &GroundStation
where
DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
{
/// 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.
fn eval(&self, rx_gs_frame: &S, almanac: Arc<Almanac>) -> Result<f64, EventError> {
let dt = rx_gs_frame.epoch();
// Then, compute the rotation matrix from the body fixed frame of the ground station to its topocentric frame SEZ.
let tx_gs_frame = self.to_orbit(dt, &almanac).unwrap();

let from = tx_gs_frame.frame.orientation_id * 1_000 + 1;
let dcm_topo2fixed = tx_gs_frame
.dcm_from_topocentric_to_body_fixed(from)
.unwrap()
.transpose();

// Now, rotate the spacecraft in the SEZ frame to compute its elevation as seen from the ground station.
// We transpose the DCM so that it's the fixed to topocentric rotation.
let rx_sez = (dcm_topo2fixed * rx_gs_frame.orbit()).unwrap();
let tx_sez = (dcm_topo2fixed * tx_gs_frame).unwrap();
// Now, let's compute the range ρ.
let rho_sez = (rx_sez - tx_sez).unwrap();

// Finally, compute the elevation (math is the same as declination)
// Source: Vallado, section 4.4.3
// Only the sine is needed as per Vallado, and the formula is the same as the declination
// because we're in the SEZ frame.
Ok(rho_sez.declination_deg() - self.elevation_mask_deg)
}

fn eval_string(&self, state: &S, almanac: Arc<Almanac>) -> Result<String, EventError> {
Ok(format!(
"Elevation from {} is {:.6} deg on {}",
self.name,
self.eval(state, almanac)? + self.elevation_mask_deg,
state.epoch()
))
}

/// Epoch precision of the election evaluator is 1 ms
fn epoch_precision(&self) -> Duration {
1 * Unit::Second
}

/// Angle precision of the elevation evaluator is 1 millidegree.
fn value_precision(&self) -> f64 {
1e-3
}
}
Loading

0 comments on commit 52a4163

Please sign in to comment.