1use hifitime::TimeScale;
3use thiserror::Error;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[derive(Error, Clone, Debug, PartialEq)]
10pub enum ParsingError {
11 #[error("unknown constellation \"{0}\"")]
12 Unknown(String),
13}
14
15#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub enum Constellation {
19 #[default]
21 GPS,
22 Glonass,
24 BeiDou,
26 QZSS,
28 Galileo,
30 IRNSS,
32 WAAS,
34 EGNOS,
36 MSAS,
38 GAGAN,
40 BDSBAS,
42 KASS,
44 SDCM,
46 ASBAS,
48 SPAN,
50 SBAS,
53 AusNZ,
55 GBAS,
57 NSAS,
59 ASAL,
61 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 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 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 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 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}