gnss_qc_traits/processing/
item.rs

1use std::{num::ParseFloatError, str::FromStr};
2use thiserror::Error;
3
4use gnss_rs::{
5    constellation::ParsingError as ConstellationParsingError,
6    prelude::{Constellation, SV},
7    sv::ParsingError as SVParsingError,
8};
9
10use hifitime::{Duration, Epoch, ParsingError as EpochParsingError};
11
12#[derive(Debug, Error)]
13pub enum ItemError {
14    #[error("unknown filter item \"{0}\"")]
15    UnknownItem(String),
16    #[error("item guessing error: {0}")]
17    TypeGuessingError(String),
18    #[error("two valid epochs are required to describe a duration")]
19    InvalidDuration,
20    #[error("invalid epoch description")]
21    InvalidEpoch,
22    #[error("invalid SNR description")]
23    InvalidSNR,
24    #[error("invalid elevation angle (0 <= e <= 90)")]
25    InvalidElevationAngle,
26    #[error("invalid azimuth angle description (0 <= a <= 360)")]
27    InvalidAzimuthAngle,
28    #[error("invalid float number")]
29    FloatParsing(#[from] ParseFloatError),
30    #[error("sv item parsing")]
31    SVParsing(#[from] SVParsingError),
32    #[error("constellation item parsing")]
33    ConstellationParing(#[from] ConstellationParsingError),
34    #[error("duration item parsing")]
35    InvalidDurationItem(#[from] EpochParsingError),
36}
37
38/// [FilterItem] represents items that filters or other
39/// GNSS processing ops may apply to.
40#[derive(Clone, Debug, PartialEq, PartialOrd)]
41pub enum FilterItem {
42    /// Epoch Item
43    EpochItem(Epoch),
44    /// Duration Item
45    DurationItem(Duration),
46    /// SNR value, expressed in [dB]
47    SNRItem(f64),
48    /// Elevation Angle Item in degrees, 0 <= e <= 90°
49    ElevationItem(f64),
50    /// Azimuth Angle Item in degrees, 0 <= a <= 360°
51    AzimuthItem(f64),
52    /// List of spacecrafts described as [SV]
53    SvItem(Vec<SV>),
54    /// List of [Constellation]s
55    ConstellationItem(Vec<Constellation>),
56    /// Clock Offset Item
57    ClockItem,
58    /// List of complex items originally described as Strings
59    ComplexItem(Vec<String>),
60}
61
62impl std::ops::BitOrAssign for FilterItem {
63    fn bitor_assign(&mut self, rhs: Self) {
64        *self = self.clone() | rhs;
65    }
66}
67
68impl std::ops::BitOr for FilterItem {
69    type Output = Self;
70    fn bitor(self, rhs: Self) -> Self {
71        match self {
72            Self::SvItem(ref lhs) => match rhs {
73                Self::SvItem(rhs) => {
74                    let mut lhs = lhs.clone();
75                    for r in rhs {
76                        lhs.push(r);
77                    }
78                    Self::SvItem(lhs)
79                }
80                _ => self.clone(),
81            },
82            Self::ConstellationItem(ref lhs) => match rhs {
83                Self::ConstellationItem(rhs) => {
84                    let mut lhs = lhs.clone();
85                    for r in rhs {
86                        lhs.push(r);
87                    }
88                    Self::ConstellationItem(lhs)
89                }
90                _ => self.clone(),
91            },
92            _ => self.clone(),
93        }
94    }
95}
96
97pub(crate) fn parse_sv_list(items: Vec<&str>) -> Result<Vec<SV>, SVParsingError> {
98    let mut ret: Vec<SV> = Vec::with_capacity(items.len());
99    for item in items {
100        let sv = SV::from_str(item.trim())?;
101        ret.push(sv);
102    }
103    Ok(ret)
104}
105
106pub(crate) fn parse_gnss_list(
107    items: Vec<&str>,
108) -> Result<Vec<Constellation>, ConstellationParsingError> {
109    let mut ret: Vec<Constellation> = Vec::with_capacity(items.len());
110    for item in items {
111        let c = Constellation::from_str(item.trim())?;
112        ret.push(c);
113    }
114    Ok(ret)
115}
116
117fn parse_float_payload(content: &str) -> Result<f64, ParseFloatError> {
118    f64::from_str(content.trim())
119}
120
121impl FilterItem {
122    pub(crate) fn from_elevation(content: &str) -> Result<Self, ItemError> {
123        if let Ok(float) = parse_float_payload(content) {
124            if float >= 0.0 && float <= 90.0 {
125                return Ok(Self::AzimuthItem(float));
126            }
127        }
128        Err(ItemError::InvalidElevationAngle)
129    }
130    pub(crate) fn from_azimuth(content: &str) -> Result<Self, ItemError> {
131        if let Ok(float) = parse_float_payload(content) {
132            if float >= 0.0 && float <= 360.0 {
133                return Ok(Self::AzimuthItem(float));
134            }
135        }
136        Err(ItemError::InvalidAzimuthAngle)
137    }
138    pub(crate) fn from_snr(content: &str) -> Result<Self, ItemError> {
139        if let Ok(float) = parse_float_payload(content) {
140            Ok(Self::SNRItem(float))
141        } else {
142            Err(ItemError::InvalidSNR)
143        }
144    }
145}
146
147// use itertools::Itertools;
148
149impl std::str::FromStr for FilterItem {
150    type Err = ItemError;
151    fn from_str(content: &str) -> Result<Self, Self::Err> {
152        /*
153         * Type guessing
154         */
155        let c = content.trim();
156        let items: Vec<&str> = c.split(',').collect();
157        /*
158         * Epoch and Durations
159         */
160        if let Ok(start) = Epoch::from_str(items[0].trim()) {
161            if items.len() == 1 {
162                Ok(Self::EpochItem(start))
163            } else if items.len() == 2 {
164                if let Ok(end) = Epoch::from_str(items[1].trim()) {
165                    Ok(Self::DurationItem(end - start))
166                } else {
167                    Err(ItemError::InvalidEpoch)
168                }
169            } else {
170                Err(ItemError::InvalidDuration)
171            }
172        /*
173         * SV
174         */
175        } else if SV::from_str(items[0].trim()).is_ok() {
176            //TODO improve this:
177            // do not test 1st entry only but all possible content
178            Ok(Self::SvItem(parse_sv_list(items)?))
179        /*
180         * GNSS Constellation
181         */
182        } else if Constellation::from_str(items[0].trim()).is_ok() {
183            //TODO improve this:
184            // do not test 1st entry only but all possible content
185            Ok(Self::ConstellationItem(parse_gnss_list(items)?))
186        } else {
187            // define this item a "complex"
188            Ok(Self::ComplexItem(
189                items.iter().map(|s| s.to_string()).collect(),
190            ))
191        }
192    }
193}
194
195impl From<Epoch> for FilterItem {
196    fn from(e: Epoch) -> Self {
197        Self::EpochItem(e)
198    }
199}
200
201impl From<Duration> for FilterItem {
202    fn from(dt: Duration) -> Self {
203        Self::DurationItem(dt)
204    }
205}
206
207impl From<SV> for FilterItem {
208    fn from(sv: SV) -> Self {
209        Self::SvItem(vec![sv])
210    }
211}
212
213impl From<Vec<SV>> for FilterItem {
214    fn from(sv: Vec<SV>) -> Self {
215        Self::SvItem(sv.clone())
216    }
217}
218
219impl From<Constellation> for FilterItem {
220    fn from(c: Constellation) -> Self {
221        Self::ConstellationItem(vec![c])
222    }
223}
224
225impl From<Vec<Constellation>> for FilterItem {
226    fn from(c: Vec<Constellation>) -> Self {
227        Self::ConstellationItem(c.clone())
228    }
229}
230
231impl std::fmt::Display for FilterItem {
232    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
233        match self {
234            Self::ConstellationItem(gnss) => {
235                write!(f, "gnss: {:?}", gnss)
236            }
237            Self::SvItem(svs) => {
238                write!(f, "sv: {:?}", svs)
239            }
240            _ => Ok(()),
241        }
242    }
243}
244
245#[cfg(test)]
246mod test {
247    use super::*;
248    use gnss_rs::prelude::{Constellation, SV};
249    use std::str::FromStr;
250    #[test]
251    fn algo_target_item() {
252        let e = Epoch::default();
253        let target: FilterItem = e.into();
254        assert_eq!(target, FilterItem::EpochItem(e));
255
256        assert_eq!(
257            FilterItem::from_str("g08,g09,R03").unwrap(),
258            FilterItem::SvItem(vec![
259                SV::from_str("G08").unwrap(),
260                SV::from_str("G09").unwrap(),
261                SV::from_str("R03").unwrap()
262            ])
263        );
264
265        assert_eq!(
266            FilterItem::from_str("GPS , BDS").unwrap(),
267            FilterItem::ConstellationItem(vec![Constellation::GPS, Constellation::BeiDou])
268        );
269
270        let dt = Duration::from_str("1 d").unwrap();
271        let target: FilterItem = dt.into();
272        assert_eq!(target, FilterItem::DurationItem(dt));
273    }
274    #[test]
275    fn test_from_elevation() {
276        let desc = "90";
277        assert!(
278            FilterItem::from_elevation(desc).is_ok(),
279            "Failed to parse Elevation Target Item"
280        );
281    }
282    #[test]
283    fn test_from_azimuth() {
284        let desc = " 12.34  ";
285        assert!(
286            FilterItem::from_azimuth(desc).is_ok(),
287            "Failed to parse Azimuth Target Item"
288        );
289    }
290    #[test]
291    fn test_from_snr() {
292        let desc = " 12.34  ";
293        assert!(
294            FilterItem::from_snr(desc).is_ok(),
295            "Failed to parse SNR Target Item"
296        );
297    }
298}