1#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9pub enum Constellation {
10 GPS,
11 GLONASS,
12 Galileo,
13 BeiDou,
14 QZSS,
15 SBAS,
16 NavIC,
17 Unknown(u8),
18}
19
20impl Constellation {
21 pub fn from_gnss_id(id: u8) -> Self {
33 match id {
34 0 => Constellation::GPS,
35 1 => Constellation::SBAS,
36 2 => Constellation::Galileo,
37 3 => Constellation::BeiDou,
38 5 => Constellation::QZSS,
39 6 => Constellation::GLONASS,
40 7 => Constellation::NavIC,
41 _ => Constellation::Unknown(id),
42 }
43 }
44
45 pub fn short_name(&self) -> &'static str {
47 match self {
48 Constellation::GPS => "GPS",
49 Constellation::GLONASS => "GLO",
50 Constellation::Galileo => "GAL",
51 Constellation::BeiDou => "BDS",
52 Constellation::QZSS => "QZS",
53 Constellation::SBAS => "SBA",
54 Constellation::NavIC => "NAV",
55 Constellation::Unknown(_) => "UNK",
56 }
57 }
58
59 pub fn prefix(&self) -> char {
61 match self {
62 Constellation::GPS => 'G',
63 Constellation::GLONASS => 'R',
64 Constellation::Galileo => 'E',
65 Constellation::BeiDou => 'C',
66 Constellation::QZSS => 'J',
67 Constellation::SBAS => 'S',
68 Constellation::NavIC => 'I',
69 Constellation::Unknown(_) => 'X',
70 }
71 }
72}
73
74impl std::fmt::Display for Constellation {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 write!(f, "{}", self.short_name())
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Hash)]
82#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
83pub struct SatelliteId {
84 pub constellation: Constellation,
85 pub prn: u8,
86}
87
88impl SatelliteId {
89 pub fn new(constellation: Constellation, prn: u8) -> Self {
91 Self { constellation, prn }
92 }
93
94 pub fn from_svid(svid: u8) -> Option<Self> {
111 if svid == 0 {
112 return None;
113 }
114
115 let (constellation, prn) = match svid {
116 1..=37 => (Constellation::GPS, svid),
117 38..=61 => (Constellation::GLONASS, svid - 37),
118 62 => (Constellation::GLONASS, 0),
119 63..=68 => (Constellation::GLONASS, svid - 38),
120 71..=106 => (Constellation::Galileo, svid - 70),
121 120..=140 => (Constellation::SBAS, svid - 100),
122 141..=180 => (Constellation::BeiDou, svid - 140),
123 181..=190 => (Constellation::QZSS, svid - 180),
124 191..=197 => (Constellation::NavIC, svid - 190),
125 198..=215 => (Constellation::SBAS, svid - 157),
126 216..=222 => (Constellation::NavIC, svid - 208),
127 223..=245 => (Constellation::BeiDou, svid - 182),
128 _ => (Constellation::Unknown(svid), svid),
129 };
130
131 Some(Self { constellation, prn })
132 }
133
134 pub fn to_svid(&self) -> u8 {
136 match self.constellation {
137 Constellation::GPS => self.prn,
138 Constellation::GLONASS => {
139 if self.prn == 0 {
140 62
141 } else if self.prn <= 24 {
142 self.prn + 37
143 } else {
144 self.prn + 38
145 }
146 }
147 Constellation::Galileo => self.prn + 70,
148 Constellation::SBAS => {
149 if self.prn <= 40 {
150 self.prn + 100
151 } else {
152 self.prn + 157
153 }
154 }
155 Constellation::BeiDou => {
156 if self.prn <= 40 {
157 self.prn + 140
158 } else {
159 self.prn + 182
160 }
161 }
162 Constellation::QZSS => self.prn + 180,
163 Constellation::NavIC => {
164 if self.prn <= 7 {
165 self.prn + 190
166 } else {
167 self.prn + 208
168 }
169 }
170 Constellation::Unknown(svid) => svid,
171 }
172 }
173
174 pub fn key(&self) -> String {
176 format!("{}{:02}", self.constellation.prefix(), self.prn)
177 }
178}
179
180impl std::fmt::Display for SatelliteId {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 write!(f, "{}", self.key())
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
189pub enum SignalType {
190 L1CA,
192 L1C,
193 L1PY,
194 L2C,
195 L2P,
196 L2PY,
197 L5,
198 E1,
200 E5a,
201 E5b,
202 E5AltBOC,
203 E6,
204 G1CA,
206 G1P,
207 G2CA,
208 G2P,
209 G3,
210 B1I,
212 B1C,
213 B2I,
214 B2a,
215 B2b,
216 B3I,
217 QZSSL1CA,
219 QZSSL2C,
220 QZSSL5,
221 QZSSL1C,
222 QZSSL1S,
223 QZSSL1CB,
224 QZSSL5S,
225 QZSSL6,
226 SBASL1,
228 SBASL5,
229 LBand,
231 NavICL5,
233 NavICL1,
234 Other(u8),
236}
237
238impl SignalType {
239 pub fn from_signal_number(sig_num: u8) -> Self {
241 match sig_num {
242 0 => SignalType::L1CA,
244 1 => SignalType::L1PY,
245 2 => SignalType::L2P,
246 3 => SignalType::L2C,
247 4 => SignalType::L5,
248 5 => SignalType::L1C,
249 6 => SignalType::QZSSL1CA,
251 7 => SignalType::QZSSL2C,
252 8 => SignalType::G1CA,
254 9 => SignalType::G1P,
255 10 => SignalType::G2P,
256 11 => SignalType::G2CA,
257 12 => SignalType::G3,
258 13 => SignalType::B1C,
260 14 => SignalType::B2a,
261 15 => SignalType::NavICL5,
263 17 => SignalType::E1,
265 19 => SignalType::E6,
266 20 => SignalType::E5a,
267 21 => SignalType::E5b,
268 22 => SignalType::E5AltBOC,
269 23 => SignalType::LBand,
271 24 => SignalType::SBASL1,
273 25 => SignalType::SBASL5,
274 26 => SignalType::QZSSL5,
276 27 => SignalType::QZSSL6,
277 28 => SignalType::B1I,
279 29 => SignalType::B2I,
280 30 => SignalType::B3I,
281 32 => SignalType::QZSSL1C,
283 33 => SignalType::QZSSL1S,
284 34 => SignalType::B2b,
286 37 => SignalType::NavICL1,
288 38 => SignalType::QZSSL1CB,
290 39 => SignalType::QZSSL5S,
291 _ => SignalType::Other(sig_num),
292 }
293 }
294
295 pub fn from_signal_number_and_constellation(
297 sig_num: u8,
298 constellation: &Constellation,
299 ) -> Self {
300 match constellation {
301 Constellation::GPS => match sig_num {
302 0 => SignalType::L1CA,
303 1 => SignalType::L1PY,
304 2 => SignalType::L2P,
305 3 => SignalType::L2C,
306 4 => SignalType::L5,
307 5 => SignalType::L1C,
308 _ => SignalType::Other(sig_num),
309 },
310 Constellation::GLONASS => match sig_num {
311 0 => SignalType::G1CA,
312 1 => SignalType::G1P,
313 2 => SignalType::G2P,
314 3 => SignalType::G2CA,
315 4 => SignalType::G3,
316 _ => SignalType::Other(sig_num),
317 },
318 Constellation::Galileo => match sig_num {
319 0 => SignalType::E1,
320 1 => SignalType::E5a,
321 2 => SignalType::E5b,
322 3 => SignalType::E5AltBOC,
323 4 => SignalType::E6,
324 _ => SignalType::Other(sig_num),
325 },
326 Constellation::BeiDou => match sig_num {
327 0 => SignalType::B1I,
328 1 => SignalType::B2I,
329 2 => SignalType::B3I,
330 3 => SignalType::B1C,
331 4 => SignalType::B2a,
332 5 => SignalType::B2b,
333 _ => SignalType::Other(sig_num),
334 },
335 Constellation::QZSS => match sig_num {
336 0 => SignalType::QZSSL1CA,
337 1 => SignalType::QZSSL1S,
338 2 => SignalType::L2P,
339 3 => SignalType::QZSSL2C,
340 4 => SignalType::QZSSL5,
341 5 => SignalType::QZSSL1C,
342 _ => SignalType::Other(sig_num),
343 },
344 Constellation::SBAS => match sig_num {
345 0 => SignalType::SBASL1,
346 1 => SignalType::SBASL5,
347 _ => SignalType::Other(sig_num),
348 },
349 Constellation::NavIC => match sig_num {
350 0 => SignalType::NavICL5,
351 1 => SignalType::NavICL1,
352 _ => SignalType::Other(sig_num),
353 },
354 Constellation::Unknown(_) => SignalType::Other(sig_num),
355 }
356 }
357
358 pub fn name(&self) -> &'static str {
360 match self {
361 SignalType::L1CA => "L1CA",
362 SignalType::L1C => "L1C",
363 SignalType::L1PY => "L1PY",
364 SignalType::L2C => "L2C",
365 SignalType::L2P => "L2P",
366 SignalType::L2PY => "L2PY",
367 SignalType::L5 => "L5",
368 SignalType::E1 => "E1",
369 SignalType::E5a => "E5a",
370 SignalType::E5b => "E5b",
371 SignalType::E5AltBOC => "E5",
372 SignalType::E6 => "E6",
373 SignalType::G1CA => "G1",
374 SignalType::G1P => "G1P",
375 SignalType::G2CA => "G2",
376 SignalType::G2P => "G2P",
377 SignalType::G3 => "G3",
378 SignalType::B1I => "B1I",
379 SignalType::B1C => "B1C",
380 SignalType::B2I => "B2I",
381 SignalType::B2a => "B2a",
382 SignalType::B2b => "B2b",
383 SignalType::B3I => "B3I",
384 SignalType::QZSSL1CA => "L1",
385 SignalType::QZSSL2C => "L2C",
386 SignalType::QZSSL5 => "L5",
387 SignalType::QZSSL1C => "L1C",
388 SignalType::QZSSL1S => "L1S",
389 SignalType::QZSSL1CB => "L1CB",
390 SignalType::QZSSL5S => "L5S",
391 SignalType::QZSSL6 => "L6",
392 SignalType::SBASL1 => "L1",
393 SignalType::SBASL5 => "L5",
394 SignalType::LBand => "LBand",
395 SignalType::NavICL5 => "L5",
396 SignalType::NavICL1 => "L1",
397 SignalType::Other(_) => "?",
398 }
399 }
400
401 pub fn band(&self) -> &'static str {
403 match self {
404 SignalType::L1CA | SignalType::L1C | SignalType::L1PY => "L1",
405 SignalType::L2C | SignalType::L2P | SignalType::L2PY => "L2",
406 SignalType::L5 => "L5",
407 SignalType::E1 => "L1",
408 SignalType::E5a => "L5",
409 SignalType::E5b => "E5b",
410 SignalType::E5AltBOC => "E5",
411 SignalType::E6 => "E6",
412 SignalType::G1CA | SignalType::G1P => "G1",
413 SignalType::G2CA | SignalType::G2P => "G2",
414 SignalType::G3 => "G3",
415 SignalType::B1I | SignalType::B1C => "B1",
416 SignalType::B2I | SignalType::B2a | SignalType::B2b => "B2",
417 SignalType::B3I => "B3",
418 SignalType::QZSSL1CA
419 | SignalType::QZSSL1C
420 | SignalType::QZSSL1S
421 | SignalType::QZSSL1CB => "L1",
422 SignalType::QZSSL2C => "L2",
423 SignalType::QZSSL5 | SignalType::QZSSL5S => "L5",
424 SignalType::QZSSL6 => "L6",
425 SignalType::SBASL1 => "L1",
426 SignalType::SBASL5 => "L5",
427 SignalType::LBand => "LBand",
428 SignalType::NavICL5 => "L5",
429 SignalType::NavICL1 => "L1",
430 SignalType::Other(_) => "?",
431 }
432 }
433}
434
435impl std::fmt::Display for SignalType {
436 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
437 write!(f, "{}", self.name())
438 }
439}
440
441#[derive(Debug, Clone, Copy, PartialEq, Eq)]
443#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
444pub enum PvtMode {
445 NoFix,
446 StandAlone,
447 Dgps,
448 Fixed,
449 FixedPpp,
450 Float,
451 FloatPpp,
452 Sbas,
453 RtkFixed,
454 RtkFloat,
455 MovingBaseRtkFixed,
456 MovingBaseRtkFloat,
457 Ppp,
458 Unknown(u8),
459}
460
461impl PvtMode {
462 pub fn from_mode_byte(mode: u8) -> Self {
464 match mode & 0x0F {
465 0 => PvtMode::NoFix,
466 1 => PvtMode::StandAlone,
467 2 => PvtMode::Dgps,
468 3 => PvtMode::Fixed,
469 4 => PvtMode::FixedPpp,
470 5 => PvtMode::Float,
471 6 => PvtMode::FloatPpp,
472 7 => PvtMode::Sbas,
473 8 => PvtMode::RtkFixed,
474 9 => PvtMode::RtkFloat,
475 10 => PvtMode::MovingBaseRtkFixed,
476 11 => PvtMode::MovingBaseRtkFloat,
477 12 => PvtMode::Ppp,
478 n => PvtMode::Unknown(n),
479 }
480 }
481
482 pub fn has_fix(&self) -> bool {
484 !matches!(self, PvtMode::NoFix | PvtMode::Unknown(_))
485 }
486
487 pub fn is_rtk(&self) -> bool {
489 matches!(
490 self,
491 PvtMode::RtkFixed
492 | PvtMode::RtkFloat
493 | PvtMode::MovingBaseRtkFixed
494 | PvtMode::MovingBaseRtkFloat
495 )
496 }
497
498 pub fn is_ppp(&self) -> bool {
500 matches!(self, PvtMode::Ppp | PvtMode::FixedPpp | PvtMode::FloatPpp)
501 }
502}
503
504impl std::fmt::Display for PvtMode {
505 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506 let s = match self {
507 PvtMode::NoFix => "No Fix",
508 PvtMode::StandAlone => "Stand-Alone",
509 PvtMode::Dgps => "DGPS",
510 PvtMode::Fixed => "Fixed",
511 PvtMode::FixedPpp => "Fixed PPP",
512 PvtMode::Float => "Float",
513 PvtMode::FloatPpp => "Float PPP",
514 PvtMode::Sbas => "SBAS",
515 PvtMode::RtkFixed => "RTK Fixed",
516 PvtMode::RtkFloat => "RTK Float",
517 PvtMode::MovingBaseRtkFixed => "Moving Base RTK Fixed",
518 PvtMode::MovingBaseRtkFloat => "Moving Base RTK Float",
519 PvtMode::Ppp => "PPP",
520 PvtMode::Unknown(_) => "Unknown",
521 };
522 write!(f, "{}", s)
523 }
524}
525
526#[derive(Debug, Clone, Copy, PartialEq, Eq)]
528#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
529pub enum PvtError {
530 None,
531 NotEnoughMeasurements,
532 NotEnoughEphemerides,
533 DopTooLarge,
534 ResidualsTooLarge,
535 NoCovergence,
536 NotEnoughMeasurementsAfterRejection,
537 PositionProhibited,
538 NotEnoughDiffCorr,
539 BaseStationCoordinatesUnavailable,
540 Unknown(u8),
541}
542
543impl PvtError {
544 pub fn from_error_byte(error: u8) -> Self {
546 match error {
547 0 => PvtError::None,
548 1 => PvtError::NotEnoughMeasurements,
549 2 => PvtError::NotEnoughEphemerides,
550 3 => PvtError::DopTooLarge,
551 4 => PvtError::ResidualsTooLarge,
552 5 => PvtError::NoCovergence,
553 6 => PvtError::NotEnoughMeasurementsAfterRejection,
554 7 => PvtError::PositionProhibited,
555 8 => PvtError::NotEnoughDiffCorr,
556 9 => PvtError::BaseStationCoordinatesUnavailable,
557 n => PvtError::Unknown(n),
558 }
559 }
560
561 pub fn is_ok(&self) -> bool {
563 matches!(self, PvtError::None)
564 }
565}
566
567impl std::fmt::Display for PvtError {
568 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
569 let s = match self {
570 PvtError::None => "None",
571 PvtError::NotEnoughMeasurements => "Not enough measurements",
572 PvtError::NotEnoughEphemerides => "Not enough ephemerides",
573 PvtError::DopTooLarge => "DOP too large",
574 PvtError::ResidualsTooLarge => "Residuals too large",
575 PvtError::NoCovergence => "No convergence",
576 PvtError::NotEnoughMeasurementsAfterRejection => {
577 "Not enough measurements after rejection"
578 }
579 PvtError::PositionProhibited => "Position prohibited",
580 PvtError::NotEnoughDiffCorr => "Not enough differential corrections",
581 PvtError::BaseStationCoordinatesUnavailable => "Base station coordinates unavailable",
582 PvtError::Unknown(_) => "Unknown",
583 };
584 write!(f, "{}", s)
585 }
586}
587
588#[cfg(test)]
589mod tests {
590 use super::*;
591
592 #[test]
593 fn test_satellite_id_from_svid() {
594 assert_eq!(
596 SatelliteId::from_svid(1),
597 Some(SatelliteId::new(Constellation::GPS, 1))
598 );
599 assert_eq!(
600 SatelliteId::from_svid(32),
601 Some(SatelliteId::new(Constellation::GPS, 32))
602 );
603
604 assert_eq!(
606 SatelliteId::from_svid(38),
607 Some(SatelliteId::new(Constellation::GLONASS, 1))
608 );
609 assert_eq!(
610 SatelliteId::from_svid(61),
611 Some(SatelliteId::new(Constellation::GLONASS, 24))
612 );
613
614 assert_eq!(
616 SatelliteId::from_svid(71),
617 Some(SatelliteId::new(Constellation::Galileo, 1))
618 );
619 assert_eq!(
620 SatelliteId::from_svid(106),
621 Some(SatelliteId::new(Constellation::Galileo, 36))
622 );
623
624 assert_eq!(
626 SatelliteId::from_svid(141),
627 Some(SatelliteId::new(Constellation::BeiDou, 1))
628 );
629
630 assert_eq!(
632 SatelliteId::from_svid(181),
633 Some(SatelliteId::new(Constellation::QZSS, 1))
634 );
635
636 assert_eq!(
638 SatelliteId::from_svid(216),
639 Some(SatelliteId::new(Constellation::NavIC, 8))
640 );
641 assert_eq!(
642 SatelliteId::from_svid(222),
643 Some(SatelliteId::new(Constellation::NavIC, 14))
644 );
645
646 assert_eq!(
648 SatelliteId::from_svid(120),
649 Some(SatelliteId::new(Constellation::SBAS, 20))
650 );
651
652 assert_eq!(SatelliteId::from_svid(0), None);
654 }
655
656 #[test]
657 fn test_satellite_id_key() {
658 let gps = SatelliteId::new(Constellation::GPS, 1);
659 assert_eq!(gps.key(), "G01");
660
661 let gal = SatelliteId::new(Constellation::Galileo, 12);
662 assert_eq!(gal.key(), "E12");
663
664 let glo = SatelliteId::new(Constellation::GLONASS, 5);
665 assert_eq!(glo.key(), "R05");
666 }
667
668 #[test]
669 fn test_satellite_id_to_svid_navic_ranges() {
670 let navic_legacy = SatelliteId::new(Constellation::NavIC, 7);
671 assert_eq!(navic_legacy.to_svid(), 197);
672
673 let navic_extended = SatelliteId::new(Constellation::NavIC, 8);
674 assert_eq!(navic_extended.to_svid(), 216);
675 }
676
677 #[test]
678 fn test_signal_type() {
679 assert_eq!(SignalType::from_signal_number(0), SignalType::L1CA);
680 assert_eq!(SignalType::from_signal_number(17), SignalType::E1);
681 assert_eq!(SignalType::from_signal_number(28), SignalType::B1I);
682 assert_eq!(SignalType::from_signal_number(23), SignalType::LBand);
683 assert_eq!(SignalType::from_signal_number(38), SignalType::QZSSL1CB);
684 assert_eq!(SignalType::from_signal_number(39), SignalType::QZSSL5S);
685 }
686
687 #[test]
688 fn test_pvt_mode() {
689 assert!(!PvtMode::NoFix.has_fix());
690 assert!(PvtMode::StandAlone.has_fix());
691 assert!(PvtMode::RtkFixed.is_rtk());
692 assert!(PvtMode::Ppp.is_ppp());
693 }
694}