diff --git a/spatial-coordinate-systems/src/lib.rs b/spatial-coordinate-systems/src/lib.rs index 8f5258b..5817651 100644 --- a/spatial-coordinate-systems/src/lib.rs +++ b/spatial-coordinate-systems/src/lib.rs @@ -6,6 +6,7 @@ pub mod dmm; pub mod dms; pub mod latlon; pub mod plus; +pub mod skyvector; pub mod urls; pub mod utm; pub use error::Error; @@ -50,10 +51,16 @@ impl Direction { pub fn is_lat(&self) -> bool { self == &Direction::North || self == &Direction::South } + /// True if this is east/west pub fn is_lon(&self) -> bool { self == &Direction::East || self == &Direction::West } + + /// True if this is "positive" (North of East) + pub fn is_positive(&self) -> bool { + self == &Self::North || self == &Self::East + } } #[derive(Debug, PartialEq, Clone, EnumDiscriminants)] @@ -71,6 +78,7 @@ pub enum Coordinate { UTM(utm::Coordinate), // Xpin(xpin::Xpin), Plus(plus::Coordinate), + Skyvector(skyvector::Coordinate), } impl Coordinate { @@ -90,6 +98,7 @@ impl Coordinate { map(dd::Coordinate::parse, Coordinate::DD), // map(xpin::Coordinate::parse, Cordinate::Xpin), map(plus::Coordinate::parse, Coordinate::Plus), + map(skyvector::Coordinate::parse, Coordinate::Skyvector), // Try to parse as a URL last map(urls::CoordinateUrls::parse, |coordinate_urls| { Coordinate::DD(dd::Coordinate::from(coordinate_urls.latlon)) @@ -111,6 +120,7 @@ impl Coordinate { CoordinateType::DMM => Self::DMM(dmm::Coordinate::from(lat_lon)), CoordinateType::UTM => Self::UTM(utm::Coordinate::try_from(lat_lon)?), CoordinateType::Plus => Self::Plus(plus::Coordinate::try_from(lat_lon)?), + CoordinateType::Skyvector => Self::Skyvector(skyvector::Coordinate::from(lat_lon)), }) } @@ -122,6 +132,7 @@ impl Coordinate { Self::UTM(_) => CoordinateType::UTM, // Self::Xpin(_) => CoordinateType::Xpin, Self::Plus(_) => CoordinateType::Plus, + Self::Skyvector(_) => CoordinateType::Skyvector, } } } @@ -141,6 +152,7 @@ impl From<&Coordinate> for LatLon { Coordinate::UTM(utm) => utm.into(), // Coordinate::Xpin(xpin) => xpin.into(), Coordinate::Plus(plus) => plus.into(), + Coordinate::Skyvector(skyvector) => skyvector.into(), } } } @@ -184,6 +196,11 @@ impl From for Coordinate { Self::Plus(c) } } +impl From for Coordinate { + fn from(c: skyvector::Coordinate) -> Self { + Self::Skyvector(c) + } +} // impl TryFrom for dms::Coordinate { // type Error = Error; @@ -218,6 +235,7 @@ impl fmt::Display for Coordinate { Coordinate::DMS(dms) => write!(f, "{}", dms), Coordinate::UTM(utm) => write!(f, "{}", utm), Coordinate::Plus(plus) => write!(f, "{}", plus), + Coordinate::Skyvector(skyvector) => write!(f, "{}", skyvector), } } } diff --git a/spatial-coordinate-systems/src/skyvector.rs b/spatial-coordinate-systems/src/skyvector.rs new file mode 100644 index 0000000..1a16a97 --- /dev/null +++ b/spatial-coordinate-systems/src/skyvector.rs @@ -0,0 +1,183 @@ +use crate::{ + common::{optional_separator, parse_direction, parse_f64}, + dms::DMS, + Direction, Error, LatLon, +}; +use nom::{ + branch::alt, + bytes::complete::take, + bytes::complete::take_while1, + character::complete::{self, digit1, space0}, + combinator::{map, map_parser, map_res, opt}, + sequence::tuple, + IResult, +}; +use std::{fmt, str::FromStr}; + +#[derive(PartialEq, Debug, Clone)] +pub struct Coordinate(String, LatLon); + +impl Coordinate { + pub fn parse(i: &str) -> IResult<&str, Self> { + map_res( + tuple(( + // Lat + Self::parse_n_digits::<2>, + opt(Self::parse_n_digits::<2>), + opt(Self::parse_n_digits::<2>), + parse_direction, + // Separator + space0, + complete::char('/'), + space0, + // Lon + Self::parse_n_digits::<3>, + opt(Self::parse_n_digits::<2>), + opt(Self::parse_n_digits::<2>), + parse_direction, + )), + |(lat_d, lat_m, lat_s, lat_direction, _, _, _, lon_d, lon_m, lon_s, lon_direction)| { + // Ensure this is a north/south then east/west direction + if !lat_direction.is_lat() { + Err(Error::InvalidLatitudeBearing(lat_direction)) + } else if !lon_direction.is_lon() { + Err(Error::InvalidLongitudeBearing(lon_direction)) + } else { + let lat = Self::dms_to_f64_helper(lat_d, lat_m, lat_s, &lat_direction); + let lon = Self::dms_to_f64_helper(lon_d, lon_m, lon_s, &lon_direction); + + let latlon = LatLon::new(lat, lon)?; + // Ok(Coordinate(..., latlon)) + + // let latlon = LatLon::new(lat.to_decimal_degrees(), lon.to_decimal_degrees())?; + // Ok(Coordinate(lat, lon, latlon)) + todo!(); + } + }, + )(i) + } + + /// Helper to convert u8, Option, Option into an f64 + fn dms_to_f64_helper(d: u8, m: Option, s: Option, direction: &Direction) -> f64 { + (d as f64 + + m.map(|m| m as f64 / 100.0_f64).unwrap_or(0.0_f64) + + s.map(|s| s as f64 / 10_000.0_f64).unwrap_or(0.0_f64)) + * if direction.is_positive() { + 1.0_f64 + } else { + -1.0_f64 + } + } + + /// Takes n digits from the input and parses it as a u8 + fn parse_n_digits(i: &str) -> IResult<&str, u8> { + map_parser(take(C), complete::u8)(i) + } + + fn latlon_to_string(latlon: &LatLon) -> String { + let dms = crate::dms::Coordinate::from(latlon.clone()); + let lat = dms.get_lat(); + let lon = dms.get_lon(); + format!( + "{}{}{}{}{}{}{}{}", + lat.degrees, + lat.minutes, + lat.seconds, + lat.direction, + lon.degrees, + lon.minutes, + lon.seconds, + lon.direction, + ) + } +} + +impl FromStr for Coordinate { + type Err = Error; + + fn from_str(i: &str) -> Result { + Self::parse(i).map_err(Into::into).map(|(_, ret)| ret) + } +} + +impl fmt::Display for Coordinate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}, {}", self.0, self.1) + } +} + +impl From<&Coordinate> for LatLon { + fn from(coordinate: &Coordinate) -> LatLon { + coordinate.1 + } +} + +impl From for Coordinate { + fn from(latlon: LatLon) -> Self { + // TODO: Encode the source and target + todo!() + // Self(ret, latlon) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_helpers() { + assert_eq!( + Coordinate::dms_to_f64_helper(90, None, None, &Direction::North), + 90.0000_f64 + ); + assert_eq!( + Coordinate::dms_to_f64_helper(90, None, None, &Direction::South), + -90.0000_f64 + ); + assert_eq!( + Coordinate::dms_to_f64_helper(90, Some(12), Some(8), &Direction::North), + 90.1208_f64 + ); + assert_eq!( + Coordinate::dms_to_f64_helper(90, None, Some(8), &Direction::North), + 90.0008_f64 + ); + } + + // fn test_general() { + // macro_rules! p { + // ($tt:tt) => {{ + // let cvt = Coordinate::from_str($tt); + // eprintln!("Testing: {} => {:?}", $tt, cvt); + // assert!(cvt.is_ok()); + // }}; + // ($tt:tt, DMS) => {{ + // let cvt = DMS::parse($tt); + // eprintln!("Testing: {} => {:?}", $tt, cvt); + // assert!(cvt.is_ok()); + // }}; + // } + // // p!(r#"0° 0' 0" N 100° 30' 1" W"#); + // // p!(r#"0 0 0 N 100 30 1 W"#); + + // p!("0° 0' N", DMS); + // p!("0° 0'N", DMS); + // p!("N 0° 0'", DMS); + + // p!("0° 0' N", DMS); + // p!("0° 0'N", DMS); + // p!("N 0° 0' 0", DMS); + + // p!(r#"E 100° 30'"#, DMS); + // p!(r#"N 0° 0' E 100° 30'"#); + + // // parse_dmm_numeric(r#"38 53.2148425"#).unwrap(); + + // // p!(r#"38 53.2148425"#, DMS); + // // p!(r#"38 53.2148425'"#, DMS); + // // p!(r#"38° 53.2148425"#, DMS); + // // p!(r#"38° 53.2148425'"#, DMS); + // // p!(r#"-77° -1.7611312866219464'"#, DMS); + // // p!(r#"38° 53.2148425', -77° -1.7611312866219464'"#); + // } +}