doris_rs/
observable.rs

1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4use crate::{error::ParsingError, frequency::Frequency};
5
6/// [Observable] describes both frequency and physics.
7/// For example, [Observable::UnambiguousPhaseRange] and [Observable::Power] are two different physics.
8/// DORIS files also provides information sampled at the ground station level for high
9/// precision models (like pressure and moisture rate).
10#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Ord, Eq)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub enum Observable {
13    /// Decoded Pseudo range (in meters).  
14    /// All frequency dependent DORIS measurements (=signal measurements) were
15    /// performed in space.
16    PseudoRange(Frequency),
17
18    /// Unambiguous carrier phase observation (in meters, not cycles).  
19    /// Unlike standard RINEX observations, ambiguities have been resolved
20    /// prior publishing a DORIS file.
21    /// All frequency dependent DORIS measurements (=signal measurements) were
22    /// performed in space.
23    UnambiguousPhaseRange(Frequency),
24
25    /// Received signal power (in dBm).  
26    /// All frequency dependent DORIS measurements (=signal measurements) were
27    /// performed in space.  
28    Power(Frequency),
29
30    /// Pressure at ground station level at epoch of spaceborn observation, in \[hPa\].
31    Pressure,
32
33    /// Dry temperature at ground station level at epoch of spaceborn observation, in celcius degrees.
34    Temperature,
35
36    /// Moisture rate at ground station level at epoch of spaceborn observation, as saturation percentage.
37    HumidityRate,
38
39    /// f1 / f2 frequency ratio (dimensionless), image of the frequency drift
40    FrequencyRatio,
41}
42
43impl Default for Observable {
44    fn default() -> Self {
45        Self::PseudoRange(Default::default())
46    }
47}
48
49impl Observable {
50    /// Returns true if both [Observable]s come from the same [Frequency]
51    pub fn same_frequency(&self, rhs: &Observable) -> bool {
52        match self {
53            Self::PseudoRange(freq) | Self::Power(freq) | Self::UnambiguousPhaseRange(freq) => {
54                match rhs {
55                    Self::PseudoRange(rhs)
56                    | Self::Power(rhs)
57                    | Self::UnambiguousPhaseRange(rhs) => rhs == freq,
58                    _ => false,
59                }
60            },
61            _ => false,
62        }
63    }
64
65    /// Returns true if Self and rhs describe the same physical observation.
66    /// For example, both are phase observations.
67    pub fn same_physics(&self, rhs: &Observable) -> bool {
68        match self {
69            Self::UnambiguousPhaseRange(_) => matches!(rhs, Self::UnambiguousPhaseRange(_)),
70            Self::PseudoRange(_) => matches!(rhs, Self::PseudoRange(_)),
71            Self::Power(_) => matches!(rhs, Self::Power(_)),
72            Self::Pressure => matches!(rhs, Self::Pressure),
73            Self::Temperature => matches!(rhs, Self::Temperature),
74            Self::HumidityRate => matches!(rhs, Self::HumidityRate),
75            Self::FrequencyRatio => matches!(rhs, Self::FrequencyRatio),
76        }
77    }
78
79    /// Returns true if this a [Observable::UnambiguousPhaseRange] measurement
80    pub fn is_phase_range_observable(&self) -> bool {
81        matches!(self, Self::UnambiguousPhaseRange(_))
82    }
83
84    /// Returns true if this [Observable] is an [Observable::UnambiguousPhaseRange] measurement
85    pub fn is_pseudo_range_observable(&self) -> bool {
86        matches!(self, Self::PseudoRange(_))
87    }
88
89    pub fn is_power_observable(&self) -> bool {
90        matches!(self, Self::Power(_))
91    }
92}
93
94impl std::fmt::Display for Observable {
95    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
96        match self {
97            Self::Pressure => write!(f, "Pressure"),
98            Self::Temperature => write!(f, "Temperature"),
99            Self::HumidityRate => write!(f, "Moisture rate"),
100            Self::FrequencyRatio => write!(f, "Frequency ratio"),
101            Self::PseudoRange(freq) => write!(f, "C{}", freq),
102            Self::UnambiguousPhaseRange(freq) => write!(f, "L{}", freq),
103            Self::Power(freq) => write!(f, "W{}", freq),
104        }
105    }
106}
107
108impl std::fmt::LowerHex for Observable {
109    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
110        match self {
111            Self::Pressure => write!(f, "P"),
112            Self::Temperature => write!(f, "T"),
113            Self::HumidityRate => write!(f, "H"),
114            Self::FrequencyRatio => write!(f, "F"),
115            Self::PseudoRange(freq) => write!(f, "C{}", freq),
116            Self::UnambiguousPhaseRange(freq) => write!(f, "L{}", freq),
117            Self::Power(freq) => write!(f, "W{}", freq),
118        }
119    }
120}
121
122impl std::str::FromStr for Observable {
123    type Err = ParsingError;
124
125    fn from_str(content: &str) -> Result<Self, Self::Err> {
126        let content = content.to_uppercase();
127        let content = content.trim();
128        match content {
129            "P" | "PRESSURE" => Ok(Self::Pressure),
130            "T" | "TEMPERATURE" => Ok(Self::Temperature),
131            "H" | "MOISTURE RATE" => Ok(Self::HumidityRate),
132            "F" | "FREQUENCY RATIO" => Ok(Self::FrequencyRatio),
133            _ => {
134                let frequency = Frequency::from_str(&content[1..])?;
135                if content.starts_with('L') {
136                    Ok(Self::UnambiguousPhaseRange(frequency))
137                } else if content.starts_with('C') {
138                    Ok(Self::PseudoRange(frequency))
139                } else if content.starts_with('W') {
140                    Ok(Self::Power(frequency))
141                } else {
142                    Err(ParsingError::Observable)
143                }
144            },
145        }
146    }
147}
148
149#[cfg(test)]
150mod test {
151    use crate::prelude::{Frequency, Observable};
152    use std::str::FromStr;
153
154    #[test]
155    fn test_default_observable() {
156        let default = Observable::default();
157
158        assert_eq!(default, Observable::PseudoRange(Frequency::DORIS1));
159
160        let formatted = default.to_string();
161
162        let parsed = Observable::from_str(&formatted).unwrap_or_else(|e| {
163            panic!("Failed to parse observable from \"{}\"", formatted);
164        });
165
166        assert_eq!(parsed, default);
167    }
168
169    #[test]
170    fn observable_parsing() {
171        for (observable, expected, formatted) in [
172            (
173                "L1",
174                Observable::UnambiguousPhaseRange(Frequency::DORIS1),
175                "L1",
176            ),
177            (
178                "L2",
179                Observable::UnambiguousPhaseRange(Frequency::DORIS2),
180                "L2",
181            ),
182            ("C1", Observable::PseudoRange(Frequency::DORIS1), "C1"),
183            ("C2", Observable::PseudoRange(Frequency::DORIS2), "C2"),
184            ("W1", Observable::Power(Frequency::DORIS1), "W1"),
185            ("W2", Observable::Power(Frequency::DORIS2), "W2"),
186            ("T", Observable::Temperature, "Temperature"),
187            ("P", Observable::Pressure, "Pressure"),
188            ("H", Observable::HumidityRate, "Moisture rate"),
189        ] {
190            let parsed = Observable::from_str(observable).unwrap_or_else(|e| {
191                panic!("failed to parse observable from \"{}\": {}", observable, e);
192            });
193
194            assert_eq!(parsed, expected);
195            assert_eq!(parsed.to_string(), formatted);
196        }
197
198        let l1 = Observable::UnambiguousPhaseRange(Frequency::DORIS1);
199        let l2 = Observable::UnambiguousPhaseRange(Frequency::DORIS2);
200        let c1 = Observable::PseudoRange(Frequency::DORIS1);
201        let c2 = Observable::PseudoRange(Frequency::DORIS2);
202
203        assert!(l1.same_physics(&l1));
204        assert!(l1.same_physics(&l2));
205
206        assert!(c1.same_physics(&c1));
207        assert!(c1.same_physics(&c2));
208
209        assert!(!l1.same_physics(&c1));
210        assert!(!l1.same_physics(&c2));
211    }
212}