1use crate::constants::{C_M_S, F_B1I_HZ, F_B3I_HZ, F_E1_HZ, F_E5A_HZ, F_L1_HZ, F_L2_HZ};
8use crate::validate;
9use crate::GnssSystem;
10
11const F_E6_HZ: f64 = 1_278_750_000.0;
12const F_E5B_HZ: f64 = 1_207_140_000.0;
13const F_E5_HZ: f64 = 1_191_795_000.0;
14const F_GLONASS_G1_BASE_HZ: f64 = 1_602_000_000.0;
15const F_GLONASS_G1_STEP_HZ: f64 = 562_500.0;
16const F_GLONASS_G2_BASE_HZ: f64 = 1_246_000_000.0;
17const F_GLONASS_G2_STEP_HZ: f64 = 437_500.0;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
21pub enum CarrierBand {
22 L1,
24 L2,
26 L5,
28 E1,
30 E5a,
32 E5b,
34 E5,
36 E6,
38 B1c,
40 B1i,
42 B2a,
44 B2b,
46 B2,
48 B3i,
50 G1,
52 G2,
54}
55
56impl CarrierBand {
57 pub fn from_name(name: &str) -> Option<Self> {
59 match name {
60 "l1" => Some(Self::L1),
61 "l2" => Some(Self::L2),
62 "l5" => Some(Self::L5),
63 "e1" => Some(Self::E1),
64 "e5a" => Some(Self::E5a),
65 "e5b" => Some(Self::E5b),
66 "e5" => Some(Self::E5),
67 "e6" => Some(Self::E6),
68 "b1c" => Some(Self::B1c),
69 "b1i" => Some(Self::B1i),
70 "b2a" => Some(Self::B2a),
71 "b2b" => Some(Self::B2b),
72 "b2" => Some(Self::B2),
73 "b3i" => Some(Self::B3i),
74 "g1" => Some(Self::G1),
75 "g2" => Some(Self::G2),
76 _ => None,
77 }
78 }
79
80 pub fn from_iono_free_name(name: &str) -> Option<Self> {
82 match name {
83 "l1" => Some(Self::L1),
84 "l2" => Some(Self::L2),
85 "e1" => Some(Self::E1),
86 "e5a" => Some(Self::E5a),
87 "b1i" => Some(Self::B1i),
88 "b3i" => Some(Self::B3i),
89 _ => None,
90 }
91 }
92
93 pub const fn name(self) -> &'static str {
95 match self {
96 Self::L1 => "l1",
97 Self::L2 => "l2",
98 Self::L5 => "l5",
99 Self::E1 => "e1",
100 Self::E5a => "e5a",
101 Self::E5b => "e5b",
102 Self::E5 => "e5",
103 Self::E6 => "e6",
104 Self::B1c => "b1c",
105 Self::B1i => "b1i",
106 Self::B2a => "b2a",
107 Self::B2b => "b2b",
108 Self::B2 => "b2",
109 Self::B3i => "b3i",
110 Self::G1 => "g1",
111 Self::G2 => "g2",
112 }
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct CarrierPair {
119 pub band1: CarrierBand,
121 pub band2: CarrierBand,
123}
124
125impl CarrierPair {
126 pub const fn new(band1: CarrierBand, band2: CarrierBand) -> Self {
128 Self { band1, band2 }
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq)]
134pub struct CarrierFrequency {
135 pub system: GnssSystem,
137 pub band: CarrierBand,
139 pub frequency_hz: f64,
141}
142
143pub const fn fixed_carrier_frequencies() -> [CarrierFrequency; 17] {
146 [
147 CarrierFrequency {
148 system: GnssSystem::Gps,
149 band: CarrierBand::L1,
150 frequency_hz: F_L1_HZ,
151 },
152 CarrierFrequency {
153 system: GnssSystem::Gps,
154 band: CarrierBand::L2,
155 frequency_hz: F_L2_HZ,
156 },
157 CarrierFrequency {
158 system: GnssSystem::Gps,
159 band: CarrierBand::L5,
160 frequency_hz: F_E5A_HZ,
161 },
162 CarrierFrequency {
163 system: GnssSystem::Qzss,
164 band: CarrierBand::L1,
165 frequency_hz: F_L1_HZ,
166 },
167 CarrierFrequency {
168 system: GnssSystem::Qzss,
169 band: CarrierBand::L2,
170 frequency_hz: F_L2_HZ,
171 },
172 CarrierFrequency {
173 system: GnssSystem::Qzss,
174 band: CarrierBand::L5,
175 frequency_hz: F_E5A_HZ,
176 },
177 CarrierFrequency {
178 system: GnssSystem::Galileo,
179 band: CarrierBand::E1,
180 frequency_hz: F_E1_HZ,
181 },
182 CarrierFrequency {
183 system: GnssSystem::Galileo,
184 band: CarrierBand::E5a,
185 frequency_hz: F_E5A_HZ,
186 },
187 CarrierFrequency {
188 system: GnssSystem::Galileo,
189 band: CarrierBand::E6,
190 frequency_hz: F_E6_HZ,
191 },
192 CarrierFrequency {
193 system: GnssSystem::Galileo,
194 band: CarrierBand::E5b,
195 frequency_hz: F_E5B_HZ,
196 },
197 CarrierFrequency {
198 system: GnssSystem::Galileo,
199 band: CarrierBand::E5,
200 frequency_hz: F_E5_HZ,
201 },
202 CarrierFrequency {
203 system: GnssSystem::BeiDou,
204 band: CarrierBand::B1c,
205 frequency_hz: F_L1_HZ,
206 },
207 CarrierFrequency {
208 system: GnssSystem::BeiDou,
209 band: CarrierBand::B1i,
210 frequency_hz: F_B1I_HZ,
211 },
212 CarrierFrequency {
213 system: GnssSystem::BeiDou,
214 band: CarrierBand::B2a,
215 frequency_hz: F_E5A_HZ,
216 },
217 CarrierFrequency {
218 system: GnssSystem::BeiDou,
219 band: CarrierBand::B3i,
220 frequency_hz: F_B3I_HZ,
221 },
222 CarrierFrequency {
223 system: GnssSystem::BeiDou,
224 band: CarrierBand::B2b,
225 frequency_hz: F_E5B_HZ,
226 },
227 CarrierFrequency {
228 system: GnssSystem::BeiDou,
229 band: CarrierBand::B2,
230 frequency_hz: F_E5_HZ,
231 },
232 ]
233}
234
235pub const fn iono_free_carrier_frequencies() -> [CarrierFrequency; 6] {
237 [
238 CarrierFrequency {
239 system: GnssSystem::Gps,
240 band: CarrierBand::L1,
241 frequency_hz: F_L1_HZ,
242 },
243 CarrierFrequency {
244 system: GnssSystem::Gps,
245 band: CarrierBand::L2,
246 frequency_hz: F_L2_HZ,
247 },
248 CarrierFrequency {
249 system: GnssSystem::Galileo,
250 band: CarrierBand::E1,
251 frequency_hz: F_E1_HZ,
252 },
253 CarrierFrequency {
254 system: GnssSystem::Galileo,
255 band: CarrierBand::E5a,
256 frequency_hz: F_E5A_HZ,
257 },
258 CarrierFrequency {
259 system: GnssSystem::BeiDou,
260 band: CarrierBand::B1i,
261 frequency_hz: F_B1I_HZ,
262 },
263 CarrierFrequency {
264 system: GnssSystem::BeiDou,
265 band: CarrierBand::B3i,
266 frequency_hz: F_B3I_HZ,
267 },
268 ]
269}
270
271pub const fn frequency_hz(system: GnssSystem, band: CarrierBand) -> Option<f64> {
273 match (system, band) {
274 (GnssSystem::Gps, CarrierBand::L1) => Some(F_L1_HZ),
275 (GnssSystem::Gps, CarrierBand::L2) => Some(F_L2_HZ),
276 (GnssSystem::Gps, CarrierBand::L5) => Some(F_E5A_HZ),
277 (GnssSystem::Qzss, CarrierBand::L1) => Some(F_L1_HZ),
278 (GnssSystem::Qzss, CarrierBand::L2) => Some(F_L2_HZ),
279 (GnssSystem::Qzss, CarrierBand::L5) => Some(F_E5A_HZ),
280 (GnssSystem::Galileo, CarrierBand::E1) => Some(F_E1_HZ),
281 (GnssSystem::Galileo, CarrierBand::E5a) => Some(F_E5A_HZ),
282 (GnssSystem::Galileo, CarrierBand::E6) => Some(F_E6_HZ),
283 (GnssSystem::Galileo, CarrierBand::E5b) => Some(F_E5B_HZ),
284 (GnssSystem::Galileo, CarrierBand::E5) => Some(F_E5_HZ),
285 (GnssSystem::BeiDou, CarrierBand::B1c) => Some(F_L1_HZ),
286 (GnssSystem::BeiDou, CarrierBand::B1i) => Some(F_B1I_HZ),
287 (GnssSystem::BeiDou, CarrierBand::B2a) => Some(F_E5A_HZ),
288 (GnssSystem::BeiDou, CarrierBand::B3i) => Some(F_B3I_HZ),
289 (GnssSystem::BeiDou, CarrierBand::B2b) => Some(F_E5B_HZ),
290 (GnssSystem::BeiDou, CarrierBand::B2) => Some(F_E5_HZ),
291 _ => None,
292 }
293}
294
295pub fn wavelength_m(system: GnssSystem, band: CarrierBand) -> Option<f64> {
297 frequency_hz(system, band).and_then(wavelength_for_frequency)
298}
299
300pub fn rinex_band_frequency_hz(
305 system: GnssSystem,
306 band: char,
307 glonass_channel: Option<i8>,
308) -> Option<f64> {
309 rinex_signal_frequency_hz(system, band, None, None, glonass_channel)
310}
311
312pub fn rinex_observation_frequency_hz(
319 system: GnssSystem,
320 code: &str,
321 rinex_version: f64,
322 glonass_channel: Option<i8>,
323) -> Option<f64> {
324 let mut chars = code.chars();
325 let _kind = chars.next()?;
326 let band = chars.next()?;
327 let tracking = chars.next();
328 rinex_signal_frequency_hz(system, band, tracking, Some(rinex_version), glonass_channel)
329}
330
331fn rinex_signal_frequency_hz(
332 system: GnssSystem,
333 band: char,
334 tracking: Option<char>,
335 rinex_version: Option<f64>,
336 glonass_channel: Option<i8>,
337) -> Option<f64> {
338 let frequency_hz = match (system, band, glonass_channel) {
339 (GnssSystem::Gps, '1', _) => frequency_hz(system, CarrierBand::L1),
340 (GnssSystem::Gps, '2', _) => frequency_hz(system, CarrierBand::L2),
341 (GnssSystem::Gps, '5', _) => frequency_hz(system, CarrierBand::L5),
342 (GnssSystem::Qzss, '1', _) => frequency_hz(system, CarrierBand::L1),
343 (GnssSystem::Qzss, '2', _) => frequency_hz(system, CarrierBand::L2),
344 (GnssSystem::Qzss, '5', _) => frequency_hz(system, CarrierBand::L5),
345 (GnssSystem::Galileo, '1', _) => frequency_hz(system, CarrierBand::E1),
346 (GnssSystem::Galileo, '5', _) => frequency_hz(system, CarrierBand::E5a),
347 (GnssSystem::Galileo, '6', _) => frequency_hz(system, CarrierBand::E6),
348 (GnssSystem::Galileo, '7', _) => frequency_hz(system, CarrierBand::E5b),
349 (GnssSystem::Galileo, '8', _) => frequency_hz(system, CarrierBand::E5),
350 (GnssSystem::BeiDou, _, _) => {
351 frequency_hz(system, rinex_beidou_band(band, tracking, rinex_version)?)
352 }
353 (GnssSystem::Glonass, '1', Some(channel)) => {
354 Some(F_GLONASS_G1_BASE_HZ + f64::from(channel) * F_GLONASS_G1_STEP_HZ)
355 }
356 (GnssSystem::Glonass, '2', Some(channel)) => {
357 Some(F_GLONASS_G2_BASE_HZ + f64::from(channel) * F_GLONASS_G2_STEP_HZ)
358 }
359 _ => None,
360 }?;
361 valid_frequency_hz(frequency_hz)
362}
363
364fn rinex_beidou_band(
365 band: char,
366 tracking: Option<char>,
367 rinex_version: Option<f64>,
368) -> Option<CarrierBand> {
369 match band {
370 '1' if tracking == Some('I') && rinex_version.is_some_and(is_rinex_302) => {
371 Some(CarrierBand::B1i)
372 }
373 '1' => Some(CarrierBand::B1c),
374 '2' => Some(CarrierBand::B1i),
375 '5' => Some(CarrierBand::B2a),
376 '6' => Some(CarrierBand::B3i),
377 '7' => Some(CarrierBand::B2b),
378 '8' => Some(CarrierBand::B2),
379 _ => None,
380 }
381}
382
383fn is_rinex_302(version: f64) -> bool {
384 (3.015..3.025).contains(&version)
385}
386
387pub fn rinex_band_wavelength_m(
389 system: GnssSystem,
390 band: char,
391 glonass_channel: Option<i8>,
392) -> Option<f64> {
393 rinex_band_frequency_hz(system, band, glonass_channel).and_then(wavelength_for_frequency)
394}
395
396pub fn rinex_observation_wavelength_m(
398 system: GnssSystem,
399 code: &str,
400 rinex_version: f64,
401 glonass_channel: Option<i8>,
402) -> Option<f64> {
403 rinex_observation_frequency_hz(system, code, rinex_version, glonass_channel)
404 .and_then(wavelength_for_frequency)
405}
406
407fn valid_frequency_hz(frequency_hz: f64) -> Option<f64> {
408 validate::finite_positive(frequency_hz, "frequency_hz").ok()
409}
410
411pub(crate) fn wavelength_for_frequency(frequency_hz: f64) -> Option<f64> {
412 valid_frequency_hz(frequency_hz).map(|frequency_hz| C_M_S / frequency_hz)
413}
414
415pub const fn default_iono_free_pair(system: GnssSystem) -> Option<CarrierPair> {
417 match system {
418 GnssSystem::Gps => Some(CarrierPair::new(CarrierBand::L1, CarrierBand::L2)),
419 GnssSystem::Galileo => Some(CarrierPair::new(CarrierBand::E1, CarrierBand::E5a)),
420 GnssSystem::BeiDou => Some(CarrierPair::new(CarrierBand::B1i, CarrierBand::B3i)),
421 _ => None,
422 }
423}
424
425pub const fn glonass_g1_frequency_hz(channel: i8) -> f64 {
435 F_GLONASS_G1_BASE_HZ + (channel as f64) * F_GLONASS_G1_STEP_HZ
436}
437
438pub const fn default_spp_carrier(system: GnssSystem) -> Option<CarrierBand> {
440 match system {
441 GnssSystem::Gps => Some(CarrierBand::L1),
442 GnssSystem::Galileo => Some(CarrierBand::E1),
443 GnssSystem::BeiDou => Some(CarrierBand::B1i),
444 _ => None,
445 }
446}
447
448pub const fn default_spp_frequency_hz(system: GnssSystem) -> Option<f64> {
450 match default_spp_carrier(system) {
451 Some(band) => frequency_hz(system, band),
452 None => None,
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn iono_free_table_matches_supported_pairs() {
462 assert_eq!(
463 default_iono_free_pair(GnssSystem::Gps),
464 Some(CarrierPair::new(CarrierBand::L1, CarrierBand::L2))
465 );
466 assert_eq!(
467 default_iono_free_pair(GnssSystem::Galileo),
468 Some(CarrierPair::new(CarrierBand::E1, CarrierBand::E5a))
469 );
470 assert_eq!(
471 default_iono_free_pair(GnssSystem::BeiDou),
472 Some(CarrierPair::new(CarrierBand::B1i, CarrierBand::B3i))
473 );
474 assert_eq!(default_iono_free_pair(GnssSystem::Glonass), None);
475 assert_eq!(iono_free_carrier_frequencies().len(), 6);
476 }
477
478 #[test]
479 fn rinex_band_table_matches_existing_frequency_bits() {
480 let cases: [(GnssSystem, char, Option<i8>, f64); 16] = [
481 (GnssSystem::Gps, '1', None, 1_575_420_000.0),
482 (GnssSystem::Gps, '2', None, 1_227_600_000.0),
483 (GnssSystem::Gps, '5', None, 1_176_450_000.0),
484 (GnssSystem::Qzss, '1', None, 1_575_420_000.0),
485 (GnssSystem::Qzss, '2', None, 1_227_600_000.0),
486 (GnssSystem::Qzss, '5', None, 1_176_450_000.0),
487 (GnssSystem::Galileo, '6', None, 1_278_750_000.0),
488 (GnssSystem::Galileo, '7', None, 1_207_140_000.0),
489 (GnssSystem::Galileo, '8', None, 1_191_795_000.0),
490 (GnssSystem::BeiDou, '1', None, 1_575_420_000.0),
491 (GnssSystem::BeiDou, '2', None, 1_561_098_000.0),
492 (GnssSystem::BeiDou, '6', None, 1_268_520_000.0),
493 (GnssSystem::BeiDou, '7', None, 1_207_140_000.0),
494 (GnssSystem::BeiDou, '8', None, 1_191_795_000.0),
495 (GnssSystem::Glonass, '1', Some(1), 1_602_562_500.0),
496 (GnssSystem::Glonass, '2', Some(1), 1_246_437_500.0),
497 ];
498 for (system, band, channel, expected) in cases {
499 assert_eq!(
500 rinex_band_frequency_hz(system, band, channel).map(f64::to_bits),
501 Some(expected.to_bits())
502 );
503 }
504 assert_eq!(
505 rinex_band_frequency_hz(GnssSystem::Glonass, '1', None),
506 None
507 );
508 }
509
510 #[test]
511 fn rinex_observation_code_resolves_beidou_302_b1i() {
512 for code in ["C1I", "L1I"] {
513 assert_eq!(
514 rinex_observation_frequency_hz(GnssSystem::BeiDou, code, 3.02, None)
515 .map(f64::to_bits),
516 Some(F_B1I_HZ.to_bits())
517 );
518 }
519 assert_eq!(
520 rinex_observation_frequency_hz(GnssSystem::BeiDou, "L1X", 3.03, None).map(f64::to_bits),
521 Some(F_L1_HZ.to_bits())
522 );
523 }
524
525 #[test]
526 fn wavelength_helpers_use_c_over_frequency() {
527 let wavelength = wavelength_m(GnssSystem::Gps, CarrierBand::L1).unwrap();
528 assert_eq!(wavelength.to_bits(), (C_M_S / F_L1_HZ).to_bits());
529
530 let glonass_wavelength = rinex_band_wavelength_m(GnssSystem::Glonass, '1', Some(-7))
531 .expect("GLONASS G1 channel");
532 let frequency = 1_602_000_000.0 + f64::from(-7) * 562_500.0;
533 assert_eq!(glonass_wavelength.to_bits(), (C_M_S / frequency).to_bits());
534 }
535
536 #[test]
537 fn glonass_g1_spp_carrier_matches_rinex_band_table() {
538 for channel in [-7_i8, -1, 0, 1, 6] {
539 assert_eq!(
540 glonass_g1_frequency_hz(channel).to_bits(),
541 rinex_band_frequency_hz(GnssSystem::Glonass, '1', Some(channel))
542 .expect("GLONASS G1 channel frequency")
543 .to_bits()
544 );
545 }
546 }
547
548 #[test]
549 fn frequency_validation_rejects_invalid_runtime_values() {
550 assert_eq!(valid_frequency_hz(f64::NAN), None);
551 assert_eq!(valid_frequency_hz(f64::INFINITY), None);
552 assert_eq!(valid_frequency_hz(0.0), None);
553 assert_eq!(valid_frequency_hz(-1.0), None);
554 assert_eq!(wavelength_for_frequency(f64::NAN), None);
555 }
556}