astrodynamics-gnss 0.9.7

GNSS domain layer (SP3, broadcast ephemeris, multi-GNSS single-point positioning, ionosphere/troposphere, DOP) built on the astrodynamics core
Documentation
//! GNSS satellite identification.
//!
//! Foundational identifier types only — no domain numerics live here.

use core::fmt;

/// A GNSS constellation (satellite system).
///
/// Variants follow the RINEX / IGS single-letter system identifiers, which are
/// the canonical keys used throughout SP3, RINEX, and IONEX products:
///
/// | Letter | Variant                  | System                          |
/// |--------|--------------------------|---------------------------------|
/// | `G`    | [`GnssSystem::Gps`]      | GPS (US)                        |
/// | `R`    | [`GnssSystem::Glonass`]  | GLONASS (RU)                    |
/// | `E`    | [`GnssSystem::Galileo`]  | Galileo (EU)                    |
/// | `C`    | [`GnssSystem::BeiDou`]   | BeiDou (CN)                     |
/// | `J`    | [`GnssSystem::Qzss`]     | QZSS (JP)                       |
/// | `I`    | [`GnssSystem::Navic`]    | NavIC / IRNSS (IN)              |
/// | `S`    | [`GnssSystem::Sbas`]     | SBAS (geostationary augmentation) |
///
/// Note that timekeeping is constellation-tagged separately (`TimeScale`):
/// GPS/Galileo/BeiDou each run their own system time, and GNSS week numbers are
/// **not** cross-comparable between systems.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum GnssSystem {
    /// GPS (United States), RINEX letter `G`.
    Gps,
    /// GLONASS (Russia), RINEX letter `R`.
    Glonass,
    /// Galileo (European Union), RINEX letter `E`.
    Galileo,
    /// BeiDou (China), RINEX letter `C`.
    BeiDou,
    /// QZSS (Japan), RINEX letter `J`.
    Qzss,
    /// NavIC / IRNSS (India), RINEX letter `I`.
    Navic,
    /// SBAS geostationary augmentation, RINEX letter `S`.
    Sbas,
}

impl GnssSystem {
    /// The canonical RINEX / IGS single-letter system identifier.
    pub const fn letter(self) -> char {
        match self {
            GnssSystem::Gps => 'G',
            GnssSystem::Glonass => 'R',
            GnssSystem::Galileo => 'E',
            GnssSystem::BeiDou => 'C',
            GnssSystem::Qzss => 'J',
            GnssSystem::Navic => 'I',
            GnssSystem::Sbas => 'S',
        }
    }

    /// Parse a RINEX / IGS single-letter system identifier.
    ///
    /// Returns `None` for an unrecognized letter. Accepts uppercase letters
    /// only, as emitted by SP3/RINEX/IONEX products.
    pub const fn from_letter(letter: char) -> Option<Self> {
        match letter {
            'G' => Some(GnssSystem::Gps),
            'R' => Some(GnssSystem::Glonass),
            'E' => Some(GnssSystem::Galileo),
            'C' => Some(GnssSystem::BeiDou),
            'J' => Some(GnssSystem::Qzss),
            'I' => Some(GnssSystem::Navic),
            'S' => Some(GnssSystem::Sbas),
            _ => None,
        }
    }
}

impl fmt::Display for GnssSystem {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            GnssSystem::Gps => "GPS",
            GnssSystem::Glonass => "GLO",
            GnssSystem::Galileo => "GAL",
            GnssSystem::BeiDou => "BDS",
            GnssSystem::Qzss => "QZSS",
            GnssSystem::Navic => "NavIC",
            GnssSystem::Sbas => "SBAS",
        })
    }
}

/// A satellite identifier: a constellation plus its within-system PRN/slot.
///
/// This is the `GnssSatelliteId { system, prn }` foundational type from the
/// spec (line 112). The `prn` is the within-constellation satellite number as
/// it appears in the product (e.g. the `01` in the SP3/RINEX token `G01`); it
/// is only meaningful in combination with [`GnssSatelliteId::system`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct GnssSatelliteId {
    /// The constellation this satellite belongs to.
    pub system: GnssSystem,
    /// The within-constellation PRN / slot number (e.g. `1` for `G01`).
    pub prn: u8,
}

impl GnssSatelliteId {
    /// Construct an identifier from a constellation and PRN.
    pub const fn new(system: GnssSystem, prn: u8) -> Self {
        Self { system, prn }
    }
}

impl fmt::Display for GnssSatelliteId {
    /// Renders the canonical SP3/RINEX token, e.g. `G01`, `E12`, `C30`.
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}{:02}", self.system.letter(), self.prn)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn letter_round_trips() {
        for sys in [
            GnssSystem::Gps,
            GnssSystem::Glonass,
            GnssSystem::Galileo,
            GnssSystem::BeiDou,
            GnssSystem::Qzss,
            GnssSystem::Navic,
            GnssSystem::Sbas,
        ] {
            assert_eq!(GnssSystem::from_letter(sys.letter()), Some(sys));
        }
        assert_eq!(GnssSystem::from_letter('X'), None);
    }

    #[test]
    fn satellite_token_formats_padded() {
        let id = GnssSatelliteId::new(GnssSystem::Gps, 1);
        assert_eq!(id.to_string(), "G01");
        assert_eq!(
            GnssSatelliteId::new(GnssSystem::BeiDou, 30).to_string(),
            "C30"
        );
    }
}