use super::nmea_parser::{NmeaFields, Parse};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NmeaGga {
pub time_of_day_ms: u32,
pub latitude_degrees_x1e7: i32,
pub longitude_degrees_x1e7: i32,
pub fix: u8,
pub satellite_count: u8,
pub hdop_x100: u16,
pub altitude_cm: i32,
pub geoid_separation_cm: i32,
}
impl Default for NmeaGga {
fn default() -> Self {
Self::new()
}
}
#[allow(unused)]
impl NmeaGga {
pub const fn new() -> Self {
Self {
time_of_day_ms: 0,
latitude_degrees_x1e7: 0,
longitude_degrees_x1e7: 0,
fix: 0,
satellite_count: 0,
hdop_x100: 0,
altitude_cm: 0,
geoid_separation_cm: 0,
}
}
}
impl NmeaGga {
pub fn parse(record: &[u8]) -> Option<Self> {
let mut fields = NmeaFields::new(record);
let talker_id = fields.next()?;
if talker_id.len() != 5 || &talker_id[2..] != b"GGA" {
return None;
}
let mut ret = Self::default();
let time = fields.next()?;
ret.time_of_day_ms = Parse::nmea_time(time)?;
let latitude = fields.next()?;
let latitude_direction = fields.next()?;
let longitude = fields.next()?;
let longitude_direction = fields.next()?;
if latitude_direction.len() != 1 || longitude_direction.len() != 1 {
return None;
}
ret.latitude_degrees_x1e7 = Parse::nmea_coordinate(latitude, latitude_direction[0])?;
ret.longitude_degrees_x1e7 = Parse::nmea_coordinate(longitude, longitude_direction[0])?;
let raw = fields.next()?;
let val = Parse::int(raw)?;
ret.fix = u8::try_from(val).ok()?;
let raw = fields.next()?;
let val = Parse::int(raw)?;
ret.satellite_count = u8::try_from(val).ok()?;
let hdop = fields.next()?;
let hdop = Parse::fixed_point(hdop, 10)?;
if hdop < 0 {
return None;
}
ret.hdop_x100 = u16::try_from(hdop).ok()?;
let altitude = fields.next()?;
let altitude_units = fields.next()?;
if altitude_units != b"M" {
return None;
}
ret.altitude_cm = Parse::fixed_point(altitude, 100)?;
let geoid_separation = fields.next()?;
let geoid_separation_units = fields.next()?;
if geoid_separation_units != b"M" {
return None;
}
ret.geoid_separation_cm = Parse::fixed_point(geoid_separation, 100)?;
Some(ret)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
use super::*;
#[test]
fn parse_gga_record_extracts_position_and_fix() {
let record = b"GPGGA,123519.500,4916.45,N,12311.12,W,1,08,0.9,545.4,M,46.9,M,,";
let result = NmeaGga::parse(record).expect("GGA record should parse");
assert_eq!(result.time_of_day_ms, 45_319_500);
assert_eq!(result.latitude_degrees_x1e7, 492_741_667);
assert_eq!(result.longitude_degrees_x1e7, -1_231_853_333);
assert_eq!(result.altitude_cm, 54_540);
assert_eq!(result.hdop_x100, 9);
assert_eq!(result.fix, 1);
assert_eq!(result.satellite_count, 8);
assert_eq!(result.geoid_separation_cm, 4_690);
}
#[test]
fn parse_gga_record_rejects_invalid_latitude() {
let record = b"GPGGA,123519,4916.X,N,12311.12,W,1,08,0.9,545.4,M,46.9,M,,";
assert_eq!(NmeaGga::parse(record), None);
}
#[test]
fn parse_gga_record_rejects_invalid_fix() {
let record = b"GPGGA,123519,4916.45,N,12311.12,W,X,08,0.9,545.4,M,46.9,M,,";
assert_eq!(NmeaGga::parse(record), None);
}
#[test]
fn parse_gga_record_handles_negative_geoid_separation() {
let record = b"GPGGA,123519.500,4916.45,N,12311.12,W,1,08,0.9,545.4,M,-46.9,M,,";
let result = NmeaGga::parse(record).expect("GGA record should parse");
assert_eq!(result.altitude_cm, 54_540);
assert_eq!(result.geoid_separation_cm, -4_690);
}
}