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 #[serde(rename = "x")]
46 X = 6,
47 #[serde(rename = "y")]
48 Y = 7,
49 #[serde(rename = "z")]
50 Z = 8,
51}
52
53impl MeasurementType {
54 pub fn unit(self) -> &'static str {
56 match self {
57 Self::Range => "km",
58 Self::Doppler => "km/s",
59 Self::Azimuth | Self::Elevation => "deg",
60 Self::ReceiveFrequency | Self::TransmitFrequency => "Hz",
61 Self::X | Self::Y | Self::Z => "km",
62 }
63 }
64
65 pub(crate) fn may_be_two_way(self) -> bool {
67 match self {
68 MeasurementType::Range | MeasurementType::Doppler => true,
69 MeasurementType::Azimuth
70 | MeasurementType::Elevation
71 | MeasurementType::ReceiveFrequency
72 | MeasurementType::TransmitFrequency
73 | MeasurementType::X
74 | MeasurementType::Y
75 | MeasurementType::Z => false,
76 }
77 }
78
79 pub fn to_field(&self) -> Field {
82 let mut meta = HashMap::new();
83 meta.insert("unit".to_string(), self.unit().to_string());
84
85 Field::new(
86 format!("{self:?} ({})", self.unit()),
87 DataType::Float64,
88 true,
89 )
90 .with_metadata(meta)
91 }
92
93 pub fn compute_one_way(self, aer: AzElRange, noise: f64) -> Result<f64, ODError> {
95 match self {
96 Self::Range => Ok(aer.range_km + noise),
97 Self::Doppler => Ok(aer.range_rate_km_s + noise),
98 Self::Azimuth => Ok(aer.azimuth_deg + noise),
99 Self::Elevation => Ok(aer.elevation_deg + noise),
100 Self::ReceiveFrequency | Self::TransmitFrequency => Err(ODError::MeasurementSimError {
101 details: format!("{self:?} is only supported in CCSDS TDM parsing"),
102 }),
103 Self::X | Self::Y | Self::Z => Err(ODError::MeasurementSimError {
104 details: format!("{self:?} must be computed directly from the state"),
105 }),
106 }
107 }
108
109 pub fn compute_two_way(
112 self,
113 aer_t0: AzElRange,
114 aer_t1: AzElRange,
115 noise: f64,
116 ) -> Result<f64, ODError> {
117 match self {
118 Self::Range => {
119 let range_km = (aer_t1.range_km + aer_t0.range_km) * 0.5;
120 Ok(range_km + noise / 2.0_f64.sqrt())
121 }
122 Self::Doppler => {
123 let doppler_km_s = (aer_t1.range_rate_km_s + aer_t0.range_rate_km_s) * 0.5;
124 Ok(doppler_km_s + noise / 2.0_f64.sqrt())
125 }
126 Self::Azimuth => {
127 let az_deg = (aer_t1.azimuth_deg + aer_t0.azimuth_deg) * 0.5;
128 Ok(az_deg + noise / 2.0_f64.sqrt())
129 }
130 Self::Elevation => {
131 let el_deg = (aer_t1.elevation_deg + aer_t0.elevation_deg) * 0.5;
132 Ok(el_deg + noise / 2.0_f64.sqrt())
133 }
134 Self::ReceiveFrequency | Self::TransmitFrequency => Err(ODError::MeasurementSimError {
135 details: format!("{self:?} is only supported in CCSDS TDM parsing"),
136 }),
137 Self::X | Self::Y | Self::Z => Err(ODError::MeasurementSimError {
138 details: format!("{self:?} is not supported for two way measurements"),
139 }),
140 }
141 }
142
143 pub fn ccsds_tdm_name(&self) -> &str {
145 match self {
146 MeasurementType::Range => "RANGE",
147 MeasurementType::Doppler => "DOPPLER_INTEGRATED",
148 MeasurementType::Azimuth => "ANGLE_1",
149 MeasurementType::Elevation => "ANGLE_2",
150 MeasurementType::ReceiveFrequency => "RECEIVE_FREQ",
151 MeasurementType::TransmitFrequency => "TRANSMIT_FREQ",
152 MeasurementType::X => "X",
153 MeasurementType::Y => "Y",
154 MeasurementType::Z => "Z",
155 }
156 }
157}
158
159impl FromStr for MeasurementType {
160 type Err = InputOutputError;
161
162 fn from_str(s: &str) -> Result<Self, Self::Err> {
163 match s.to_lowercase().as_str() {
164 "range" => Ok(Self::Range),
165 "doppler" => Ok(Self::Doppler),
166 "azimuth" => Ok(Self::Azimuth),
167 "elevation" => Ok(Self::Elevation),
168 "x" => Ok(Self::X),
169 "y" => Ok(Self::Y),
170 "z" => Ok(Self::Z),
171 _ => Err(InputOutputError::UnsupportedData {
172 which: s.to_string(),
173 }),
174 }
175 }
176}