use crate::ais::armor::{extract_i32, extract_u32};
use super::common::NavigationStatus;
#[derive(Debug, Clone, PartialEq)]
pub struct LongRangePosition {
pub mmsi: u32,
pub position_accuracy: bool,
pub raim: bool,
pub nav_status: Option<NavigationStatus>,
pub longitude: Option<f64>,
pub latitude: Option<f64>,
pub sog: Option<u8>,
pub cog: Option<u16>,
pub gnss_position_status: bool,
}
impl LongRangePosition {
pub(crate) fn decode(bits: &[u8]) -> Option<Self> {
if bits.len() < 96 {
return None;
}
let mmsi = extract_u32(bits, 8, 30)?;
let accuracy = extract_u32(bits, 38, 1)? == 1;
let raim = extract_u32(bits, 39, 1)? == 1;
let nav_raw = extract_u32(bits, 40, 4)? as u8;
let lon_raw = extract_i32(bits, 44, 18)?;
let lat_raw = extract_i32(bits, 62, 17)?;
let sog_raw = extract_u32(bits, 79, 6)? as u8;
let cog_raw = extract_u32(bits, 85, 9)? as u16;
let gnss = extract_u32(bits, 94, 1)? == 1;
let longitude = {
let deg = f64::from(lon_raw) / 10.0;
if !(-180.0..=180.0).contains(°) {
None
} else {
Some(deg)
}
};
let latitude = {
let deg = f64::from(lat_raw) / 10.0;
if !(-90.0..=90.0).contains(°) {
None
} else {
Some(deg)
}
};
Some(Self {
mmsi,
position_accuracy: accuracy,
raim,
nav_status: Some(NavigationStatus::from(nav_raw)),
longitude,
latitude,
sog: if sog_raw == 63 { None } else { Some(sog_raw) },
cog: if cog_raw == 511 { None } else { Some(cog_raw) },
gnss_position_status: gnss,
})
}
}
#[cfg(test)]
mod tests {
use crate::ais::{AisMessage, AisParser};
use crate::parse_frame;
#[test]
fn long_range_gpsd() {
let mut parser = AisParser::new();
let frame = parse_frame("!AIVDM,1,1,,A,KCQ9r=hrFUnH7P00,0*41").expect("valid");
let msg = parser.decode(&frame).expect("decoded");
if let AisMessage::LongRangePosition(pos) = msg {
assert!(pos.mmsi > 0, "MMSI must be set");
if let (Some(lat), Some(lon)) = (pos.latitude, pos.longitude) {
assert!((-90.0..=90.0).contains(&lat), "lat out of range: {lat}");
assert!((-180.0..=180.0).contains(&lon), "lon out of range: {lon}");
}
} else {
panic!("expected LongRangePosition, got {msg:?}");
}
}
#[test]
fn long_range_position_sentinel() {
let mut parser = AisParser::new();
let frame = parse_frame("!AIVDM,1,1,,A,KCQ9r=hrFUnH7P00,0*41").expect("valid");
let msg = parser.decode(&frame).expect("decoded");
if let AisMessage::LongRangePosition(pos) = msg {
if let Some(lat) = pos.latitude {
assert!(
(-90.0..=90.0).contains(&lat),
"lat sentinel not filtered: {lat}"
);
}
if let Some(lon) = pos.longitude {
assert!(
(-180.0..=180.0).contains(&lon),
"lon sentinel not filtered: {lon}"
);
}
} else {
panic!("expected LongRangePosition, got {msg:?}");
}
}
}