Skip to main content

nmea_parser/gnss/
gga.rs

1/*
2Copyright 2020 Timo Saarinen
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use super::*;
18
19/// GGA - time, position, and fix related data
20#[derive(Clone, Debug, PartialEq, Serialize)]
21pub struct GgaData {
22    /// Navigation system
23    pub source: NavigationSystem,
24
25    /// UTC of position fix
26    #[serde(with = "json_date_time_utc")]
27    pub timestamp: Option<DateTime<Utc>>,
28
29    /// Latitude in degrees
30    pub latitude: Option<f64>,
31
32    /// Longitude in degrees
33    pub longitude: Option<f64>,
34
35    /// GNSS Quality indicator
36    pub quality: GgaQualityIndicator,
37
38    /// Number of satellites in use
39    pub satellite_count: Option<u8>,
40
41    /// Horizontal dilution of position
42    pub hdop: Option<f64>,
43
44    /// Altitude above mean sea level (metres)
45    pub altitude: Option<f64>,
46
47    /// Height of geoid (mean sea level) above WGS84 ellipsoid
48    pub geoid_separation: Option<f64>,
49
50    /// Age of differential GPS data record, Type 1 or Type 9.
51    pub age_of_dgps: Option<f64>,
52
53    /// Reference station ID, range 0000-4095
54    pub ref_station_id: Option<u16>,
55}
56
57impl LatLon for GgaData {
58    fn latitude(&self) -> Option<f64> {
59        self.latitude
60    }
61
62    fn longitude(&self) -> Option<f64> {
63        self.longitude
64    }
65}
66
67/// GGA GPS quality indicator
68#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
69pub enum GgaQualityIndicator {
70    Invalid,                // 0
71    GpsFix,                 // 1
72    DGpsFix,                // 2
73    PpsFix,                 // 3
74    RealTimeKinematic,      // 4
75    RealTimeKinematicFloat, // 5
76    DeadReckoning,          // 6
77    ManualInputMode,        // 7
78    SimulationMode,         // 8
79}
80
81impl GgaQualityIndicator {
82    pub fn new(a: u8) -> GgaQualityIndicator {
83        match a {
84            0 => GgaQualityIndicator::Invalid,
85            1 => GgaQualityIndicator::GpsFix,
86            2 => GgaQualityIndicator::DGpsFix,
87            3 => GgaQualityIndicator::PpsFix,
88            4 => GgaQualityIndicator::RealTimeKinematic,
89            5 => GgaQualityIndicator::RealTimeKinematicFloat,
90            6 => GgaQualityIndicator::DeadReckoning,
91            7 => GgaQualityIndicator::ManualInputMode,
92            8 => GgaQualityIndicator::SimulationMode,
93            _ => GgaQualityIndicator::Invalid,
94        }
95    }
96}
97
98impl core::fmt::Display for GgaQualityIndicator {
99    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100        match self {
101            GgaQualityIndicator::Invalid => write!(f, "invalid"),
102            GgaQualityIndicator::GpsFix => write!(f, "GPS fix"),
103            GgaQualityIndicator::DGpsFix => write!(f, "DGPS fix"),
104            GgaQualityIndicator::PpsFix => write!(f, "PPS fix"),
105            GgaQualityIndicator::RealTimeKinematic => write!(f, "Real-Time Kinematic"),
106            GgaQualityIndicator::RealTimeKinematicFloat => {
107                write!(f, "Real-Time Kinematic (floating point)")
108            }
109            GgaQualityIndicator::DeadReckoning => write!(f, "dead reckoning"),
110            GgaQualityIndicator::ManualInputMode => write!(f, "manual input mode"),
111            GgaQualityIndicator::SimulationMode => write!(f, "simulation mode"),
112        }
113    }
114}
115
116// -------------------------------------------------------------------------------------------------
117
118/// xxGGA: Global Positioning System Fix Data
119pub(crate) fn handle(
120    sentence: &str,
121    nav_system: NavigationSystem,
122) -> Result<ParsedMessage, ParseError> {
123    let now: DateTime<Utc> = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).single().unwrap();
124    let split: Vec<&str> = sentence.split(',').collect();
125
126    Ok(ParsedMessage::Gga(GgaData {
127        source: nav_system,
128        timestamp: parse_hhmmss(split.get(1).unwrap_or(&""), now).ok(),
129        latitude: parse_latitude_ddmm_mmm(
130            split.get(2).unwrap_or(&""),
131            split.get(3).unwrap_or(&""),
132        )?,
133        longitude: parse_longitude_dddmm_mmm(
134            split.get(4).unwrap_or(&""),
135            split.get(5).unwrap_or(&""),
136        )?,
137        quality: GgaQualityIndicator::new(pick_number_field(&split, 6)?.unwrap_or(0)),
138        satellite_count: pick_number_field(&split, 7)?,
139        hdop: pick_number_field(&split, 8)?,
140        altitude: pick_number_field(&split, 9)?,
141        geoid_separation: pick_number_field(&split, 11)?,
142        age_of_dgps: pick_number_field(&split, 13)?,
143        ref_station_id: pick_number_field(&split, 14)?,
144    }))
145}
146
147// -------------------------------------------------------------------------------------------------
148
149#[cfg(test)]
150mod test {
151    use super::*;
152
153    #[test]
154    fn test_parse_cpgga() {
155        // General test
156        let mut p = NmeaParser::new();
157        match p.parse_sentence("$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47")
158        {
159            Ok(ps) => {
160                match ps {
161                    // The expected result
162                    ParsedMessage::Gga(gga) => {
163                        assert_eq!(gga.timestamp, {
164                            Utc.with_ymd_and_hms(2000, 01, 01, 12, 35, 19).single()
165                        });
166                        assert::close(gga.latitude.unwrap_or(0.0), 48.117, 0.001);
167                        assert::close(gga.longitude.unwrap_or(0.0), 11.517, 0.001);
168                        assert_eq!(gga.quality, GgaQualityIndicator::GpsFix);
169                        assert_eq!(gga.satellite_count.unwrap_or(0), 8);
170                        assert::close(gga.hdop.unwrap_or(0.0), 0.9, 0.1);
171                        assert::close(gga.altitude.unwrap_or(0.0), 545.4, 0.1);
172                        assert::close(gga.geoid_separation.unwrap_or(0.0), 46.9, 0.1);
173                        assert_eq!(gga.age_of_dgps, None);
174                        assert_eq!(gga.ref_station_id, None);
175                    }
176                    ParsedMessage::Incomplete => {
177                        assert!(false);
178                    }
179                    _ => {
180                        assert!(false);
181                    }
182                }
183            }
184            Err(e) => {
185                assert_eq!(e.to_string(), "OK");
186            }
187        }
188
189        // Southwest test
190        let mut p = NmeaParser::new();
191        match p.parse_sentence("$GPGGA,123519,4807.0,S,01131.0,W,1,08,0.9,545.4,M,46.9,M,,") {
192            Ok(ps) => {
193                match ps {
194                    // The expected result
195                    ParsedMessage::Gga(gga) => {
196                        assert_eq!(
197                            (gga.latitude.unwrap_or(0.0) * 1000.0).round() as i32,
198                            -48117
199                        );
200                        assert_eq!(
201                            (gga.longitude.unwrap_or(0.0) * 1000.0).round() as i32,
202                            -11517
203                        );
204                    }
205                    ParsedMessage::Incomplete => {
206                        assert!(false);
207                    }
208                    _ => {
209                        assert!(false);
210                    }
211                }
212            }
213            Err(e) => {
214                assert_eq!(e.to_string(), "OK");
215            }
216        }
217
218        // Empty fields test
219        let mut p = NmeaParser::new();
220        match p.parse_sentence("$GPGGA,123519,,,,,,,,,,,,,*5B") {
221            Ok(ps) => {
222                match ps {
223                    // The expected result
224                    ParsedMessage::Gga(gga) => {
225                        assert_eq!(gga.timestamp, {
226                            Utc.with_ymd_and_hms(2000, 01, 01, 12, 35, 19).single()
227                        });
228                        assert_eq!(gga.latitude, None);
229                        assert_eq!(gga.longitude, None);
230                        assert_eq!(gga.quality, GgaQualityIndicator::Invalid);
231                        assert_eq!(gga.satellite_count, None);
232                        assert_eq!(gga.hdop, None);
233                        assert_eq!(gga.altitude, None);
234                        assert_eq!(gga.geoid_separation, None);
235                        assert_eq!(gga.age_of_dgps, None);
236                        assert_eq!(gga.ref_station_id, None);
237                    }
238                    ParsedMessage::Incomplete => {
239                        assert!(false);
240                    }
241                    _ => {
242                        assert!(false);
243                    }
244                }
245            }
246            Err(e) => {
247                assert_eq!(e.to_string(), "OK");
248            }
249        }
250    }
251}