sidereon_core/ntrip/
gga.rs1use crate::nmea::{self, Gga, GgaQuality, NmeaTalker, NmeaTime};
2use crate::{Error, Result, Wgs84Geodetic};
3
4#[derive(Clone, Debug, PartialEq)]
5pub struct GgaPosition {
6 pub lat_deg: f64,
7 pub lon_deg: f64,
8 pub height_m: f64,
9 pub fix_quality: u8,
10 pub num_satellites: u8,
11 pub hdop: f64,
12}
13
14impl Default for GgaPosition {
15 fn default() -> Self {
16 Self {
17 lat_deg: 0.0,
18 lon_deg: 0.0,
19 height_m: 0.0,
20 fix_quality: 1,
21 num_satellites: 10,
22 hdop: 1.0,
23 }
24 }
25}
26
27pub fn format_gga(position: &GgaPosition, utc_seconds_of_day: f64) -> Result<Vec<u8>> {
28 validate(position, utc_seconds_of_day)?;
29 let geodetic = Wgs84Geodetic::new(
30 position.lat_deg.to_radians(),
31 position.lon_deg.to_radians(),
32 position.height_m,
33 )
34 .map_err(|err| Error::InvalidInput(err.to_string()))?;
35 let mut gga = Gga::vrs_position(
36 geodetic,
37 format_time(utc_seconds_of_day)?,
38 quality(position.fix_quality),
39 position.num_satellites,
40 position.hdop,
41 7,
42 )
43 .map_err(|err| Error::InvalidInput(err.to_string()))?;
44 gga.geoid_separation_m = None;
45 nmea::write_gga(NmeaTalker::System(crate::GnssSystem::Gps), &gga)
46 .map(|sentence| sentence.into_bytes())
47 .map_err(|err| Error::InvalidInput(err.to_string()))
48}
49
50fn validate(position: &GgaPosition, utc_seconds_of_day: f64) -> Result<()> {
51 if !position.lat_deg.is_finite()
52 || !position.lon_deg.is_finite()
53 || !position.height_m.is_finite()
54 || !position.hdop.is_finite()
55 || !utc_seconds_of_day.is_finite()
56 {
57 return Err(Error::InvalidInput("GGA inputs must be finite".into()));
58 }
59 if !(-90.0..=90.0).contains(&position.lat_deg) {
60 return Err(Error::InvalidInput("GGA latitude outside [-90, 90]".into()));
61 }
62 if !(-180.0..=180.0).contains(&position.lon_deg) {
63 return Err(Error::InvalidInput(
64 "GGA longitude outside [-180, 180]".into(),
65 ));
66 }
67 if position.hdop < 0.0 {
68 return Err(Error::InvalidInput("GGA HDOP must be non-negative".into()));
69 }
70 if !(0.0..86400.0).contains(&utc_seconds_of_day) {
71 return Err(Error::InvalidInput("GGA time must be in [0, 86400)".into()));
72 }
73 Ok(())
74}
75
76fn format_time(seconds: f64) -> Result<NmeaTime> {
77 NmeaTime::from_seconds_of_day_floor_centis(seconds)
78 .map_err(|err| Error::InvalidInput(err.to_string()))
79}
80
81fn quality(value: u8) -> GgaQuality {
82 match value {
83 0 => GgaQuality::Invalid,
84 1 => GgaQuality::GpsSps,
85 2 => GgaQuality::Differential,
86 3 => GgaQuality::Pps,
87 4 => GgaQuality::RtkFixed,
88 5 => GgaQuality::RtkFloat,
89 6 => GgaQuality::Estimated,
90 7 => GgaQuality::Manual,
91 8 => GgaQuality::Simulator,
92 other => GgaQuality::Other(other),
93 }
94}