nyx_space/od/msr/
types.rs1use anise::astro::AzElRange;
20use arrow::datatypes::{DataType, Field};
21use serde::{Deserialize, Serialize};
22use std::{collections::HashMap, str::FromStr};
23
24use crate::{io::InputOutputError, od::ODError};
25
26#[cfg(feature = "python")]
27use pyo3::prelude::*;
28
29#[cfg_attr(feature = "python", pyclass, pyo3(module = "nyx_space.od"))]
30#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, der::Enumerated)]
31#[repr(u8)]
32pub enum MeasurementType {
33 #[serde(rename = "range_km")]
34 Range = 0,
35 #[serde(rename = "doppler_km_s")]
36 Doppler = 1,
37 #[serde(rename = "azimuth_deg")]
38 Azimuth = 2,
39 #[serde(rename = "elevation_deg")]
40 Elevation = 3,
41 #[serde(rename = "receive_freq")]
42 ReceiveFrequency = 4,
43 #[serde(rename = "transmit_freq")]
44 TransmitFrequency = 5,
45}
46
47impl MeasurementType {
48 pub fn unit(self) -> &'static str {
50 match self {
51 Self::Range => "km",
52 Self::Doppler => "km/s",
53 Self::Azimuth | Self::Elevation => "deg",
54 Self::ReceiveFrequency | Self::TransmitFrequency => "Hz",
55 }
56 }
57
58 pub(crate) fn may_be_two_way(self) -> bool {
60 match self {
61 MeasurementType::Range | MeasurementType::Doppler => true,
62 MeasurementType::Azimuth
63 | MeasurementType::Elevation
64 | MeasurementType::ReceiveFrequency
65 | MeasurementType::TransmitFrequency => false,
66 }
67 }
68
69 pub fn to_field(&self) -> Field {
72 let mut meta = HashMap::new();
73 meta.insert("unit".to_string(), self.unit().to_string());
74
75 Field::new(
76 format!("{self:?} ({})", self.unit()),
77 DataType::Float64,
78 true,
79 )
80 .with_metadata(meta)
81 }
82
83 pub fn compute_one_way(self, aer: AzElRange, noise: f64) -> Result<f64, ODError> {
85 match self {
86 Self::Range => Ok(aer.range_km + noise),
87 Self::Doppler => Ok(aer.range_rate_km_s + noise),
88 Self::Azimuth => Ok(aer.azimuth_deg + noise),
89 Self::Elevation => Ok(aer.elevation_deg + noise),
90 Self::ReceiveFrequency | Self::TransmitFrequency => Err(ODError::MeasurementSimError {
91 details: format!("{self:?} is only supported in CCSDS TDM parsing"),
92 }),
93 }
94 }
95
96 pub fn compute_two_way(
99 self,
100 aer_t0: AzElRange,
101 aer_t1: AzElRange,
102 noise: f64,
103 ) -> Result<f64, ODError> {
104 match self {
105 Self::Range => {
106 let range_km = (aer_t1.range_km + aer_t0.range_km) * 0.5;
107 Ok(range_km + noise / 2.0_f64.sqrt())
108 }
109 Self::Doppler => {
110 let doppler_km_s = (aer_t1.range_rate_km_s + aer_t0.range_rate_km_s) * 0.5;
111 Ok(doppler_km_s + noise / 2.0_f64.sqrt())
112 }
113 Self::Azimuth => {
114 let az_deg = (aer_t1.azimuth_deg + aer_t0.azimuth_deg) * 0.5;
115 Ok(az_deg + noise / 2.0_f64.sqrt())
116 }
117 Self::Elevation => {
118 let el_deg = (aer_t1.elevation_deg + aer_t0.elevation_deg) * 0.5;
119 Ok(el_deg + noise / 2.0_f64.sqrt())
120 }
121 Self::ReceiveFrequency | Self::TransmitFrequency => Err(ODError::MeasurementSimError {
122 details: format!("{self:?} is only supported in CCSDS TDM parsing"),
123 }),
124 }
125 }
126
127 pub fn ccsds_tdm_name(&self) -> &str {
129 match self {
130 MeasurementType::Range => "RANGE",
131 MeasurementType::Doppler => "DOPPLER_INTEGRATED",
132 MeasurementType::Azimuth => "ANGLE_1",
133 MeasurementType::Elevation => "ANGLE_2",
134 MeasurementType::ReceiveFrequency => "RECEIVE_FREQ",
135 MeasurementType::TransmitFrequency => "TRANSMIT_FREQ",
136 }
137 }
138}
139
140impl FromStr for MeasurementType {
141 type Err = InputOutputError;
142
143 fn from_str(s: &str) -> Result<Self, Self::Err> {
144 match s.to_lowercase().as_str() {
145 "range" => Ok(Self::Range),
146 "doppler" => Ok(Self::Doppler),
147 "azimuth" => Ok(Self::Azimuth),
148 "elevation" => Ok(Self::Elevation),
149 _ => Err(InputOutputError::UnsupportedData {
150 which: s.to_string(),
151 }),
152 }
153 }
154}