nyx_space/od/msr/
types.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*
    Nyx, blazing fast astrodynamics
    Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

use anise::astro::AzElRange;
use arrow::datatypes::{DataType, Field};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::od::ODError;

#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq)]
pub enum MeasurementType {
    #[serde(rename = "range_km")]
    Range,
    #[serde(rename = "doppler_km_s")]
    Doppler,
    #[serde(rename = "azimuth_deg")]
    Azimuth,
    #[serde(rename = "elevation_deg")]
    Elevation,
    #[serde(rename = "receive_freq")]
    ReceiveFrequency,
    #[serde(rename = "transmit_freq")]
    TransmitFrequency,
}

impl MeasurementType {
    /// Returns the expected unit of this measurement type
    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",
        }
    }

    /// Returns true if this measurement type could be a two-way measurement.
    pub(crate) fn may_be_two_way(self) -> bool {
        match self {
            MeasurementType::Range | MeasurementType::Doppler => true,
            MeasurementType::Azimuth
            | MeasurementType::Elevation
            | MeasurementType::ReceiveFrequency
            | MeasurementType::TransmitFrequency => false,
        }
    }

    /// Returns the fields for this kind of measurement. The metadata includes a `unit` field with the unit.
    /// Column is nullable in case there is no such measurement at a given epoch.
    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)
    }

    /// Computes the one way measurement from an AER object and the noise of this measurement type, returned in the units of this measurement type.
    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 => Err(ODError::MeasurementSimError {
                details: format!("{self:?} is only supported in CCSDS TDM parsing"),
            }),
        }
    }

    /// Computes the two way measurement from two AER values and the noise of this measurement type, returned in the units of this measurement type.
    /// Two way is modeled by averaging the measurement in between both times, and adding the noise divided by sqrt(2).
    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 => Err(ODError::MeasurementSimError {
                details: format!("{self:?} is only supported in CCSDS TDM parsing"),
            }),
        }
    }
}