use anise::astro::AzElRange;
use arrow::datatypes::{DataType, Field};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, str::FromStr};
use crate::{io::InputOutputError, od::ODError};
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg_attr(
feature = "python",
pyclass(from_py_object),
pyo3(module = "nyx_space.od")
)]
#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, der::Enumerated)]
#[repr(u8)]
pub enum MeasurementType {
#[serde(rename = "range_km")]
Range = 0,
#[serde(rename = "doppler_km_s")]
Doppler = 1,
#[serde(rename = "azimuth_deg")]
Azimuth = 2,
#[serde(rename = "elevation_deg")]
Elevation = 3,
#[serde(rename = "receive_freq")]
ReceiveFrequency = 4,
#[serde(rename = "transmit_freq")]
TransmitFrequency = 5,
#[serde(rename = "transmit_freq_rate")]
TransmitFrequencyRate = 9,
#[serde(rename = "x")]
X = 6,
#[serde(rename = "y")]
Y = 7,
#[serde(rename = "z")]
Z = 8,
}
impl MeasurementType {
pub fn unit(self) -> &'static str {
match self {
Self::Range => "km",
Self::Doppler => "km/s",
Self::Azimuth | Self::Elevation => "deg",
Self::ReceiveFrequency | Self::TransmitFrequency => "Hz",
Self::TransmitFrequencyRate => "Hz/s",
Self::X | Self::Y | Self::Z => "km",
}
}
pub(crate) fn may_be_two_way(self) -> bool {
match self {
MeasurementType::Range | MeasurementType::Doppler => true,
MeasurementType::Azimuth
| MeasurementType::Elevation
| MeasurementType::ReceiveFrequency
| MeasurementType::TransmitFrequency
| MeasurementType::TransmitFrequencyRate
| MeasurementType::X
| MeasurementType::Y
| MeasurementType::Z => false,
}
}
pub fn to_field(&self) -> Field {
let mut meta = HashMap::new();
meta.insert("unit".to_string(), self.unit().to_string());
Field::new(
format!("{self:?} ({})", self.unit()),
DataType::Float64,
true,
)
.with_metadata(meta)
}
pub fn compute_one_way(self, aer: AzElRange, noise: f64) -> Result<f64, ODError> {
match self {
Self::Range => Ok(aer.range_km + noise),
Self::Doppler => Ok(aer.range_rate_km_s + noise),
Self::Azimuth => Ok(aer.azimuth_deg + noise),
Self::Elevation => Ok(aer.elevation_deg + noise),
Self::ReceiveFrequency | Self::TransmitFrequency | Self::TransmitFrequencyRate => {
Err(ODError::MeasurementSimError {
details: format!("{self:?} is only supported in CCSDS TDM parsing"),
})
}
Self::X | Self::Y | Self::Z => Err(ODError::MeasurementSimError {
details: format!("{self:?} must be computed directly from the state"),
}),
}
}
pub fn compute_two_way(
self,
aer_t0: AzElRange,
aer_t1: AzElRange,
noise: f64,
) -> Result<f64, ODError> {
match self {
Self::Range => {
let range_km = (aer_t1.range_km + aer_t0.range_km) * 0.5;
Ok(range_km + noise / 2.0_f64.sqrt())
}
Self::Doppler => {
let doppler_km_s = (aer_t1.range_rate_km_s + aer_t0.range_rate_km_s) * 0.5;
Ok(doppler_km_s + noise / 2.0_f64.sqrt())
}
Self::Azimuth => {
let az_deg = (aer_t1.azimuth_deg + aer_t0.azimuth_deg) * 0.5;
Ok(az_deg + noise / 2.0_f64.sqrt())
}
Self::Elevation => {
let el_deg = (aer_t1.elevation_deg + aer_t0.elevation_deg) * 0.5;
Ok(el_deg + noise / 2.0_f64.sqrt())
}
Self::ReceiveFrequency | Self::TransmitFrequency | Self::TransmitFrequencyRate => {
Err(ODError::MeasurementSimError {
details: format!("{self:?} is only supported in CCSDS TDM parsing"),
})
}
Self::X | Self::Y | Self::Z => Err(ODError::MeasurementSimError {
details: format!("{self:?} is not supported for two way measurements"),
}),
}
}
pub fn ccsds_tdm_name(&self) -> &str {
match self {
MeasurementType::Range => "RANGE",
MeasurementType::Doppler => "DOPPLER_INTEGRATED",
MeasurementType::Azimuth => "ANGLE_1",
MeasurementType::Elevation => "ANGLE_2",
MeasurementType::ReceiveFrequency => "RECEIVE_FREQ",
MeasurementType::TransmitFrequency => "TRANSMIT_FREQ",
MeasurementType::TransmitFrequencyRate => "TRANSMIT_FREQ_RATE",
MeasurementType::X => "X",
MeasurementType::Y => "Y",
MeasurementType::Z => "Z",
}
}
}
impl FromStr for MeasurementType {
type Err = InputOutputError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"range" => Ok(Self::Range),
"doppler" => Ok(Self::Doppler),
"azimuth" => Ok(Self::Azimuth),
"elevation" => Ok(Self::Elevation),
"x" => Ok(Self::X),
"y" => Ok(Self::Y),
"z" => Ok(Self::Z),
_ => Err(InputOutputError::UnsupportedData {
which: s.to_string(),
}),
}
}
}