gnss_rs/
constellation.rs

1//! GNSS constellations
2use hifitime::TimeScale;
3use thiserror::Error;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8/// Constellation parsing & identification related errors
9#[derive(Error, Clone, Debug, PartialEq)]
10pub enum ParsingError {
11    #[error("unknown constellation \"{0}\"")]
12    Unknown(String),
13}
14
15/// Describes all known `GNSS` constellations
16#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub enum Constellation {
19    /// `GPS` american constellation,
20    #[default]
21    GPS,
22    /// `Glonass` russian constellation
23    Glonass,
24    /// `BeiDou` chinese constellation
25    BeiDou,
26    /// `QZSS` japanese constellation
27    QZSS,
28    /// `Galileo` european constellation
29    Galileo,
30    /// `IRNSS` constellation, renamed "NavIC"
31    IRNSS,
32    /// American augmentation system,
33    WAAS,
34    /// European augmentation system
35    EGNOS,
36    /// Japanese MTSAT Space Based augmentation system
37    MSAS,
38    /// Indian augmentation system
39    GAGAN,
40    /// Chinese augmentation system
41    BDSBAS,
42    /// South Korean augmentation system
43    KASS,
44    /// Russian augmentation system
45    SDCM,
46    /// South African augmentation system
47    ASBAS,
48    /// Autralia / NZ augmentation system
49    SPAN,
50    /// SBAS is used to describe SBAS (augmentation)
51    /// vehicles without much more information
52    SBAS,
53    /// Australia-NZ Geoscience system
54    AusNZ,
55    /// Group Based SBAS
56    GBAS,
57    /// Nigerian SBAS
58    NSAS,
59    /// Algerian SBAS
60    ASAL,
61    /// `Mixed` for Mixed constellations
62    /// RINEX files description
63    Mixed,
64}
65
66impl std::fmt::Display for Constellation {
67    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
68        match self {
69            Self::GPS => write!(f, "GPS"),
70            Self::Glonass => write!(f, "Glonass"),
71            Self::BeiDou => write!(f, "BDS"),
72            Self::QZSS => write!(f, "QZSS"),
73            Self::Galileo => write!(f, "Galileo"),
74            Self::IRNSS => write!(f, "IRNSS"),
75            Self::WAAS => write!(f, "WAAS"),
76            Self::EGNOS => write!(f, "EGNOS"),
77            Self::MSAS => write!(f, "MSAS"),
78            Self::GAGAN => write!(f, "GAGAN"),
79            Self::BDSBAS => write!(f, "BDSBAS"),
80            Self::KASS => write!(f, "KASS"),
81            Self::SDCM => write!(f, "SDCM"),
82            Self::ASBAS => write!(f, "ASBAS"),
83            Self::SPAN => write!(f, "SPAN"),
84            Self::SBAS => write!(f, "SBAS"),
85            Self::AusNZ => write!(f, "AusNZ"),
86            Self::GBAS => write!(f, "GBAS"),
87            Self::NSAS => write!(f, "NSAS"),
88            Self::ASAL => write!(f, "ASAL"),
89            Self::Mixed => write!(f, "MIXED"),
90        }
91    }
92}
93
94impl Constellation {
95    /// Returns true if Self is an augmentation system
96    pub fn is_sbas(&self) -> bool {
97        matches!(
98            *self,
99            Constellation::WAAS
100                | Constellation::KASS
101                | Constellation::BDSBAS
102                | Constellation::EGNOS
103                | Constellation::GAGAN
104                | Constellation::SDCM
105                | Constellation::ASBAS
106                | Constellation::SPAN
107                | Constellation::MSAS
108                | Constellation::NSAS
109                | Constellation::ASAL
110                | Constellation::AusNZ
111                | Constellation::SBAS
112        )
113    }
114    pub(crate) fn is_mixed(&self) -> bool {
115        *self == Constellation::Mixed
116    }
117    /// Returns associated time scale. Returns None
118    /// if related time scale is not supported.
119    pub fn timescale(&self) -> Option<TimeScale> {
120        match self {
121            Self::GPS => Some(TimeScale::GPST),
122            Self::QZSS => Some(TimeScale::QZSST),
123            Self::Galileo => Some(TimeScale::GST),
124            Self::BeiDou => Some(TimeScale::BDT),
125            Self::Glonass => Some(TimeScale::UTC),
126            c => {
127                if c.is_sbas() {
128                    Some(TimeScale::GPST)
129                } else {
130                    None
131                }
132            },
133        }
134    }
135}
136
137impl std::str::FromStr for Constellation {
138    type Err = ParsingError;
139    fn from_str(string: &str) -> Result<Self, Self::Err> {
140        let s = string.trim().to_lowercase();
141        match s.as_str() {
142            "g" | "gps" => Ok(Self::GPS),
143            "c" | "bds" => Ok(Self::BeiDou),
144            "e" | "gal" => Ok(Self::Galileo),
145            "r" | "glo" => Ok(Self::Glonass),
146            "j" | "qzss" => Ok(Self::QZSS),
147            "i" | "irnss" => Ok(Self::IRNSS),
148            "s" | "sbas" => Ok(Self::SBAS),
149            "m" | "mixed" => Ok(Self::Mixed),
150            "ausnz" => Ok(Self::AusNZ),
151            "egnos" => Ok(Self::EGNOS),
152            "waas" => Ok(Self::WAAS),
153            "kass" => Ok(Self::KASS),
154            "gagan" => Ok(Self::GAGAN),
155            "asbas" => Ok(Self::ASBAS),
156            "nsas" => Ok(Self::NSAS),
157            "asal" => Ok(Self::ASAL),
158            "msas" => Ok(Self::MSAS),
159            "span" => Ok(Self::SPAN),
160            "gbas" => Ok(Self::GBAS),
161            "sdcm" => Ok(Self::SDCM),
162            "bdsbas" => Ok(Self::BDSBAS),
163            _ if s.contains("gps") => Ok(Self::GPS),
164            _ if s.contains("glonass") => Ok(Self::Glonass),
165            _ if s.contains("beidou") => Ok(Self::BeiDou),
166            _ if s.contains("galileo") => Ok(Self::Galileo),
167            _ if s.contains("qzss") => Ok(Self::QZSS),
168            _ if s.contains("sbas") | s.contains("geo") => Ok(Self::SBAS),
169            _ if s.contains("irnss") | s.contains("navic") => Ok(Self::IRNSS),
170            _ if s.contains("mix") => Ok(Self::Mixed),
171            _ => Err(ParsingError::Unknown(string.to_string())),
172        }
173    }
174}
175
176impl std::fmt::LowerHex for Constellation {
177    /*
178     * {:x}: formats Self as single letter standard code
179     */
180    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
181        match self {
182            Self::GPS => write!(f, "G"),
183            Self::Glonass => write!(f, "R"),
184            Self::Galileo => write!(f, "E"),
185            Self::BeiDou => write!(f, "C"),
186            Self::QZSS => write!(f, "J"),
187            Self::IRNSS => write!(f, "I"),
188            c => {
189                if c.is_sbas() {
190                    write!(f, "S")
191                } else if c.is_mixed() {
192                    write!(f, "M")
193                } else {
194                    Err(std::fmt::Error)
195                }
196            },
197        }
198    }
199}
200
201impl std::fmt::UpperHex for Constellation {
202    /*
203     * {:X} formats Self as 3 letter standard code
204     */
205    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
206        match self {
207            Self::GPS => write!(f, "GPS"),
208            Self::Glonass => write!(f, "GLO"),
209            Self::Galileo => write!(f, "GAL"),
210            Self::BeiDou => write!(f, "BDS"),
211            Self::QZSS => write!(f, "QZSS"),
212            Self::IRNSS => write!(f, "IRNSS"),
213            Self::SBAS => write!(f, "SBAS"),
214            Self::WAAS => write!(f, "WAAS"),
215            Self::AusNZ => write!(f, "AUSNZ"),
216            Self::EGNOS => write!(f, "EGNOS"),
217            Self::KASS => write!(f, "KASS"),
218            Self::GAGAN => write!(f, "GAGAN"),
219            Self::GBAS => write!(f, "GBAS"),
220            Self::NSAS => write!(f, "NSAS"),
221            Self::MSAS => write!(f, "MSAS"),
222            Self::SPAN => write!(f, "SPAN"),
223            Self::SDCM => write!(f, "SDCM"),
224            Self::BDSBAS => write!(f, "BDSBAS"),
225            Self::ASBAS => write!(f, "ASBAS"),
226            Self::ASAL => write!(f, "ASAL"),
227            Self::Mixed => write!(f, "MIXED"),
228        }
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235    use hifitime::TimeScale;
236    use std::str::FromStr;
237    #[test]
238    fn from_str() {
239        for (desc, expected) in vec![
240            ("G", Ok(Constellation::GPS)),
241            ("GPS", Ok(Constellation::GPS)),
242            ("R", Ok(Constellation::Glonass)),
243            ("GLO", Ok(Constellation::Glonass)),
244            ("J", Ok(Constellation::QZSS)),
245            ("M", Ok(Constellation::Mixed)),
246            ("WAAS", Ok(Constellation::WAAS)),
247            ("KASS", Ok(Constellation::KASS)),
248            ("GBAS", Ok(Constellation::GBAS)),
249            ("NSAS", Ok(Constellation::NSAS)),
250            ("SPAN", Ok(Constellation::SPAN)),
251            ("EGNOS", Ok(Constellation::EGNOS)),
252            ("ASBAS", Ok(Constellation::ASBAS)),
253            ("MSAS", Ok(Constellation::MSAS)),
254            ("GAGAN", Ok(Constellation::GAGAN)),
255            ("BDSBAS", Ok(Constellation::BDSBAS)),
256            ("ASAL", Ok(Constellation::ASAL)),
257            ("SDCM", Ok(Constellation::SDCM)),
258        ] {
259            assert_eq!(
260                Constellation::from_str(desc),
261                expected,
262                "failed to parse constellation from \"{}\"",
263                desc
264            );
265        }
266
267        for desc in ["X", "x", "GPX", "gpx", "unknown", "blah"] {
268            assert!(Constellation::from_str(desc).is_err());
269        }
270    }
271    #[test]
272    fn format() {
273        for (constell, expected) in [
274            (Constellation::GPS, "GPS"),
275            (Constellation::BeiDou, "BDS"),
276            (Constellation::Glonass, "Glonass"),
277            (Constellation::Galileo, "Galileo"),
278            (Constellation::QZSS, "QZSS"),
279            (Constellation::IRNSS, "IRNSS"),
280            (Constellation::WAAS, "WAAS"),
281            (Constellation::Mixed, "MIXED"),
282        ] {
283            assert_eq!(constell.to_string(), expected);
284        }
285    }
286    #[test]
287    fn test_sbas() {
288        for sbas in ["WAAS", "KASS", "EGNOS", "ASBAS", "MSAS", "GAGAN", "ASAL"] {
289            assert!(Constellation::from_str(sbas).unwrap().is_sbas());
290        }
291    }
292    #[test]
293    fn timescale() {
294        for (gnss, expected) in [
295            (Constellation::GPS, TimeScale::GPST),
296            (Constellation::Galileo, TimeScale::GST),
297            (Constellation::BeiDou, TimeScale::BDT),
298        ] {
299            assert_eq!(gnss.timescale(), Some(expected));
300        }
301    }
302}