nyx_space/od/ground_station/
trk_device.rsuse super::{ODAlmanacSnafu, ODError, ODTrajSnafu, TrackingDevice};
use crate::md::prelude::{Interpolatable, Traj};
use crate::od::msr::measurement::Measurement;
use crate::od::msr::MeasurementType;
use crate::time::Epoch;
use crate::Spacecraft;
use anise::errors::AlmanacResult;
use anise::frames::Frame;
use anise::prelude::{Almanac, Orbit};
use hifitime::TimeUnits;
use indexmap::IndexSet;
use rand_pcg::Pcg64Mcg;
use snafu::ResultExt;
use std::sync::Arc;
use super::GroundStation;
impl TrackingDevice<Spacecraft> for GroundStation {
fn measurement_types(&self) -> &IndexSet<MeasurementType> {
&self.measurement_types
}
fn measure(
&mut self,
epoch: Epoch,
traj: &Traj<Spacecraft>,
rng: Option<&mut Pcg64Mcg>,
almanac: Arc<Almanac>,
) -> Result<Option<Measurement>, ODError> {
match self.integration_time {
Some(integration_time) => {
let rx_0 = match traj.at(epoch - integration_time) {
Ok(rx) => rx,
Err(_) => return Ok(None),
};
let rx_1 = match traj.at(epoch).context(ODTrajSnafu) {
Ok(rx) => rx,
Err(_) => return Ok(None),
};
let obstructing_body = if !self.frame.ephem_origin_match(rx_0.frame()) {
Some(rx_0.frame())
} else {
None
};
let aer_t0 = self
.azimuth_elevation_of(rx_0.orbit, obstructing_body, &almanac)
.context(ODAlmanacSnafu {
action: "computing AER",
})?;
let aer_t1 = self
.azimuth_elevation_of(rx_1.orbit, obstructing_body, &almanac)
.context(ODAlmanacSnafu {
action: "computing AER",
})?;
if aer_t0.elevation_deg < self.elevation_mask_deg
|| aer_t1.elevation_deg < self.elevation_mask_deg
{
debug!(
"{} (el. mask {:.3} deg) but object moves from {:.3} to {:.3} deg -- no measurement",
self.name, self.elevation_mask_deg, aer_t0.elevation_deg, aer_t1.elevation_deg
);
return Ok(None);
} else if aer_t0.is_obstructed() || aer_t1.is_obstructed() {
debug!(
"{} obstruction at t0={}, t1={} -- no measurement",
self.name,
aer_t0.is_obstructed(),
aer_t1.is_obstructed()
);
return Ok(None);
}
let noises = self.noises(epoch - integration_time * 0.5, rng)?;
let mut msr = Measurement::new(self.name.clone(), epoch + noises[0].seconds());
for (ii, msr_type) in self.measurement_types.iter().enumerate() {
let msr_value = msr_type.compute_two_way(aer_t0, aer_t1, noises[ii + 1])?;
msr.push(*msr_type, msr_value);
}
Ok(Some(msr))
}
None => self.measure_instantaneous(traj.at(epoch).context(ODTrajSnafu)?, rng, almanac),
}
}
fn name(&self) -> String {
self.name.clone()
}
fn location(&self, epoch: Epoch, frame: Frame, almanac: Arc<Almanac>) -> AlmanacResult<Orbit> {
almanac.transform_to(self.to_orbit(epoch, &almanac).unwrap(), frame, None)
}
fn measure_instantaneous(
&mut self,
rx: Spacecraft,
rng: Option<&mut Pcg64Mcg>,
almanac: Arc<Almanac>,
) -> Result<Option<Measurement>, ODError> {
let obstructing_body = if !self.frame.ephem_origin_match(rx.frame()) {
Some(rx.frame())
} else {
None
};
let aer = self
.azimuth_elevation_of(rx.orbit, obstructing_body, &almanac)
.context(ODAlmanacSnafu {
action: "computing AER",
})?;
if aer.elevation_deg >= self.elevation_mask_deg && !aer.is_obstructed() {
let noises = self.noises(rx.orbit.epoch, rng)?;
let mut msr = Measurement::new(self.name.clone(), rx.orbit.epoch + noises[0].seconds());
for (ii, msr_type) in self.measurement_types.iter().enumerate() {
let msr_value = msr_type.compute_one_way(aer, noises[ii + 1])?;
msr.push(*msr_type, msr_value);
}
Ok(Some(msr))
} else {
debug!(
"{} {} (el. mask {:.3} deg), object at {:.3} deg -- no measurement",
self.name, rx.orbit.epoch, self.elevation_mask_deg, aer.elevation_deg
);
Ok(None)
}
}
fn measurement_covar(&self, msr_type: MeasurementType, epoch: Epoch) -> Result<f64, ODError> {
let stochastics = self.stochastic_noises.as_ref().unwrap();
Ok(stochastics
.get(&msr_type)
.ok_or(ODError::NoiseNotConfigured {
kind: format!("{msr_type:?}"),
})?
.covariance(epoch))
}
fn measurement_bias(&self, msr_type: MeasurementType, _epoch: Epoch) -> Result<f64, ODError> {
let stochastics = self.stochastic_noises.as_ref().unwrap();
if let Some(gm) = stochastics
.get(&msr_type)
.ok_or(ODError::NoiseNotConfigured {
kind: format!("{msr_type:?}"),
})?
.bias
{
Ok(gm.constant.unwrap_or(0.0))
} else {
Ok(0.0)
}
}
}