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 write!(f, "{:X}", self)
69 }
70}
71
72impl Constellation {
73 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 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 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 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}