use crate::nmea::field::{FieldReader, FieldWriter};
#[derive(Debug, Clone, PartialEq)]
pub struct Gga {
pub time: Option<String>,
pub lat: Option<f64>,
pub ns: Option<char>,
pub lon: Option<f64>,
pub ew: Option<char>,
pub quality: Option<u8>,
pub num_sats: Option<u8>,
pub hdop: Option<f32>,
pub altitude: Option<f32>,
pub alt_unit: Option<char>,
pub geoid_sep: Option<f32>,
pub geoid_unit: Option<char>,
pub dgps_age: Option<f32>,
pub dgps_station: Option<String>,
}
impl Gga {
pub const SENTENCE_TYPE: &str = "GGA";
pub fn parse(fields: &[&str]) -> Option<Self> {
let mut r = FieldReader::new(fields);
Some(Self {
time: r.string(),
lat: r.f64(),
ns: r.char(),
lon: r.f64(),
ew: r.char(),
quality: r.u8(),
num_sats: r.u8(),
hdop: r.f32(),
altitude: r.f32(),
alt_unit: r.char(),
geoid_sep: r.f32(),
geoid_unit: r.char(),
dgps_age: r.f32(),
dgps_station: r.string(),
})
}
pub fn encode(&self) -> Vec<String> {
let mut w = FieldWriter::new();
w.string(self.time.as_deref());
w.f64(self.lat);
w.char(self.ns);
w.f64(self.lon);
w.char(self.ew);
w.u8(self.quality);
w.u8(self.num_sats);
w.f32(self.hdop);
w.f32(self.altitude);
w.char(self.alt_unit);
w.f32(self.geoid_sep);
w.char(self.geoid_unit);
w.f32(self.dgps_age);
w.string(self.dgps_station.as_deref());
w.finish()
}
pub fn to_sentence(&self, talker: &str) -> String {
let fields = self.encode();
let field_refs: Vec<&str> = fields.iter().map(|s| s.as_str()).collect();
crate::encode_frame('$', talker, Self::SENTENCE_TYPE, &field_refs)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse_frame;
#[test]
fn gga_empty() {
let frame = parse_frame("$GPGGA,,,,,,,,,,,,,,*56").expect("valid empty GGA frame");
let gga = Gga::parse(&frame.fields).expect("parse empty GGA");
assert!(gga.time.is_none());
assert!(gga.lat.is_none());
assert!(gga.quality.is_none());
assert!(gga.altitude.is_none());
}
#[test]
fn gga_full_signalk() {
let frame = parse_frame("$GPGGA,172814.0,3723.46587704,N,12202.26957864,W,2,6,1.2,18.893,M,-25.669,M,2.0,0031*4F")
.expect("valid GGA frame");
let gga = Gga::parse(&frame.fields).expect("parse GGA");
assert_eq!(gga.time, Some("172814.0".to_string()));
assert!((gga.lat.expect("lat") - 3723.46587704).abs() < 0.0001);
assert_eq!(gga.ns, Some('N'));
assert_eq!(gga.quality, Some(2));
assert_eq!(gga.num_sats, Some(6));
assert!((gga.hdop.expect("hdop") - 1.2).abs() < 0.01);
assert!((gga.altitude.expect("alt") - 18.893).abs() < 0.001);
}
#[test]
fn gga_multi_constellation_pynmeagps() {
let frame =
parse_frame("$GNGGA,103607.00,5327.03942,N,00214.42462,W,1,06,5.88,56.0,M,48.5,M,,*64")
.expect("valid GN GGA frame");
let gga = Gga::parse(&frame.fields).expect("parse GN GGA");
assert_eq!(gga.time, Some("103607.00".to_string()));
assert_eq!(gga.quality, Some(1));
assert_eq!(gga.num_sats, Some(6));
assert!((gga.hdop.expect("hdop") - 5.88).abs() < 0.01);
assert!((gga.altitude.expect("alt") - 56.0).abs() < 0.1);
assert!((gga.geoid_sep.expect("geoid") - 48.5).abs() < 0.1);
}
#[test]
fn gga_roundtrip() {
let gga = Gga {
time: Some("120000.00".to_string()),
lat: Some(4807.038),
ns: Some('N'),
lon: Some(1131.0),
ew: Some('E'),
quality: Some(1),
num_sats: Some(8),
hdop: Some(0.9),
altitude: Some(545.4),
alt_unit: Some('M'),
geoid_sep: Some(46.9),
geoid_unit: Some('M'),
dgps_age: None,
dgps_station: None,
};
let sentence = gga.to_sentence("GP");
let frame = parse_frame(sentence.trim()).expect("re-parse GGA");
let gga2 = Gga::parse(&frame.fields).expect("parse roundtrip GGA");
assert_eq!(gga.quality, gga2.quality);
assert_eq!(gga.num_sats, gga2.num_sats);
}
}