ogn-parser 0.3.19

OGN message parser for Rust
Documentation
use serde::Serialize;

use crate::AprsError;
use crate::EncodeError;
use std::ops::Deref;
use std::ops::DerefMut;
use std::str::FromStr;

#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Default, Serialize)]
pub struct Latitude(f64);

impl Deref for Latitude {
    type Target = f64;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Latitude {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl FromStr for Latitude {
    type Err = AprsError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let b = s.as_bytes();

        if b.len() != 8 || b[4] as char != '.' {
            return Err(Self::Err::InvalidLatitude(s.to_owned()));
        }

        let north = match b[7] as char {
            'N' => true,
            'S' => false,
            _ => return Err(Self::Err::InvalidLatitude(s.to_owned())),
        };

        let deg = s[0..2]
            .parse::<u32>()
            .map_err(|_| Self::Err::InvalidLatitude(s.to_owned()))? as f64;
        let min = s[2..4]
            .parse::<u32>()
            .map_err(|_| Self::Err::InvalidLatitude(s.to_owned()))? as f64;
        let min_frac = s[5..7]
            .parse::<u32>()
            .map_err(|_| Self::Err::InvalidLatitude(s.to_owned()))? as f64;

        let value = deg + min / 60. + min_frac / 6_000.;
        let value = if north { value } else { -value };

        if value > 90. || value < -90. {
            return Err(Self::Err::InvalidLatitude(s.to_owned()));
        }

        Ok(Self(value))
    }
}

#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Default, Serialize)]
pub struct Longitude(f64);

impl Deref for Longitude {
    type Target = f64;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Longitude {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl FromStr for Longitude {
    type Err = AprsError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let b = s.as_bytes();

        if b.len() != 9 || b[5] as char != '.' {
            return Err(Self::Err::InvalidLongitude(s.to_owned()));
        }

        let east = match b[8] as char {
            'E' => true,
            'W' => false,
            _ => return Err(Self::Err::InvalidLongitude(s.to_owned())),
        };

        let deg = s[0..3]
            .parse::<u32>()
            .map_err(|_| Self::Err::InvalidLongitude(s.to_owned()))? as f64;
        let min = s[3..5]
            .parse::<u32>()
            .map_err(|_| Self::Err::InvalidLongitude(s.to_owned()))? as f64;
        let min_frac = s[6..8]
            .parse::<u32>()
            .map_err(|_| Self::Err::InvalidLongitude(s.to_owned()))? as f64;

        let value = deg + min / 60. + min_frac / 6_000.;
        let value = if east { value } else { -value };

        if value > 180. || value < -180. {
            return Err(Self::Err::InvalidLongitude(s.to_owned()));
        }

        Ok(Self(value))
    }
}

pub fn encode_latitude(lat: Latitude) -> Result<String, EncodeError> {
    let lat = lat.0;

    if !(-90.0..=90.0).contains(&lat) {
        return Err(EncodeError::InvalidLatitude(lat));
    }

    let (dir, lat) = if lat >= 0.0 { ('N', lat) } else { ('S', -lat) };

    let deg = lat as u32;
    let min = ((lat - (deg as f64)) * 60.0) as u32;
    let min_frac = ((lat - (deg as f64) - (min as f64 / 60.0)) * 6000.0).round() as u32;

    Ok(format!("{deg:02}{min:02}.{min_frac:02}{dir}"))
}

pub fn encode_longitude(lon: Longitude) -> Result<String, EncodeError> {
    let lon = lon.0;

    if !(-180.0..=180.0).contains(&lon) {
        return Err(EncodeError::InvalidLongitude(lon));
    }

    let (dir, lon) = if lon >= 0.0 { ('E', lon) } else { ('W', -lon) };

    let deg = lon as u32;
    let min = ((lon - (deg as f64)) * 60.0) as u32;
    let min_frac = ((lon - (deg as f64) - (min as f64 / 60.0)) * 6000.0).round() as u32;

    Ok(format!("{deg:03}{min:02}.{min_frac:02}{dir}"))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_latitude() {
        assert_relative_eq!(*"4903.50N".parse::<Latitude>().unwrap(), 49.05833333333333);
        assert_relative_eq!(*"4903.50S".parse::<Latitude>().unwrap(), -49.05833333333333);
        assert_eq!(
            "4903.50W".parse::<Latitude>(),
            Err(AprsError::InvalidLatitude("4903.50W".to_owned()))
        );
        assert_eq!(
            "4903.50E".parse::<Latitude>(),
            Err(AprsError::InvalidLatitude("4903.50E".to_owned()))
        );
        assert_eq!(
            "9903.50N".parse::<Latitude>(),
            Err(AprsError::InvalidLatitude("9903.50N".to_owned()))
        );
        assert_relative_eq!(*"0000.00N".parse::<Latitude>().unwrap(), 0.0);
        assert_relative_eq!(*"0000.00S".parse::<Latitude>().unwrap(), 0.0);
    }

    #[test]
    fn test_longitude() {
        assert_relative_eq!(
            *"12903.50E".parse::<Longitude>().unwrap(),
            129.05833333333334
        );
        assert_relative_eq!(
            *"04903.50W".parse::<Longitude>().unwrap(),
            -49.05833333333333
        );
        assert_eq!(
            "04903.50N".parse::<Longitude>(),
            Err(AprsError::InvalidLongitude("04903.50N".to_owned()))
        );
        assert_eq!(
            "04903.50S".parse::<Longitude>(),
            Err(AprsError::InvalidLongitude("04903.50S".to_owned()))
        );
        assert_eq!(
            "18903.50E".parse::<Longitude>(),
            Err(AprsError::InvalidLongitude("18903.50E".to_owned()))
        );
        assert_relative_eq!(*"00000.00E".parse::<Longitude>().unwrap(), 0.0);
        assert_relative_eq!(*"00000.00W".parse::<Longitude>().unwrap(), 0.0);
    }

    #[test]
    fn test_deref_mut() {
        let mut lat = "4903.50N".parse::<Latitude>().unwrap();
        let mut lon = "12903.50E".parse::<Longitude>().unwrap();
        *lat -= 1.0;
        *lon -= 1.0;
        assert_relative_eq!(*lat, 48.05833333333333);
        assert_relative_eq!(*lon, 128.05833333333334);
    }

    #[test]
    fn test_encode_latitude() {
        assert_eq!(
            encode_latitude(Latitude(49.05833333333333)),
            Ok("4903.50N".to_string())
        );
        assert_eq!(
            encode_latitude(Latitude(-49.05833333333333)),
            Ok("4903.50S".to_string())
        );
        assert_eq!(
            encode_latitude(Latitude(-90.1)),
            Err(EncodeError::InvalidLatitude(-90.1))
        );
        assert_eq!(
            encode_latitude(Latitude(90.1)),
            Err(EncodeError::InvalidLatitude(90.1))
        );
        assert_eq!(encode_latitude(Latitude(0.0)), Ok("0000.00N".to_string()));
    }

    #[test]
    fn test_encode_longitude() {
        assert_eq!(
            encode_longitude(Longitude(129.05833333333334)),
            Ok("12903.50E".to_string())
        );
        assert_eq!(
            encode_longitude(Longitude(-49.05833333333333)),
            Ok("04903.50W".to_string())
        );
        assert_eq!(
            encode_longitude(Longitude(-180.1)),
            Err(EncodeError::InvalidLongitude(-180.1))
        );
        assert_eq!(
            encode_longitude(Longitude(180.1)),
            Err(EncodeError::InvalidLongitude(180.1))
        );
        assert_eq!(
            encode_longitude(Longitude(0.0)),
            Ok("00000.00E".to_string())
        );
    }
}