use core::fmt;
use crate::constants::{C_M_S, F_B1I_HZ, F_B3I_HZ, F_E1_HZ, F_E5A_HZ, F_L1_HZ, F_L2_HZ};
use crate::validate;
use crate::GnssSystem;
const F_E6_HZ: f64 = 1_278_750_000.0;
const F_E5B_HZ: f64 = 1_207_140_000.0;
const F_E5_HZ: f64 = 1_191_795_000.0;
const F_GLONASS_G1_BASE_HZ: f64 = 1_602_000_000.0;
const F_GLONASS_G1_STEP_HZ: f64 = 562_500.0;
const F_GLONASS_G2_BASE_HZ: f64 = 1_246_000_000.0;
const F_GLONASS_G2_STEP_HZ: f64 = 437_500.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CarrierBand {
L1,
L2,
L5,
E1,
E5a,
E5b,
E5,
E6,
B1c,
B1i,
B2a,
B2b,
B2,
B3i,
G1,
G2,
}
impl CarrierBand {
pub fn from_name(name: &str) -> Option<Self> {
match name {
"l1" => Some(Self::L1),
"l2" => Some(Self::L2),
"l5" => Some(Self::L5),
"e1" => Some(Self::E1),
"e5a" => Some(Self::E5a),
"e5b" => Some(Self::E5b),
"e5" => Some(Self::E5),
"e6" => Some(Self::E6),
"b1c" => Some(Self::B1c),
"b1i" => Some(Self::B1i),
"b2a" => Some(Self::B2a),
"b2b" => Some(Self::B2b),
"b2" => Some(Self::B2),
"b3i" => Some(Self::B3i),
"g1" => Some(Self::G1),
"g2" => Some(Self::G2),
_ => None,
}
}
pub fn from_iono_free_name(name: &str) -> Option<Self> {
match name {
"l1" => Some(Self::L1),
"l2" => Some(Self::L2),
"e1" => Some(Self::E1),
"e5a" => Some(Self::E5a),
"b1i" => Some(Self::B1i),
"b3i" => Some(Self::B3i),
_ => None,
}
}
pub const fn as_str(&self) -> &'static str {
match *self {
Self::L1 => "l1",
Self::L2 => "l2",
Self::L5 => "l5",
Self::E1 => "e1",
Self::E5a => "e5a",
Self::E5b => "e5b",
Self::E5 => "e5",
Self::E6 => "e6",
Self::B1c => "b1c",
Self::B1i => "b1i",
Self::B2a => "b2a",
Self::B2b => "b2b",
Self::B2 => "b2",
Self::B3i => "b3i",
Self::G1 => "g1",
Self::G2 => "g2",
}
}
pub const fn name(self) -> &'static str {
self.as_str()
}
}
impl fmt::Display for CarrierBand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CarrierPair {
pub band1: CarrierBand,
pub band2: CarrierBand,
}
impl CarrierPair {
pub const fn new(band1: CarrierBand, band2: CarrierBand) -> Self {
Self { band1, band2 }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CarrierFrequency {
pub system: GnssSystem,
pub band: CarrierBand,
pub frequency_hz: f64,
}
pub const fn fixed_carrier_frequencies() -> [CarrierFrequency; 17] {
[
CarrierFrequency {
system: GnssSystem::Gps,
band: CarrierBand::L1,
frequency_hz: F_L1_HZ,
},
CarrierFrequency {
system: GnssSystem::Gps,
band: CarrierBand::L2,
frequency_hz: F_L2_HZ,
},
CarrierFrequency {
system: GnssSystem::Gps,
band: CarrierBand::L5,
frequency_hz: F_E5A_HZ,
},
CarrierFrequency {
system: GnssSystem::Qzss,
band: CarrierBand::L1,
frequency_hz: F_L1_HZ,
},
CarrierFrequency {
system: GnssSystem::Qzss,
band: CarrierBand::L2,
frequency_hz: F_L2_HZ,
},
CarrierFrequency {
system: GnssSystem::Qzss,
band: CarrierBand::L5,
frequency_hz: F_E5A_HZ,
},
CarrierFrequency {
system: GnssSystem::Galileo,
band: CarrierBand::E1,
frequency_hz: F_E1_HZ,
},
CarrierFrequency {
system: GnssSystem::Galileo,
band: CarrierBand::E5a,
frequency_hz: F_E5A_HZ,
},
CarrierFrequency {
system: GnssSystem::Galileo,
band: CarrierBand::E6,
frequency_hz: F_E6_HZ,
},
CarrierFrequency {
system: GnssSystem::Galileo,
band: CarrierBand::E5b,
frequency_hz: F_E5B_HZ,
},
CarrierFrequency {
system: GnssSystem::Galileo,
band: CarrierBand::E5,
frequency_hz: F_E5_HZ,
},
CarrierFrequency {
system: GnssSystem::BeiDou,
band: CarrierBand::B1c,
frequency_hz: F_L1_HZ,
},
CarrierFrequency {
system: GnssSystem::BeiDou,
band: CarrierBand::B1i,
frequency_hz: F_B1I_HZ,
},
CarrierFrequency {
system: GnssSystem::BeiDou,
band: CarrierBand::B2a,
frequency_hz: F_E5A_HZ,
},
CarrierFrequency {
system: GnssSystem::BeiDou,
band: CarrierBand::B3i,
frequency_hz: F_B3I_HZ,
},
CarrierFrequency {
system: GnssSystem::BeiDou,
band: CarrierBand::B2b,
frequency_hz: F_E5B_HZ,
},
CarrierFrequency {
system: GnssSystem::BeiDou,
band: CarrierBand::B2,
frequency_hz: F_E5_HZ,
},
]
}
pub const fn iono_free_carrier_frequencies() -> [CarrierFrequency; 6] {
[
CarrierFrequency {
system: GnssSystem::Gps,
band: CarrierBand::L1,
frequency_hz: F_L1_HZ,
},
CarrierFrequency {
system: GnssSystem::Gps,
band: CarrierBand::L2,
frequency_hz: F_L2_HZ,
},
CarrierFrequency {
system: GnssSystem::Galileo,
band: CarrierBand::E1,
frequency_hz: F_E1_HZ,
},
CarrierFrequency {
system: GnssSystem::Galileo,
band: CarrierBand::E5a,
frequency_hz: F_E5A_HZ,
},
CarrierFrequency {
system: GnssSystem::BeiDou,
band: CarrierBand::B1i,
frequency_hz: F_B1I_HZ,
},
CarrierFrequency {
system: GnssSystem::BeiDou,
band: CarrierBand::B3i,
frequency_hz: F_B3I_HZ,
},
]
}
pub const fn frequency_hz(system: GnssSystem, band: CarrierBand) -> Option<f64> {
match (system, band) {
(GnssSystem::Gps, CarrierBand::L1) => Some(F_L1_HZ),
(GnssSystem::Gps, CarrierBand::L2) => Some(F_L2_HZ),
(GnssSystem::Gps, CarrierBand::L5) => Some(F_E5A_HZ),
(GnssSystem::Qzss, CarrierBand::L1) => Some(F_L1_HZ),
(GnssSystem::Qzss, CarrierBand::L2) => Some(F_L2_HZ),
(GnssSystem::Qzss, CarrierBand::L5) => Some(F_E5A_HZ),
(GnssSystem::Galileo, CarrierBand::E1) => Some(F_E1_HZ),
(GnssSystem::Galileo, CarrierBand::E5a) => Some(F_E5A_HZ),
(GnssSystem::Galileo, CarrierBand::E6) => Some(F_E6_HZ),
(GnssSystem::Galileo, CarrierBand::E5b) => Some(F_E5B_HZ),
(GnssSystem::Galileo, CarrierBand::E5) => Some(F_E5_HZ),
(GnssSystem::BeiDou, CarrierBand::B1c) => Some(F_L1_HZ),
(GnssSystem::BeiDou, CarrierBand::B1i) => Some(F_B1I_HZ),
(GnssSystem::BeiDou, CarrierBand::B2a) => Some(F_E5A_HZ),
(GnssSystem::BeiDou, CarrierBand::B3i) => Some(F_B3I_HZ),
(GnssSystem::BeiDou, CarrierBand::B2b) => Some(F_E5B_HZ),
(GnssSystem::BeiDou, CarrierBand::B2) => Some(F_E5_HZ),
_ => None,
}
}
pub fn wavelength_m(system: GnssSystem, band: CarrierBand) -> Option<f64> {
frequency_hz(system, band).and_then(wavelength_for_frequency)
}
pub fn rinex_band_frequency_hz(
system: GnssSystem,
band: char,
glonass_channel: Option<i8>,
) -> Option<f64> {
rinex_signal_frequency_hz(system, band, None, None, glonass_channel)
}
pub fn rinex_observation_frequency_hz(
system: GnssSystem,
code: &str,
rinex_version: f64,
glonass_channel: Option<i8>,
) -> Option<f64> {
let mut chars = code.chars();
let _kind = chars.next()?;
let band = chars.next()?;
let tracking = chars.next();
rinex_signal_frequency_hz(system, band, tracking, Some(rinex_version), glonass_channel)
}
fn rinex_signal_frequency_hz(
system: GnssSystem,
band: char,
tracking: Option<char>,
rinex_version: Option<f64>,
glonass_channel: Option<i8>,
) -> Option<f64> {
let frequency_hz = match (system, band, glonass_channel) {
(GnssSystem::Gps, '1', _) => frequency_hz(system, CarrierBand::L1),
(GnssSystem::Gps, '2', _) => frequency_hz(system, CarrierBand::L2),
(GnssSystem::Gps, '5', _) => frequency_hz(system, CarrierBand::L5),
(GnssSystem::Qzss, '1', _) => frequency_hz(system, CarrierBand::L1),
(GnssSystem::Qzss, '2', _) => frequency_hz(system, CarrierBand::L2),
(GnssSystem::Qzss, '5', _) => frequency_hz(system, CarrierBand::L5),
(GnssSystem::Galileo, '1', _) => frequency_hz(system, CarrierBand::E1),
(GnssSystem::Galileo, '5', _) => frequency_hz(system, CarrierBand::E5a),
(GnssSystem::Galileo, '6', _) => frequency_hz(system, CarrierBand::E6),
(GnssSystem::Galileo, '7', _) => frequency_hz(system, CarrierBand::E5b),
(GnssSystem::Galileo, '8', _) => frequency_hz(system, CarrierBand::E5),
(GnssSystem::BeiDou, _, _) => {
frequency_hz(system, rinex_beidou_band(band, tracking, rinex_version)?)
}
(GnssSystem::Glonass, '1', Some(channel)) => {
Some(F_GLONASS_G1_BASE_HZ + f64::from(channel) * F_GLONASS_G1_STEP_HZ)
}
(GnssSystem::Glonass, '2', Some(channel)) => {
Some(F_GLONASS_G2_BASE_HZ + f64::from(channel) * F_GLONASS_G2_STEP_HZ)
}
_ => None,
}?;
valid_frequency_hz(frequency_hz)
}
fn rinex_beidou_band(
band: char,
tracking: Option<char>,
rinex_version: Option<f64>,
) -> Option<CarrierBand> {
match band {
'1' if tracking == Some('I') && rinex_version.is_some_and(is_rinex_302) => {
Some(CarrierBand::B1i)
}
'1' => Some(CarrierBand::B1c),
'2' => Some(CarrierBand::B1i),
'5' => Some(CarrierBand::B2a),
'6' => Some(CarrierBand::B3i),
'7' => Some(CarrierBand::B2b),
'8' => Some(CarrierBand::B2),
_ => None,
}
}
fn is_rinex_302(version: f64) -> bool {
(3.015..3.025).contains(&version)
}
pub fn rinex_band_wavelength_m(
system: GnssSystem,
band: char,
glonass_channel: Option<i8>,
) -> Option<f64> {
rinex_band_frequency_hz(system, band, glonass_channel).and_then(wavelength_for_frequency)
}
pub fn rinex_observation_wavelength_m(
system: GnssSystem,
code: &str,
rinex_version: f64,
glonass_channel: Option<i8>,
) -> Option<f64> {
rinex_observation_frequency_hz(system, code, rinex_version, glonass_channel)
.and_then(wavelength_for_frequency)
}
fn valid_frequency_hz(frequency_hz: f64) -> Option<f64> {
validate::finite_positive(frequency_hz, "frequency_hz").ok()
}
pub(crate) fn wavelength_for_frequency(frequency_hz: f64) -> Option<f64> {
valid_frequency_hz(frequency_hz).map(|frequency_hz| C_M_S / frequency_hz)
}
pub const fn default_iono_free_pair(system: GnssSystem) -> Option<CarrierPair> {
match system {
GnssSystem::Gps => Some(CarrierPair::new(CarrierBand::L1, CarrierBand::L2)),
GnssSystem::Galileo => Some(CarrierPair::new(CarrierBand::E1, CarrierBand::E5a)),
GnssSystem::BeiDou => Some(CarrierPair::new(CarrierBand::B1i, CarrierBand::B3i)),
_ => None,
}
}
pub const fn glonass_g1_frequency_hz(channel: i8) -> f64 {
F_GLONASS_G1_BASE_HZ + (channel as f64) * F_GLONASS_G1_STEP_HZ
}
pub const fn default_spp_carrier(system: GnssSystem) -> Option<CarrierBand> {
match system {
GnssSystem::Gps => Some(CarrierBand::L1),
GnssSystem::Galileo => Some(CarrierBand::E1),
GnssSystem::BeiDou => Some(CarrierBand::B1i),
_ => None,
}
}
pub const fn default_spp_frequency_hz(system: GnssSystem) -> Option<f64> {
match default_spp_carrier(system) {
Some(band) => frequency_hz(system, band),
None => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn iono_free_table_matches_supported_pairs() {
assert_eq!(
default_iono_free_pair(GnssSystem::Gps),
Some(CarrierPair::new(CarrierBand::L1, CarrierBand::L2))
);
assert_eq!(
default_iono_free_pair(GnssSystem::Galileo),
Some(CarrierPair::new(CarrierBand::E1, CarrierBand::E5a))
);
assert_eq!(
default_iono_free_pair(GnssSystem::BeiDou),
Some(CarrierPair::new(CarrierBand::B1i, CarrierBand::B3i))
);
assert_eq!(default_iono_free_pair(GnssSystem::Glonass), None);
assert_eq!(iono_free_carrier_frequencies().len(), 6);
}
#[test]
fn carrier_band_labels_are_canonical_lowercase() {
let cases = [
(CarrierBand::L1, "l1"),
(CarrierBand::L2, "l2"),
(CarrierBand::L5, "l5"),
(CarrierBand::E1, "e1"),
(CarrierBand::E5a, "e5a"),
(CarrierBand::E5b, "e5b"),
(CarrierBand::E5, "e5"),
(CarrierBand::E6, "e6"),
(CarrierBand::B1c, "b1c"),
(CarrierBand::B1i, "b1i"),
(CarrierBand::B2a, "b2a"),
(CarrierBand::B2b, "b2b"),
(CarrierBand::B2, "b2"),
(CarrierBand::B3i, "b3i"),
(CarrierBand::G1, "g1"),
(CarrierBand::G2, "g2"),
];
for (band, label) in cases {
assert_eq!(band.as_str(), label);
assert_eq!(band.name(), label);
assert_eq!(band.to_string(), label);
assert_eq!(CarrierBand::from_name(label), Some(band));
}
}
#[test]
fn rinex_band_table_matches_existing_frequency_bits() {
let cases: [(GnssSystem, char, Option<i8>, f64); 16] = [
(GnssSystem::Gps, '1', None, 1_575_420_000.0),
(GnssSystem::Gps, '2', None, 1_227_600_000.0),
(GnssSystem::Gps, '5', None, 1_176_450_000.0),
(GnssSystem::Qzss, '1', None, 1_575_420_000.0),
(GnssSystem::Qzss, '2', None, 1_227_600_000.0),
(GnssSystem::Qzss, '5', None, 1_176_450_000.0),
(GnssSystem::Galileo, '6', None, 1_278_750_000.0),
(GnssSystem::Galileo, '7', None, 1_207_140_000.0),
(GnssSystem::Galileo, '8', None, 1_191_795_000.0),
(GnssSystem::BeiDou, '1', None, 1_575_420_000.0),
(GnssSystem::BeiDou, '2', None, 1_561_098_000.0),
(GnssSystem::BeiDou, '6', None, 1_268_520_000.0),
(GnssSystem::BeiDou, '7', None, 1_207_140_000.0),
(GnssSystem::BeiDou, '8', None, 1_191_795_000.0),
(GnssSystem::Glonass, '1', Some(1), 1_602_562_500.0),
(GnssSystem::Glonass, '2', Some(1), 1_246_437_500.0),
];
for (system, band, channel, expected) in cases {
assert_eq!(
rinex_band_frequency_hz(system, band, channel).map(f64::to_bits),
Some(expected.to_bits())
);
}
assert_eq!(
rinex_band_frequency_hz(GnssSystem::Glonass, '1', None),
None
);
}
#[test]
fn rinex_observation_code_resolves_beidou_302_b1i() {
for code in ["C1I", "L1I"] {
assert_eq!(
rinex_observation_frequency_hz(GnssSystem::BeiDou, code, 3.02, None)
.map(f64::to_bits),
Some(F_B1I_HZ.to_bits())
);
}
assert_eq!(
rinex_observation_frequency_hz(GnssSystem::BeiDou, "L1X", 3.03, None).map(f64::to_bits),
Some(F_L1_HZ.to_bits())
);
}
#[test]
fn wavelength_helpers_use_c_over_frequency() {
let wavelength = wavelength_m(GnssSystem::Gps, CarrierBand::L1).unwrap();
assert_eq!(wavelength.to_bits(), (C_M_S / F_L1_HZ).to_bits());
let glonass_wavelength = rinex_band_wavelength_m(GnssSystem::Glonass, '1', Some(-7))
.expect("GLONASS G1 channel");
let frequency = 1_602_000_000.0 + f64::from(-7) * 562_500.0;
assert_eq!(glonass_wavelength.to_bits(), (C_M_S / frequency).to_bits());
}
#[test]
fn glonass_g1_spp_carrier_matches_rinex_band_table() {
for channel in [-7_i8, -1, 0, 1, 6] {
assert_eq!(
glonass_g1_frequency_hz(channel).to_bits(),
rinex_band_frequency_hz(GnssSystem::Glonass, '1', Some(channel))
.expect("GLONASS G1 channel frequency")
.to_bits()
);
}
}
#[test]
fn frequency_validation_rejects_invalid_runtime_values() {
assert_eq!(valid_frequency_hz(f64::NAN), None);
assert_eq!(valid_frequency_hz(f64::INFINITY), None);
assert_eq!(valid_frequency_hz(0.0), None);
assert_eq!(valid_frequency_hz(-1.0), None);
assert_eq!(wavelength_for_frequency(f64::NAN), None);
}
}