astrodynamics_gnss/id.rs
1//! GNSS satellite identification.
2//!
3//! Foundational identifier types only — no domain numerics live here.
4
5use core::fmt;
6
7/// A GNSS constellation (satellite system).
8///
9/// Variants follow the RINEX / IGS single-letter system identifiers, which are
10/// the canonical keys used throughout SP3, RINEX, and IONEX products:
11///
12/// | Letter | Variant | System |
13/// |--------|--------------------------|---------------------------------|
14/// | `G` | [`GnssSystem::Gps`] | GPS (US) |
15/// | `R` | [`GnssSystem::Glonass`] | GLONASS (RU) |
16/// | `E` | [`GnssSystem::Galileo`] | Galileo (EU) |
17/// | `C` | [`GnssSystem::BeiDou`] | BeiDou (CN) |
18/// | `J` | [`GnssSystem::Qzss`] | QZSS (JP) |
19/// | `I` | [`GnssSystem::Navic`] | NavIC / IRNSS (IN) |
20/// | `S` | [`GnssSystem::Sbas`] | SBAS (geostationary augmentation) |
21///
22/// Note that timekeeping is constellation-tagged separately (`TimeScale`):
23/// GPS/Galileo/BeiDou each run their own system time, and GNSS week numbers are
24/// **not** cross-comparable between systems.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub enum GnssSystem {
27 /// GPS (United States), RINEX letter `G`.
28 Gps,
29 /// GLONASS (Russia), RINEX letter `R`.
30 Glonass,
31 /// Galileo (European Union), RINEX letter `E`.
32 Galileo,
33 /// BeiDou (China), RINEX letter `C`.
34 BeiDou,
35 /// QZSS (Japan), RINEX letter `J`.
36 Qzss,
37 /// NavIC / IRNSS (India), RINEX letter `I`.
38 Navic,
39 /// SBAS geostationary augmentation, RINEX letter `S`.
40 Sbas,
41}
42
43impl GnssSystem {
44 /// The canonical RINEX / IGS single-letter system identifier.
45 pub const fn letter(self) -> char {
46 match self {
47 GnssSystem::Gps => 'G',
48 GnssSystem::Glonass => 'R',
49 GnssSystem::Galileo => 'E',
50 GnssSystem::BeiDou => 'C',
51 GnssSystem::Qzss => 'J',
52 GnssSystem::Navic => 'I',
53 GnssSystem::Sbas => 'S',
54 }
55 }
56
57 /// Parse a RINEX / IGS single-letter system identifier.
58 ///
59 /// Returns `None` for an unrecognized letter. Accepts uppercase letters
60 /// only, as emitted by SP3/RINEX/IONEX products.
61 pub const fn from_letter(letter: char) -> Option<Self> {
62 match letter {
63 'G' => Some(GnssSystem::Gps),
64 'R' => Some(GnssSystem::Glonass),
65 'E' => Some(GnssSystem::Galileo),
66 'C' => Some(GnssSystem::BeiDou),
67 'J' => Some(GnssSystem::Qzss),
68 'I' => Some(GnssSystem::Navic),
69 'S' => Some(GnssSystem::Sbas),
70 _ => None,
71 }
72 }
73}
74
75impl fmt::Display for GnssSystem {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 f.write_str(match self {
78 GnssSystem::Gps => "GPS",
79 GnssSystem::Glonass => "GLO",
80 GnssSystem::Galileo => "GAL",
81 GnssSystem::BeiDou => "BDS",
82 GnssSystem::Qzss => "QZSS",
83 GnssSystem::Navic => "NavIC",
84 GnssSystem::Sbas => "SBAS",
85 })
86 }
87}
88
89/// A satellite identifier: a constellation plus its within-system PRN/slot.
90///
91/// This is the `GnssSatelliteId { system, prn }` foundational type from the
92/// spec (line 112). The `prn` is the within-constellation satellite number as
93/// it appears in the product (e.g. the `01` in the SP3/RINEX token `G01`); it
94/// is only meaningful in combination with [`GnssSatelliteId::system`].
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
96pub struct GnssSatelliteId {
97 /// The constellation this satellite belongs to.
98 pub system: GnssSystem,
99 /// The within-constellation PRN / slot number (e.g. `1` for `G01`).
100 pub prn: u8,
101}
102
103impl GnssSatelliteId {
104 /// Construct an identifier from a constellation and PRN.
105 pub const fn new(system: GnssSystem, prn: u8) -> Self {
106 Self { system, prn }
107 }
108}
109
110impl fmt::Display for GnssSatelliteId {
111 /// Renders the canonical SP3/RINEX token, e.g. `G01`, `E12`, `C30`.
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(f, "{}{:02}", self.system.letter(), self.prn)
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn letter_round_trips() {
123 for sys in [
124 GnssSystem::Gps,
125 GnssSystem::Glonass,
126 GnssSystem::Galileo,
127 GnssSystem::BeiDou,
128 GnssSystem::Qzss,
129 GnssSystem::Navic,
130 GnssSystem::Sbas,
131 ] {
132 assert_eq!(GnssSystem::from_letter(sys.letter()), Some(sys));
133 }
134 assert_eq!(GnssSystem::from_letter('X'), None);
135 }
136
137 #[test]
138 fn satellite_token_formats_padded() {
139 let id = GnssSatelliteId::new(GnssSystem::Gps, 1);
140 assert_eq!(id.to_string(), "G01");
141 assert_eq!(
142 GnssSatelliteId::new(GnssSystem::BeiDou, 30).to_string(),
143 "C30"
144 );
145 }
146}