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(
30 feature = "python",
31 pyclass(from_py_object),
32 pyo3(module = "nyx_space.od")
33)]
34#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, der::Enumerated)]
35#[repr(u8)]
36pub enum MeasurementType {
37 #[serde(rename = "range_km")]
38 Range = 0,
39 #[serde(rename = "doppler_km_s")]
40 Doppler = 1,
41 #[serde(rename = "azimuth_deg")]
42 Azimuth = 2,
43 #[serde(rename = "elevation_deg")]
44 Elevation = 3,
45 #[serde(rename = "receive_freq")]
46 ReceiveFrequency = 4,
47 #[serde(rename = "transmit_freq")]
48 TransmitFrequency = 5,
49 #[serde(rename = "transmit_freq_rate")]
50 TransmitFrequencyRate = 9,
51 #[serde(rename = "x")]
52 X = 6,
53 #[serde(rename = "y")]
54 Y = 7,
55 #[serde(rename = "z")]
56 Z = 8,
57}
58
59impl MeasurementType {
60 pub fn unit(self) -> &'static str {
62 match self {
63 Self::Range => "km",
64 Self::Doppler => "km/s",
65 Self::Azimuth | Self::Elevation => "deg",
66 Self::ReceiveFrequency | Self::TransmitFrequency => "Hz",
67 Self::TransmitFrequencyRate => "Hz/s",
68 Self::X | Self::Y | Self::Z => "km",
69 }
70 }
71
72 pub(crate) fn may_be_two_way(self) -> bool {
74 match self {
75 MeasurementType::Range | MeasurementType::Doppler => true,
76 MeasurementType::Azimuth
77 | MeasurementType::Elevation
78 | MeasurementType::ReceiveFrequency
79 | MeasurementType::TransmitFrequency
80 | MeasurementType::TransmitFrequencyRate
81 | MeasurementType::X
82 | MeasurementType::Y
83 | MeasurementType::Z => false,
84 }
85 }
86
87 pub fn to_field(&self) -> Field {
90 let mut meta = HashMap::new();
91 meta.insert("unit".to_string(), self.unit().to_string());
92
93 Field::new(
94 format!("{self:?} ({})", self.unit()),
95 DataType::Float64,
96 true,
97 )
98 .with_metadata(meta)
99 }
100
101 pub fn compute_one_way(self, aer: AzElRange, noise: f64) -> Result<f64, ODError> {
103 match self {
104 Self::Range => Ok(aer.range_km + noise),
105 Self::Doppler => Ok(aer.range_rate_km_s + noise),
106 Self::Azimuth => Ok(aer.azimuth_deg + noise),
107 Self::Elevation => Ok(aer.elevation_deg + noise),
108 Self::ReceiveFrequency | Self::TransmitFrequency | Self::TransmitFrequencyRate => {
109 Err(ODError::MeasurementSimError {
110 details: format!("{self:?} is only supported in CCSDS TDM parsing"),
111 })
112 }
113 Self::X | Self::Y | Self::Z => Err(ODError::MeasurementSimError {
114 details: format!("{self:?} must be computed directly from the state"),
115 }),
116 }
117 }
118
119 pub fn compute_two_way(
122 self,
123 aer_t0: AzElRange,
124 aer_t1: AzElRange,
125 noise: f64,
126 ) -> Result<f64, ODError> {
127 match self {
128 Self::Range => {
129 let range_km = (aer_t1.range_km + aer_t0.range_km) * 0.5;
130 Ok(range_km + noise / 2.0_f64.sqrt())
131 }
132 Self::Doppler => {
133 let doppler_km_s = (aer_t1.range_rate_km_s + aer_t0.range_rate_km_s) * 0.5;
134 Ok(doppler_km_s + noise / 2.0_f64.sqrt())
135 }
136 Self::Azimuth => {
137 let az_deg = (aer_t1.azimuth_deg + aer_t0.azimuth_deg) * 0.5;
138 Ok(az_deg + noise / 2.0_f64.sqrt())
139 }
140 Self::Elevation => {
141 let el_deg = (aer_t1.elevation_deg + aer_t0.elevation_deg) * 0.5;
142 Ok(el_deg + noise / 2.0_f64.sqrt())
143 }
144 Self::ReceiveFrequency | Self::TransmitFrequency | Self::TransmitFrequencyRate => {
145 Err(ODError::MeasurementSimError {
146 details: format!("{self:?} is only supported in CCSDS TDM parsing"),
147 })
148 }
149 Self::X | Self::Y | Self::Z => Err(ODError::MeasurementSimError {
150 details: format!("{self:?} is not supported for two way measurements"),
151 }),
152 }
153 }
154
155 pub fn ccsds_tdm_name(&self) -> &str {
157 match self {
158 MeasurementType::Range => "RANGE",
159 MeasurementType::Doppler => "DOPPLER_INTEGRATED",
160 MeasurementType::Azimuth => "ANGLE_1",
161 MeasurementType::Elevation => "ANGLE_2",
162 MeasurementType::ReceiveFrequency => "RECEIVE_FREQ",
163 MeasurementType::TransmitFrequency => "TRANSMIT_FREQ",
164 MeasurementType::TransmitFrequencyRate => "TRANSMIT_FREQ_RATE",
165 MeasurementType::X => "X",
166 MeasurementType::Y => "Y",
167 MeasurementType::Z => "Z",
168 }
169 }
170}
171
172impl FromStr for MeasurementType {
173 type Err = InputOutputError;
174
175 fn from_str(s: &str) -> Result<Self, Self::Err> {
176 match s.to_lowercase().as_str() {
177 "range" => Ok(Self::Range),
178 "doppler" => Ok(Self::Doppler),
179 "azimuth" => Ok(Self::Azimuth),
180 "elevation" => Ok(Self::Elevation),
181 "x" => Ok(Self::X),
182 "y" => Ok(Self::Y),
183 "z" => Ok(Self::Z),
184 _ => Err(InputOutputError::UnsupportedData {
185 which: s.to_string(),
186 }),
187 }
188 }
189}