1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use crate::common;
use crate::coords::{Altitude, Latitude, Longitude};
use crate::datetime::Time;
use crate::Source;
use core::time::Duration;

/// Geographic coordinates including altitude, GPS solution quality, DGPS usage information.
#[derive(Debug, PartialEq, Clone)]
pub struct GGA {
    /// Navigational system.
    pub source: Source,
    /// Time of fix in UTC.
    pub time: Time,
    /// Latitude in reference datum, typically WGS-84.
    pub latitude: Latitude,
    /// Logitude in reference datum, typically WGS-84.
    pub longitude: Longitude,
    /// Quality of GPS solution.
    pub gps_quality: GPSQuality,
    /// Sattelites in use
    pub sat_in_use: u8,
    /// Horizontal dilusion of presicion. Indicates precision of solution.
    pub hdop: f32,
    /// Altitude over ground, typically WGS-84.
    pub altitude: Altitude,
    /// The difference between reference ellipsoid surface and mean-sea-level.
    pub geoidal_separation: Option<f32>,
    /// DGPS data age. None if DGPS not in use.
    pub age_dgps: Option<Duration>,
    /// ID of reference DGPS station used for fix. None if DGPS not in use.
    pub dgps_station_id: Option<u16>,
}

impl GGA {
    pub(crate) fn parse<'a>(
        source: Source,
        fields: &mut core::str::Split<'a, char>,
    ) -> Result<Option<Self>, &'static str> {
        let time = Time::parse_from_hhmmss(fields.next())?;
        let latitude = Latitude::parse(fields.next(), fields.next())?;
        let longitude = Longitude::parse(fields.next(), fields.next())?;
        let gps_quality = GPSQuality::parse(fields.next())?;
        let sat_in_use = common::parse_u8(fields.next())?;
        let hdop = common::parse_f32(fields.next())?;
        let altitude = Altitude::parse(fields.next())?;
        fields.next(); // Skip altitude type (always meters according to NMEA spec)
        let geoidal_separation = common::parse_f32(fields.next())?;
        fields.next(); // Skip geoidal separation type (always meters according to NMEA spec)
        let age_dgps = common::parse_f32(fields.next())?
            .and_then(|a| Some(Duration::from_millis((a * 1000f32) as u64)));
        let dgps_station_id = common::parse_u16(fields.next())?;
        if let (
            Some(time),
            Some(latitude),
            Some(longitude),
            Some(gps_quality),
            Some(sat_in_use),
            Some(hdop),
            Some(altitude),
        ) = (
            time,
            latitude,
            longitude,
            gps_quality,
            sat_in_use,
            hdop,
            altitude,
        ) {
            Ok(Some(GGA {
                source,
                time,
                latitude,
                longitude,
                gps_quality,
                sat_in_use,
                hdop,
                altitude,
                geoidal_separation,
                age_dgps,
                dgps_station_id,
            }))
        } else {
            Ok(None)
        }
    }
}

/// Quality of GPS solution
#[derive(Debug, PartialEq, Clone)]
pub enum GPSQuality {
    /// No solution
    NoFix,
    /// Ordinary GPS solution
    GPS,
    /// Differential correction used.
    DGPS,
    /// Locked PPS (pulse per second).
    PPS,
    /// RTK correction is in use.
    RTK,
    /// Float RTK correction is in use.
    FRTK,
    /// Estimated by movement model.
    Estimated,
    /// Set by operator.
    Manual,
    /// Simulated.
    Simulated,
}

impl GPSQuality {
    pub(crate) fn parse(input: Option<&str>) -> Result<Option<GPSQuality>, &'static str> {
        match input {
            Some("0") => Ok(Some(GPSQuality::NoFix)),
            Some("1") => Ok(Some(GPSQuality::GPS)),
            Some("2") => Ok(Some(GPSQuality::DGPS)),
            Some("3") => Ok(Some(GPSQuality::PPS)),
            Some("4") => Ok(Some(GPSQuality::RTK)),
            Some("5") => Ok(Some(GPSQuality::FRTK)),
            Some("6") => Ok(Some(GPSQuality::Estimated)),
            Some("7") => Ok(Some(GPSQuality::Manual)),
            Some("8") => Ok(Some(GPSQuality::Simulated)),
            Some("") => Ok(None),
            None => Ok(None),
            _ => Err("Wrong GPSQuality indicator type!"),
        }
    }
}

#[test]
fn test_parse_gpsquality() {
    assert_eq!(GPSQuality::parse(Some("0")), Ok(Some(GPSQuality::NoFix)));
    assert_eq!(GPSQuality::parse(Some("1")), Ok(Some(GPSQuality::GPS)));
    assert_eq!(GPSQuality::parse(Some("2")), Ok(Some(GPSQuality::DGPS)));
    assert_eq!(GPSQuality::parse(Some("3")), Ok(Some(GPSQuality::PPS)));
    assert_eq!(GPSQuality::parse(Some("4")), Ok(Some(GPSQuality::RTK)));
    assert_eq!(GPSQuality::parse(Some("5")), Ok(Some(GPSQuality::FRTK)));
    assert_eq!(
        GPSQuality::parse(Some("6")),
        Ok(Some(GPSQuality::Estimated))
    );
    assert_eq!(GPSQuality::parse(Some("7")), Ok(Some(GPSQuality::Manual)));
    assert_eq!(
        GPSQuality::parse(Some("8")),
        Ok(Some(GPSQuality::Simulated))
    );
    assert_eq!(GPSQuality::parse(Some("")), Ok(None));
    assert_eq!(GPSQuality::parse(None), Ok(None));
    assert!(GPSQuality::parse(Some("9")).is_err());
}