use anise::almanac::Almanac;
use anise::ephemerides::EphemerisError;
use anise::errors::{AlmanacError, AlmanacResult};
use anise::math::interpolation::InterpolationError;
use anise::prelude::{Frame, Orbit};
use hifitime::{Epoch, TimeUnits};
use indexmap::IndexSet;
use rand_pcg::Pcg64Mcg;
use snafu::ResultExt;
use crate::State;
use crate::md::prelude::Traj;
use crate::od::TrackingDevice;
use crate::od::groundpnt::GroundAsset;
use crate::od::interlink::InterlinkTxSpacecraft;
use crate::od::msr::MeasurementType;
use crate::od::prelude::{Measurement, ODError};
use crate::od::{ODAlmanacSnafu, ODTrajSnafu};
impl TrackingDevice<GroundAsset> for InterlinkTxSpacecraft {
fn name(&self) -> String {
self.traj.name.clone().unwrap_or("unnamed".to_string())
}
fn measurement_types(&self) -> &IndexSet<MeasurementType> {
&self.measurement_types
}
fn location(&self, epoch: Epoch, frame: Frame, almanac: &Almanac) -> AlmanacResult<Orbit> {
match self.traj.at(epoch) {
Ok(state) => almanac.transform_to(state.orbit, frame, self.ab_corr),
Err(_) => Err(AlmanacError::Ephemeris {
action: "computing location from Interlink",
source: Box::new(EphemerisError::EphemInterpolation {
source: InterpolationError::MissingInterpolationData { epoch },
}),
}),
}
}
fn measure(
&mut self,
epoch: Epoch,
traj: &Traj<GroundAsset>,
rng: Option<&mut Pcg64Mcg>,
almanac: &Almanac,
) -> Result<Option<Measurement>, ODError> {
match self.integration_time {
Some(integration_time) => {
let rx_0 = match traj.at(epoch - integration_time).context(ODTrajSnafu {
details: format!(
"fetching state {epoch} at start of ground station integration time {integration_time}"
),
}) {
Ok(rx) => rx,
Err(_) => return Ok(None),
};
let rx_1 = match traj.at(epoch).context(ODTrajSnafu {
details: format!(
"fetching state {epoch} at end of ground station integration time"
),
}) {
Ok(rx) => rx,
Err(_) => return Ok(None),
};
let msr_t0_opt = self.measure_instantaneous(rx_0, None, almanac)?;
let msr_t1_opt = self.measure_instantaneous(rx_1, None, almanac)?;
if let Some(msr_t0) = msr_t0_opt {
if let Some(msr_t1) = msr_t1_opt {
let noises = self.noises(epoch - integration_time * 0.5, rng)?;
let mut msr = Measurement::new(
"GroundAsset".to_string(),
epoch + noises[0].seconds(),
);
for (ii, msr_type) in self.measurement_types.iter().enumerate() {
let msr_value_0 = msr_t0.data[msr_type];
let msr_value_1 = msr_t1.data[msr_type];
let msr_value =
(msr_value_1 + msr_value_0) * 0.5 + noises[ii + 1] / 2.0_f64.sqrt();
msr.push(*msr_type, msr_value);
}
Ok(Some(msr))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
None => self.measure_instantaneous(
traj.at(epoch).context(ODTrajSnafu {
details: "fetching state for instantaneous measurement".to_string(),
})?,
rng,
almanac,
),
}
}
fn measure_instantaneous(
&mut self,
rx: GroundAsset,
rng: Option<&mut Pcg64Mcg>,
almanac: &Almanac,
) -> Result<Option<Measurement>, ODError> {
let pnt_veh = self.traj.at(rx.epoch()).context(ODTrajSnafu {
details: format!("fetching state {} for interlink", rx.epoch()),
})?;
let asset_loc = rx.to_location();
let aer = almanac
.azimuth_elevation_range_sez_from_location(pnt_veh.orbit, asset_loc, None, None)
.context(ODAlmanacSnafu {
action: "transforming receiver to transmitter frame",
})?;
if aer.elevation_above_mask_deg() < 0.0 {
Ok(None)
} else {
let noises = self.noises(rx.epoch, rng)?;
let mut msr =
Measurement::new("GroundAsset".to_string(), rx.epoch + noises[0].seconds());
for (ii, msr_type) in self.measurement_types.iter().enumerate() {
let msr_value = match *msr_type {
MeasurementType::Range => aer.range_km,
MeasurementType::Doppler => aer.range_rate_km_s,
_ => unreachable!("unsupported measurement type for interlink: {:?}", msr_type),
} + noises[ii + 1];
msr.push(*msr_type, msr_value);
}
Ok(Some(msr))
}
}
fn measurement_covar(&self, msr_type: MeasurementType, epoch: Epoch) -> Result<f64, ODError> {
let stochastics =
self.stochastic_noises
.as_ref()
.ok_or_else(|| ODError::NoiseNotConfigured {
kind: "stochastic noises for interlink transmitter".to_string(),
})?;
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)
}
}
}