gnss_qc_traits/processing/
mask.rs1use crate::processing::{FilterItem, ItemError};
2use thiserror::Error;
3
4#[derive(Error, Debug)]
6pub enum Error {
7 #[error("invalid mask item")]
8 InvalidMaskitem(#[from] ItemError),
9 #[error("missing mask operand")]
10 MissingOperand,
11 #[error("invalid mask operand")]
12 InvalidOperand,
13 #[error("invalid mask target \"{0}\"")]
14 NonSupportedTarget(String),
15 #[error("invalid mask description")]
16 InvalidDescriptor,
17}
18
19pub trait Masking {
22 fn mask_mut(&mut self, mask: &MaskFilter);
24 fn mask(&self, mask: &MaskFilter) -> Self;
26}
27
28#[derive(Debug, Clone, PartialEq)]
30pub enum MaskOperand {
31 GreaterThan,
33 GreaterEquals,
35 LowerThan,
37 LowerEquals,
39 Equals,
42 NotEquals,
44}
45
46impl std::str::FromStr for MaskOperand {
47 type Err = Error;
48 fn from_str(content: &str) -> Result<Self, Self::Err> {
49 let c = content.trim();
50 if c.starts_with(">=") {
51 Ok(Self::GreaterEquals)
52 } else if c.starts_with('>') {
53 Ok(Self::GreaterThan)
54 } else if c.starts_with("<=") {
55 Ok(Self::LowerEquals)
56 } else if c.starts_with('<') {
57 Ok(Self::LowerThan)
58 } else if c.starts_with('=') {
59 Ok(Self::Equals)
60 } else if c.starts_with("!=") {
61 Ok(Self::NotEquals)
62 } else {
63 Err(Error::InvalidOperand)
64 }
65 }
66}
67
68impl MaskOperand {
69 pub(crate) const fn formatted_len(&self) -> usize {
70 match &self {
71 Self::Equals | Self::GreaterThan | Self::LowerThan => 1,
72 Self::NotEquals | Self::LowerEquals | Self::GreaterEquals => 2,
73 }
74 }
75}
76
77impl std::ops::Not for MaskOperand {
78 type Output = Self;
79 fn not(self) -> Self {
80 match self {
81 Self::Equals => Self::NotEquals,
82 Self::NotEquals => Self::Equals,
83 Self::GreaterEquals => Self::LowerEquals,
84 Self::GreaterThan => Self::LowerThan,
85 Self::LowerThan => Self::GreaterThan,
86 Self::LowerEquals => Self::GreaterEquals,
87 }
88 }
89}
90
91#[derive(Debug, Clone, PartialEq)]
93pub struct MaskFilter {
94 pub item: FilterItem,
96 pub operand: MaskOperand,
98}
99
100impl std::ops::Not for MaskFilter {
101 type Output = MaskFilter;
102 fn not(self) -> Self {
103 Self {
104 operand: !self.operand,
105 item: self.item,
106 }
107 }
108}
109
110impl std::ops::BitOr for MaskFilter {
111 type Output = Self;
112 fn bitor(self, rhs: Self) -> Self {
113 if self.operand == rhs.operand {
114 Self {
115 operand: self.operand,
116 item: self.item | rhs.item,
117 }
118 } else {
119 self.clone()
121 }
122 }
123}
124
125impl std::ops::BitOrAssign for MaskFilter {
126 fn bitor_assign(&mut self, rhs: Self) {
127 self.item = self.item.clone() | rhs.item;
128 }
129}
130
131impl std::str::FromStr for MaskFilter {
132 type Err = Error;
133 fn from_str(content: &str) -> Result<Self, Self::Err> {
134 let cleanedup = content.trim_start();
135 if cleanedup.len() < 2 {
136 return Err(Error::InvalidDescriptor);
141 }
142
143 let mut operand: Option<MaskOperand> = None;
144 let mut operand_offset: Option<usize> = None;
145 for i in 0..cleanedup.len() - 1 {
151 if i < cleanedup.len() - 2 {
152 if let Ok(op) = MaskOperand::from_str(&cleanedup[i..i + 2]) {
153 operand = Some(op.clone());
154 operand_offset = Some(i);
155 break;
156 }
157 } else if let Ok(op) = MaskOperand::from_str(&cleanedup[i..i + 1]) {
158 operand = Some(op.clone());
159 operand_offset = Some(i);
160 break;
161 }
162 }
163
164 let operand_omitted = operand_offset.is_none();
165
166 let (operand, operand_offset): (MaskOperand, usize) = match operand_offset.is_some() {
167 true => (operand.unwrap(), operand_offset.unwrap()),
168 false => {
169 (MaskOperand::Equals, 0)
174 }
175 };
176
177 if operand_offset > 0 {
178 let start = &cleanedup[..operand_offset];
186 if start[0..1].eq("e") {
187 let float_offset = operand_offset + operand.formatted_len() + 2;
189 Ok(Self {
190 operand,
191 item: FilterItem::from_elevation(cleanedup[float_offset..].trim())?,
192 })
193 } else if content[0..1].eq("a") {
194 let float_offset = operand_offset + operand.formatted_len() + 2;
196 Ok(Self {
197 operand,
198 item: FilterItem::from_azimuth(cleanedup[float_offset..].trim())?,
199 })
200 } else {
201 let float_offset = operand_offset + operand.formatted_len() + 2;
203 if content[0..3].eq("snr") {
204 Ok(Self {
205 operand,
206 item: FilterItem::from_snr(cleanedup[float_offset..].trim())?,
207 })
208 } else {
209 Err(Error::NonSupportedTarget(
210 cleanedup[..operand_offset].to_string(),
211 ))
212 }
213 }
214 } else {
215 let offset: usize = match operand_omitted {
218 false => operand_offset + operand.formatted_len(),
219 true => 0,
220 };
221
222 Ok(Self {
223 operand,
224 item: FilterItem::from_str(cleanedup[offset..].trim_start())?,
225 })
226 }
227 }
228}
229
230#[cfg(test)]
231mod test {
232 use super::*;
233 use gnss_rs::prelude::{Constellation, SV};
234 use hifitime::Epoch;
235 use std::str::FromStr;
236 #[test]
237 fn mask_operand() {
238 for (descriptor, opposite_desc) in [
239 (">=", "<="),
240 (">", "<"),
241 ("=", "!="),
242 ("<", ">"),
243 ("<=", ">="),
244 ] {
245 let operand = MaskOperand::from_str(descriptor);
246 assert!(
247 operand.is_ok(),
248 "{} \"{}\"",
249 "Failed to parse MaskOperand from",
250 descriptor
251 );
252 let opposite = MaskOperand::from_str(opposite_desc);
253 assert!(
254 opposite.is_ok(),
255 "{} \"{}\"",
256 "Failed to parse MaskOperand from",
257 opposite_desc
258 );
259 assert_eq!(!operand.unwrap(), opposite.unwrap(), "MaskOperand::Not()");
260 }
261
262 let operand = MaskOperand::from_str("a");
263 assert!(
264 operand.is_err(),
265 "Parsed unexpectedly \"{}\" MaskOperand correctly",
266 "a"
267 );
268 }
269 #[test]
270 fn mask_epoch() {
271 let mask = MaskFilter::from_str(">2020-01-14T00:31:55 UTC").unwrap();
272 assert_eq!(
273 mask,
274 MaskFilter {
275 operand: MaskOperand::GreaterThan,
276 item: FilterItem::EpochItem(Epoch::from_str("2020-01-14T00:31:55 UTC").unwrap()),
277 }
278 );
279 let mask = MaskFilter::from_str(">JD 2452312.500372511 TAI");
280 assert!(mask.is_ok());
281 }
282 #[test]
283 fn mask_elev() {
284 for (desc, valid) in [
285 ("e>1.0", true),
286 ("e< 40.0", true),
287 ("e != 30", true),
288 (" e<40.0", true),
289 (" e < 40.0", true),
290 (" e > 120", false),
291 (" e >= 120", false),
292 (" e = 30", true),
293 ] {
294 let mask = MaskFilter::from_str(desc);
295 assert_eq!(
296 mask.is_ok(),
297 valid,
298 "failed to parse elevation mask filter \"{}\"",
299 desc
300 );
301 }
302 }
303 #[test]
304 fn mask_gnss() {
305 for (descriptor, opposite_desc) in [
306 (" = GPS", "!= GPS"),
307 ("= GAL,GPS", "!= GAL,GPS"),
308 (" =GLO,GAL", "!= GLO,GAL"),
309 ] {
310 let mask = MaskFilter::from_str(descriptor);
311 assert!(
312 mask.is_ok(),
313 "Unable to parse MaskFilter from \"{}\"",
314 descriptor
315 );
316 let opposite = MaskFilter::from_str(opposite_desc);
317 assert!(
318 opposite.is_ok(),
319 "Unable to parse MaskFilter from \"{}\"",
320 opposite_desc
321 );
322 assert_eq!(!mask.unwrap(), opposite.unwrap(), "{}", "MaskFilter::Not()");
323 }
324
325 let mask = MaskFilter::from_str("=GPS,GAL,GLO").unwrap();
326 assert_eq!(
327 mask,
328 MaskFilter {
329 operand: MaskOperand::Equals,
330 item: FilterItem::ConstellationItem(vec![
331 Constellation::GPS,
332 Constellation::Galileo,
333 Constellation::Glonass
334 ]),
335 }
336 );
337
338 let mask = MaskFilter::from_str("!=BDS").unwrap();
339 assert_eq!(
340 mask,
341 MaskFilter {
342 operand: MaskOperand::NotEquals,
343 item: FilterItem::ConstellationItem(vec![Constellation::BeiDou]),
344 }
345 );
346 }
347 #[test]
348 fn mask_sv() {
349 for (descriptor, opposite_desc) in [(" = G01", "!= G01"), ("= R03, G31", "!= R03, G31")] {
350 let mask = MaskFilter::from_str(descriptor);
351 assert!(
352 mask.is_ok(),
353 "Unable to parse MaskFilter from \"{}\"",
354 descriptor
355 );
356 let opposite = MaskFilter::from_str(opposite_desc);
357 assert!(
358 opposite.is_ok(),
359 "Unable to parse MaskFilter from \"{}\"",
360 opposite_desc
361 );
362 assert_eq!(!mask.unwrap(), opposite.unwrap(), "{}", "MaskFilter::Not()");
363 }
364
365 let mask = MaskFilter::from_str("=G08, G09, R03").unwrap();
366 assert_eq!(
367 mask,
368 MaskFilter {
369 operand: MaskOperand::Equals,
370 item: FilterItem::SvItem(vec![
371 SV::from_str("G08").unwrap(),
372 SV::from_str("G09").unwrap(),
373 SV::from_str("R03").unwrap(),
374 ]),
375 }
376 );
377 let m2 = MaskFilter::from_str("G08,G09,R03").unwrap();
378 assert_eq!(mask, m2);
379
380 let mask = MaskFilter::from_str("!=G31").unwrap();
381 assert_eq!(
382 mask,
383 MaskFilter {
384 operand: MaskOperand::NotEquals,
385 item: FilterItem::SvItem(vec![SV::from_str("G31").unwrap(),]),
386 }
387 );
388 let m2 = MaskFilter::from_str("!=G31").unwrap();
389 assert_eq!(mask, m2);
390 }
391 #[test]
392 fn mask_complex() {
393 let mask = MaskFilter::from_str("=L1C,S1C,D1P,C1W").unwrap();
394 assert_eq!(
395 mask,
396 MaskFilter {
397 operand: MaskOperand::Equals,
398 item: FilterItem::ComplexItem(vec![
399 "L1C".to_string(),
400 "S1C".to_string(),
401 "D1P".to_string(),
402 "C1W".to_string()
403 ])
404 }
405 );
406 }
407}