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