Skip to main content

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}