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        write!(f, "{:X}", self)
69    }
70}
71
72impl Constellation {
73    /// Returns true if Self is an augmentation system
74    pub fn is_sbas(&self) -> bool {
75        matches!(
76            *self,
77            Constellation::WAAS
78                | Constellation::KASS
79                | Constellation::BDSBAS
80                | Constellation::EGNOS
81                | Constellation::GAGAN
82                | Constellation::SDCM
83                | Constellation::ASBAS
84                | Constellation::SPAN
85                | Constellation::MSAS
86                | Constellation::NSAS
87                | Constellation::ASAL
88                | Constellation::AusNZ
89                | Constellation::SBAS
90        )
91    }
92    pub(crate) fn is_mixed(&self) -> bool {
93        *self == Constellation::Mixed
94    }
95    /// Returns associated time scale. Returns None
96    /// if related time scale is not supported.
97    pub fn timescale(&self) -> Option<TimeScale> {
98        match self {
99            Self::GPS => Some(TimeScale::GPST),
100            Self::QZSS => Some(TimeScale::QZSST),
101            Self::Galileo => Some(TimeScale::GST),
102            Self::BeiDou => Some(TimeScale::BDT),
103            Self::Glonass => Some(TimeScale::UTC),
104            c => {
105                if c.is_sbas() {
106                    Some(TimeScale::GPST)
107                } else {
108                    None
109                }
110            },
111        }
112    }
113}
114
115impl std::str::FromStr for Constellation {
116    type Err = ParsingError;
117    fn from_str(string: &str) -> Result<Self, Self::Err> {
118        let s = string.trim().to_lowercase();
119        match s.as_str() {
120            "g" | "gps" => Ok(Self::GPS),
121            "c" | "bds" => Ok(Self::BeiDou),
122            "e" | "gal" => Ok(Self::Galileo),
123            "r" | "glo" => Ok(Self::Glonass),
124            "j" | "qzss" => Ok(Self::QZSS),
125            "i" | "irnss" => Ok(Self::IRNSS),
126            "s" | "sbas" => Ok(Self::SBAS),
127            "m" | "mixed" => Ok(Self::Mixed),
128            "ausnz" => Ok(Self::AusNZ),
129            "egnos" => Ok(Self::EGNOS),
130            "waas" => Ok(Self::WAAS),
131            "kass" => Ok(Self::KASS),
132            "gagan" => Ok(Self::GAGAN),
133            "asbas" => Ok(Self::ASBAS),
134            "nsas" => Ok(Self::NSAS),
135            "asal" => Ok(Self::ASAL),
136            "msas" => Ok(Self::MSAS),
137            "span" => Ok(Self::SPAN),
138            "gbas" => Ok(Self::GBAS),
139            "sdcm" => Ok(Self::SDCM),
140            "bdsbas" => Ok(Self::BDSBAS),
141            _ if s.contains("gps") => Ok(Self::GPS),
142            _ if s.contains("glonass") => Ok(Self::Glonass),
143            _ if s.contains("beidou") => Ok(Self::BeiDou),
144            _ if s.contains("galileo") => Ok(Self::Galileo),
145            _ if s.contains("qzss") => Ok(Self::QZSS),
146            _ if s.contains("sbas") | s.contains("geo") => Ok(Self::SBAS),
147            _ if s.contains("irnss") | s.contains("navic") => Ok(Self::IRNSS),
148            _ if s.contains("mix") => Ok(Self::Mixed),
149            _ => Err(ParsingError::Unknown(string.to_string())),
150        }
151    }
152}
153
154impl std::fmt::LowerHex for Constellation {
155    /*
156     * {:x}: formats Self as single letter standard code
157     */
158    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
159        match self {
160            Self::GPS => write!(f, "G"),
161            Self::Glonass => write!(f, "R"),
162            Self::Galileo => write!(f, "E"),
163            Self::BeiDou => write!(f, "C"),
164            Self::QZSS => write!(f, "J"),
165            Self::IRNSS => write!(f, "I"),
166            c => {
167                if c.is_sbas() {
168                    write!(f, "S")
169                } else if c.is_mixed() {
170                    write!(f, "M")
171                } else {
172                    Err(std::fmt::Error)
173                }
174            },
175        }
176    }
177}
178
179impl std::fmt::UpperHex for Constellation {
180    /*
181     * {:X} formats Self as 3 letter standard code
182     */
183    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
184        match self {
185            Self::GPS => write!(f, "GPS"),
186            Self::Glonass => write!(f, "GLO"),
187            Self::Galileo => write!(f, "GAL"),
188            Self::BeiDou => write!(f, "BDS"),
189            Self::QZSS => write!(f, "QZSS"),
190            Self::IRNSS => write!(f, "IRNSS"),
191            Self::SBAS => write!(f, "SBAS"),
192            Self::WAAS => write!(f, "WAAS"),
193            Self::AusNZ => write!(f, "AUSNZ"),
194            Self::EGNOS => write!(f, "EGNOS"),
195            Self::KASS => write!(f, "KASS"),
196            Self::GAGAN => write!(f, "GAGAN"),
197            Self::GBAS => write!(f, "GBAS"),
198            Self::NSAS => write!(f, "NSAS"),
199            Self::MSAS => write!(f, "MSAS"),
200            Self::SPAN => write!(f, "SPAN"),
201            Self::SDCM => write!(f, "SDCM"),
202            Self::BDSBAS => write!(f, "BDSBAS"),
203            Self::ASBAS => write!(f, "ASBAS"),
204            Self::ASAL => write!(f, "ASAL"),
205            Self::Mixed => write!(f, "MIXED"),
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use hifitime::TimeScale;
214    use std::str::FromStr;
215    #[test]
216    fn from_str() {
217        for (desc, expected) in vec![
218            ("G", Ok(Constellation::GPS)),
219            ("GPS", Ok(Constellation::GPS)),
220            ("R", Ok(Constellation::Glonass)),
221            ("GLO", Ok(Constellation::Glonass)),
222            ("J", Ok(Constellation::QZSS)),
223            ("M", Ok(Constellation::Mixed)),
224            ("WAAS", Ok(Constellation::WAAS)),
225            ("KASS", Ok(Constellation::KASS)),
226            ("GBAS", Ok(Constellation::GBAS)),
227            ("NSAS", Ok(Constellation::NSAS)),
228            ("SPAN", Ok(Constellation::SPAN)),
229            ("EGNOS", Ok(Constellation::EGNOS)),
230            ("ASBAS", Ok(Constellation::ASBAS)),
231            ("MSAS", Ok(Constellation::MSAS)),
232            ("GAGAN", Ok(Constellation::GAGAN)),
233            ("BDSBAS", Ok(Constellation::BDSBAS)),
234            ("ASAL", Ok(Constellation::ASAL)),
235            ("SDCM", Ok(Constellation::SDCM)),
236        ] {
237            assert_eq!(
238                Constellation::from_str(desc),
239                expected,
240                "failed to parse constellation from \"{}\"",
241                desc
242            );
243        }
244
245        for desc in ["X", "x", "GPX", "gpx", "unknown", "blah"] {
246            assert!(Constellation::from_str(desc).is_err());
247        }
248    }
249    #[test]
250    fn format() {
251        for (constell, expected) in [
252            (Constellation::GPS, "GPS"),
253            (Constellation::BeiDou, "BDS"),
254            (Constellation::Glonass, "GLO"),
255            (Constellation::Galileo, "GAL"),
256            (Constellation::QZSS, "QZSS"),
257            (Constellation::IRNSS, "IRNSS"),
258            (Constellation::WAAS, "WAAS"),
259            (Constellation::MSAS, "MSAS"),
260            (Constellation::GAGAN, "GAGAN"),
261            (Constellation::BDSBAS, "BDSBAS"),
262            (Constellation::KASS, "KASS"),
263            (Constellation::SDCM, "SDCM"),
264            (Constellation::ASBAS, "ASBAS"),
265            (Constellation::SPAN, "SPAN"),
266            (Constellation::SBAS, "SBAS"),
267            (Constellation::AusNZ, "AUSNZ"),
268            (Constellation::GBAS, "GBAS"),
269            (Constellation::NSAS, "NSAS"),
270            (Constellation::ASAL, "ASAL"),
271            (Constellation::Mixed, "MIXED"),
272        ] {
273            assert_eq!(constell.to_string(), expected);
274        }
275    }
276    #[test]
277    fn test_sbas() {
278        for sbas in ["WAAS", "KASS", "EGNOS", "ASBAS", "MSAS", "GAGAN", "ASAL"] {
279            assert!(Constellation::from_str(sbas).unwrap().is_sbas());
280        }
281    }
282    #[test]
283    fn timescale() {
284        for (gnss, expected) in [
285            (Constellation::GPS, TimeScale::GPST),
286            (Constellation::Galileo, TimeScale::GST),
287            (Constellation::BeiDou, TimeScale::BDT),
288        ] {
289            assert_eq!(gnss.timescale(), Some(expected));
290        }
291    }
292}