1use crate::astro::time::model::{GnssWeekTow, TimeScale};
17use crate::broadcast::{ClockPolynomial, KeplerianElements};
18use crate::constants::SECONDS_PER_HOUR;
19use crate::error::{Error, Result};
20use crate::id::{GnssSatelliteId, GnssSystem};
21use crate::rinex_nav::{
22 gps_fit_interval_from_flag, gps_ura_index_to_meters, BroadcastGroupDelays, BroadcastIssue,
23 BroadcastRecord, NavMessage,
24};
25
26use super::bits::{BitReader, BitWriter};
27use super::DecodeResult;
28
29const SEMICIRCLE_TO_RAD: f64 = core::f64::consts::PI;
30const GALILEO_WEEK_OFFSET_TO_GPS: u32 = 1024;
31
32fn scaled_i(value: impl Into<i64>, exponent: i32) -> f64 {
33 (value.into() as f64) * 2.0_f64.powi(exponent)
34}
35
36fn scaled_u(value: u64, exponent: i32) -> f64 {
37 (value as f64) * 2.0_f64.powi(exponent)
38}
39
40fn scaled_semicircle(value: impl Into<i64>, exponent: i32) -> f64 {
41 scaled_i(value, exponent) * SEMICIRCLE_TO_RAD
42}
43
44fn gnss_week_tow(
45 system: TimeScale,
46 week: u32,
47 tow_s: f64,
48 field: &'static str,
49) -> Result<GnssWeekTow> {
50 GnssWeekTow::new(system, week, tow_s)
51 .and_then(GnssWeekTow::normalized)
52 .map_err(|_| Error::InvalidInput(format!("RTCM broadcast {field} is not representable")))
53}
54
55fn galileo_sisa_m(index: u8) -> f64 {
56 match index {
57 0..=49 => f64::from(index) * 0.01,
58 50..=74 => 0.50 + f64::from(index - 50) * 0.02,
59 75..=99 => 1.00 + f64::from(index - 75) * 0.04,
60 100..=125 => 2.00 + f64::from(index - 100) * 0.16,
61 _ => 8192.0,
62 }
63}
64
65fn ura_or_wide(index: u8) -> f64 {
66 gps_ura_index_to_meters(i64::from(index)).unwrap_or(8192.0)
67}
68
69fn raw_health(healthy: bool) -> f64 {
70 if healthy {
71 0.0
72 } else {
73 1.0
74 }
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub struct GpsEphemeris {
84 pub satellite_id: u8,
86 pub week_number: u16,
88 pub sv_accuracy: u8,
90 pub code_on_l2: u8,
92 pub idot: i32,
94 pub iode: u8,
96 pub t_oc: u16,
98 pub a_f2: i16,
100 pub a_f1: i32,
102 pub a_f0: i32,
104 pub iodc: u16,
106 pub c_rs: i32,
108 pub delta_n: i32,
110 pub m0: i64,
112 pub c_uc: i32,
114 pub eccentricity: u64,
116 pub c_us: i32,
118 pub sqrt_a: u64,
120 pub t_oe: u16,
122 pub c_ic: i32,
124 pub omega0: i64,
126 pub c_is: i32,
128 pub i0: i64,
130 pub c_rc: i32,
132 pub omega: i64,
134 pub omega_dot: i32,
136 pub t_gd: i16,
138 pub sv_health: u8,
140 pub l2_p_data_flag: bool,
142 pub fit_interval: bool,
144}
145
146impl GpsEphemeris {
147 pub fn satellite(&self) -> Result<GnssSatelliteId> {
149 GnssSatelliteId::new(GnssSystem::Gps, self.satellite_id)
150 .map_err(|e| Error::Parse(format!("invalid GPS PRN in 1019: {e}")))
151 }
152
153 pub fn decode(body: &[u8]) -> Result<Self> {
155 Self::decode_inner(body).map_err(Into::into)
156 }
157
158 pub(crate) fn decode_inner(body: &[u8]) -> DecodeResult<Self> {
159 let mut r = BitReader::new(body);
160 let message_number = r.u(12)? as u16;
161 if message_number != 1019 {
162 return Err(Error::Parse(format!(
163 "message {message_number} is not GPS ephemeris 1019"
164 ))
165 .into());
166 }
167 Ok(Self {
168 satellite_id: r.u(6)? as u8,
169 week_number: r.u(10)? as u16,
170 sv_accuracy: r.u(4)? as u8,
171 code_on_l2: r.u(2)? as u8,
172 idot: r.i(14)? as i32,
173 iode: r.u(8)? as u8,
174 t_oc: r.u(16)? as u16,
175 a_f2: r.i(8)? as i16,
176 a_f1: r.i(16)? as i32,
177 a_f0: r.i(22)? as i32,
178 iodc: r.u(10)? as u16,
179 c_rs: r.i(16)? as i32,
180 delta_n: r.i(16)? as i32,
181 m0: r.i(32)?,
182 c_uc: r.i(16)? as i32,
183 eccentricity: r.u(32)?,
184 c_us: r.i(16)? as i32,
185 sqrt_a: r.u(32)?,
186 t_oe: r.u(16)? as u16,
187 c_ic: r.i(16)? as i32,
188 omega0: r.i(32)?,
189 c_is: r.i(16)? as i32,
190 i0: r.i(32)?,
191 c_rc: r.i(16)? as i32,
192 omega: r.i(32)?,
193 omega_dot: r.i(24)? as i32,
194 t_gd: r.i(8)? as i16,
195 sv_health: r.u(6)? as u8,
196 l2_p_data_flag: r.flag()?,
197 fit_interval: r.flag()?,
198 })
199 }
200
201 pub fn encode(&self) -> Vec<u8> {
203 let mut w = BitWriter::new();
204 w.push_u(1019, 12);
205 w.push_u(u64::from(self.satellite_id), 6);
206 w.push_u(u64::from(self.week_number), 10);
207 w.push_u(u64::from(self.sv_accuracy), 4);
208 w.push_u(u64::from(self.code_on_l2), 2);
209 w.push_i(i64::from(self.idot), 14);
210 w.push_u(u64::from(self.iode), 8);
211 w.push_u(u64::from(self.t_oc), 16);
212 w.push_i(i64::from(self.a_f2), 8);
213 w.push_i(i64::from(self.a_f1), 16);
214 w.push_i(i64::from(self.a_f0), 22);
215 w.push_u(u64::from(self.iodc), 10);
216 w.push_i(i64::from(self.c_rs), 16);
217 w.push_i(i64::from(self.delta_n), 16);
218 w.push_i(self.m0, 32);
219 w.push_i(i64::from(self.c_uc), 16);
220 w.push_u(self.eccentricity, 32);
221 w.push_i(i64::from(self.c_us), 16);
222 w.push_u(self.sqrt_a, 32);
223 w.push_u(u64::from(self.t_oe), 16);
224 w.push_i(i64::from(self.c_ic), 16);
225 w.push_i(self.omega0, 32);
226 w.push_i(i64::from(self.c_is), 16);
227 w.push_i(self.i0, 32);
228 w.push_i(i64::from(self.c_rc), 16);
229 w.push_i(self.omega, 32);
230 w.push_i(i64::from(self.omega_dot), 24);
231 w.push_i(i64::from(self.t_gd), 8);
232 w.push_u(u64::from(self.sv_health), 6);
233 w.push_flag(self.l2_p_data_flag);
234 w.push_flag(self.fit_interval);
235 w.into_bytes()
236 }
237
238 pub fn to_broadcast_record(&self, full_week: u32) -> Result<BroadcastRecord> {
242 if full_week % 1024 != u32::from(self.week_number) {
243 return Err(Error::InvalidInput(format!(
244 "GPS full week {full_week} disagrees with 10-bit RTCM week {}",
245 self.week_number
246 )));
247 }
248 let satellite_id = self.satellite()?;
249 let toe_sow = f64::from(self.t_oe) * 16.0;
250 let toc_sow = f64::from(self.t_oc) * 16.0;
251 let toe = gnss_week_tow(TimeScale::Gpst, full_week, toe_sow, "GPS toe")?;
252 let toc = gnss_week_tow(TimeScale::Gpst, full_week, toc_sow, "GPS toc")?;
253 let fit_interval_s = gps_fit_interval_from_flag(
254 i64::from(u8::from(self.fit_interval)),
255 i64::from(self.iode),
256 i64::from(self.iodc),
257 )
258 .map_err(|e| Error::InvalidInput(e.to_string()))?;
259 Ok(BroadcastRecord {
260 satellite_id,
261 message: NavMessage::GpsLnav,
262 issue_of_data: BroadcastIssue {
263 issue: u32::from(self.iode),
264 message: NavMessage::GpsLnav,
265 },
266 week: full_week,
267 toe,
268 toc,
269 elements: KeplerianElements {
270 sqrt_a: scaled_u(self.sqrt_a, -19),
271 e: scaled_u(self.eccentricity, -33),
272 m0: scaled_semicircle(self.m0, -31),
273 delta_n: scaled_semicircle(self.delta_n, -43),
274 omega0: scaled_semicircle(self.omega0, -31),
275 i0: scaled_semicircle(self.i0, -31),
276 omega: scaled_semicircle(self.omega, -31),
277 omega_dot: scaled_semicircle(self.omega_dot, -43),
278 idot: scaled_semicircle(self.idot, -43),
279 cuc: scaled_i(self.c_uc, -29),
280 cus: scaled_i(self.c_us, -29),
281 crc: scaled_i(self.c_rc, -5),
282 crs: scaled_i(self.c_rs, -5),
283 cic: scaled_i(self.c_ic, -29),
284 cis: scaled_i(self.c_is, -29),
285 toe_sow,
286 },
287 clock: ClockPolynomial {
288 af0: scaled_i(self.a_f0, -31),
289 af1: scaled_i(self.a_f1, -43),
290 af2: scaled_i(self.a_f2, -55),
291 toc_sow,
292 },
293 group_delays: BroadcastGroupDelays::gps_lnav(scaled_i(self.t_gd, -31)),
294 cnav: None,
295 sv_health: f64::from(self.sv_health),
296 sv_accuracy_m: ura_or_wide(self.sv_accuracy),
297 fit_interval_s: Some(fit_interval_s),
298 })
299 }
300}
301
302#[derive(Clone, Copy, Debug, PartialEq, Eq)]
304pub struct GalileoFnavEphemeris {
305 pub satellite_id: u8,
306 pub week_number: u16,
307 pub iod_nav: u16,
308 pub sisa: u8,
309 pub idot: i32,
310 pub t_oc: u16,
311 pub a_f2: i16,
312 pub a_f1: i32,
313 pub a_f0: i64,
314 pub c_rs: i32,
315 pub delta_n: i32,
316 pub m0: i64,
317 pub c_uc: i32,
318 pub eccentricity: u64,
319 pub c_us: i32,
320 pub sqrt_a: u64,
321 pub t_oe: u16,
322 pub c_ic: i32,
323 pub omega0: i64,
324 pub c_is: i32,
325 pub i0: i64,
326 pub c_rc: i32,
327 pub omega: i64,
328 pub omega_dot: i32,
329 pub bgd_e5a_e1: i16,
330 pub e5a_signal_health: u8,
331 pub e5a_data_validity: bool,
332 pub reserved: u8,
333}
334
335impl GalileoFnavEphemeris {
336 pub fn satellite(&self) -> Result<GnssSatelliteId> {
337 GnssSatelliteId::new(GnssSystem::Galileo, self.satellite_id)
338 .map_err(|e| Error::Parse(format!("invalid Galileo SVID in 1045: {e}")))
339 }
340
341 pub fn decode(body: &[u8]) -> Result<Self> {
342 Self::decode_inner(body).map_err(Into::into)
343 }
344
345 pub(crate) fn decode_inner(body: &[u8]) -> DecodeResult<Self> {
346 let mut r = BitReader::new(body);
347 let message_number = r.u(12)? as u16;
348 if message_number != 1045 {
349 return Err(Error::Parse(format!(
350 "message {message_number} is not Galileo F/NAV ephemeris 1045"
351 ))
352 .into());
353 }
354 Ok(Self {
355 satellite_id: r.u(6)? as u8,
356 week_number: r.u(12)? as u16,
357 iod_nav: r.u(10)? as u16,
358 sisa: r.u(8)? as u8,
359 idot: r.i(14)? as i32,
360 t_oc: r.u(14)? as u16,
361 a_f2: r.i(6)? as i16,
362 a_f1: r.i(21)? as i32,
363 a_f0: r.i(31)?,
364 c_rs: r.i(16)? as i32,
365 delta_n: r.i(16)? as i32,
366 m0: r.i(32)?,
367 c_uc: r.i(16)? as i32,
368 eccentricity: r.u(32)?,
369 c_us: r.i(16)? as i32,
370 sqrt_a: r.u(32)?,
371 t_oe: r.u(14)? as u16,
372 c_ic: r.i(16)? as i32,
373 omega0: r.i(32)?,
374 c_is: r.i(16)? as i32,
375 i0: r.i(32)?,
376 c_rc: r.i(16)? as i32,
377 omega: r.i(32)?,
378 omega_dot: r.i(24)? as i32,
379 bgd_e5a_e1: r.i(10)? as i16,
380 e5a_signal_health: r.u(2)? as u8,
381 e5a_data_validity: r.flag()?,
382 reserved: r.u(7)? as u8,
383 })
384 }
385
386 pub fn encode(&self) -> Vec<u8> {
387 let mut w = BitWriter::new();
388 w.push_u(1045, 12);
389 w.push_u(u64::from(self.satellite_id), 6);
390 w.push_u(u64::from(self.week_number), 12);
391 w.push_u(u64::from(self.iod_nav), 10);
392 w.push_u(u64::from(self.sisa), 8);
393 w.push_i(i64::from(self.idot), 14);
394 w.push_u(u64::from(self.t_oc), 14);
395 w.push_i(i64::from(self.a_f2), 6);
396 w.push_i(i64::from(self.a_f1), 21);
397 w.push_i(self.a_f0, 31);
398 w.push_i(i64::from(self.c_rs), 16);
399 w.push_i(i64::from(self.delta_n), 16);
400 w.push_i(self.m0, 32);
401 w.push_i(i64::from(self.c_uc), 16);
402 w.push_u(self.eccentricity, 32);
403 w.push_i(i64::from(self.c_us), 16);
404 w.push_u(self.sqrt_a, 32);
405 w.push_u(u64::from(self.t_oe), 14);
406 w.push_i(i64::from(self.c_ic), 16);
407 w.push_i(self.omega0, 32);
408 w.push_i(i64::from(self.c_is), 16);
409 w.push_i(self.i0, 32);
410 w.push_i(i64::from(self.c_rc), 16);
411 w.push_i(self.omega, 32);
412 w.push_i(i64::from(self.omega_dot), 24);
413 w.push_i(i64::from(self.bgd_e5a_e1), 10);
414 w.push_u(u64::from(self.e5a_signal_health), 2);
415 w.push_flag(self.e5a_data_validity);
416 w.push_u(u64::from(self.reserved), 7);
417 w.into_bytes()
418 }
419
420 pub fn to_broadcast_record(&self) -> Result<BroadcastRecord> {
421 galileo_to_record(
422 self.satellite()?,
423 u32::from(self.week_number),
424 self.iod_nav,
425 self.sisa,
426 self.idot,
427 self.t_oc,
428 self.a_f2,
429 self.a_f1,
430 self.a_f0,
431 self.c_rs,
432 self.delta_n,
433 self.m0,
434 self.c_uc,
435 self.eccentricity,
436 self.c_us,
437 self.sqrt_a,
438 self.t_oe,
439 self.c_ic,
440 self.omega0,
441 self.c_is,
442 self.i0,
443 self.c_rc,
444 self.omega,
445 self.omega_dot,
446 self.bgd_e5a_e1,
447 0,
448 raw_health(self.e5a_signal_health == 0 && !self.e5a_data_validity),
449 NavMessage::GalileoFnav,
450 )
451 }
452}
453
454#[derive(Clone, Copy, Debug, PartialEq, Eq)]
456pub struct GalileoInavEphemeris {
457 pub satellite_id: u8,
458 pub week_number: u16,
459 pub iod_nav: u16,
460 pub sisa_index: u8,
461 pub idot: i32,
462 pub t_oc: u16,
463 pub a_f2: i16,
464 pub a_f1: i32,
465 pub a_f0: i64,
466 pub c_rs: i32,
467 pub delta_n: i32,
468 pub m0: i64,
469 pub c_uc: i32,
470 pub eccentricity: u64,
471 pub c_us: i32,
472 pub sqrt_a: u64,
473 pub t_oe: u16,
474 pub c_ic: i32,
475 pub omega0: i64,
476 pub c_is: i32,
477 pub i0: i64,
478 pub c_rc: i32,
479 pub omega: i64,
480 pub omega_dot: i32,
481 pub bgd_e5a_e1: i16,
482 pub bgd_e5b_e1: i16,
483 pub e5b_signal_health: u8,
484 pub e5b_data_validity: bool,
485 pub e1b_signal_health: u8,
486 pub e1b_data_validity: bool,
487 pub reserved: u8,
488}
489
490impl GalileoInavEphemeris {
491 pub fn satellite(&self) -> Result<GnssSatelliteId> {
492 GnssSatelliteId::new(GnssSystem::Galileo, self.satellite_id)
493 .map_err(|e| Error::Parse(format!("invalid Galileo SVID in 1046: {e}")))
494 }
495
496 pub fn decode(body: &[u8]) -> Result<Self> {
497 Self::decode_inner(body).map_err(Into::into)
498 }
499
500 pub(crate) fn decode_inner(body: &[u8]) -> DecodeResult<Self> {
501 let mut r = BitReader::new(body);
502 let message_number = r.u(12)? as u16;
503 if message_number != 1046 {
504 return Err(Error::Parse(format!(
505 "message {message_number} is not Galileo I/NAV ephemeris 1046"
506 ))
507 .into());
508 }
509 Ok(Self {
510 satellite_id: r.u(6)? as u8,
511 week_number: r.u(12)? as u16,
512 iod_nav: r.u(10)? as u16,
513 sisa_index: r.u(8)? as u8,
514 idot: r.i(14)? as i32,
515 t_oc: r.u(14)? as u16,
516 a_f2: r.i(6)? as i16,
517 a_f1: r.i(21)? as i32,
518 a_f0: r.i(31)?,
519 c_rs: r.i(16)? as i32,
520 delta_n: r.i(16)? as i32,
521 m0: r.i(32)?,
522 c_uc: r.i(16)? as i32,
523 eccentricity: r.u(32)?,
524 c_us: r.i(16)? as i32,
525 sqrt_a: r.u(32)?,
526 t_oe: r.u(14)? as u16,
527 c_ic: r.i(16)? as i32,
528 omega0: r.i(32)?,
529 c_is: r.i(16)? as i32,
530 i0: r.i(32)?,
531 c_rc: r.i(16)? as i32,
532 omega: r.i(32)?,
533 omega_dot: r.i(24)? as i32,
534 bgd_e5a_e1: r.i(10)? as i16,
535 bgd_e5b_e1: r.i(10)? as i16,
536 e5b_signal_health: r.u(2)? as u8,
537 e5b_data_validity: r.flag()?,
538 e1b_signal_health: r.u(2)? as u8,
539 e1b_data_validity: r.flag()?,
540 reserved: r.u(2)? as u8,
541 })
542 }
543
544 pub fn encode(&self) -> Vec<u8> {
545 let mut w = BitWriter::new();
546 w.push_u(1046, 12);
547 w.push_u(u64::from(self.satellite_id), 6);
548 w.push_u(u64::from(self.week_number), 12);
549 w.push_u(u64::from(self.iod_nav), 10);
550 w.push_u(u64::from(self.sisa_index), 8);
551 w.push_i(i64::from(self.idot), 14);
552 w.push_u(u64::from(self.t_oc), 14);
553 w.push_i(i64::from(self.a_f2), 6);
554 w.push_i(i64::from(self.a_f1), 21);
555 w.push_i(self.a_f0, 31);
556 w.push_i(i64::from(self.c_rs), 16);
557 w.push_i(i64::from(self.delta_n), 16);
558 w.push_i(self.m0, 32);
559 w.push_i(i64::from(self.c_uc), 16);
560 w.push_u(self.eccentricity, 32);
561 w.push_i(i64::from(self.c_us), 16);
562 w.push_u(self.sqrt_a, 32);
563 w.push_u(u64::from(self.t_oe), 14);
564 w.push_i(i64::from(self.c_ic), 16);
565 w.push_i(self.omega0, 32);
566 w.push_i(i64::from(self.c_is), 16);
567 w.push_i(self.i0, 32);
568 w.push_i(i64::from(self.c_rc), 16);
569 w.push_i(self.omega, 32);
570 w.push_i(i64::from(self.omega_dot), 24);
571 w.push_i(i64::from(self.bgd_e5a_e1), 10);
572 w.push_i(i64::from(self.bgd_e5b_e1), 10);
573 w.push_u(u64::from(self.e5b_signal_health), 2);
574 w.push_flag(self.e5b_data_validity);
575 w.push_u(u64::from(self.e1b_signal_health), 2);
576 w.push_flag(self.e1b_data_validity);
577 w.push_u(u64::from(self.reserved), 2);
578 w.into_bytes()
579 }
580
581 pub fn to_broadcast_record(&self) -> Result<BroadcastRecord> {
582 galileo_to_record(
583 self.satellite()?,
584 u32::from(self.week_number),
585 self.iod_nav,
586 self.sisa_index,
587 self.idot,
588 self.t_oc,
589 self.a_f2,
590 self.a_f1,
591 self.a_f0,
592 self.c_rs,
593 self.delta_n,
594 self.m0,
595 self.c_uc,
596 self.eccentricity,
597 self.c_us,
598 self.sqrt_a,
599 self.t_oe,
600 self.c_ic,
601 self.omega0,
602 self.c_is,
603 self.i0,
604 self.c_rc,
605 self.omega,
606 self.omega_dot,
607 self.bgd_e5a_e1,
608 self.bgd_e5b_e1,
609 raw_health(
610 self.e1b_signal_health == 0
611 && !self.e1b_data_validity
612 && self.e5b_signal_health == 0
613 && !self.e5b_data_validity,
614 ),
615 NavMessage::GalileoInav,
616 )
617 }
618}
619
620#[allow(clippy::too_many_arguments)]
621fn galileo_to_record(
622 satellite_id: GnssSatelliteId,
623 week: u32,
624 iod_nav: u16,
625 sisa: u8,
626 idot: i32,
627 t_oc: u16,
628 a_f2: i16,
629 a_f1: i32,
630 a_f0: i64,
631 c_rs: i32,
632 delta_n: i32,
633 m0: i64,
634 c_uc: i32,
635 eccentricity: u64,
636 c_us: i32,
637 sqrt_a: u64,
638 t_oe: u16,
639 c_ic: i32,
640 omega0: i64,
641 c_is: i32,
642 i0: i64,
643 c_rc: i32,
644 omega: i64,
645 omega_dot: i32,
646 bgd_e5a_e1: i16,
647 bgd_e5b_e1: i16,
648 sv_health: f64,
649 message: NavMessage,
650) -> Result<BroadcastRecord> {
651 let toe_sow = f64::from(t_oe) * 60.0;
652 let toc_sow = f64::from(t_oc) * 60.0;
653 let gps_aligned_week = week
654 .checked_add(GALILEO_WEEK_OFFSET_TO_GPS)
655 .ok_or_else(|| Error::InvalidInput("RTCM Galileo week overflows GPST axis".to_string()))?;
656 let toe = gnss_week_tow(TimeScale::Gst, gps_aligned_week, toe_sow, "Galileo toe")?;
657 let toc = gnss_week_tow(TimeScale::Gst, gps_aligned_week, toc_sow, "Galileo toc")?;
658 Ok(BroadcastRecord {
659 satellite_id,
660 message,
661 issue_of_data: BroadcastIssue {
662 issue: u32::from(iod_nav),
663 message,
664 },
665 week: gps_aligned_week,
666 toe,
667 toc,
668 elements: KeplerianElements {
669 sqrt_a: scaled_u(sqrt_a, -19),
670 e: scaled_u(eccentricity, -33),
671 m0: scaled_semicircle(m0, -31),
672 delta_n: scaled_semicircle(delta_n, -43),
673 omega0: scaled_semicircle(omega0, -31),
674 i0: scaled_semicircle(i0, -31),
675 omega: scaled_semicircle(omega, -31),
676 omega_dot: scaled_semicircle(omega_dot, -43),
677 idot: scaled_semicircle(idot, -43),
678 cuc: scaled_i(c_uc, -29),
679 cus: scaled_i(c_us, -29),
680 crc: scaled_i(c_rc, -5),
681 crs: scaled_i(c_rs, -5),
682 cic: scaled_i(c_ic, -29),
683 cis: scaled_i(c_is, -29),
684 toe_sow,
685 },
686 clock: ClockPolynomial {
687 af0: scaled_i(a_f0, -34),
688 af1: scaled_i(a_f1, -46),
689 af2: scaled_i(a_f2, -59),
690 toc_sow,
691 },
692 group_delays: BroadcastGroupDelays::galileo(
693 scaled_i(bgd_e5a_e1, -32),
694 scaled_i(bgd_e5b_e1, -32),
695 ),
696 cnav: None,
697 sv_health,
698 sv_accuracy_m: galileo_sisa_m(sisa),
699 fit_interval_s: None,
700 })
701}
702
703#[derive(Clone, Copy, Debug, PartialEq, Eq)]
705pub struct BeidouEphemeris {
706 pub satellite_id: u8,
707 pub week_number: u16,
708 pub sv_urai: u8,
709 pub idot: i32,
710 pub aode: u8,
711 pub t_oc: u32,
712 pub a_f2: i16,
713 pub a_f1: i32,
714 pub a_f0: i32,
715 pub aodc: u8,
716 pub c_rs: i32,
717 pub delta_n: i32,
718 pub m0: i64,
719 pub c_uc: i32,
720 pub eccentricity: u64,
721 pub c_us: i32,
722 pub sqrt_a: u64,
723 pub t_oe: u32,
724 pub c_ic: i32,
725 pub omega0: i64,
726 pub c_is: i32,
727 pub i0: i64,
728 pub c_rc: i32,
729 pub omega: i64,
730 pub omega_dot: i32,
731 pub t_gd1: i16,
732 pub t_gd2: i16,
733 pub sv_health: bool,
734}
735
736impl BeidouEphemeris {
737 pub fn satellite(&self) -> Result<GnssSatelliteId> {
738 GnssSatelliteId::new(GnssSystem::BeiDou, self.satellite_id)
739 .map_err(|e| Error::Parse(format!("invalid BeiDou satellite ID in 1042: {e}")))
740 }
741
742 pub fn decode(body: &[u8]) -> Result<Self> {
743 Self::decode_inner(body).map_err(Into::into)
744 }
745
746 pub(crate) fn decode_inner(body: &[u8]) -> DecodeResult<Self> {
747 let mut r = BitReader::new(body);
748 let message_number = r.u(12)? as u16;
749 if message_number != 1042 {
750 return Err(Error::Parse(format!(
751 "message {message_number} is not BeiDou ephemeris 1042"
752 ))
753 .into());
754 }
755 Ok(Self {
756 satellite_id: r.u(6)? as u8,
757 week_number: r.u(13)? as u16,
758 sv_urai: r.u(4)? as u8,
759 idot: r.i(14)? as i32,
760 aode: r.u(5)? as u8,
761 t_oc: r.u(17)? as u32,
762 a_f2: r.i(11)? as i16,
763 a_f1: r.i(22)? as i32,
764 a_f0: r.i(24)? as i32,
765 aodc: r.u(5)? as u8,
766 c_rs: r.i(18)? as i32,
767 delta_n: r.i(16)? as i32,
768 m0: r.i(32)?,
769 c_uc: r.i(18)? as i32,
770 eccentricity: r.u(32)?,
771 c_us: r.i(18)? as i32,
772 sqrt_a: r.u(32)?,
773 t_oe: r.u(17)? as u32,
774 c_ic: r.i(18)? as i32,
775 omega0: r.i(32)?,
776 c_is: r.i(18)? as i32,
777 i0: r.i(32)?,
778 c_rc: r.i(18)? as i32,
779 omega: r.i(32)?,
780 omega_dot: r.i(24)? as i32,
781 t_gd1: r.i(10)? as i16,
782 t_gd2: r.i(10)? as i16,
783 sv_health: r.flag()?,
784 })
785 }
786
787 pub fn encode(&self) -> Vec<u8> {
788 let mut w = BitWriter::new();
789 w.push_u(1042, 12);
790 w.push_u(u64::from(self.satellite_id), 6);
791 w.push_u(u64::from(self.week_number), 13);
792 w.push_u(u64::from(self.sv_urai), 4);
793 w.push_i(i64::from(self.idot), 14);
794 w.push_u(u64::from(self.aode), 5);
795 w.push_u(u64::from(self.t_oc), 17);
796 w.push_i(i64::from(self.a_f2), 11);
797 w.push_i(i64::from(self.a_f1), 22);
798 w.push_i(i64::from(self.a_f0), 24);
799 w.push_u(u64::from(self.aodc), 5);
800 w.push_i(i64::from(self.c_rs), 18);
801 w.push_i(i64::from(self.delta_n), 16);
802 w.push_i(self.m0, 32);
803 w.push_i(i64::from(self.c_uc), 18);
804 w.push_u(self.eccentricity, 32);
805 w.push_i(i64::from(self.c_us), 18);
806 w.push_u(self.sqrt_a, 32);
807 w.push_u(u64::from(self.t_oe), 17);
808 w.push_i(i64::from(self.c_ic), 18);
809 w.push_i(self.omega0, 32);
810 w.push_i(i64::from(self.c_is), 18);
811 w.push_i(self.i0, 32);
812 w.push_i(i64::from(self.c_rc), 18);
813 w.push_i(self.omega, 32);
814 w.push_i(i64::from(self.omega_dot), 24);
815 w.push_i(i64::from(self.t_gd1), 10);
816 w.push_i(i64::from(self.t_gd2), 10);
817 w.push_flag(self.sv_health);
818 w.into_bytes()
819 }
820
821 pub fn to_broadcast_record(&self) -> Result<BroadcastRecord> {
822 let satellite_id = self.satellite()?;
823 let week = u32::from(self.week_number);
824 let toe_sow = f64::from(self.t_oe) * 8.0;
825 let toc_sow = f64::from(self.t_oc) * 8.0;
826 let toe = gnss_week_tow(TimeScale::Bdt, week, toe_sow, "BeiDou toe")?;
827 let toc = gnss_week_tow(TimeScale::Bdt, week, toc_sow, "BeiDou toc")?;
828 let message = if crate::rinex_nav::is_beidou_geo(satellite_id) {
829 NavMessage::BeidouD2
830 } else {
831 NavMessage::BeidouD1
832 };
833 Ok(BroadcastRecord {
834 satellite_id,
835 message,
836 issue_of_data: BroadcastIssue {
837 issue: u32::from(self.aode),
838 message,
839 },
840 week,
841 toe,
842 toc,
843 elements: KeplerianElements {
844 sqrt_a: scaled_u(self.sqrt_a, -19),
845 e: scaled_u(self.eccentricity, -33),
846 m0: scaled_semicircle(self.m0, -31),
847 delta_n: scaled_semicircle(self.delta_n, -43),
848 omega0: scaled_semicircle(self.omega0, -31),
849 i0: scaled_semicircle(self.i0, -31),
850 omega: scaled_semicircle(self.omega, -31),
851 omega_dot: scaled_semicircle(self.omega_dot, -43),
852 idot: scaled_semicircle(self.idot, -43),
853 cuc: scaled_i(self.c_uc, -31),
854 cus: scaled_i(self.c_us, -31),
855 crc: scaled_i(self.c_rc, -6),
856 crs: scaled_i(self.c_rs, -6),
857 cic: scaled_i(self.c_ic, -31),
858 cis: scaled_i(self.c_is, -31),
859 toe_sow,
860 },
861 clock: ClockPolynomial {
862 af0: scaled_i(self.a_f0, -33),
863 af1: scaled_i(self.a_f1, -50),
864 af2: scaled_i(self.a_f2, -66),
865 toc_sow,
866 },
867 group_delays: BroadcastGroupDelays::beidou(
868 f64::from(self.t_gd1) * 1.0e-10,
869 f64::from(self.t_gd2) * 1.0e-10,
870 ),
871 cnav: None,
872 sv_health: f64::from(u8::from(self.sv_health)),
873 sv_accuracy_m: ura_or_wide(self.sv_urai),
874 fit_interval_s: None,
875 })
876 }
877}
878
879#[derive(Clone, Copy, Debug, PartialEq, Eq)]
881pub struct QzssEphemeris {
882 pub satellite_id: u8,
883 pub t_oc: u16,
884 pub a_f2: i16,
885 pub a_f1: i32,
886 pub a_f0: i32,
887 pub iode: u8,
888 pub c_rs: i32,
889 pub delta_n: i32,
890 pub m0: i64,
891 pub c_uc: i32,
892 pub eccentricity: u64,
893 pub c_us: i32,
894 pub sqrt_a: u64,
895 pub t_oe: u16,
896 pub c_ic: i32,
897 pub omega0: i64,
898 pub c_is: i32,
899 pub i0: i64,
900 pub c_rc: i32,
901 pub omega: i64,
902 pub omega_dot: i32,
903 pub idot: i32,
904 pub codes_on_l2: u8,
905 pub week_number: u16,
906 pub ura: u8,
907 pub sv_health: u8,
908 pub t_gd: i16,
909 pub iodc: u16,
910 pub fit_interval: bool,
911}
912
913impl QzssEphemeris {
914 pub fn satellite(&self) -> Result<GnssSatelliteId> {
915 GnssSatelliteId::new(GnssSystem::Qzss, self.satellite_id)
916 .map_err(|e| Error::Parse(format!("invalid QZSS satellite ID in 1044: {e}")))
917 }
918
919 pub fn decode(body: &[u8]) -> Result<Self> {
920 Self::decode_inner(body).map_err(Into::into)
921 }
922
923 pub(crate) fn decode_inner(body: &[u8]) -> DecodeResult<Self> {
924 let mut r = BitReader::new(body);
925 let message_number = r.u(12)? as u16;
926 if message_number != 1044 {
927 return Err(Error::Parse(format!(
928 "message {message_number} is not QZSS ephemeris 1044"
929 ))
930 .into());
931 }
932 Ok(Self {
933 satellite_id: r.u(4)? as u8,
934 t_oc: r.u(16)? as u16,
935 a_f2: r.i(8)? as i16,
936 a_f1: r.i(16)? as i32,
937 a_f0: r.i(22)? as i32,
938 iode: r.u(8)? as u8,
939 c_rs: r.i(16)? as i32,
940 delta_n: r.i(16)? as i32,
941 m0: r.i(32)?,
942 c_uc: r.i(16)? as i32,
943 eccentricity: r.u(32)?,
944 c_us: r.i(16)? as i32,
945 sqrt_a: r.u(32)?,
946 t_oe: r.u(16)? as u16,
947 c_ic: r.i(16)? as i32,
948 omega0: r.i(32)?,
949 c_is: r.i(16)? as i32,
950 i0: r.i(32)?,
951 c_rc: r.i(16)? as i32,
952 omega: r.i(32)?,
953 omega_dot: r.i(24)? as i32,
954 idot: r.i(14)? as i32,
955 codes_on_l2: r.u(2)? as u8,
956 week_number: r.u(10)? as u16,
957 ura: r.u(4)? as u8,
958 sv_health: r.u(6)? as u8,
959 t_gd: r.i(8)? as i16,
960 iodc: r.u(10)? as u16,
961 fit_interval: r.flag()?,
962 })
963 }
964
965 pub fn encode(&self) -> Vec<u8> {
966 let mut w = BitWriter::new();
967 w.push_u(1044, 12);
968 w.push_u(u64::from(self.satellite_id), 4);
969 w.push_u(u64::from(self.t_oc), 16);
970 w.push_i(i64::from(self.a_f2), 8);
971 w.push_i(i64::from(self.a_f1), 16);
972 w.push_i(i64::from(self.a_f0), 22);
973 w.push_u(u64::from(self.iode), 8);
974 w.push_i(i64::from(self.c_rs), 16);
975 w.push_i(i64::from(self.delta_n), 16);
976 w.push_i(self.m0, 32);
977 w.push_i(i64::from(self.c_uc), 16);
978 w.push_u(self.eccentricity, 32);
979 w.push_i(i64::from(self.c_us), 16);
980 w.push_u(self.sqrt_a, 32);
981 w.push_u(u64::from(self.t_oe), 16);
982 w.push_i(i64::from(self.c_ic), 16);
983 w.push_i(self.omega0, 32);
984 w.push_i(i64::from(self.c_is), 16);
985 w.push_i(self.i0, 32);
986 w.push_i(i64::from(self.c_rc), 16);
987 w.push_i(self.omega, 32);
988 w.push_i(i64::from(self.omega_dot), 24);
989 w.push_i(i64::from(self.idot), 14);
990 w.push_u(u64::from(self.codes_on_l2), 2);
991 w.push_u(u64::from(self.week_number), 10);
992 w.push_u(u64::from(self.ura), 4);
993 w.push_u(u64::from(self.sv_health), 6);
994 w.push_i(i64::from(self.t_gd), 8);
995 w.push_u(u64::from(self.iodc), 10);
996 w.push_flag(self.fit_interval);
997 w.into_bytes()
998 }
999
1000 pub fn to_broadcast_record(&self, full_week: u32) -> Result<BroadcastRecord> {
1001 if full_week % 1024 != u32::from(self.week_number) {
1002 return Err(Error::InvalidInput(format!(
1003 "QZSS full week {full_week} disagrees with 10-bit RTCM week {}",
1004 self.week_number
1005 )));
1006 }
1007 let satellite_id = self.satellite()?;
1008 let toe_sow = f64::from(self.t_oe) * 16.0;
1009 let toc_sow = f64::from(self.t_oc) * 16.0;
1010 let toe = gnss_week_tow(TimeScale::Gpst, full_week, toe_sow, "QZSS toe")?;
1011 let toc = gnss_week_tow(TimeScale::Gpst, full_week, toc_sow, "QZSS toc")?;
1012 Ok(BroadcastRecord {
1013 satellite_id,
1014 message: NavMessage::QzssLnav,
1015 issue_of_data: BroadcastIssue {
1016 issue: u32::from(self.iode),
1017 message: NavMessage::QzssLnav,
1018 },
1019 week: full_week,
1020 toe,
1021 toc,
1022 elements: KeplerianElements {
1023 sqrt_a: scaled_u(self.sqrt_a, -19),
1024 e: scaled_u(self.eccentricity, -33),
1025 m0: scaled_semicircle(self.m0, -31),
1026 delta_n: scaled_semicircle(self.delta_n, -43),
1027 omega0: scaled_semicircle(self.omega0, -31),
1028 i0: scaled_semicircle(self.i0, -31),
1029 omega: scaled_semicircle(self.omega, -31),
1030 omega_dot: scaled_semicircle(self.omega_dot, -43),
1031 idot: scaled_semicircle(self.idot, -43),
1032 cuc: scaled_i(self.c_uc, -29),
1033 cus: scaled_i(self.c_us, -29),
1034 crc: scaled_i(self.c_rc, -5),
1035 crs: scaled_i(self.c_rs, -5),
1036 cic: scaled_i(self.c_ic, -29),
1037 cis: scaled_i(self.c_is, -29),
1038 toe_sow,
1039 },
1040 clock: ClockPolynomial {
1041 af0: scaled_i(self.a_f0, -31),
1042 af1: scaled_i(self.a_f1, -43),
1043 af2: scaled_i(self.a_f2, -55),
1044 toc_sow,
1045 },
1046 group_delays: BroadcastGroupDelays::gps_lnav(scaled_i(self.t_gd, -31)),
1047 cnav: None,
1048 sv_health: f64::from(self.sv_health),
1049 sv_accuracy_m: ura_or_wide(self.ura),
1050 fit_interval_s: Some(if self.fit_interval {
1051 6.0 * SECONDS_PER_HOUR
1052 } else {
1053 2.0 * SECONDS_PER_HOUR
1054 }),
1055 })
1056 }
1057}
1058
1059#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1065pub struct GlonassEphemeris {
1066 pub satellite_id: u8,
1068 pub frequency_channel: u8,
1070 pub almanac_health: bool,
1072 pub almanac_health_availability: bool,
1074 pub p1: u8,
1076 pub t_k: u16,
1078 pub b_n_msb: bool,
1080 pub p2: bool,
1082 pub t_b: u8,
1084 pub xn_dot: i32,
1086 pub xn: i32,
1088 pub xn_dot_dot: i8,
1090 pub yn_dot: i32,
1092 pub yn: i32,
1094 pub yn_dot_dot: i8,
1096 pub zn_dot: i32,
1098 pub zn: i32,
1100 pub zn_dot_dot: i8,
1102 pub p3: bool,
1104 pub gamma_n: i16,
1107 pub m_p: u8,
1109 pub m_l_n_third: bool,
1111 pub tau_n: i32,
1113 pub delta_tau_n: i8,
1116 pub e_n: u8,
1118 pub m_p4: bool,
1120 pub m_f_t: u8,
1122 pub m_n_t: u16,
1124 pub m_m: u8,
1126 pub additional_data_available: bool,
1128 pub n_a: u16,
1130 pub tau_c: i64,
1133 pub m_n4: u8,
1135 pub m_tau_gps: i32,
1138 pub m_l_n_fifth: bool,
1140 pub reserved: u8,
1142}
1143
1144impl GlonassEphemeris {
1145 pub fn satellite(&self) -> Result<GnssSatelliteId> {
1147 GnssSatelliteId::new(GnssSystem::Glonass, self.satellite_id)
1148 .map_err(|e| Error::Parse(format!("invalid GLONASS slot in 1020: {e}")))
1149 }
1150
1151 pub fn decode(body: &[u8]) -> Result<Self> {
1153 Self::decode_inner(body).map_err(Into::into)
1154 }
1155
1156 pub(crate) fn decode_inner(body: &[u8]) -> DecodeResult<Self> {
1157 let mut r = BitReader::new(body);
1158 let message_number = r.u(12)? as u16;
1159 if message_number != 1020 {
1160 return Err(Error::Parse(format!(
1161 "message {message_number} is not GLONASS ephemeris 1020"
1162 ))
1163 .into());
1164 }
1165 Ok(Self {
1166 satellite_id: r.u(6)? as u8,
1167 frequency_channel: r.u(5)? as u8,
1168 almanac_health: r.flag()?,
1169 almanac_health_availability: r.flag()?,
1170 p1: r.u(2)? as u8,
1171 t_k: r.u(12)? as u16,
1172 b_n_msb: r.flag()?,
1173 p2: r.flag()?,
1174 t_b: r.u(7)? as u8,
1175 xn_dot: r.ism(24)? as i32,
1176 xn: r.ism(27)? as i32,
1177 xn_dot_dot: r.ism(5)? as i8,
1178 yn_dot: r.ism(24)? as i32,
1179 yn: r.ism(27)? as i32,
1180 yn_dot_dot: r.ism(5)? as i8,
1181 zn_dot: r.ism(24)? as i32,
1182 zn: r.ism(27)? as i32,
1183 zn_dot_dot: r.ism(5)? as i8,
1184 p3: r.flag()?,
1185 gamma_n: r.ism(11)? as i16,
1186 m_p: r.u(2)? as u8,
1187 m_l_n_third: r.flag()?,
1188 tau_n: r.ism(22)? as i32,
1189 delta_tau_n: r.ism(5)? as i8,
1190 e_n: r.u(5)? as u8,
1191 m_p4: r.flag()?,
1192 m_f_t: r.u(4)? as u8,
1193 m_n_t: r.u(11)? as u16,
1194 m_m: r.u(2)? as u8,
1195 additional_data_available: r.flag()?,
1196 n_a: r.u(11)? as u16,
1197 tau_c: r.ism(32)?,
1198 m_n4: r.u(5)? as u8,
1199 m_tau_gps: r.ism(22)? as i32,
1200 m_l_n_fifth: r.flag()?,
1201 reserved: r.u(7)? as u8,
1202 })
1203 }
1204
1205 pub fn encode(&self) -> Vec<u8> {
1207 let mut w = BitWriter::new();
1208 w.push_u(1020, 12);
1209 w.push_u(u64::from(self.satellite_id), 6);
1210 w.push_u(u64::from(self.frequency_channel), 5);
1211 w.push_flag(self.almanac_health);
1212 w.push_flag(self.almanac_health_availability);
1213 w.push_u(u64::from(self.p1), 2);
1214 w.push_u(u64::from(self.t_k), 12);
1215 w.push_flag(self.b_n_msb);
1216 w.push_flag(self.p2);
1217 w.push_u(u64::from(self.t_b), 7);
1218 w.push_ism(i64::from(self.xn_dot), 24);
1219 w.push_ism(i64::from(self.xn), 27);
1220 w.push_ism(i64::from(self.xn_dot_dot), 5);
1221 w.push_ism(i64::from(self.yn_dot), 24);
1222 w.push_ism(i64::from(self.yn), 27);
1223 w.push_ism(i64::from(self.yn_dot_dot), 5);
1224 w.push_ism(i64::from(self.zn_dot), 24);
1225 w.push_ism(i64::from(self.zn), 27);
1226 w.push_ism(i64::from(self.zn_dot_dot), 5);
1227 w.push_flag(self.p3);
1228 w.push_ism(i64::from(self.gamma_n), 11);
1229 w.push_u(u64::from(self.m_p), 2);
1230 w.push_flag(self.m_l_n_third);
1231 w.push_ism(i64::from(self.tau_n), 22);
1232 w.push_ism(i64::from(self.delta_tau_n), 5);
1233 w.push_u(u64::from(self.e_n), 5);
1234 w.push_flag(self.m_p4);
1235 w.push_u(u64::from(self.m_f_t), 4);
1236 w.push_u(u64::from(self.m_n_t), 11);
1237 w.push_u(u64::from(self.m_m), 2);
1238 w.push_flag(self.additional_data_available);
1239 w.push_u(u64::from(self.n_a), 11);
1240 w.push_ism(self.tau_c, 32);
1241 w.push_u(u64::from(self.m_n4), 5);
1242 w.push_ism(i64::from(self.m_tau_gps), 22);
1243 w.push_flag(self.m_l_n_fifth);
1244 w.push_u(u64::from(self.reserved), 7);
1245 w.into_bytes()
1246 }
1247}