1use crate::error::{SbfError, SbfResult};
4use crate::header::SbfHeader;
5use crate::types::{PvtError, PvtMode};
6
7use super::block_ids;
8use super::dnu::{u16_or_none, u8_or_none, F32_DNU, F64_DNU, I16_DNU, U16_DNU};
9use super::SbfBlockParse;
10
11fn num_satellites_or_zero(nr_sv: u8) -> u8 {
16 u8_or_none(nr_sv).unwrap_or(0)
17}
18
19fn dop_or_none(raw: u16) -> Option<f32> {
20 u16_or_none(raw)
21 .filter(|&raw| raw != 0)
22 .map(|raw| raw as f32 * 0.01)
23}
24
25#[derive(Debug, Clone)]
33#[allow(dead_code)]
34pub struct PvtGeodeticBlock {
35 tow_ms: u32,
37 wnc: u16,
39 mode: u8,
41 error: u8,
43 latitude_rad: f64,
45 longitude_rad: f64,
47 height_m: f64,
49 undulation_m: f32,
51 vn_mps: f32,
53 ve_mps: f32,
55 vu_mps: f32,
57 cog_deg: f32,
59 rx_clk_bias_ms: f64,
61 rx_clk_drift_ppm: f32,
63 pub time_system: u8,
65 pub datum: u8,
67 nr_sv: u8,
69 pub wa_corr_info: u8,
71 pub reference_id: u16,
73 mean_corr_age_raw: u16,
75 pub signal_info: u32,
77 pub alert_flag: u8,
79 pub nr_bases: u8,
81 pub ppp_info: u16,
83 latency_raw: u16,
85 h_accuracy_raw: u16,
87 v_accuracy_raw: u16,
89}
90
91impl PvtGeodeticBlock {
92 pub fn tow_seconds(&self) -> f64 {
94 self.tow_ms as f64 * 0.001
95 }
96 pub fn tow_ms(&self) -> u32 {
97 self.tow_ms
98 }
99 pub fn wnc(&self) -> u16 {
100 self.wnc
101 }
102
103 pub fn mode(&self) -> PvtMode {
105 PvtMode::from_mode_byte(self.mode)
106 }
107 pub fn mode_raw(&self) -> u8 {
108 self.mode
109 }
110 pub fn error(&self) -> PvtError {
111 PvtError::from_error_byte(self.error)
112 }
113 pub fn error_raw(&self) -> u8 {
114 self.error
115 }
116 pub fn has_fix(&self) -> bool {
117 self.mode().has_fix() && self.error().is_ok()
118 }
119
120 pub fn latitude_deg(&self) -> Option<f64> {
122 if self.latitude_rad == F64_DNU {
123 None
124 } else {
125 Some(self.latitude_rad.to_degrees())
126 }
127 }
128 pub fn longitude_deg(&self) -> Option<f64> {
129 if self.longitude_rad == F64_DNU {
130 None
131 } else {
132 Some(self.longitude_rad.to_degrees())
133 }
134 }
135 pub fn height_m(&self) -> Option<f64> {
136 if self.height_m == F64_DNU {
137 None
138 } else {
139 Some(self.height_m)
140 }
141 }
142 pub fn undulation_m(&self) -> Option<f32> {
143 if self.undulation_m == F32_DNU {
144 None
145 } else {
146 Some(self.undulation_m)
147 }
148 }
149
150 pub fn latitude_rad(&self) -> f64 {
152 self.latitude_rad
153 }
154 pub fn longitude_rad(&self) -> f64 {
155 self.longitude_rad
156 }
157
158 pub fn velocity_north_mps(&self) -> Option<f32> {
160 if self.vn_mps == F32_DNU {
161 None
162 } else {
163 Some(self.vn_mps)
164 }
165 }
166 pub fn velocity_east_mps(&self) -> Option<f32> {
167 if self.ve_mps == F32_DNU {
168 None
169 } else {
170 Some(self.ve_mps)
171 }
172 }
173 pub fn velocity_up_mps(&self) -> Option<f32> {
174 if self.vu_mps == F32_DNU {
175 None
176 } else {
177 Some(self.vu_mps)
178 }
179 }
180 pub fn course_over_ground_deg(&self) -> Option<f32> {
181 if self.cog_deg == F32_DNU {
182 None
183 } else {
184 Some(self.cog_deg)
185 }
186 }
187
188 pub fn clock_bias_ms(&self) -> Option<f64> {
190 if self.rx_clk_bias_ms == F64_DNU {
191 None
192 } else {
193 Some(self.rx_clk_bias_ms)
194 }
195 }
196 pub fn clock_drift_ppm(&self) -> Option<f32> {
197 if self.rx_clk_drift_ppm == F32_DNU {
198 None
199 } else {
200 Some(self.rx_clk_drift_ppm)
201 }
202 }
203
204 pub fn num_satellites(&self) -> u8 {
210 num_satellites_or_zero(self.nr_sv)
211 }
212 pub fn num_satellites_opt(&self) -> Option<u8> {
214 u8_or_none(self.nr_sv)
215 }
216 pub fn num_satellites_raw(&self) -> u8 {
218 self.nr_sv
219 }
220
221 pub fn h_accuracy_m(&self) -> Option<f32> {
223 if self.h_accuracy_raw == U16_DNU {
224 None
225 } else {
226 Some(self.h_accuracy_raw as f32 * 0.01)
227 }
228 }
229 pub fn v_accuracy_m(&self) -> Option<f32> {
230 if self.v_accuracy_raw == U16_DNU {
231 None
232 } else {
233 Some(self.v_accuracy_raw as f32 * 0.01)
234 }
235 }
236
237 pub fn h_accuracy_raw(&self) -> u16 {
239 self.h_accuracy_raw
240 }
241 pub fn v_accuracy_raw(&self) -> u16 {
242 self.v_accuracy_raw
243 }
244
245 pub fn mean_corr_age_seconds(&self) -> Option<f32> {
247 if self.mean_corr_age_raw == U16_DNU {
248 None
249 } else {
250 Some(self.mean_corr_age_raw as f32 * 0.01)
251 }
252 }
253}
254
255impl SbfBlockParse for PvtGeodeticBlock {
256 const BLOCK_ID: u16 = block_ids::PVT_GEODETIC;
257
258 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
259 if data.len() < 83 {
260 return Err(SbfError::ParseError("PVTGeodetic too short".into()));
261 }
262
263 let mode = data[12];
287 let error = data[13];
288
289 let latitude_rad = f64::from_le_bytes(data[14..22].try_into().unwrap());
290 let longitude_rad = f64::from_le_bytes(data[22..30].try_into().unwrap());
291 let height_m = f64::from_le_bytes(data[30..38].try_into().unwrap());
292 let undulation_m = f32::from_le_bytes(data[38..42].try_into().unwrap());
293
294 let vn_mps = f32::from_le_bytes(data[42..46].try_into().unwrap());
295 let ve_mps = f32::from_le_bytes(data[46..50].try_into().unwrap());
296 let vu_mps = f32::from_le_bytes(data[50..54].try_into().unwrap());
297 let cog_deg = f32::from_le_bytes(data[54..58].try_into().unwrap());
298
299 let rx_clk_bias_ms = f64::from_le_bytes(data[58..66].try_into().unwrap());
300 let rx_clk_drift_ppm = f32::from_le_bytes(data[66..70].try_into().unwrap());
301
302 let time_system = data[70];
303 let datum = data[71];
304 let nr_sv = data[72];
305 let wa_corr_info = data[73];
306 let reference_id = u16::from_le_bytes([data[74], data[75]]);
307 let mean_corr_age_raw = u16::from_le_bytes([data[76], data[77]]);
308 let signal_info = u32::from_le_bytes(data[78..82].try_into().unwrap());
309 let alert_flag = data[82];
310
311 let (nr_bases, ppp_info, latency_raw, h_accuracy_raw, v_accuracy_raw) =
313 if header.block_rev >= 1 && data.len() >= 92 {
314 (
315 data[83],
316 u16::from_le_bytes([data[84], data[85]]),
317 u16::from_le_bytes([data[86], data[87]]),
318 u16::from_le_bytes([data[88], data[89]]),
319 u16::from_le_bytes([data[90], data[91]]),
320 )
321 } else {
322 (0, 0, 0, U16_DNU, U16_DNU)
323 };
324
325 Ok(Self {
326 tow_ms: header.tow_ms,
327 wnc: header.wnc,
328 mode,
329 error,
330 latitude_rad,
331 longitude_rad,
332 height_m,
333 undulation_m,
334 vn_mps,
335 ve_mps,
336 vu_mps,
337 cog_deg,
338 rx_clk_bias_ms,
339 rx_clk_drift_ppm,
340 time_system,
341 datum,
342 nr_sv,
343 wa_corr_info,
344 reference_id,
345 mean_corr_age_raw,
346 signal_info,
347 alert_flag,
348 nr_bases,
349 ppp_info,
350 latency_raw,
351 h_accuracy_raw,
352 v_accuracy_raw,
353 })
354 }
355}
356
357#[derive(Debug, Clone)]
365#[allow(dead_code)]
366pub struct PvtCartesianBlock {
367 tow_ms: u32,
368 wnc: u16,
369 mode: u8,
370 error: u8,
371 x_m: f64,
372 y_m: f64,
373 z_m: f64,
374 undulation_m: f32,
375 vx_mps: f32,
376 vy_mps: f32,
377 vz_mps: f32,
378 cog_deg: f32,
379 rx_clk_bias_ms: f64,
380 rx_clk_drift_ppm: f32,
381 pub time_system: u8,
382 pub datum: u8,
383 nr_sv: u8,
384 pub wa_corr_info: u8,
385 pub reference_id: u16,
386 mean_corr_age_raw: u16,
387 pub signal_info: u32,
388 pub alert_flag: u8,
389 pub nr_bases: u8,
390}
391
392impl PvtCartesianBlock {
393 pub fn tow_seconds(&self) -> f64 {
394 self.tow_ms as f64 * 0.001
395 }
396 pub fn tow_ms(&self) -> u32 {
397 self.tow_ms
398 }
399 pub fn wnc(&self) -> u16 {
400 self.wnc
401 }
402
403 pub fn mode(&self) -> PvtMode {
404 PvtMode::from_mode_byte(self.mode)
405 }
406 pub fn error(&self) -> PvtError {
407 PvtError::from_error_byte(self.error)
408 }
409 pub fn has_fix(&self) -> bool {
410 self.mode().has_fix() && self.error().is_ok()
411 }
412
413 pub fn x_m(&self) -> Option<f64> {
415 if self.x_m == F64_DNU {
416 None
417 } else {
418 Some(self.x_m)
419 }
420 }
421 pub fn y_m(&self) -> Option<f64> {
422 if self.y_m == F64_DNU {
423 None
424 } else {
425 Some(self.y_m)
426 }
427 }
428 pub fn z_m(&self) -> Option<f64> {
429 if self.z_m == F64_DNU {
430 None
431 } else {
432 Some(self.z_m)
433 }
434 }
435
436 pub fn vx_mps(&self) -> Option<f32> {
438 if self.vx_mps == F32_DNU {
439 None
440 } else {
441 Some(self.vx_mps)
442 }
443 }
444 pub fn vy_mps(&self) -> Option<f32> {
445 if self.vy_mps == F32_DNU {
446 None
447 } else {
448 Some(self.vy_mps)
449 }
450 }
451 pub fn vz_mps(&self) -> Option<f32> {
452 if self.vz_mps == F32_DNU {
453 None
454 } else {
455 Some(self.vz_mps)
456 }
457 }
458
459 pub fn num_satellites(&self) -> u8 {
464 num_satellites_or_zero(self.nr_sv)
465 }
466 pub fn num_satellites_opt(&self) -> Option<u8> {
468 u8_or_none(self.nr_sv)
469 }
470 pub fn num_satellites_raw(&self) -> u8 {
472 self.nr_sv
473 }
474}
475
476impl SbfBlockParse for PvtCartesianBlock {
477 const BLOCK_ID: u16 = block_ids::PVT_CARTESIAN;
478
479 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
480 if data.len() < 83 {
481 return Err(SbfError::ParseError("PVTCartesian too short".into()));
482 }
483
484 let mode = data[12];
485 let error = data[13];
486
487 let x_m = f64::from_le_bytes(data[14..22].try_into().unwrap());
488 let y_m = f64::from_le_bytes(data[22..30].try_into().unwrap());
489 let z_m = f64::from_le_bytes(data[30..38].try_into().unwrap());
490 let undulation_m = f32::from_le_bytes(data[38..42].try_into().unwrap());
491
492 let vx_mps = f32::from_le_bytes(data[42..46].try_into().unwrap());
493 let vy_mps = f32::from_le_bytes(data[46..50].try_into().unwrap());
494 let vz_mps = f32::from_le_bytes(data[50..54].try_into().unwrap());
495 let cog_deg = f32::from_le_bytes(data[54..58].try_into().unwrap());
496
497 let rx_clk_bias_ms = f64::from_le_bytes(data[58..66].try_into().unwrap());
498 let rx_clk_drift_ppm = f32::from_le_bytes(data[66..70].try_into().unwrap());
499
500 let time_system = data[70];
501 let datum = data[71];
502 let nr_sv = data[72];
503 let wa_corr_info = data[73];
504 let reference_id = u16::from_le_bytes([data[74], data[75]]);
505 let mean_corr_age_raw = u16::from_le_bytes([data[76], data[77]]);
506 let signal_info = u32::from_le_bytes(data[78..82].try_into().unwrap());
507 let alert_flag = data[82];
508
509 let nr_bases = if header.block_rev >= 1 && data.len() >= 84 {
510 data[83]
511 } else {
512 0
513 };
514
515 Ok(Self {
516 tow_ms: header.tow_ms,
517 wnc: header.wnc,
518 mode,
519 error,
520 x_m,
521 y_m,
522 z_m,
523 undulation_m,
524 vx_mps,
525 vy_mps,
526 vz_mps,
527 cog_deg,
528 rx_clk_bias_ms,
529 rx_clk_drift_ppm,
530 time_system,
531 datum,
532 nr_sv,
533 wa_corr_info,
534 reference_id,
535 mean_corr_age_raw,
536 signal_info,
537 alert_flag,
538 nr_bases,
539 })
540 }
541}
542
543#[derive(Debug, Clone)]
551pub struct DopBlock {
552 tow_ms: u32,
553 wnc: u16,
554 nr_sv: u8,
555 pdop_raw: u16,
556 tdop_raw: u16,
557 hdop_raw: u16,
558 vdop_raw: u16,
559 hpl_m: f32,
560 vpl_m: f32,
561}
562
563impl DopBlock {
564 pub fn tow_seconds(&self) -> f64 {
565 self.tow_ms as f64 * 0.001
566 }
567 pub fn tow_ms(&self) -> u32 {
568 self.tow_ms
569 }
570 pub fn wnc(&self) -> u16 {
571 self.wnc
572 }
573 pub fn num_satellites(&self) -> u8 {
578 if self.nr_sv == 0 {
579 0
580 } else {
581 num_satellites_or_zero(self.nr_sv)
582 }
583 }
584 pub fn num_satellites_opt(&self) -> Option<u8> {
586 if self.nr_sv == 0 {
587 None
588 } else {
589 u8_or_none(self.nr_sv)
590 }
591 }
592 pub fn num_satellites_raw(&self) -> u8 {
594 self.nr_sv
595 }
596
597 pub fn pdop(&self) -> f32 {
600 self.pdop_opt().unwrap_or(0.0)
601 }
602 pub fn tdop(&self) -> f32 {
603 self.tdop_opt().unwrap_or(0.0)
604 }
605 pub fn hdop(&self) -> f32 {
606 self.hdop_opt().unwrap_or(0.0)
607 }
608 pub fn vdop(&self) -> f32 {
609 self.vdop_opt().unwrap_or(0.0)
610 }
611 pub fn pdop_opt(&self) -> Option<f32> {
613 dop_or_none(self.pdop_raw)
614 }
615 pub fn tdop_opt(&self) -> Option<f32> {
617 dop_or_none(self.tdop_raw)
618 }
619 pub fn hdop_opt(&self) -> Option<f32> {
621 dop_or_none(self.hdop_raw)
622 }
623 pub fn vdop_opt(&self) -> Option<f32> {
625 dop_or_none(self.vdop_raw)
626 }
627 pub fn gdop(&self) -> f32 {
629 self.gdop_opt().unwrap_or(0.0)
630 }
631 pub fn gdop_opt(&self) -> Option<f32> {
633 let pdop = self.pdop_opt()?;
634 let tdop = self.tdop_opt()?;
635 Some((pdop * pdop + tdop * tdop).sqrt())
636 }
637
638 pub fn pdop_raw(&self) -> u16 {
640 self.pdop_raw
641 }
642 pub fn tdop_raw(&self) -> u16 {
643 self.tdop_raw
644 }
645 pub fn hdop_raw(&self) -> u16 {
646 self.hdop_raw
647 }
648 pub fn vdop_raw(&self) -> u16 {
649 self.vdop_raw
650 }
651
652 pub fn hpl_m(&self) -> Option<f32> {
654 if self.hpl_m == F32_DNU {
655 None
656 } else {
657 Some(self.hpl_m)
658 }
659 }
660 pub fn vpl_m(&self) -> Option<f32> {
661 if self.vpl_m == F32_DNU {
662 None
663 } else {
664 Some(self.vpl_m)
665 }
666 }
667}
668
669impl SbfBlockParse for DopBlock {
670 const BLOCK_ID: u16 = block_ids::DOP;
671
672 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
673 if data.len() < 22 {
674 return Err(SbfError::ParseError("DOP block too short".into()));
675 }
676
677 let nr_sv = data[12];
688 let pdop_raw = u16::from_le_bytes([data[14], data[15]]);
689 let tdop_raw = u16::from_le_bytes([data[16], data[17]]);
690 let hdop_raw = u16::from_le_bytes([data[18], data[19]]);
691 let vdop_raw = u16::from_le_bytes([data[20], data[21]]);
692
693 let (hpl_m, vpl_m) = if data.len() >= 30 {
694 (
695 f32::from_le_bytes(data[22..26].try_into().unwrap()),
696 f32::from_le_bytes(data[26..30].try_into().unwrap()),
697 )
698 } else {
699 (F32_DNU, F32_DNU)
700 };
701
702 Ok(Self {
703 tow_ms: header.tow_ms,
704 wnc: header.wnc,
705 nr_sv,
706 pdop_raw,
707 tdop_raw,
708 hdop_raw,
709 vdop_raw,
710 hpl_m,
711 vpl_m,
712 })
713 }
714}
715
716#[derive(Debug, Clone)]
724pub struct PosCartBlock {
725 tow_ms: u32,
726 wnc: u16,
727 mode: u8,
728 error: u8,
729 x_m: f64,
730 y_m: f64,
731 z_m: f64,
732 base_x_m: f64,
733 base_y_m: f64,
734 base_z_m: f64,
735 pub cov_xx: f32,
737 pub cov_yy: f32,
738 pub cov_zz: f32,
739 pub cov_xy: f32,
740 pub cov_xz: f32,
741 pub cov_yz: f32,
742 pdop_raw: u16,
743 hdop_raw: u16,
744 vdop_raw: u16,
745 pub misc: u8,
746 pub alert_flag: u8,
747 pub datum: u8,
748 nr_sv: u8,
749 pub wa_corr_info: u8,
750 pub reference_id: u16,
751 mean_corr_age_raw: u16,
752 pub signal_info: u32,
753}
754
755impl PosCartBlock {
756 pub fn tow_seconds(&self) -> f64 {
757 self.tow_ms as f64 * 0.001
758 }
759 pub fn tow_ms(&self) -> u32 {
760 self.tow_ms
761 }
762 pub fn wnc(&self) -> u16 {
763 self.wnc
764 }
765
766 pub fn mode(&self) -> PvtMode {
767 PvtMode::from_mode_byte(self.mode)
768 }
769 pub fn error(&self) -> PvtError {
770 PvtError::from_error_byte(self.error)
771 }
772
773 pub fn x_m(&self) -> Option<f64> {
774 if self.x_m == F64_DNU {
775 None
776 } else {
777 Some(self.x_m)
778 }
779 }
780 pub fn y_m(&self) -> Option<f64> {
781 if self.y_m == F64_DNU {
782 None
783 } else {
784 Some(self.y_m)
785 }
786 }
787 pub fn z_m(&self) -> Option<f64> {
788 if self.z_m == F64_DNU {
789 None
790 } else {
791 Some(self.z_m)
792 }
793 }
794 pub fn base_to_rover_x_m(&self) -> Option<f64> {
795 if self.base_x_m == F64_DNU {
796 None
797 } else {
798 Some(self.base_x_m)
799 }
800 }
801 pub fn base_to_rover_y_m(&self) -> Option<f64> {
802 if self.base_y_m == F64_DNU {
803 None
804 } else {
805 Some(self.base_y_m)
806 }
807 }
808 pub fn base_to_rover_z_m(&self) -> Option<f64> {
809 if self.base_z_m == F64_DNU {
810 None
811 } else {
812 Some(self.base_z_m)
813 }
814 }
815
816 pub fn x_std_m(&self) -> Option<f32> {
817 if self.cov_xx == F32_DNU || self.cov_xx < 0.0 {
818 None
819 } else {
820 Some(self.cov_xx.sqrt())
821 }
822 }
823 pub fn y_std_m(&self) -> Option<f32> {
824 if self.cov_yy == F32_DNU || self.cov_yy < 0.0 {
825 None
826 } else {
827 Some(self.cov_yy.sqrt())
828 }
829 }
830 pub fn z_std_m(&self) -> Option<f32> {
831 if self.cov_zz == F32_DNU || self.cov_zz < 0.0 {
832 None
833 } else {
834 Some(self.cov_zz.sqrt())
835 }
836 }
837
838 pub fn pdop(&self) -> Option<f32> {
839 dop_or_none(self.pdop_raw)
840 }
841 pub fn hdop(&self) -> Option<f32> {
842 dop_or_none(self.hdop_raw)
843 }
844 pub fn vdop(&self) -> Option<f32> {
845 dop_or_none(self.vdop_raw)
846 }
847
848 pub fn pdop_raw(&self) -> u16 {
849 self.pdop_raw
850 }
851 pub fn hdop_raw(&self) -> u16 {
852 self.hdop_raw
853 }
854 pub fn vdop_raw(&self) -> u16 {
855 self.vdop_raw
856 }
857
858 pub fn num_satellites(&self) -> u8 {
863 num_satellites_or_zero(self.nr_sv)
864 }
865 pub fn num_satellites_opt(&self) -> Option<u8> {
867 u8_or_none(self.nr_sv)
868 }
869 pub fn num_satellites_raw(&self) -> u8 {
871 self.nr_sv
872 }
873
874 pub fn mean_corr_age_seconds(&self) -> Option<f32> {
875 if self.mean_corr_age_raw == U16_DNU {
876 None
877 } else {
878 Some(self.mean_corr_age_raw as f32 * 0.01)
879 }
880 }
881}
882
883impl SbfBlockParse for PosCartBlock {
884 const BLOCK_ID: u16 = block_ids::POS_CART;
885
886 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
887 if data.len() < 106 {
888 return Err(SbfError::ParseError("PosCart too short".into()));
889 }
890
891 let mode = data[12];
892 let error = data[13];
893 let x_m = f64::from_le_bytes(data[15..23].try_into().unwrap());
896 let y_m = f64::from_le_bytes(data[23..31].try_into().unwrap());
897 let z_m = f64::from_le_bytes(data[31..39].try_into().unwrap());
898
899 let base_x_m = f64::from_le_bytes(data[39..47].try_into().unwrap());
900 let base_y_m = f64::from_le_bytes(data[47..55].try_into().unwrap());
901 let base_z_m = f64::from_le_bytes(data[55..63].try_into().unwrap());
902
903 let cov_xx = f32::from_le_bytes(data[63..67].try_into().unwrap());
904 let cov_yy = f32::from_le_bytes(data[67..71].try_into().unwrap());
905 let cov_zz = f32::from_le_bytes(data[71..75].try_into().unwrap());
906 let cov_xy = f32::from_le_bytes(data[75..79].try_into().unwrap());
907 let cov_xz = f32::from_le_bytes(data[79..83].try_into().unwrap());
908 let cov_yz = f32::from_le_bytes(data[83..87].try_into().unwrap());
909
910 let pdop_raw = u16::from_le_bytes([data[87], data[88]]);
911 let hdop_raw = u16::from_le_bytes([data[89], data[90]]);
912 let vdop_raw = u16::from_le_bytes([data[91], data[92]]);
913
914 let misc = data[93];
915 let alert_flag = data[94];
916 let datum = data[95];
917 let nr_sv = data[96];
918 let wa_corr_info = data[97];
919 let reference_id = u16::from_le_bytes([data[98], data[99]]);
920 let mean_corr_age_raw = u16::from_le_bytes([data[100], data[101]]);
921 let signal_info = u32::from_le_bytes(data[102..106].try_into().unwrap());
922
923 Ok(Self {
924 tow_ms: header.tow_ms,
925 wnc: header.wnc,
926 mode,
927 error,
928 x_m,
929 y_m,
930 z_m,
931 base_x_m,
932 base_y_m,
933 base_z_m,
934 cov_xx,
935 cov_yy,
936 cov_zz,
937 cov_xy,
938 cov_xz,
939 cov_yz,
940 pdop_raw,
941 hdop_raw,
942 vdop_raw,
943 misc,
944 alert_flag,
945 datum,
946 nr_sv,
947 wa_corr_info,
948 reference_id,
949 mean_corr_age_raw,
950 signal_info,
951 })
952 }
953}
954
955#[derive(Debug, Clone)]
961pub struct PvtSatCartesianSatPos {
962 pub svid: u8,
963 pub freq_nr: u8,
964 pub iode: u16,
965 x_m: f64,
966 y_m: f64,
967 z_m: f64,
968 vx_mps: f32,
969 vy_mps: f32,
970 vz_mps: f32,
971}
972
973impl PvtSatCartesianSatPos {
974 pub fn x_m(&self) -> Option<f64> {
975 if self.x_m == F64_DNU {
976 None
977 } else {
978 Some(self.x_m)
979 }
980 }
981
982 pub fn y_m(&self) -> Option<f64> {
983 if self.y_m == F64_DNU {
984 None
985 } else {
986 Some(self.y_m)
987 }
988 }
989
990 pub fn z_m(&self) -> Option<f64> {
991 if self.z_m == F64_DNU {
992 None
993 } else {
994 Some(self.z_m)
995 }
996 }
997
998 pub fn vx_mps(&self) -> Option<f32> {
999 if self.vx_mps == F32_DNU {
1000 None
1001 } else {
1002 Some(self.vx_mps)
1003 }
1004 }
1005
1006 pub fn vy_mps(&self) -> Option<f32> {
1007 if self.vy_mps == F32_DNU {
1008 None
1009 } else {
1010 Some(self.vy_mps)
1011 }
1012 }
1013
1014 pub fn vz_mps(&self) -> Option<f32> {
1015 if self.vz_mps == F32_DNU {
1016 None
1017 } else {
1018 Some(self.vz_mps)
1019 }
1020 }
1021}
1022
1023#[derive(Debug, Clone)]
1025pub struct PvtSatCartesianBlock {
1026 tow_ms: u32,
1027 wnc: u16,
1028 pub satellites: Vec<PvtSatCartesianSatPos>,
1029}
1030
1031impl PvtSatCartesianBlock {
1032 pub fn tow_seconds(&self) -> f64 {
1033 self.tow_ms as f64 * 0.001
1034 }
1035
1036 pub fn tow_ms(&self) -> u32 {
1037 self.tow_ms
1038 }
1039
1040 pub fn wnc(&self) -> u16 {
1041 self.wnc
1042 }
1043
1044 pub fn num_satellites(&self) -> usize {
1045 self.satellites.len()
1046 }
1047}
1048
1049impl SbfBlockParse for PvtSatCartesianBlock {
1050 const BLOCK_ID: u16 = block_ids::PVT_SAT_CARTESIAN;
1051
1052 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1053 if data.len() < 14 {
1054 return Err(SbfError::ParseError("PVTSatCartesian too short".into()));
1055 }
1056
1057 let n = data[12] as usize;
1058 let sb_length = data[13] as usize;
1059 if sb_length < 40 {
1060 return Err(SbfError::ParseError(
1061 "PVTSatCartesian SBLength too small".into(),
1062 ));
1063 }
1064
1065 let mut satellites = Vec::new();
1066 let mut offset = 14;
1067
1068 for _ in 0..n {
1069 if offset + sb_length > data.len() {
1070 break;
1071 }
1072
1073 let svid = data[offset];
1074 let freq_nr = data[offset + 1];
1075 let iode = u16::from_le_bytes([data[offset + 2], data[offset + 3]]);
1076 let x_m = f64::from_le_bytes(data[offset + 4..offset + 12].try_into().unwrap());
1077 let y_m = f64::from_le_bytes(data[offset + 12..offset + 20].try_into().unwrap());
1078 let z_m = f64::from_le_bytes(data[offset + 20..offset + 28].try_into().unwrap());
1079 let vx_mps = f32::from_le_bytes(data[offset + 28..offset + 32].try_into().unwrap());
1080 let vy_mps = f32::from_le_bytes(data[offset + 32..offset + 36].try_into().unwrap());
1081 let vz_mps = f32::from_le_bytes(data[offset + 36..offset + 40].try_into().unwrap());
1082
1083 satellites.push(PvtSatCartesianSatPos {
1084 svid,
1085 freq_nr,
1086 iode,
1087 x_m,
1088 y_m,
1089 z_m,
1090 vx_mps,
1091 vy_mps,
1092 vz_mps,
1093 });
1094
1095 offset += sb_length;
1096 }
1097
1098 Ok(Self {
1099 tow_ms: header.tow_ms,
1100 wnc: header.wnc,
1101 satellites,
1102 })
1103 }
1104}
1105
1106#[derive(Debug, Clone)]
1112pub struct PvtResidualsV2ResidualInfo {
1113 e_i_m: f32,
1114 w_i_raw: u16,
1115 mdb_raw: u16,
1116}
1117
1118impl PvtResidualsV2ResidualInfo {
1119 pub fn residual_m(&self) -> Option<f32> {
1120 if self.e_i_m == F32_DNU {
1121 None
1122 } else {
1123 Some(self.e_i_m)
1124 }
1125 }
1126
1127 pub fn weight(&self) -> Option<u16> {
1128 if self.w_i_raw == U16_DNU {
1129 None
1130 } else {
1131 Some(self.w_i_raw)
1132 }
1133 }
1134
1135 pub fn mdb(&self) -> Option<u16> {
1136 if self.mdb_raw == U16_DNU {
1137 None
1138 } else {
1139 Some(self.mdb_raw)
1140 }
1141 }
1142}
1143
1144#[derive(Debug, Clone)]
1146pub struct PvtResidualsV2SatSignalInfo {
1147 pub svid: u8,
1148 pub freq_nr: u8,
1149 pub signal_type: u8,
1150 pub ref_svid: u8,
1151 pub ref_freq_nr: u8,
1152 pub meas_info: u8,
1153 pub iode: u16,
1154 corr_age_raw: u16,
1155 pub reference_id: u16,
1156 pub residuals: Vec<PvtResidualsV2ResidualInfo>,
1157}
1158
1159impl PvtResidualsV2SatSignalInfo {
1160 pub fn corr_age_seconds(&self) -> Option<f32> {
1161 if self.corr_age_raw == U16_DNU {
1162 None
1163 } else {
1164 Some(self.corr_age_raw as f32 * 0.01)
1165 }
1166 }
1167
1168 pub fn expected_residual_count(&self) -> usize {
1169 residual_count_from_meas_info(self.meas_info)
1170 }
1171}
1172
1173#[derive(Debug, Clone)]
1175pub struct PvtResidualsV2Block {
1176 tow_ms: u32,
1177 wnc: u16,
1178 pub sat_signal_info: Vec<PvtResidualsV2SatSignalInfo>,
1179}
1180
1181impl PvtResidualsV2Block {
1182 pub fn tow_seconds(&self) -> f64 {
1183 self.tow_ms as f64 * 0.001
1184 }
1185
1186 pub fn tow_ms(&self) -> u32 {
1187 self.tow_ms
1188 }
1189
1190 pub fn wnc(&self) -> u16 {
1191 self.wnc
1192 }
1193
1194 pub fn num_sat_signals(&self) -> usize {
1195 self.sat_signal_info.len()
1196 }
1197}
1198
1199fn residual_count_from_meas_info(meas_info: u8) -> usize {
1200 ((meas_info & (1 << 2) != 0) as usize)
1201 + ((meas_info & (1 << 3) != 0) as usize)
1202 + ((meas_info & (1 << 4) != 0) as usize)
1203}
1204
1205impl SbfBlockParse for PvtResidualsV2Block {
1206 const BLOCK_ID: u16 = block_ids::PVT_RESIDUALS_V2;
1207
1208 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1209 if data.len() < 15 {
1210 return Err(SbfError::ParseError("PVTResiduals_v2 too short".into()));
1211 }
1212
1213 let n = data[12] as usize;
1214 let sb1_length = data[13] as usize;
1215 let sb2_length = data[14] as usize;
1216
1217 if sb1_length < 12 {
1218 return Err(SbfError::ParseError(
1219 "PVTResiduals_v2 SB1Length too small".into(),
1220 ));
1221 }
1222 if sb2_length < 8 {
1223 return Err(SbfError::ParseError(
1224 "PVTResiduals_v2 SB2Length too small".into(),
1225 ));
1226 }
1227
1228 let mut sat_signal_info = Vec::new();
1229 let mut offset = 15;
1230
1231 for _ in 0..n {
1232 if offset + sb1_length > data.len() {
1233 break;
1234 }
1235
1236 let svid = data[offset];
1237 let freq_nr = data[offset + 1];
1238 let signal_type = data[offset + 2];
1239 let ref_svid = data[offset + 3];
1240 let ref_freq_nr = data[offset + 4];
1241 let meas_info = data[offset + 5];
1242 let iode = u16::from_le_bytes([data[offset + 6], data[offset + 7]]);
1243 let corr_age_raw = u16::from_le_bytes([data[offset + 8], data[offset + 9]]);
1244 let reference_id = u16::from_le_bytes([data[offset + 10], data[offset + 11]]);
1245 offset += sb1_length;
1246
1247 let residual_count = residual_count_from_meas_info(meas_info);
1248 let mut residuals = Vec::new();
1249 for _ in 0..residual_count {
1250 if offset + sb2_length > data.len() {
1251 break;
1252 }
1253
1254 let e_i_m = f32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
1255 let w_i_raw = u16::from_le_bytes([data[offset + 4], data[offset + 5]]);
1256 let mdb_raw = u16::from_le_bytes([data[offset + 6], data[offset + 7]]);
1257 residuals.push(PvtResidualsV2ResidualInfo {
1258 e_i_m,
1259 w_i_raw,
1260 mdb_raw,
1261 });
1262
1263 offset += sb2_length;
1264 }
1265
1266 sat_signal_info.push(PvtResidualsV2SatSignalInfo {
1267 svid,
1268 freq_nr,
1269 signal_type,
1270 ref_svid,
1271 ref_freq_nr,
1272 meas_info,
1273 iode,
1274 corr_age_raw,
1275 reference_id,
1276 residuals,
1277 });
1278 }
1279
1280 Ok(Self {
1281 tow_ms: header.tow_ms,
1282 wnc: header.wnc,
1283 sat_signal_info,
1284 })
1285 }
1286}
1287
1288#[derive(Debug, Clone)]
1294pub struct RaimStatisticsV2Block {
1295 tow_ms: u32,
1296 wnc: u16,
1297 pub integrity_flag: u8,
1298 herl_position_m: f32,
1299 verl_position_m: f32,
1300 herl_velocity_mps: f32,
1301 verl_velocity_mps: f32,
1302 overall_model: u16,
1303}
1304
1305impl RaimStatisticsV2Block {
1306 pub fn tow_seconds(&self) -> f64 {
1307 self.tow_ms as f64 * 0.001
1308 }
1309
1310 pub fn tow_ms(&self) -> u32 {
1311 self.tow_ms
1312 }
1313
1314 pub fn wnc(&self) -> u16 {
1315 self.wnc
1316 }
1317
1318 pub fn herl_position_m(&self) -> Option<f32> {
1319 if self.herl_position_m == F32_DNU {
1320 None
1321 } else {
1322 Some(self.herl_position_m)
1323 }
1324 }
1325
1326 pub fn verl_position_m(&self) -> Option<f32> {
1327 if self.verl_position_m == F32_DNU {
1328 None
1329 } else {
1330 Some(self.verl_position_m)
1331 }
1332 }
1333
1334 pub fn herl_velocity_mps(&self) -> Option<f32> {
1335 if self.herl_velocity_mps == F32_DNU {
1336 None
1337 } else {
1338 Some(self.herl_velocity_mps)
1339 }
1340 }
1341
1342 pub fn verl_velocity_mps(&self) -> Option<f32> {
1343 if self.verl_velocity_mps == F32_DNU {
1344 None
1345 } else {
1346 Some(self.verl_velocity_mps)
1347 }
1348 }
1349
1350 pub fn overall_model(&self) -> Option<u16> {
1351 if self.overall_model == U16_DNU {
1352 None
1353 } else {
1354 Some(self.overall_model)
1355 }
1356 }
1357}
1358
1359impl SbfBlockParse for RaimStatisticsV2Block {
1360 const BLOCK_ID: u16 = block_ids::RAIM_STATISTICS_V2;
1361
1362 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1363 if data.len() < 34 {
1364 return Err(SbfError::ParseError("RAIMStatistics_v2 too short".into()));
1365 }
1366
1367 let integrity_flag = data[12];
1368 let herl_position_m = f32::from_le_bytes(data[14..18].try_into().unwrap());
1369 let verl_position_m = f32::from_le_bytes(data[18..22].try_into().unwrap());
1370 let herl_velocity_mps = f32::from_le_bytes(data[22..26].try_into().unwrap());
1371 let verl_velocity_mps = f32::from_le_bytes(data[26..30].try_into().unwrap());
1372 let overall_model = u16::from_le_bytes([data[30], data[31]]);
1373
1374 Ok(Self {
1375 tow_ms: header.tow_ms,
1376 wnc: header.wnc,
1377 integrity_flag,
1378 herl_position_m,
1379 verl_position_m,
1380 herl_velocity_mps,
1381 verl_velocity_mps,
1382 overall_model,
1383 })
1384 }
1385}
1386
1387#[derive(Debug, Clone)]
1393pub struct BaseVectorCartInfo {
1394 pub nr_sv: u8,
1395 error: u8,
1396 mode: u8,
1397 pub misc: u8,
1398 dx_m: f64,
1399 dy_m: f64,
1400 dz_m: f64,
1401 dvx_mps: f32,
1402 dvy_mps: f32,
1403 dvz_mps: f32,
1404 azimuth_raw: u16,
1405 elevation_raw: i16,
1406 pub reference_id: u16,
1407 corr_age_raw: u16,
1408 pub signal_info: u32,
1409}
1410
1411impl BaseVectorCartInfo {
1412 pub fn mode(&self) -> PvtMode {
1413 PvtMode::from_mode_byte(self.mode)
1414 }
1415 pub fn error(&self) -> PvtError {
1416 PvtError::from_error_byte(self.error)
1417 }
1418
1419 pub fn dx_m(&self) -> Option<f64> {
1420 if self.dx_m == F64_DNU {
1421 None
1422 } else {
1423 Some(self.dx_m)
1424 }
1425 }
1426 pub fn dy_m(&self) -> Option<f64> {
1427 if self.dy_m == F64_DNU {
1428 None
1429 } else {
1430 Some(self.dy_m)
1431 }
1432 }
1433 pub fn dz_m(&self) -> Option<f64> {
1434 if self.dz_m == F64_DNU {
1435 None
1436 } else {
1437 Some(self.dz_m)
1438 }
1439 }
1440 pub fn dvx_mps(&self) -> Option<f32> {
1441 if self.dvx_mps == F32_DNU {
1442 None
1443 } else {
1444 Some(self.dvx_mps)
1445 }
1446 }
1447 pub fn dvy_mps(&self) -> Option<f32> {
1448 if self.dvy_mps == F32_DNU {
1449 None
1450 } else {
1451 Some(self.dvy_mps)
1452 }
1453 }
1454 pub fn dvz_mps(&self) -> Option<f32> {
1455 if self.dvz_mps == F32_DNU {
1456 None
1457 } else {
1458 Some(self.dvz_mps)
1459 }
1460 }
1461
1462 pub fn azimuth_deg(&self) -> Option<f64> {
1463 if self.azimuth_raw == U16_DNU {
1464 None
1465 } else {
1466 Some(self.azimuth_raw as f64 * 0.01)
1467 }
1468 }
1469 pub fn elevation_deg(&self) -> Option<f64> {
1470 if self.elevation_raw == I16_DNU {
1471 None
1472 } else {
1473 Some(self.elevation_raw as f64 * 0.01)
1474 }
1475 }
1476
1477 pub fn corr_age_seconds(&self) -> Option<f32> {
1478 if self.corr_age_raw == U16_DNU {
1479 None
1480 } else {
1481 Some(self.corr_age_raw as f32 * 0.01)
1482 }
1483 }
1484}
1485
1486#[derive(Debug, Clone)]
1488pub struct BaseVectorCartBlock {
1489 tow_ms: u32,
1490 wnc: u16,
1491 pub vectors: Vec<BaseVectorCartInfo>,
1492}
1493
1494impl BaseVectorCartBlock {
1495 pub fn tow_seconds(&self) -> f64 {
1496 self.tow_ms as f64 * 0.001
1497 }
1498 pub fn tow_ms(&self) -> u32 {
1499 self.tow_ms
1500 }
1501 pub fn wnc(&self) -> u16 {
1502 self.wnc
1503 }
1504
1505 pub fn num_vectors(&self) -> usize {
1506 self.vectors.len()
1507 }
1508}
1509
1510impl SbfBlockParse for BaseVectorCartBlock {
1511 const BLOCK_ID: u16 = block_ids::BASE_VECTOR_CART;
1512
1513 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1514 if data.len() < 14 {
1515 return Err(SbfError::ParseError("BaseVectorCart too short".into()));
1516 }
1517
1518 let n = data[12] as usize;
1519 let sb_length = data[13] as usize;
1520
1521 if sb_length < 52 {
1522 return Err(SbfError::ParseError(
1523 "BaseVectorCart SBLength too small".into(),
1524 ));
1525 }
1526
1527 let mut vectors = Vec::new();
1528 let mut offset = 14;
1529
1530 for _ in 0..n {
1531 if offset + sb_length > data.len() {
1532 break;
1533 }
1534
1535 let nr_sv = data[offset];
1536 let error = data[offset + 1];
1537 let mode = data[offset + 2];
1538 let misc = data[offset + 3];
1539
1540 let dx_m = f64::from_le_bytes(data[offset + 4..offset + 12].try_into().unwrap());
1541 let dy_m = f64::from_le_bytes(data[offset + 12..offset + 20].try_into().unwrap());
1542 let dz_m = f64::from_le_bytes(data[offset + 20..offset + 28].try_into().unwrap());
1543
1544 let dvx_mps = f32::from_le_bytes(data[offset + 28..offset + 32].try_into().unwrap());
1545 let dvy_mps = f32::from_le_bytes(data[offset + 32..offset + 36].try_into().unwrap());
1546 let dvz_mps = f32::from_le_bytes(data[offset + 36..offset + 40].try_into().unwrap());
1547
1548 let azimuth_raw = u16::from_le_bytes([data[offset + 40], data[offset + 41]]);
1549 let elevation_raw = i16::from_le_bytes([data[offset + 42], data[offset + 43]]);
1550 let reference_id = u16::from_le_bytes([data[offset + 44], data[offset + 45]]);
1551 let corr_age_raw = u16::from_le_bytes([data[offset + 46], data[offset + 47]]);
1552 let signal_info =
1553 u32::from_le_bytes(data[offset + 48..offset + 52].try_into().unwrap());
1554
1555 vectors.push(BaseVectorCartInfo {
1556 nr_sv,
1557 error,
1558 mode,
1559 misc,
1560 dx_m,
1561 dy_m,
1562 dz_m,
1563 dvx_mps,
1564 dvy_mps,
1565 dvz_mps,
1566 azimuth_raw,
1567 elevation_raw,
1568 reference_id,
1569 corr_age_raw,
1570 signal_info,
1571 });
1572
1573 offset += sb_length;
1574 }
1575
1576 Ok(Self {
1577 tow_ms: header.tow_ms,
1578 wnc: header.wnc,
1579 vectors,
1580 })
1581 }
1582}
1583
1584#[derive(Debug, Clone)]
1590pub struct BaseVectorGeodInfo {
1591 pub nr_sv: u8,
1592 error: u8,
1593 mode: u8,
1594 pub misc: u8,
1595 de_m: f64,
1596 dn_m: f64,
1597 du_m: f64,
1598 dve_mps: f32,
1599 dvn_mps: f32,
1600 dvu_mps: f32,
1601 azimuth_raw: u16,
1602 elevation_raw: i16,
1603 pub reference_id: u16,
1604 corr_age_raw: u16,
1605 pub signal_info: u32,
1606}
1607
1608impl BaseVectorGeodInfo {
1609 pub fn mode(&self) -> PvtMode {
1610 PvtMode::from_mode_byte(self.mode)
1611 }
1612 pub fn error(&self) -> PvtError {
1613 PvtError::from_error_byte(self.error)
1614 }
1615
1616 pub fn de_m(&self) -> Option<f64> {
1617 if self.de_m == F64_DNU {
1618 None
1619 } else {
1620 Some(self.de_m)
1621 }
1622 }
1623 pub fn dn_m(&self) -> Option<f64> {
1624 if self.dn_m == F64_DNU {
1625 None
1626 } else {
1627 Some(self.dn_m)
1628 }
1629 }
1630 pub fn du_m(&self) -> Option<f64> {
1631 if self.du_m == F64_DNU {
1632 None
1633 } else {
1634 Some(self.du_m)
1635 }
1636 }
1637 pub fn dve_mps(&self) -> Option<f32> {
1638 if self.dve_mps == F32_DNU {
1639 None
1640 } else {
1641 Some(self.dve_mps)
1642 }
1643 }
1644 pub fn dvn_mps(&self) -> Option<f32> {
1645 if self.dvn_mps == F32_DNU {
1646 None
1647 } else {
1648 Some(self.dvn_mps)
1649 }
1650 }
1651 pub fn dvu_mps(&self) -> Option<f32> {
1652 if self.dvu_mps == F32_DNU {
1653 None
1654 } else {
1655 Some(self.dvu_mps)
1656 }
1657 }
1658
1659 pub fn azimuth_deg(&self) -> Option<f64> {
1660 if self.azimuth_raw == U16_DNU {
1661 None
1662 } else {
1663 Some(self.azimuth_raw as f64 * 0.01)
1664 }
1665 }
1666 pub fn elevation_deg(&self) -> Option<f64> {
1667 if self.elevation_raw == I16_DNU {
1668 None
1669 } else {
1670 Some(self.elevation_raw as f64 * 0.01)
1671 }
1672 }
1673
1674 pub fn corr_age_seconds(&self) -> Option<f32> {
1675 if self.corr_age_raw == U16_DNU {
1676 None
1677 } else {
1678 Some(self.corr_age_raw as f32 * 0.01)
1679 }
1680 }
1681}
1682
1683#[derive(Debug, Clone)]
1685pub struct BaseVectorGeodBlock {
1686 tow_ms: u32,
1687 wnc: u16,
1688 pub vectors: Vec<BaseVectorGeodInfo>,
1689}
1690
1691impl BaseVectorGeodBlock {
1692 pub fn tow_seconds(&self) -> f64 {
1693 self.tow_ms as f64 * 0.001
1694 }
1695 pub fn tow_ms(&self) -> u32 {
1696 self.tow_ms
1697 }
1698 pub fn wnc(&self) -> u16 {
1699 self.wnc
1700 }
1701
1702 pub fn num_vectors(&self) -> usize {
1703 self.vectors.len()
1704 }
1705}
1706
1707impl SbfBlockParse for BaseVectorGeodBlock {
1708 const BLOCK_ID: u16 = block_ids::BASE_VECTOR_GEOD;
1709
1710 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1711 if data.len() < 14 {
1712 return Err(SbfError::ParseError("BaseVectorGeod too short".into()));
1713 }
1714
1715 let n = data[12] as usize;
1716 let sb_length = data[13] as usize;
1717
1718 if sb_length < 52 {
1719 return Err(SbfError::ParseError(
1720 "BaseVectorGeod SBLength too small".into(),
1721 ));
1722 }
1723
1724 let mut vectors = Vec::new();
1725 let mut offset = 14;
1726
1727 for _ in 0..n {
1728 if offset + sb_length > data.len() {
1729 break;
1730 }
1731
1732 let nr_sv = data[offset];
1733 let error = data[offset + 1];
1734 let mode = data[offset + 2];
1735 let misc = data[offset + 3];
1736
1737 let de_m = f64::from_le_bytes(data[offset + 4..offset + 12].try_into().unwrap());
1738 let dn_m = f64::from_le_bytes(data[offset + 12..offset + 20].try_into().unwrap());
1739 let du_m = f64::from_le_bytes(data[offset + 20..offset + 28].try_into().unwrap());
1740
1741 let dve_mps = f32::from_le_bytes(data[offset + 28..offset + 32].try_into().unwrap());
1742 let dvn_mps = f32::from_le_bytes(data[offset + 32..offset + 36].try_into().unwrap());
1743 let dvu_mps = f32::from_le_bytes(data[offset + 36..offset + 40].try_into().unwrap());
1744
1745 let azimuth_raw = u16::from_le_bytes([data[offset + 40], data[offset + 41]]);
1746 let elevation_raw = i16::from_le_bytes([data[offset + 42], data[offset + 43]]);
1747 let reference_id = u16::from_le_bytes([data[offset + 44], data[offset + 45]]);
1748 let corr_age_raw = u16::from_le_bytes([data[offset + 46], data[offset + 47]]);
1749 let signal_info =
1750 u32::from_le_bytes(data[offset + 48..offset + 52].try_into().unwrap());
1751
1752 vectors.push(BaseVectorGeodInfo {
1753 nr_sv,
1754 error,
1755 mode,
1756 misc,
1757 de_m,
1758 dn_m,
1759 du_m,
1760 dve_mps,
1761 dvn_mps,
1762 dvu_mps,
1763 azimuth_raw,
1764 elevation_raw,
1765 reference_id,
1766 corr_age_raw,
1767 signal_info,
1768 });
1769
1770 offset += sb_length;
1771 }
1772
1773 Ok(Self {
1774 tow_ms: header.tow_ms,
1775 wnc: header.wnc,
1776 vectors,
1777 })
1778 }
1779}
1780
1781#[derive(Debug, Clone)]
1787pub struct GeoCorrectionsSatCorr {
1788 pub svid: u8,
1789 pub iode: u8,
1790 prc_m: f32,
1791 corr_age_fc_s: f32,
1792 delta_x_m: f32,
1793 delta_y_m: f32,
1794 delta_z_m: f32,
1795 delta_clock_m: f32,
1796 corr_age_lt_s: f32,
1797 iono_pp_lat_rad: f32,
1798 iono_pp_lon_rad: f32,
1799 slant_iono_m: f32,
1800 corr_age_iono_s: f32,
1801 var_flt_m2: f32,
1802 var_uire_m2: f32,
1803 var_air_m2: f32,
1804 var_tropo_m2: f32,
1805}
1806
1807impl GeoCorrectionsSatCorr {
1808 pub fn prc_m(&self) -> Option<f32> {
1809 if self.prc_m == F32_DNU {
1810 None
1811 } else {
1812 Some(self.prc_m)
1813 }
1814 }
1815 pub fn corr_age_fc_seconds(&self) -> Option<f32> {
1816 if self.corr_age_fc_s == F32_DNU {
1817 None
1818 } else {
1819 Some(self.corr_age_fc_s)
1820 }
1821 }
1822 pub fn delta_x_m(&self) -> Option<f32> {
1823 if self.delta_x_m == F32_DNU {
1824 None
1825 } else {
1826 Some(self.delta_x_m)
1827 }
1828 }
1829 pub fn delta_y_m(&self) -> Option<f32> {
1830 if self.delta_y_m == F32_DNU {
1831 None
1832 } else {
1833 Some(self.delta_y_m)
1834 }
1835 }
1836 pub fn delta_z_m(&self) -> Option<f32> {
1837 if self.delta_z_m == F32_DNU {
1838 None
1839 } else {
1840 Some(self.delta_z_m)
1841 }
1842 }
1843 pub fn delta_clock_m(&self) -> Option<f32> {
1844 if self.delta_clock_m == F32_DNU {
1845 None
1846 } else {
1847 Some(self.delta_clock_m)
1848 }
1849 }
1850 pub fn corr_age_lt_seconds(&self) -> Option<f32> {
1851 if self.corr_age_lt_s == F32_DNU {
1852 None
1853 } else {
1854 Some(self.corr_age_lt_s)
1855 }
1856 }
1857 pub fn iono_pp_lat_rad(&self) -> Option<f32> {
1858 if self.iono_pp_lat_rad == F32_DNU {
1859 None
1860 } else {
1861 Some(self.iono_pp_lat_rad)
1862 }
1863 }
1864 pub fn iono_pp_lon_rad(&self) -> Option<f32> {
1865 if self.iono_pp_lon_rad == F32_DNU {
1866 None
1867 } else {
1868 Some(self.iono_pp_lon_rad)
1869 }
1870 }
1871 pub fn slant_iono_m(&self) -> Option<f32> {
1872 if self.slant_iono_m == F32_DNU {
1873 None
1874 } else {
1875 Some(self.slant_iono_m)
1876 }
1877 }
1878 pub fn corr_age_iono_seconds(&self) -> Option<f32> {
1879 if self.corr_age_iono_s == F32_DNU {
1880 None
1881 } else {
1882 Some(self.corr_age_iono_s)
1883 }
1884 }
1885 pub fn var_flt_m2(&self) -> Option<f32> {
1886 if self.var_flt_m2 == F32_DNU || self.var_flt_m2 < 0.0 {
1887 None
1888 } else {
1889 Some(self.var_flt_m2)
1890 }
1891 }
1892 pub fn var_uire_m2(&self) -> Option<f32> {
1893 if self.var_uire_m2 == F32_DNU || self.var_uire_m2 < 0.0 {
1894 None
1895 } else {
1896 Some(self.var_uire_m2)
1897 }
1898 }
1899 pub fn var_air_m2(&self) -> Option<f32> {
1900 if self.var_air_m2 == F32_DNU || self.var_air_m2 < 0.0 {
1901 None
1902 } else {
1903 Some(self.var_air_m2)
1904 }
1905 }
1906 pub fn var_tropo_m2(&self) -> Option<f32> {
1907 if self.var_tropo_m2 == F32_DNU || self.var_tropo_m2 < 0.0 {
1908 None
1909 } else {
1910 Some(self.var_tropo_m2)
1911 }
1912 }
1913}
1914
1915#[derive(Debug, Clone)]
1919pub struct GeoCorrectionsBlock {
1920 tow_ms: u32,
1921 wnc: u16,
1922 pub sat_corrections: Vec<GeoCorrectionsSatCorr>,
1923}
1924
1925impl GeoCorrectionsBlock {
1926 pub fn tow_seconds(&self) -> f64 {
1927 self.tow_ms as f64 * 0.001
1928 }
1929 pub fn tow_ms(&self) -> u32 {
1930 self.tow_ms
1931 }
1932 pub fn wnc(&self) -> u16 {
1933 self.wnc
1934 }
1935 pub fn num_satellites(&self) -> usize {
1936 self.sat_corrections.len()
1937 }
1938}
1939
1940impl SbfBlockParse for GeoCorrectionsBlock {
1941 const BLOCK_ID: u16 = block_ids::GEO_CORRECTIONS;
1942
1943 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1944 if data.len() < 14 {
1945 return Err(SbfError::ParseError("GEOCorrections too short".into()));
1946 }
1947
1948 let n = data[12] as usize;
1949 let sb_length = data[13] as usize;
1950 const SAT_CORR_MIN: usize = 62;
1952 if sb_length < SAT_CORR_MIN {
1953 return Err(SbfError::ParseError(
1954 "GEOCorrections SBLength too small".into(),
1955 ));
1956 }
1957
1958 let mut sat_corrections = Vec::new();
1959 let mut offset = 14;
1960
1961 for _ in 0..n {
1962 if offset + sb_length > data.len() {
1963 break;
1964 }
1965
1966 let svid = data[offset];
1967 let iode = data[offset + 1];
1968 let prc_m = f32::from_le_bytes(data[offset + 2..offset + 6].try_into().unwrap());
1969 let corr_age_fc_s =
1970 f32::from_le_bytes(data[offset + 6..offset + 10].try_into().unwrap());
1971 let delta_x_m = f32::from_le_bytes(data[offset + 10..offset + 14].try_into().unwrap());
1972 let delta_y_m = f32::from_le_bytes(data[offset + 14..offset + 18].try_into().unwrap());
1973 let delta_z_m = f32::from_le_bytes(data[offset + 18..offset + 22].try_into().unwrap());
1974 let delta_clock_m =
1975 f32::from_le_bytes(data[offset + 22..offset + 26].try_into().unwrap());
1976 let corr_age_lt_s =
1977 f32::from_le_bytes(data[offset + 26..offset + 30].try_into().unwrap());
1978 let iono_pp_lat_rad =
1979 f32::from_le_bytes(data[offset + 30..offset + 34].try_into().unwrap());
1980 let iono_pp_lon_rad =
1981 f32::from_le_bytes(data[offset + 34..offset + 38].try_into().unwrap());
1982 let slant_iono_m =
1983 f32::from_le_bytes(data[offset + 38..offset + 42].try_into().unwrap());
1984 let corr_age_iono_s =
1985 f32::from_le_bytes(data[offset + 42..offset + 46].try_into().unwrap());
1986 let var_flt_m2 = f32::from_le_bytes(data[offset + 46..offset + 50].try_into().unwrap());
1987 let var_uire_m2 =
1988 f32::from_le_bytes(data[offset + 50..offset + 54].try_into().unwrap());
1989 let var_air_m2 = f32::from_le_bytes(data[offset + 54..offset + 58].try_into().unwrap());
1990 let var_tropo_m2 =
1991 f32::from_le_bytes(data[offset + 58..offset + 62].try_into().unwrap());
1992
1993 sat_corrections.push(GeoCorrectionsSatCorr {
1994 svid,
1995 iode,
1996 prc_m,
1997 corr_age_fc_s,
1998 delta_x_m,
1999 delta_y_m,
2000 delta_z_m,
2001 delta_clock_m,
2002 corr_age_lt_s,
2003 iono_pp_lat_rad,
2004 iono_pp_lon_rad,
2005 slant_iono_m,
2006 corr_age_iono_s,
2007 var_flt_m2,
2008 var_uire_m2,
2009 var_air_m2,
2010 var_tropo_m2,
2011 });
2012
2013 offset += sb_length;
2014 }
2015
2016 Ok(Self {
2017 tow_ms: header.tow_ms,
2018 wnc: header.wnc,
2019 sat_corrections,
2020 })
2021 }
2022}
2023
2024#[derive(Debug, Clone)]
2032pub struct BaseStationBlock {
2033 tow_ms: u32,
2034 wnc: u16,
2035 pub base_station_id: u16,
2036 pub base_type: u8,
2037 pub source: u8,
2038 pub datum: u8,
2039 x_m: f64,
2040 y_m: f64,
2041 z_m: f64,
2042}
2043
2044impl BaseStationBlock {
2045 pub fn tow_seconds(&self) -> f64 {
2046 self.tow_ms as f64 * 0.001
2047 }
2048 pub fn tow_ms(&self) -> u32 {
2049 self.tow_ms
2050 }
2051 pub fn wnc(&self) -> u16 {
2052 self.wnc
2053 }
2054
2055 pub fn x_m(&self) -> Option<f64> {
2056 if self.x_m == F64_DNU {
2057 None
2058 } else {
2059 Some(self.x_m)
2060 }
2061 }
2062 pub fn y_m(&self) -> Option<f64> {
2063 if self.y_m == F64_DNU {
2064 None
2065 } else {
2066 Some(self.y_m)
2067 }
2068 }
2069 pub fn z_m(&self) -> Option<f64> {
2070 if self.z_m == F64_DNU {
2071 None
2072 } else {
2073 Some(self.z_m)
2074 }
2075 }
2076}
2077
2078impl SbfBlockParse for BaseStationBlock {
2079 const BLOCK_ID: u16 = block_ids::BASE_STATION;
2080
2081 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2082 if data.len() < 42 {
2083 return Err(SbfError::ParseError("BaseStation too short".into()));
2084 }
2085
2086 let base_station_id = u16::from_le_bytes([data[12], data[13]]);
2087 let base_type = data[14];
2088 let source = data[15];
2089 let datum = data[16];
2090 let x_m = f64::from_le_bytes(data[18..26].try_into().unwrap());
2092 let y_m = f64::from_le_bytes(data[26..34].try_into().unwrap());
2093 let z_m = f64::from_le_bytes(data[34..42].try_into().unwrap());
2094
2095 Ok(Self {
2096 tow_ms: header.tow_ms,
2097 wnc: header.wnc,
2098 base_station_id,
2099 base_type,
2100 source,
2101 datum,
2102 x_m,
2103 y_m,
2104 z_m,
2105 })
2106 }
2107}
2108
2109#[derive(Debug, Clone)]
2117pub struct PosCovCartesianBlock {
2118 tow_ms: u32,
2119 wnc: u16,
2120 mode: u8,
2121 error: u8,
2122 pub cov_xx: f32,
2124 pub cov_yy: f32,
2126 pub cov_zz: f32,
2128 pub cov_bb: f32,
2130 pub cov_xy: f32,
2132 pub cov_xz: f32,
2134 pub cov_xb: f32,
2136 pub cov_yz: f32,
2138 pub cov_yb: f32,
2140 pub cov_zb: f32,
2142}
2143
2144impl PosCovCartesianBlock {
2145 pub fn tow_seconds(&self) -> f64 {
2146 self.tow_ms as f64 * 0.001
2147 }
2148 pub fn tow_ms(&self) -> u32 {
2149 self.tow_ms
2150 }
2151 pub fn wnc(&self) -> u16 {
2152 self.wnc
2153 }
2154 pub fn mode(&self) -> PvtMode {
2155 PvtMode::from_mode_byte(self.mode)
2156 }
2157 pub fn error(&self) -> PvtError {
2158 PvtError::from_error_byte(self.error)
2159 }
2160
2161 pub fn x_std_m(&self) -> Option<f32> {
2163 if self.cov_xx == F32_DNU || self.cov_xx < 0.0 {
2164 None
2165 } else {
2166 Some(self.cov_xx.sqrt())
2167 }
2168 }
2169
2170 pub fn y_std_m(&self) -> Option<f32> {
2172 if self.cov_yy == F32_DNU || self.cov_yy < 0.0 {
2173 None
2174 } else {
2175 Some(self.cov_yy.sqrt())
2176 }
2177 }
2178
2179 pub fn z_std_m(&self) -> Option<f32> {
2181 if self.cov_zz == F32_DNU || self.cov_zz < 0.0 {
2182 None
2183 } else {
2184 Some(self.cov_zz.sqrt())
2185 }
2186 }
2187
2188 pub fn clock_std_m(&self) -> Option<f32> {
2190 if self.cov_bb == F32_DNU || self.cov_bb < 0.0 {
2191 None
2192 } else {
2193 Some(self.cov_bb.sqrt())
2194 }
2195 }
2196}
2197
2198#[derive(Debug, Clone)]
2207pub struct PvtSupportBlock {
2208 tow_ms: u32,
2209 wnc: u16,
2210}
2211
2212impl PvtSupportBlock {
2213 pub fn tow_seconds(&self) -> f64 {
2214 self.tow_ms as f64 * 0.001
2215 }
2216 pub fn tow_ms(&self) -> u32 {
2217 self.tow_ms
2218 }
2219 pub fn wnc(&self) -> u16 {
2220 self.wnc
2221 }
2222}
2223
2224impl SbfBlockParse for PvtSupportBlock {
2225 const BLOCK_ID: u16 = block_ids::PVT_SUPPORT;
2226
2227 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2228 if data.len() < 12 {
2229 return Err(SbfError::ParseError("PVTSupport too short".into()));
2230 }
2231
2232 Ok(Self {
2233 tow_ms: header.tow_ms,
2234 wnc: header.wnc,
2235 })
2236 }
2237}
2238
2239#[derive(Debug, Clone)]
2244pub struct PvtSupportABlock {
2245 tow_ms: u32,
2246 wnc: u16,
2247 payload: Vec<u8>,
2248}
2249
2250impl PvtSupportABlock {
2251 pub fn tow_seconds(&self) -> f64 {
2252 self.tow_ms as f64 * 0.001
2253 }
2254 pub fn tow_ms(&self) -> u32 {
2255 self.tow_ms
2256 }
2257 pub fn wnc(&self) -> u16 {
2258 self.wnc
2259 }
2260 pub fn payload(&self) -> &[u8] {
2261 &self.payload
2262 }
2263}
2264
2265impl SbfBlockParse for PvtSupportABlock {
2266 const BLOCK_ID: u16 = block_ids::PVT_SUPPORT_A;
2267
2268 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2269 let block_len = header.length as usize;
2270 let data_len = block_len.saturating_sub(2);
2271 if data_len < 12 || data.len() < data_len {
2272 return Err(SbfError::ParseError("PVTSupportA too short".into()));
2273 }
2274
2275 Ok(Self {
2276 tow_ms: header.tow_ms,
2277 wnc: header.wnc,
2278 payload: data[12..data_len].to_vec(),
2279 })
2280 }
2281}
2282
2283impl SbfBlockParse for PosCovCartesianBlock {
2284 const BLOCK_ID: u16 = block_ids::POS_COV_CARTESIAN;
2285
2286 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2287 if data.len() < 54 {
2288 return Err(SbfError::ParseError("PosCovCartesian too short".into()));
2289 }
2290
2291 let mode = data[12];
2292 let error = data[13];
2293
2294 let cov_xx = f32::from_le_bytes(data[14..18].try_into().unwrap());
2295 let cov_yy = f32::from_le_bytes(data[18..22].try_into().unwrap());
2296 let cov_zz = f32::from_le_bytes(data[22..26].try_into().unwrap());
2297 let cov_bb = f32::from_le_bytes(data[26..30].try_into().unwrap());
2298 let cov_xy = f32::from_le_bytes(data[30..34].try_into().unwrap());
2299 let cov_xz = f32::from_le_bytes(data[34..38].try_into().unwrap());
2300 let cov_xb = f32::from_le_bytes(data[38..42].try_into().unwrap());
2301 let cov_yz = f32::from_le_bytes(data[42..46].try_into().unwrap());
2302 let cov_yb = f32::from_le_bytes(data[46..50].try_into().unwrap());
2303 let cov_zb = f32::from_le_bytes(data[50..54].try_into().unwrap());
2304
2305 Ok(Self {
2306 tow_ms: header.tow_ms,
2307 wnc: header.wnc,
2308 mode,
2309 error,
2310 cov_xx,
2311 cov_yy,
2312 cov_zz,
2313 cov_bb,
2314 cov_xy,
2315 cov_xz,
2316 cov_xb,
2317 cov_yz,
2318 cov_yb,
2319 cov_zb,
2320 })
2321 }
2322}
2323
2324#[derive(Debug, Clone)]
2332pub struct PosCovGeodeticBlock {
2333 tow_ms: u32,
2334 wnc: u16,
2335 mode: u8,
2336 error: u8,
2337 pub cov_lat_lat: f32,
2339 pub cov_lon_lon: f32,
2341 pub cov_h_h: f32,
2343 pub cov_b_b: f32,
2345 pub cov_lat_lon: f32,
2347 pub cov_lat_h: f32,
2349 pub cov_lat_b: f32,
2351 pub cov_lon_h: f32,
2353 pub cov_lon_b: f32,
2355 pub cov_h_b: f32,
2357}
2358
2359impl PosCovGeodeticBlock {
2360 pub fn tow_seconds(&self) -> f64 {
2361 self.tow_ms as f64 * 0.001
2362 }
2363 pub fn tow_ms(&self) -> u32 {
2364 self.tow_ms
2365 }
2366 pub fn wnc(&self) -> u16 {
2367 self.wnc
2368 }
2369 pub fn mode(&self) -> PvtMode {
2370 PvtMode::from_mode_byte(self.mode)
2371 }
2372 pub fn error(&self) -> PvtError {
2373 PvtError::from_error_byte(self.error)
2374 }
2375
2376 pub fn lat_std_m(&self) -> Option<f32> {
2378 if self.cov_lat_lat == F32_DNU || self.cov_lat_lat < 0.0 {
2379 None
2380 } else {
2381 Some(self.cov_lat_lat.sqrt())
2382 }
2383 }
2384
2385 pub fn lon_std_m(&self) -> Option<f32> {
2387 if self.cov_lon_lon == F32_DNU || self.cov_lon_lon < 0.0 {
2388 None
2389 } else {
2390 Some(self.cov_lon_lon.sqrt())
2391 }
2392 }
2393
2394 pub fn height_std_m(&self) -> Option<f32> {
2396 if self.cov_h_h == F32_DNU || self.cov_h_h < 0.0 {
2397 None
2398 } else {
2399 Some(self.cov_h_h.sqrt())
2400 }
2401 }
2402}
2403
2404impl SbfBlockParse for PosCovGeodeticBlock {
2405 const BLOCK_ID: u16 = block_ids::POS_COV_GEODETIC;
2406
2407 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2408 if data.len() < 54 {
2409 return Err(SbfError::ParseError("PosCovGeodetic too short".into()));
2410 }
2411
2412 let mode = data[12];
2413 let error = data[13];
2414
2415 let cov_lat_lat = f32::from_le_bytes(data[14..18].try_into().unwrap());
2416 let cov_lon_lon = f32::from_le_bytes(data[18..22].try_into().unwrap());
2417 let cov_h_h = f32::from_le_bytes(data[22..26].try_into().unwrap());
2418 let cov_b_b = f32::from_le_bytes(data[26..30].try_into().unwrap());
2419 let cov_lat_lon = f32::from_le_bytes(data[30..34].try_into().unwrap());
2420 let cov_lat_h = f32::from_le_bytes(data[34..38].try_into().unwrap());
2421 let cov_lat_b = f32::from_le_bytes(data[38..42].try_into().unwrap());
2422 let cov_lon_h = f32::from_le_bytes(data[42..46].try_into().unwrap());
2423 let cov_lon_b = f32::from_le_bytes(data[46..50].try_into().unwrap());
2424 let cov_h_b = f32::from_le_bytes(data[50..54].try_into().unwrap());
2425
2426 Ok(Self {
2427 tow_ms: header.tow_ms,
2428 wnc: header.wnc,
2429 mode,
2430 error,
2431 cov_lat_lat,
2432 cov_lon_lon,
2433 cov_h_h,
2434 cov_b_b,
2435 cov_lat_lon,
2436 cov_lat_h,
2437 cov_lat_b,
2438 cov_lon_h,
2439 cov_lon_b,
2440 cov_h_b,
2441 })
2442 }
2443}
2444
2445#[derive(Debug, Clone)]
2453pub struct VelCovGeodeticBlock {
2454 tow_ms: u32,
2455 wnc: u16,
2456 mode: u8,
2457 error: u8,
2458 pub cov_vn_vn: f32,
2460 pub cov_ve_ve: f32,
2462 pub cov_vu_vu: f32,
2464 pub cov_dt_dt: f32,
2466 pub cov_vn_ve: f32,
2468 pub cov_vn_vu: f32,
2470 pub cov_vn_dt: f32,
2472 pub cov_ve_vu: f32,
2474 pub cov_ve_dt: f32,
2476 pub cov_vu_dt: f32,
2478}
2479
2480impl VelCovGeodeticBlock {
2481 pub fn tow_seconds(&self) -> f64 {
2482 self.tow_ms as f64 * 0.001
2483 }
2484 pub fn tow_ms(&self) -> u32 {
2485 self.tow_ms
2486 }
2487 pub fn wnc(&self) -> u16 {
2488 self.wnc
2489 }
2490 pub fn mode(&self) -> PvtMode {
2491 PvtMode::from_mode_byte(self.mode)
2492 }
2493 pub fn error(&self) -> PvtError {
2494 PvtError::from_error_byte(self.error)
2495 }
2496
2497 pub fn vn_std_mps(&self) -> Option<f32> {
2499 if self.cov_vn_vn == F32_DNU || self.cov_vn_vn < 0.0 {
2500 None
2501 } else {
2502 Some(self.cov_vn_vn.sqrt())
2503 }
2504 }
2505
2506 pub fn ve_std_mps(&self) -> Option<f32> {
2508 if self.cov_ve_ve == F32_DNU || self.cov_ve_ve < 0.0 {
2509 None
2510 } else {
2511 Some(self.cov_ve_ve.sqrt())
2512 }
2513 }
2514
2515 pub fn vu_std_mps(&self) -> Option<f32> {
2517 if self.cov_vu_vu == F32_DNU || self.cov_vu_vu < 0.0 {
2518 None
2519 } else {
2520 Some(self.cov_vu_vu.sqrt())
2521 }
2522 }
2523}
2524
2525impl SbfBlockParse for VelCovGeodeticBlock {
2526 const BLOCK_ID: u16 = block_ids::VEL_COV_GEODETIC;
2527
2528 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2529 if data.len() < 54 {
2530 return Err(SbfError::ParseError("VelCovGeodetic too short".into()));
2531 }
2532
2533 let mode = data[12];
2534 let error = data[13];
2535
2536 let cov_vn_vn = f32::from_le_bytes(data[14..18].try_into().unwrap());
2537 let cov_ve_ve = f32::from_le_bytes(data[18..22].try_into().unwrap());
2538 let cov_vu_vu = f32::from_le_bytes(data[22..26].try_into().unwrap());
2539 let cov_dt_dt = f32::from_le_bytes(data[26..30].try_into().unwrap());
2540 let cov_vn_ve = f32::from_le_bytes(data[30..34].try_into().unwrap());
2541 let cov_vn_vu = f32::from_le_bytes(data[34..38].try_into().unwrap());
2542 let cov_vn_dt = f32::from_le_bytes(data[38..42].try_into().unwrap());
2543 let cov_ve_vu = f32::from_le_bytes(data[42..46].try_into().unwrap());
2544 let cov_ve_dt = f32::from_le_bytes(data[46..50].try_into().unwrap());
2545 let cov_vu_dt = f32::from_le_bytes(data[50..54].try_into().unwrap());
2546
2547 Ok(Self {
2548 tow_ms: header.tow_ms,
2549 wnc: header.wnc,
2550 mode,
2551 error,
2552 cov_vn_vn,
2553 cov_ve_ve,
2554 cov_vu_vu,
2555 cov_dt_dt,
2556 cov_vn_ve,
2557 cov_vn_vu,
2558 cov_vn_dt,
2559 cov_ve_vu,
2560 cov_ve_dt,
2561 cov_vu_dt,
2562 })
2563 }
2564}
2565
2566#[derive(Debug, Clone)]
2574pub struct VelCovCartesianBlock {
2575 tow_ms: u32,
2576 wnc: u16,
2577 mode: u8,
2578 error: u8,
2579 pub cov_vx_vx: f32,
2581 pub cov_vy_vy: f32,
2583 pub cov_vz_vz: f32,
2585 pub cov_dt_dt: f32,
2587 pub cov_vx_vy: f32,
2589 pub cov_vx_vz: f32,
2591 pub cov_vx_dt: f32,
2593 pub cov_vy_vz: f32,
2595 pub cov_vy_dt: f32,
2597 pub cov_vz_dt: f32,
2599}
2600
2601impl VelCovCartesianBlock {
2602 pub fn tow_seconds(&self) -> f64 {
2603 self.tow_ms as f64 * 0.001
2604 }
2605 pub fn tow_ms(&self) -> u32 {
2606 self.tow_ms
2607 }
2608 pub fn wnc(&self) -> u16 {
2609 self.wnc
2610 }
2611 pub fn mode(&self) -> PvtMode {
2612 PvtMode::from_mode_byte(self.mode)
2613 }
2614 pub fn error(&self) -> PvtError {
2615 PvtError::from_error_byte(self.error)
2616 }
2617
2618 pub fn vx_std_mps(&self) -> Option<f32> {
2620 if self.cov_vx_vx == F32_DNU || self.cov_vx_vx < 0.0 {
2621 None
2622 } else {
2623 Some(self.cov_vx_vx.sqrt())
2624 }
2625 }
2626
2627 pub fn vy_std_mps(&self) -> Option<f32> {
2629 if self.cov_vy_vy == F32_DNU || self.cov_vy_vy < 0.0 {
2630 None
2631 } else {
2632 Some(self.cov_vy_vy.sqrt())
2633 }
2634 }
2635
2636 pub fn vz_std_mps(&self) -> Option<f32> {
2638 if self.cov_vz_vz == F32_DNU || self.cov_vz_vz < 0.0 {
2639 None
2640 } else {
2641 Some(self.cov_vz_vz.sqrt())
2642 }
2643 }
2644
2645 pub fn clock_drift_std(&self) -> Option<f32> {
2647 if self.cov_dt_dt == F32_DNU || self.cov_dt_dt < 0.0 {
2648 None
2649 } else {
2650 Some(self.cov_dt_dt.sqrt())
2651 }
2652 }
2653}
2654
2655impl SbfBlockParse for VelCovCartesianBlock {
2656 const BLOCK_ID: u16 = block_ids::VEL_COV_CARTESIAN;
2657
2658 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2659 if data.len() < 54 {
2660 return Err(SbfError::ParseError("VelCovCartesian too short".into()));
2661 }
2662
2663 let mode = data[12];
2664 let error = data[13];
2665
2666 let cov_vx_vx = f32::from_le_bytes(data[14..18].try_into().unwrap());
2667 let cov_vy_vy = f32::from_le_bytes(data[18..22].try_into().unwrap());
2668 let cov_vz_vz = f32::from_le_bytes(data[22..26].try_into().unwrap());
2669 let cov_dt_dt = f32::from_le_bytes(data[26..30].try_into().unwrap());
2670 let cov_vx_vy = f32::from_le_bytes(data[30..34].try_into().unwrap());
2671 let cov_vx_vz = f32::from_le_bytes(data[34..38].try_into().unwrap());
2672 let cov_vx_dt = f32::from_le_bytes(data[38..42].try_into().unwrap());
2673 let cov_vy_vz = f32::from_le_bytes(data[42..46].try_into().unwrap());
2674 let cov_vy_dt = f32::from_le_bytes(data[46..50].try_into().unwrap());
2675 let cov_vz_dt = f32::from_le_bytes(data[50..54].try_into().unwrap());
2676
2677 Ok(Self {
2678 tow_ms: header.tow_ms,
2679 wnc: header.wnc,
2680 mode,
2681 error,
2682 cov_vx_vx,
2683 cov_vy_vy,
2684 cov_vz_vz,
2685 cov_dt_dt,
2686 cov_vx_vy,
2687 cov_vx_vz,
2688 cov_vx_dt,
2689 cov_vy_vz,
2690 cov_vy_dt,
2691 cov_vz_dt,
2692 })
2693 }
2694}
2695
2696#[cfg(test)]
2697mod tests {
2698 use super::*;
2699 use crate::header::SbfHeader;
2700
2701 fn header_for(block_id: u16, data_len: usize, tow_ms: u32, wnc: u16) -> SbfHeader {
2702 SbfHeader {
2703 crc: 0,
2704 block_id,
2705 block_rev: 0,
2706 length: (data_len + 2) as u16,
2707 tow_ms,
2708 wnc,
2709 }
2710 }
2711
2712 #[test]
2713 fn test_pvt_nr_sv_dnu_handling() {
2714 let mut geodetic_data = vec![0u8; 83];
2715 geodetic_data[72] = 255;
2716 let header = header_for(block_ids::PVT_GEODETIC, geodetic_data.len(), 1000, 2200);
2717 let geodetic = PvtGeodeticBlock::parse(&header, &geodetic_data).unwrap();
2718 assert_eq!(geodetic.num_satellites_raw(), 255);
2719 assert_eq!(geodetic.num_satellites_opt(), None);
2720 assert_eq!(geodetic.num_satellites(), 0);
2721
2722 let mut cartesian_data = vec![0u8; 83];
2723 cartesian_data[72] = 255;
2724 let header = header_for(block_ids::PVT_CARTESIAN, cartesian_data.len(), 1000, 2200);
2725 let cartesian = PvtCartesianBlock::parse(&header, &cartesian_data).unwrap();
2726 assert_eq!(cartesian.num_satellites_raw(), 255);
2727 assert_eq!(cartesian.num_satellites_opt(), None);
2728 assert_eq!(cartesian.num_satellites(), 0);
2729 }
2730
2731 #[test]
2732 fn test_pvt_sat_cartesian_accessors() {
2733 let sat = PvtSatCartesianSatPos {
2734 svid: 12,
2735 freq_nr: 1,
2736 iode: 22,
2737 x_m: 10.0,
2738 y_m: 20.0,
2739 z_m: 30.0,
2740 vx_mps: 1.0,
2741 vy_mps: 2.0,
2742 vz_mps: 3.0,
2743 };
2744 let block = PvtSatCartesianBlock {
2745 tow_ms: 2500,
2746 wnc: 2345,
2747 satellites: vec![sat],
2748 };
2749
2750 assert!((block.tow_seconds() - 2.5).abs() < 1e-6);
2751 assert_eq!(block.num_satellites(), 1);
2752 let sat = &block.satellites[0];
2753 assert!((sat.x_m().unwrap() - 10.0).abs() < 1e-6);
2754 assert!((sat.vz_mps().unwrap() - 3.0).abs() < 1e-6);
2755 }
2756
2757 #[test]
2758 fn test_pvt_sat_cartesian_dnu_handling() {
2759 let sat = PvtSatCartesianSatPos {
2760 svid: 1,
2761 freq_nr: 0,
2762 iode: 0,
2763 x_m: F64_DNU,
2764 y_m: 1.0,
2765 z_m: F64_DNU,
2766 vx_mps: F32_DNU,
2767 vy_mps: 0.5,
2768 vz_mps: F32_DNU,
2769 };
2770
2771 assert!(sat.x_m().is_none());
2772 assert!(sat.y_m().is_some());
2773 assert!(sat.z_m().is_none());
2774 assert!(sat.vx_mps().is_none());
2775 assert!(sat.vy_mps().is_some());
2776 assert!(sat.vz_mps().is_none());
2777 }
2778
2779 #[test]
2780 fn test_pvt_sat_cartesian_parse() {
2781 let mut data = vec![0u8; 14 + 40];
2782 data[12] = 1;
2783 data[13] = 40;
2784
2785 let offset = 14;
2786 let iode = 512_u16;
2787 let x = 11.0_f64;
2788 let y = 22.0_f64;
2789 let z = 33.0_f64;
2790 let vx = 0.1_f32;
2791 let vy = 0.2_f32;
2792 let vz = 0.3_f32;
2793
2794 data[offset] = 31;
2795 data[offset + 1] = 2;
2796 data[offset + 2..offset + 4].copy_from_slice(&iode.to_le_bytes());
2797 data[offset + 4..offset + 12].copy_from_slice(&x.to_le_bytes());
2798 data[offset + 12..offset + 20].copy_from_slice(&y.to_le_bytes());
2799 data[offset + 20..offset + 28].copy_from_slice(&z.to_le_bytes());
2800 data[offset + 28..offset + 32].copy_from_slice(&vx.to_le_bytes());
2801 data[offset + 32..offset + 36].copy_from_slice(&vy.to_le_bytes());
2802 data[offset + 36..offset + 40].copy_from_slice(&vz.to_le_bytes());
2803
2804 let header = header_for(block_ids::PVT_SAT_CARTESIAN, data.len(), 123000, 2201);
2805 let block = PvtSatCartesianBlock::parse(&header, &data).unwrap();
2806
2807 assert_eq!(block.tow_ms(), 123000);
2808 assert_eq!(block.wnc(), 2201);
2809 assert_eq!(block.num_satellites(), 1);
2810 let sat = &block.satellites[0];
2811 assert_eq!(sat.svid, 31);
2812 assert_eq!(sat.iode, iode);
2813 assert!((sat.x_m().unwrap() - x).abs() < 1e-6);
2814 assert!((sat.vy_mps().unwrap() - vy).abs() < 1e-6);
2815 }
2816
2817 #[test]
2818 fn test_pvt_residuals_v2_accessors() {
2819 let residual = PvtResidualsV2ResidualInfo {
2820 e_i_m: 0.25,
2821 w_i_raw: 120,
2822 mdb_raw: 42,
2823 };
2824 let sat = PvtResidualsV2SatSignalInfo {
2825 svid: 7,
2826 freq_nr: 1,
2827 signal_type: 17,
2828 ref_svid: 33,
2829 ref_freq_nr: 0,
2830 meas_info: (1 << 2) | (1 << 4),
2831 iode: 300,
2832 corr_age_raw: 150,
2833 reference_id: 12,
2834 residuals: vec![residual],
2835 };
2836 let block = PvtResidualsV2Block {
2837 tow_ms: 2000,
2838 wnc: 100,
2839 sat_signal_info: vec![sat],
2840 };
2841
2842 assert!((block.tow_seconds() - 2.0).abs() < 1e-6);
2843 assert_eq!(block.num_sat_signals(), 1);
2844 let sat = &block.sat_signal_info[0];
2845 assert!((sat.corr_age_seconds().unwrap() - 1.5).abs() < 1e-6);
2846 assert_eq!(sat.expected_residual_count(), 2);
2847 assert!((sat.residuals[0].residual_m().unwrap() - 0.25).abs() < 1e-6);
2848 assert_eq!(sat.residuals[0].weight().unwrap(), 120);
2849 }
2850
2851 #[test]
2852 fn test_pvt_residuals_v2_dnu_handling() {
2853 let residual = PvtResidualsV2ResidualInfo {
2854 e_i_m: F32_DNU,
2855 w_i_raw: U16_DNU,
2856 mdb_raw: U16_DNU,
2857 };
2858 let sat = PvtResidualsV2SatSignalInfo {
2859 svid: 0,
2860 freq_nr: 0,
2861 signal_type: 0,
2862 ref_svid: 0,
2863 ref_freq_nr: 0,
2864 meas_info: 0,
2865 iode: 0,
2866 corr_age_raw: U16_DNU,
2867 reference_id: 0,
2868 residuals: vec![residual],
2869 };
2870
2871 assert!(sat.corr_age_seconds().is_none());
2872 assert!(sat.residuals[0].residual_m().is_none());
2873 assert!(sat.residuals[0].weight().is_none());
2874 assert!(sat.residuals[0].mdb().is_none());
2875 }
2876
2877 #[test]
2878 fn test_pvt_residuals_v2_parse() {
2879 let mut data = vec![0u8; 15 + 12 + (2 * 8)];
2880 data[12] = 1; data[13] = 12; data[14] = 8; let mut offset = 15;
2886 data[offset] = 8; data[offset + 1] = 1; data[offset + 2] = 17; data[offset + 3] = 33; data[offset + 4] = 2; data[offset + 5] = (1 << 2) | (1 << 4); data[offset + 6..offset + 8].copy_from_slice(&0x1234_u16.to_le_bytes()); data[offset + 8..offset + 10].copy_from_slice(&250_u16.to_le_bytes()); data[offset + 10..offset + 12].copy_from_slice(&77_u16.to_le_bytes()); offset += 12;
2896
2897 let e1 = 0.5_f32;
2898 let e2 = -0.25_f32;
2899 data[offset..offset + 4].copy_from_slice(&e1.to_le_bytes());
2900 data[offset + 4..offset + 6].copy_from_slice(&100_u16.to_le_bytes());
2901 data[offset + 6..offset + 8].copy_from_slice(&200_u16.to_le_bytes());
2902 offset += 8;
2903
2904 data[offset..offset + 4].copy_from_slice(&e2.to_le_bytes());
2905 data[offset + 4..offset + 6].copy_from_slice(&101_u16.to_le_bytes());
2906 data[offset + 6..offset + 8].copy_from_slice(&201_u16.to_le_bytes());
2907
2908 let header = header_for(block_ids::PVT_RESIDUALS_V2, data.len(), 654321, 2222);
2909 let block = PvtResidualsV2Block::parse(&header, &data).unwrap();
2910
2911 assert_eq!(block.num_sat_signals(), 1);
2912 let sat = &block.sat_signal_info[0];
2913 assert_eq!(sat.svid, 8);
2914 assert_eq!(sat.expected_residual_count(), 2);
2915 assert_eq!(sat.residuals.len(), 2);
2916 assert!((sat.corr_age_seconds().unwrap() - 2.5).abs() < 1e-6);
2917 assert!((sat.residuals[0].residual_m().unwrap() - e1).abs() < 1e-6);
2918 assert!((sat.residuals[1].residual_m().unwrap() - e2).abs() < 1e-6);
2919 }
2920
2921 #[test]
2922 fn test_raim_statistics_v2_accessors() {
2923 let block = RaimStatisticsV2Block {
2924 tow_ms: 3000,
2925 wnc: 123,
2926 integrity_flag: 2,
2927 herl_position_m: 5.0,
2928 verl_position_m: 6.0,
2929 herl_velocity_mps: 0.7,
2930 verl_velocity_mps: 0.8,
2931 overall_model: 42,
2932 };
2933
2934 assert!((block.tow_seconds() - 3.0).abs() < 1e-6);
2935 assert_eq!(block.integrity_flag, 2);
2936 assert!((block.herl_position_m().unwrap() - 5.0).abs() < 1e-6);
2937 assert!((block.verl_velocity_mps().unwrap() - 0.8).abs() < 1e-6);
2938 assert_eq!(block.overall_model().unwrap(), 42);
2939 }
2940
2941 #[test]
2942 fn test_raim_statistics_v2_dnu_handling() {
2943 let block = RaimStatisticsV2Block {
2944 tow_ms: 0,
2945 wnc: 0,
2946 integrity_flag: 0,
2947 herl_position_m: F32_DNU,
2948 verl_position_m: F32_DNU,
2949 herl_velocity_mps: F32_DNU,
2950 verl_velocity_mps: F32_DNU,
2951 overall_model: U16_DNU,
2952 };
2953
2954 assert!(block.herl_position_m().is_none());
2955 assert!(block.verl_position_m().is_none());
2956 assert!(block.herl_velocity_mps().is_none());
2957 assert!(block.verl_velocity_mps().is_none());
2958 assert!(block.overall_model().is_none());
2959 }
2960
2961 #[test]
2962 fn test_raim_statistics_v2_parse() {
2963 let mut data = vec![0u8; 34];
2964 data[12] = 3; data[13] = 0; let herl_position = 7.5_f32;
2968 let verl_position = 8.5_f32;
2969 let herl_velocity = 0.9_f32;
2970 let verl_velocity = 1.1_f32;
2971 let overall_model = 321_u16;
2972
2973 data[14..18].copy_from_slice(&herl_position.to_le_bytes());
2974 data[18..22].copy_from_slice(&verl_position.to_le_bytes());
2975 data[22..26].copy_from_slice(&herl_velocity.to_le_bytes());
2976 data[26..30].copy_from_slice(&verl_velocity.to_le_bytes());
2977 data[30..32].copy_from_slice(&overall_model.to_le_bytes());
2978
2979 let header = header_for(block_ids::RAIM_STATISTICS_V2, data.len(), 111222, 3333);
2980 let block = RaimStatisticsV2Block::parse(&header, &data).unwrap();
2981
2982 assert_eq!(block.tow_ms(), 111222);
2983 assert_eq!(block.wnc(), 3333);
2984 assert_eq!(block.integrity_flag, 3);
2985 assert!((block.herl_position_m().unwrap() - herl_position).abs() < 1e-6);
2986 assert!((block.verl_position_m().unwrap() - verl_position).abs() < 1e-6);
2987 assert!((block.herl_velocity_mps().unwrap() - herl_velocity).abs() < 1e-6);
2988 assert!((block.verl_velocity_mps().unwrap() - verl_velocity).abs() < 1e-6);
2989 assert_eq!(block.overall_model().unwrap(), overall_model);
2990 }
2991
2992 #[test]
2993 fn test_dop_scaling() {
2994 let dop = DopBlock {
2995 tow_ms: 0,
2996 wnc: 0,
2997 nr_sv: 10,
2998 pdop_raw: 150, tdop_raw: 100, hdop_raw: 120, vdop_raw: 200, hpl_m: F32_DNU,
3003 vpl_m: F32_DNU,
3004 };
3005
3006 assert!((dop.pdop() - 1.50).abs() < 0.001);
3007 assert!((dop.hdop() - 1.20).abs() < 0.001);
3008 assert!((dop.gdop() - (1.50_f32.powi(2) + 1.0_f32.powi(2)).sqrt()).abs() < 0.001);
3009 assert_eq!(dop.pdop_opt(), Some(1.50));
3010 assert_eq!(dop.tdop_opt(), Some(1.00));
3011 assert_eq!(dop.num_satellites_opt(), Some(10));
3012 }
3013
3014 #[test]
3015 fn test_dop_dnu_handling() {
3016 let mut data = vec![0u8; 30];
3017 data[12] = 0; data[14..16].copy_from_slice(&0_u16.to_le_bytes());
3019 data[16..18].copy_from_slice(&0_u16.to_le_bytes());
3020 data[18..20].copy_from_slice(&0_u16.to_le_bytes());
3021 data[20..22].copy_from_slice(&0_u16.to_le_bytes());
3022
3023 let header = header_for(block_ids::DOP, data.len(), 1000, 2200);
3024 let dop = DopBlock::parse(&header, &data).unwrap();
3025 assert_eq!(dop.num_satellites_raw(), 0);
3026 assert_eq!(dop.num_satellites_opt(), None);
3027 assert_eq!(dop.num_satellites(), 0);
3028 assert_eq!(dop.pdop_raw(), 0);
3029 assert_eq!(dop.pdop_opt(), None);
3030 assert_eq!(dop.tdop_opt(), None);
3031 assert_eq!(dop.hdop_opt(), None);
3032 assert_eq!(dop.vdop_opt(), None);
3033 assert_eq!(dop.gdop_opt(), None);
3034 assert_eq!(dop.pdop(), 0.0);
3035 assert_eq!(dop.gdop(), 0.0);
3036 }
3037
3038 #[test]
3039 fn test_dop_generic_u16_dnu_does_not_scale_to_655_35() {
3040 let mut data = vec![0u8; 30];
3041 data[12] = 255;
3042 data[14..16].copy_from_slice(&U16_DNU.to_le_bytes());
3043 data[16..18].copy_from_slice(&U16_DNU.to_le_bytes());
3044 data[18..20].copy_from_slice(&U16_DNU.to_le_bytes());
3045 data[20..22].copy_from_slice(&U16_DNU.to_le_bytes());
3046
3047 let header = header_for(block_ids::DOP, data.len(), 1000, 2200);
3048 let dop = DopBlock::parse(&header, &data).unwrap();
3049 assert_eq!(dop.num_satellites_raw(), 255);
3050 assert_eq!(dop.num_satellites_opt(), None);
3051 assert_eq!(dop.num_satellites(), 0);
3052 assert_eq!(dop.pdop_raw(), U16_DNU);
3053 assert_eq!(dop.pdop_opt(), None);
3054 assert_eq!(dop.pdop(), 0.0);
3055 assert_ne!(dop.pdop(), 655.35);
3056 assert_eq!(dop.gdop_opt(), None);
3057 }
3058
3059 #[test]
3060 fn test_pos_cov_cartesian_std_accessors() {
3061 let block = PosCovCartesianBlock {
3063 tow_ms: 100000,
3064 wnc: 2300,
3065 mode: 4, error: 0,
3067 cov_xx: 4.0,
3068 cov_yy: 9.0,
3069 cov_zz: 16.0,
3070 cov_bb: 25.0,
3071 cov_xy: 1.0,
3072 cov_xz: 2.0,
3073 cov_xb: 3.0,
3074 cov_yz: 4.0,
3075 cov_yb: 5.0,
3076 cov_zb: 6.0,
3077 };
3078
3079 assert!((block.x_std_m().unwrap() - 2.0).abs() < 0.001);
3080 assert!((block.y_std_m().unwrap() - 3.0).abs() < 0.001);
3081 assert!((block.z_std_m().unwrap() - 4.0).abs() < 0.001);
3082 assert!((block.clock_std_m().unwrap() - 5.0).abs() < 0.001);
3083 assert!((block.tow_seconds() - 100.0).abs() < 0.001);
3084 }
3085
3086 #[test]
3087 fn test_pos_cov_cartesian_dnu_handling() {
3088 let block = PosCovCartesianBlock {
3089 tow_ms: 0,
3090 wnc: 0,
3091 mode: 0,
3092 error: 0,
3093 cov_xx: F32_DNU,
3094 cov_yy: -1.0, cov_zz: 4.0,
3096 cov_bb: F32_DNU,
3097 cov_xy: 0.0,
3098 cov_xz: 0.0,
3099 cov_xb: 0.0,
3100 cov_yz: 0.0,
3101 cov_yb: 0.0,
3102 cov_zb: 0.0,
3103 };
3104
3105 assert!(block.x_std_m().is_none()); assert!(block.y_std_m().is_none()); assert!(block.z_std_m().is_some()); assert!(block.clock_std_m().is_none()); }
3110
3111 #[test]
3112 fn test_pos_cov_cartesian_parse() {
3113 let mut data = vec![0u8; 54];
3116
3117 data[12] = 4; data[13] = 0;
3124
3125 let cov_xx: f32 = 1.0;
3127 let cov_yy: f32 = 4.0;
3128 let cov_zz: f32 = 9.0;
3129 let cov_bb: f32 = 16.0;
3130 let cov_xy: f32 = 0.5;
3131 let cov_xz: f32 = 0.6;
3132 let cov_xb: f32 = 0.7;
3133 let cov_yz: f32 = 0.8;
3134 let cov_yb: f32 = 0.9;
3135 let cov_zb: f32 = 1.1;
3136
3137 data[14..18].copy_from_slice(&cov_xx.to_le_bytes());
3138 data[18..22].copy_from_slice(&cov_yy.to_le_bytes());
3139 data[22..26].copy_from_slice(&cov_zz.to_le_bytes());
3140 data[26..30].copy_from_slice(&cov_bb.to_le_bytes());
3141 data[30..34].copy_from_slice(&cov_xy.to_le_bytes());
3142 data[34..38].copy_from_slice(&cov_xz.to_le_bytes());
3143 data[38..42].copy_from_slice(&cov_xb.to_le_bytes());
3144 data[42..46].copy_from_slice(&cov_yz.to_le_bytes());
3145 data[46..50].copy_from_slice(&cov_yb.to_le_bytes());
3146 data[50..54].copy_from_slice(&cov_zb.to_le_bytes());
3147
3148 let header = SbfHeader {
3149 crc: 0,
3150 block_id: block_ids::POS_COV_CARTESIAN,
3151 block_rev: 0,
3152 length: 56, tow_ms: 123456,
3154 wnc: 2300,
3155 };
3156
3157 let block = PosCovCartesianBlock::parse(&header, &data).unwrap();
3158
3159 assert_eq!(block.tow_ms(), 123456);
3160 assert_eq!(block.wnc(), 2300);
3161 assert!((block.x_std_m().unwrap() - 1.0).abs() < 0.001);
3162 assert!((block.y_std_m().unwrap() - 2.0).abs() < 0.001);
3163 assert!((block.z_std_m().unwrap() - 3.0).abs() < 0.001);
3164 assert!((block.clock_std_m().unwrap() - 4.0).abs() < 0.001);
3165 assert!((block.cov_xy - 0.5).abs() < 0.001);
3166 }
3167
3168 #[test]
3169 fn test_vel_cov_cartesian_std_accessors() {
3170 let block = VelCovCartesianBlock {
3172 tow_ms: 200000,
3173 wnc: 2300,
3174 mode: 4,
3175 error: 0,
3176 cov_vx_vx: 0.04,
3177 cov_vy_vy: 0.09,
3178 cov_vz_vz: 0.16,
3179 cov_dt_dt: 0.25,
3180 cov_vx_vy: 0.01,
3181 cov_vx_vz: 0.02,
3182 cov_vx_dt: 0.03,
3183 cov_vy_vz: 0.04,
3184 cov_vy_dt: 0.05,
3185 cov_vz_dt: 0.06,
3186 };
3187
3188 assert!((block.vx_std_mps().unwrap() - 0.2).abs() < 0.001);
3189 assert!((block.vy_std_mps().unwrap() - 0.3).abs() < 0.001);
3190 assert!((block.vz_std_mps().unwrap() - 0.4).abs() < 0.001);
3191 assert!((block.clock_drift_std().unwrap() - 0.5).abs() < 0.001);
3192 assert!((block.tow_seconds() - 200.0).abs() < 0.001);
3193 }
3194
3195 #[test]
3196 fn test_vel_cov_cartesian_dnu_handling() {
3197 let block = VelCovCartesianBlock {
3198 tow_ms: 0,
3199 wnc: 0,
3200 mode: 0,
3201 error: 0,
3202 cov_vx_vx: F32_DNU,
3203 cov_vy_vy: -0.01, cov_vz_vz: 0.04,
3205 cov_dt_dt: F32_DNU,
3206 cov_vx_vy: 0.0,
3207 cov_vx_vz: 0.0,
3208 cov_vx_dt: 0.0,
3209 cov_vy_vz: 0.0,
3210 cov_vy_dt: 0.0,
3211 cov_vz_dt: 0.0,
3212 };
3213
3214 assert!(block.vx_std_mps().is_none()); assert!(block.vy_std_mps().is_none()); assert!(block.vz_std_mps().is_some()); assert!(block.clock_drift_std().is_none()); }
3219
3220 #[test]
3221 fn test_vel_cov_cartesian_parse() {
3222 let mut data = vec![0u8; 54];
3224
3225 data[12] = 4; data[13] = 0; let cov_vx_vx: f32 = 0.01;
3229 let cov_vy_vy: f32 = 0.04;
3230 let cov_vz_vz: f32 = 0.09;
3231 let cov_dt_dt: f32 = 0.16;
3232 let cov_vx_vy: f32 = 0.001;
3233 let cov_vx_vz: f32 = 0.002;
3234 let cov_vx_dt: f32 = 0.003;
3235 let cov_vy_vz: f32 = 0.004;
3236 let cov_vy_dt: f32 = 0.005;
3237 let cov_vz_dt: f32 = 0.006;
3238
3239 data[14..18].copy_from_slice(&cov_vx_vx.to_le_bytes());
3240 data[18..22].copy_from_slice(&cov_vy_vy.to_le_bytes());
3241 data[22..26].copy_from_slice(&cov_vz_vz.to_le_bytes());
3242 data[26..30].copy_from_slice(&cov_dt_dt.to_le_bytes());
3243 data[30..34].copy_from_slice(&cov_vx_vy.to_le_bytes());
3244 data[34..38].copy_from_slice(&cov_vx_vz.to_le_bytes());
3245 data[38..42].copy_from_slice(&cov_vx_dt.to_le_bytes());
3246 data[42..46].copy_from_slice(&cov_vy_vz.to_le_bytes());
3247 data[46..50].copy_from_slice(&cov_vy_dt.to_le_bytes());
3248 data[50..54].copy_from_slice(&cov_vz_dt.to_le_bytes());
3249
3250 let header = SbfHeader {
3251 crc: 0,
3252 block_id: block_ids::VEL_COV_CARTESIAN,
3253 block_rev: 0,
3254 length: 56,
3255 tow_ms: 345678,
3256 wnc: 2301,
3257 };
3258
3259 let block = VelCovCartesianBlock::parse(&header, &data).unwrap();
3260
3261 assert_eq!(block.tow_ms(), 345678);
3262 assert_eq!(block.wnc(), 2301);
3263 assert!((block.vx_std_mps().unwrap() - 0.1).abs() < 0.001);
3264 assert!((block.vy_std_mps().unwrap() - 0.2).abs() < 0.001);
3265 assert!((block.vz_std_mps().unwrap() - 0.3).abs() < 0.001);
3266 assert!((block.clock_drift_std().unwrap() - 0.4).abs() < 0.001);
3267 assert!((block.cov_vx_vy - 0.001).abs() < 0.0001);
3268 }
3269
3270 #[test]
3271 fn test_pos_cart_scaled_accessors() {
3272 let block = PosCartBlock {
3273 tow_ms: 5000,
3274 wnc: 2000,
3275 mode: 4,
3276 error: 0,
3277 x_m: 10.0,
3278 y_m: 20.0,
3279 z_m: 30.0,
3280 base_x_m: 1.0,
3281 base_y_m: 2.0,
3282 base_z_m: 3.0,
3283 cov_xx: 4.0,
3284 cov_yy: 9.0,
3285 cov_zz: 16.0,
3286 cov_xy: 0.0,
3287 cov_xz: 0.0,
3288 cov_yz: 0.0,
3289 pdop_raw: 200,
3290 hdop_raw: 150,
3291 vdop_raw: 250,
3292 misc: 0,
3293 alert_flag: 0,
3294 datum: 0,
3295 nr_sv: 12,
3296 wa_corr_info: 1,
3297 reference_id: 10,
3298 mean_corr_age_raw: 150,
3299 signal_info: 0,
3300 };
3301
3302 assert!((block.pdop().unwrap() - 2.0).abs() < 1e-6);
3303 assert!((block.hdop().unwrap() - 1.5).abs() < 1e-6);
3304 assert!((block.vdop().unwrap() - 2.5).abs() < 1e-6);
3305 assert!((block.mean_corr_age_seconds().unwrap() - 1.5).abs() < 1e-6);
3306 assert!((block.x_std_m().unwrap() - 2.0).abs() < 1e-6);
3307 assert!((block.tow_seconds() - 5.0).abs() < 1e-6);
3308 }
3309
3310 #[test]
3311 fn test_pos_cart_dnu_handling() {
3312 let block = PosCartBlock {
3313 tow_ms: 0,
3314 wnc: 0,
3315 mode: 0,
3316 error: 0,
3317 x_m: F64_DNU,
3318 y_m: 1.0,
3319 z_m: 1.0,
3320 base_x_m: 1.0,
3321 base_y_m: 1.0,
3322 base_z_m: 1.0,
3323 cov_xx: F32_DNU,
3324 cov_yy: -1.0,
3325 cov_zz: 4.0,
3326 cov_xy: 0.0,
3327 cov_xz: 0.0,
3328 cov_yz: 0.0,
3329 pdop_raw: 0,
3330 hdop_raw: U16_DNU,
3331 vdop_raw: 0,
3332 misc: 0,
3333 alert_flag: 0,
3334 datum: 0,
3335 nr_sv: 255,
3336 wa_corr_info: 0,
3337 reference_id: 0,
3338 mean_corr_age_raw: U16_DNU,
3339 signal_info: 0,
3340 };
3341
3342 assert!(block.x_m().is_none());
3343 assert!(block.x_std_m().is_none());
3344 assert!(block.y_std_m().is_none());
3345 assert!(block.pdop().is_none());
3346 assert!(block.hdop().is_none());
3347 assert!(block.vdop().is_none());
3348 assert_eq!(block.num_satellites_raw(), 255);
3349 assert_eq!(block.num_satellites_opt(), None);
3350 assert_eq!(block.num_satellites(), 0);
3351 assert!(block.mean_corr_age_seconds().is_none());
3352 }
3353
3354 #[test]
3355 fn test_pos_cart_parse() {
3356 let mut data = vec![0u8; 106];
3357 data[12] = 8;
3358 data[13] = 0;
3359 data[14] = 0; let x_m = 123.0_f64;
3362 let y_m = 456.0_f64;
3363 let z_m = 789.0_f64;
3364 let base_x = 10.0_f64;
3365 let base_y = 20.0_f64;
3366 let base_z = 30.0_f64;
3367 let cov_xx = 1.0_f32;
3368 let cov_yy = 4.0_f32;
3369 let cov_zz = 9.0_f32;
3370 let cov_xy = 0.1_f32;
3371 let cov_xz = 0.2_f32;
3372 let cov_yz = 0.3_f32;
3373 let pdop_raw = 250_u16;
3374 let hdop_raw = 150_u16;
3375 let vdop_raw = 350_u16;
3376 let mean_corr_age_raw = 120_u16;
3377 let signal_info = 0x12345678_u32;
3378
3379 data[15..23].copy_from_slice(&x_m.to_le_bytes());
3380 data[23..31].copy_from_slice(&y_m.to_le_bytes());
3381 data[31..39].copy_from_slice(&z_m.to_le_bytes());
3382 data[39..47].copy_from_slice(&base_x.to_le_bytes());
3383 data[47..55].copy_from_slice(&base_y.to_le_bytes());
3384 data[55..63].copy_from_slice(&base_z.to_le_bytes());
3385 data[63..67].copy_from_slice(&cov_xx.to_le_bytes());
3386 data[67..71].copy_from_slice(&cov_yy.to_le_bytes());
3387 data[71..75].copy_from_slice(&cov_zz.to_le_bytes());
3388 data[75..79].copy_from_slice(&cov_xy.to_le_bytes());
3389 data[79..83].copy_from_slice(&cov_xz.to_le_bytes());
3390 data[83..87].copy_from_slice(&cov_yz.to_le_bytes());
3391 data[87..89].copy_from_slice(&pdop_raw.to_le_bytes());
3392 data[89..91].copy_from_slice(&hdop_raw.to_le_bytes());
3393 data[91..93].copy_from_slice(&vdop_raw.to_le_bytes());
3394 data[93] = 0;
3395 data[94] = 0;
3396 data[95] = 1;
3397 data[96] = 8;
3398 data[97] = 2;
3399 data[98..100].copy_from_slice(&55_u16.to_le_bytes());
3400 data[100..102].copy_from_slice(&mean_corr_age_raw.to_le_bytes());
3401 data[102..106].copy_from_slice(&signal_info.to_le_bytes());
3402
3403 let header = header_for(block_ids::POS_CART, data.len(), 123456, 2222);
3404 let block = PosCartBlock::parse(&header, &data).unwrap();
3405
3406 assert_eq!(block.tow_ms(), 123456);
3407 assert_eq!(block.wnc(), 2222);
3408 assert!((block.x_m().unwrap() - x_m).abs() < 1e-6);
3409 assert!((block.pdop().unwrap() - 2.5).abs() < 1e-6);
3410 assert_eq!(block.reference_id, 55);
3411 assert_eq!(block.signal_info, signal_info);
3412 }
3413
3414 #[test]
3415 fn test_base_vector_cart_scaled_accessors() {
3416 let info = BaseVectorCartInfo {
3417 nr_sv: 12,
3418 error: 0,
3419 mode: 8,
3420 misc: 0,
3421 dx_m: 1.0,
3422 dy_m: 2.0,
3423 dz_m: 3.0,
3424 dvx_mps: 0.1,
3425 dvy_mps: 0.2,
3426 dvz_mps: 0.3,
3427 azimuth_raw: 12345,
3428 elevation_raw: 250,
3429 reference_id: 7,
3430 corr_age_raw: 150,
3431 signal_info: 0,
3432 };
3433
3434 assert!((info.azimuth_deg().unwrap() - 123.45).abs() < 1e-2);
3435 assert!((info.elevation_deg().unwrap() - 2.5).abs() < 1e-6);
3436 assert!((info.corr_age_seconds().unwrap() - 1.5).abs() < 1e-6);
3437 assert!((info.dvx_mps().unwrap() - 0.1).abs() < 1e-6);
3438 }
3439
3440 #[test]
3441 fn test_base_vector_cart_dnu_handling() {
3442 let info = BaseVectorCartInfo {
3443 nr_sv: 0,
3444 error: 0,
3445 mode: 0,
3446 misc: 0,
3447 dx_m: F64_DNU,
3448 dy_m: 1.0,
3449 dz_m: 1.0,
3450 dvx_mps: F32_DNU,
3451 dvy_mps: 0.0,
3452 dvz_mps: 0.0,
3453 azimuth_raw: U16_DNU,
3454 elevation_raw: I16_DNU,
3455 reference_id: 0,
3456 corr_age_raw: U16_DNU,
3457 signal_info: 0,
3458 };
3459
3460 assert!(info.dx_m().is_none());
3461 assert!(info.dvx_mps().is_none());
3462 assert!(info.azimuth_deg().is_none());
3463 assert!(info.elevation_deg().is_none());
3464 assert!(info.corr_age_seconds().is_none());
3465 }
3466
3467 #[test]
3468 fn test_base_vector_cart_parse() {
3469 let mut data = vec![0u8; 14 + 52];
3470 data[12] = 1;
3471 data[13] = 52;
3472
3473 let offset = 14;
3474 data[offset] = 10;
3475 data[offset + 1] = 0;
3476 data[offset + 2] = 8;
3477 data[offset + 3] = 0;
3478
3479 let dx_m = 1.5_f64;
3480 let dy_m = 2.5_f64;
3481 let dz_m = 3.5_f64;
3482 let dvx_mps = 0.25_f32;
3483 let dvy_mps = 0.5_f32;
3484 let dvz_mps = 0.75_f32;
3485 let azimuth_raw = 9000_u16;
3486 let elevation_raw = 450_i16;
3487 let reference_id = 22_u16;
3488 let corr_age_raw = 80_u16;
3489 let signal_info = 0x87654321_u32;
3490
3491 data[offset + 4..offset + 12].copy_from_slice(&dx_m.to_le_bytes());
3492 data[offset + 12..offset + 20].copy_from_slice(&dy_m.to_le_bytes());
3493 data[offset + 20..offset + 28].copy_from_slice(&dz_m.to_le_bytes());
3494 data[offset + 28..offset + 32].copy_from_slice(&dvx_mps.to_le_bytes());
3495 data[offset + 32..offset + 36].copy_from_slice(&dvy_mps.to_le_bytes());
3496 data[offset + 36..offset + 40].copy_from_slice(&dvz_mps.to_le_bytes());
3497 data[offset + 40..offset + 42].copy_from_slice(&azimuth_raw.to_le_bytes());
3498 data[offset + 42..offset + 44].copy_from_slice(&elevation_raw.to_le_bytes());
3499 data[offset + 44..offset + 46].copy_from_slice(&reference_id.to_le_bytes());
3500 data[offset + 46..offset + 48].copy_from_slice(&corr_age_raw.to_le_bytes());
3501 data[offset + 48..offset + 52].copy_from_slice(&signal_info.to_le_bytes());
3502
3503 let header = header_for(block_ids::BASE_VECTOR_CART, data.len(), 999, 7);
3504 let block = BaseVectorCartBlock::parse(&header, &data).unwrap();
3505
3506 assert_eq!(block.num_vectors(), 1);
3507 let info = &block.vectors[0];
3508 assert_eq!(info.nr_sv, 10);
3509 assert_eq!(info.reference_id, reference_id);
3510 assert!((info.dx_m().unwrap() - dx_m).abs() < 1e-6);
3511 assert!((info.azimuth_deg().unwrap() - 90.0).abs() < 1e-6);
3512 assert!((info.corr_age_seconds().unwrap() - 0.8).abs() < 1e-6);
3513 }
3514
3515 #[test]
3516 fn test_base_vector_geod_scaled_accessors() {
3517 let info = BaseVectorGeodInfo {
3518 nr_sv: 8,
3519 error: 0,
3520 mode: 9,
3521 misc: 0,
3522 de_m: 1.0,
3523 dn_m: 2.0,
3524 du_m: 3.0,
3525 dve_mps: 0.1,
3526 dvn_mps: 0.2,
3527 dvu_mps: 0.3,
3528 azimuth_raw: 18000,
3529 elevation_raw: 100,
3530 reference_id: 9,
3531 corr_age_raw: 200,
3532 signal_info: 0,
3533 };
3534
3535 assert!((info.azimuth_deg().unwrap() - 180.0).abs() < 1e-6);
3536 assert!((info.elevation_deg().unwrap() - 1.0).abs() < 1e-6);
3537 assert!((info.corr_age_seconds().unwrap() - 2.0).abs() < 1e-6);
3538 assert!((info.dvn_mps().unwrap() - 0.2).abs() < 1e-6);
3539 }
3540
3541 #[test]
3542 fn test_base_vector_geod_dnu_handling() {
3543 let info = BaseVectorGeodInfo {
3544 nr_sv: 0,
3545 error: 0,
3546 mode: 0,
3547 misc: 0,
3548 de_m: F64_DNU,
3549 dn_m: 1.0,
3550 du_m: 1.0,
3551 dve_mps: F32_DNU,
3552 dvn_mps: 0.0,
3553 dvu_mps: 0.0,
3554 azimuth_raw: U16_DNU,
3555 elevation_raw: I16_DNU,
3556 reference_id: 0,
3557 corr_age_raw: U16_DNU,
3558 signal_info: 0,
3559 };
3560
3561 assert!(info.de_m().is_none());
3562 assert!(info.dve_mps().is_none());
3563 assert!(info.azimuth_deg().is_none());
3564 assert!(info.elevation_deg().is_none());
3565 assert!(info.corr_age_seconds().is_none());
3566 }
3567
3568 #[test]
3569 fn test_base_vector_geod_parse() {
3570 let mut data = vec![0u8; 14 + 52];
3571 data[12] = 1;
3572 data[13] = 52;
3573
3574 let offset = 14;
3575 data[offset] = 6;
3576 data[offset + 1] = 0;
3577 data[offset + 2] = 8;
3578 data[offset + 3] = 0;
3579
3580 let de_m = 4.5_f64;
3581 let dn_m = 5.5_f64;
3582 let du_m = 6.5_f64;
3583 let dve_mps = 0.15_f32;
3584 let dvn_mps = 0.25_f32;
3585 let dvu_mps = 0.35_f32;
3586 let azimuth_raw = 27000_u16;
3587 let elevation_raw = 300_i16;
3588 let reference_id = 33_u16;
3589 let corr_age_raw = 90_u16;
3590 let signal_info = 0x12340000_u32;
3591
3592 data[offset + 4..offset + 12].copy_from_slice(&de_m.to_le_bytes());
3593 data[offset + 12..offset + 20].copy_from_slice(&dn_m.to_le_bytes());
3594 data[offset + 20..offset + 28].copy_from_slice(&du_m.to_le_bytes());
3595 data[offset + 28..offset + 32].copy_from_slice(&dve_mps.to_le_bytes());
3596 data[offset + 32..offset + 36].copy_from_slice(&dvn_mps.to_le_bytes());
3597 data[offset + 36..offset + 40].copy_from_slice(&dvu_mps.to_le_bytes());
3598 data[offset + 40..offset + 42].copy_from_slice(&azimuth_raw.to_le_bytes());
3599 data[offset + 42..offset + 44].copy_from_slice(&elevation_raw.to_le_bytes());
3600 data[offset + 44..offset + 46].copy_from_slice(&reference_id.to_le_bytes());
3601 data[offset + 46..offset + 48].copy_from_slice(&corr_age_raw.to_le_bytes());
3602 data[offset + 48..offset + 52].copy_from_slice(&signal_info.to_le_bytes());
3603
3604 let header = header_for(block_ids::BASE_VECTOR_GEOD, data.len(), 777, 5);
3605 let block = BaseVectorGeodBlock::parse(&header, &data).unwrap();
3606
3607 assert_eq!(block.num_vectors(), 1);
3608 let info = &block.vectors[0];
3609 assert_eq!(info.nr_sv, 6);
3610 assert_eq!(info.reference_id, reference_id);
3611 assert!((info.de_m().unwrap() - de_m).abs() < 1e-6);
3612 assert!((info.azimuth_deg().unwrap() - 270.0).abs() < 1e-6);
3613 assert!((info.corr_age_seconds().unwrap() - 0.9).abs() < 1e-6);
3614 }
3615
3616 #[test]
3617 fn test_geo_corrections_accessors() {
3618 let corr = GeoCorrectionsSatCorr {
3619 svid: 131,
3620 iode: 5,
3621 prc_m: 2.5,
3622 corr_age_fc_s: 1.2,
3623 delta_x_m: 0.1,
3624 delta_y_m: 0.2,
3625 delta_z_m: 0.3,
3626 delta_clock_m: 0.01,
3627 corr_age_lt_s: 120.0,
3628 iono_pp_lat_rad: 0.5,
3629 iono_pp_lon_rad: -0.3,
3630 slant_iono_m: 0.8,
3631 corr_age_iono_s: 60.0,
3632 var_flt_m2: 0.25,
3633 var_uire_m2: 0.5,
3634 var_air_m2: 1.0,
3635 var_tropo_m2: 0.1,
3636 };
3637 let block = GeoCorrectionsBlock {
3638 tow_ms: 5000,
3639 wnc: 2100,
3640 sat_corrections: vec![corr],
3641 };
3642
3643 assert!((block.tow_seconds() - 5.0).abs() < 1e-6);
3644 assert_eq!(block.num_satellites(), 1);
3645 let c = &block.sat_corrections[0];
3646 assert_eq!(c.svid, 131);
3647 assert!((c.prc_m().unwrap() - 2.5).abs() < 1e-6);
3648 assert!((c.corr_age_fc_seconds().unwrap() - 1.2).abs() < 1e-6);
3649 assert!((c.delta_x_m().unwrap() - 0.1).abs() < 1e-6);
3650 assert!((c.slant_iono_m().unwrap() - 0.8).abs() < 1e-6);
3651 assert!((c.var_flt_m2().unwrap() - 0.25).abs() < 1e-6);
3652 }
3653
3654 #[test]
3655 fn test_geo_corrections_dnu_handling() {
3656 let corr = GeoCorrectionsSatCorr {
3657 svid: 0,
3658 iode: 0,
3659 prc_m: F32_DNU,
3660 corr_age_fc_s: F32_DNU,
3661 delta_x_m: 0.0,
3662 delta_y_m: F32_DNU,
3663 delta_z_m: 0.0,
3664 delta_clock_m: F32_DNU,
3665 corr_age_lt_s: F32_DNU,
3666 iono_pp_lat_rad: F32_DNU,
3667 iono_pp_lon_rad: 0.0,
3668 slant_iono_m: F32_DNU,
3669 corr_age_iono_s: F32_DNU,
3670 var_flt_m2: F32_DNU,
3671 var_uire_m2: -1.0,
3672 var_air_m2: 0.0,
3673 var_tropo_m2: F32_DNU,
3674 };
3675
3676 assert!(corr.prc_m().is_none());
3677 assert!(corr.corr_age_fc_seconds().is_none());
3678 assert!(corr.delta_y_m().is_none());
3679 assert!(corr.var_flt_m2().is_none());
3680 assert!(corr.var_uire_m2().is_none());
3681 }
3682
3683 #[test]
3684 fn test_geo_corrections_parse() {
3685 let sb_len = 62usize;
3686 let mut data = vec![0u8; 14 + sb_len];
3687 data[12] = 1;
3688 data[13] = sb_len as u8;
3689
3690 let offset = 14;
3691 data[offset] = 132;
3692 data[offset + 1] = 7;
3693 let prc = 3.1_f32;
3694 let delta_x = 0.5_f32;
3695 data[offset + 2..offset + 6].copy_from_slice(&prc.to_le_bytes());
3696 data[offset + 10..offset + 14].copy_from_slice(&delta_x.to_le_bytes());
3697
3698 let header = header_for(block_ids::GEO_CORRECTIONS, data.len(), 88888, 2200);
3699 let block = GeoCorrectionsBlock::parse(&header, &data).unwrap();
3700
3701 assert_eq!(block.tow_ms(), 88888);
3702 assert_eq!(block.wnc(), 2200);
3703 assert_eq!(block.num_satellites(), 1);
3704 let c = &block.sat_corrections[0];
3705 assert_eq!(c.svid, 132);
3706 assert_eq!(c.iode, 7);
3707 assert!((c.prc_m().unwrap() - prc).abs() < 1e-6);
3708 assert!((c.delta_x_m().unwrap() - delta_x).abs() < 1e-6);
3709 }
3710
3711 #[test]
3712 fn test_base_station_accessors() {
3713 let block = BaseStationBlock {
3714 tow_ms: 10000,
3715 wnc: 2300,
3716 base_station_id: 42,
3717 base_type: 1,
3718 source: 2,
3719 datum: 0,
3720 x_m: 4e6,
3721 y_m: 3e6,
3722 z_m: -5e6,
3723 };
3724
3725 assert!((block.tow_seconds() - 10.0).abs() < 1e-6);
3726 assert_eq!(block.base_station_id, 42);
3727 assert!((block.x_m().unwrap() - 4e6).abs() < 1.0);
3728 assert!((block.y_m().unwrap() - 3e6).abs() < 1.0);
3729 assert!((block.z_m().unwrap() - (-5e6)).abs() < 1.0);
3730 }
3731
3732 #[test]
3733 fn test_base_station_dnu_handling() {
3734 let block = BaseStationBlock {
3735 tow_ms: 0,
3736 wnc: 0,
3737 base_station_id: 0,
3738 base_type: 0,
3739 source: 0,
3740 datum: 0,
3741 x_m: F64_DNU,
3742 y_m: 1.0,
3743 z_m: F64_DNU,
3744 };
3745
3746 assert!(block.x_m().is_none());
3747 assert!(block.y_m().is_some());
3748 assert!(block.z_m().is_none());
3749 }
3750
3751 #[test]
3752 fn test_base_station_parse() {
3753 let mut data = vec![0u8; 42];
3754 let base_id = 100_u16;
3755 let x_m = 4.0e6_f64;
3756 let y_m = 3.0e6_f64;
3757 let z_m = -5.5e6_f64;
3758
3759 data[12..14].copy_from_slice(&base_id.to_le_bytes());
3760 data[14] = 2;
3761 data[15] = 1;
3762 data[16] = 0;
3763 data[17] = 0; data[18..26].copy_from_slice(&x_m.to_le_bytes());
3765 data[26..34].copy_from_slice(&y_m.to_le_bytes());
3766 data[34..42].copy_from_slice(&z_m.to_le_bytes());
3767
3768 let header = header_for(block_ids::BASE_STATION, data.len(), 123456, 2345);
3769 let block = BaseStationBlock::parse(&header, &data).unwrap();
3770
3771 assert_eq!(block.tow_ms(), 123456);
3772 assert_eq!(block.wnc(), 2345);
3773 assert_eq!(block.base_station_id, base_id);
3774 assert_eq!(block.base_type, 2);
3775 assert!((block.x_m().unwrap() - x_m).abs() < 0.01);
3776 assert!((block.y_m().unwrap() - y_m).abs() < 0.01);
3777 assert!((block.z_m().unwrap() - z_m).abs() < 0.01);
3778 }
3779
3780 #[test]
3781 fn test_pvt_support_parse_and_accessors() {
3782 let data = vec![0u8; 12];
3783 let header = header_for(block_ids::PVT_SUPPORT, data.len(), 5000, 2100);
3784 let block = PvtSupportBlock::parse(&header, &data).unwrap();
3785
3786 assert_eq!(block.tow_ms(), 5000);
3787 assert_eq!(block.wnc(), 2100);
3788 assert!((block.tow_seconds() - 5.0).abs() < 1e-6);
3789 }
3790
3791 #[test]
3792 fn test_pvt_support_too_short() {
3793 let data = vec![0u8; 8];
3794 let header = header_for(block_ids::PVT_SUPPORT, data.len(), 0, 0);
3795 let result = PvtSupportBlock::parse(&header, &data);
3796 assert!(result.is_err());
3797 }
3798}