nyx_space/od/msr/
types.rs

1/*
2    Nyx, blazing fast astrodynamics
3    Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19use anise::astro::AzElRange;
20use arrow::datatypes::{DataType, Field};
21use serde_derive::{Deserialize, Serialize};
22use std::collections::HashMap;
23
24use crate::od::ODError;
25
26#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq)]
27pub enum MeasurementType {
28    #[serde(rename = "range_km")]
29    Range,
30    #[serde(rename = "doppler_km_s")]
31    Doppler,
32    #[serde(rename = "azimuth_deg")]
33    Azimuth,
34    #[serde(rename = "elevation_deg")]
35    Elevation,
36    #[serde(rename = "receive_freq")]
37    ReceiveFrequency,
38    #[serde(rename = "transmit_freq")]
39    TransmitFrequency,
40}
41
42impl MeasurementType {
43    /// Returns the expected unit of this measurement type
44    pub fn unit(self) -> &'static str {
45        match self {
46            Self::Range => "km",
47            Self::Doppler => "km/s",
48            Self::Azimuth | Self::Elevation => "deg",
49            Self::ReceiveFrequency | Self::TransmitFrequency => "Hz",
50        }
51    }
52
53    /// Returns true if this measurement type could be a two-way measurement.
54    pub(crate) fn may_be_two_way(self) -> bool {
55        match self {
56            MeasurementType::Range | MeasurementType::Doppler => true,
57            MeasurementType::Azimuth
58            | MeasurementType::Elevation
59            | MeasurementType::ReceiveFrequency
60            | MeasurementType::TransmitFrequency => false,
61        }
62    }
63
64    /// Returns the fields for this kind of measurement. The metadata includes a `unit` field with the unit.
65    /// Column is nullable in case there is no such measurement at a given epoch.
66    pub fn to_field(&self) -> Field {
67        let mut meta = HashMap::new();
68        meta.insert("unit".to_string(), self.unit().to_string());
69
70        Field::new(
71            format!("{self:?} ({})", self.unit()),
72            DataType::Float64,
73            true,
74        )
75        .with_metadata(meta)
76    }
77
78    /// Computes the one way measurement from an AER object and the noise of this measurement type, returned in the units of this measurement type.
79    pub fn compute_one_way(self, aer: AzElRange, noise: f64) -> Result<f64, ODError> {
80        match self {
81            Self::Range => Ok(aer.range_km + noise),
82            Self::Doppler => Ok(aer.range_rate_km_s + noise),
83            Self::Azimuth => Ok(aer.azimuth_deg + noise),
84            Self::Elevation => Ok(aer.elevation_deg + noise),
85            Self::ReceiveFrequency | Self::TransmitFrequency => Err(ODError::MeasurementSimError {
86                details: format!("{self:?} is only supported in CCSDS TDM parsing"),
87            }),
88        }
89    }
90
91    /// Computes the two way measurement from two AER values and the noise of this measurement type, returned in the units of this measurement type.
92    /// Two way is modeled by averaging the measurement in between both times, and adding the noise divided by sqrt(2).
93    pub fn compute_two_way(
94        self,
95        aer_t0: AzElRange,
96        aer_t1: AzElRange,
97        noise: f64,
98    ) -> Result<f64, ODError> {
99        match self {
100            Self::Range => {
101                let range_km = (aer_t1.range_km + aer_t0.range_km) * 0.5;
102                Ok(range_km + noise / 2.0_f64.sqrt())
103            }
104            Self::Doppler => {
105                let doppler_km_s = (aer_t1.range_rate_km_s + aer_t0.range_rate_km_s) * 0.5;
106                Ok(doppler_km_s + noise / 2.0_f64.sqrt())
107            }
108            Self::Azimuth => {
109                let az_deg = (aer_t1.azimuth_deg + aer_t0.azimuth_deg) * 0.5;
110                Ok(az_deg + noise / 2.0_f64.sqrt())
111            }
112            Self::Elevation => {
113                let el_deg = (aer_t1.elevation_deg + aer_t0.elevation_deg) * 0.5;
114                Ok(el_deg + noise / 2.0_f64.sqrt())
115            }
116            Self::ReceiveFrequency | Self::TransmitFrequency => Err(ODError::MeasurementSimError {
117                details: format!("{self:?} is only supported in CCSDS TDM parsing"),
118            }),
119        }
120    }
121}