From 11e158b0e8a245e3ec0c1d9962c75720271651b9 Mon Sep 17 00:00:00 2001 From: Austen Adler Date: Sun, 19 Mar 2023 22:33:30 -0400 Subject: [PATCH] Add TryFrom and TryInto for LatLon --- spatial-coordinate-systems/src/common.rs | 24 +++++++++- spatial-coordinate-systems/src/dd.rs | 22 +++++++-- spatial-coordinate-systems/src/dmm.rs | 36 ++++++++++++++- spatial-coordinate-systems/src/dms.rs | 38 ++++++++++++++- spatial-coordinate-systems/src/plus.rs | 51 ++++++++++++++++---- spatial-coordinate-systems/src/utm.rs | 59 ++++++++++++++++-------- 6 files changed, 193 insertions(+), 37 deletions(-) diff --git a/spatial-coordinate-systems/src/common.rs b/spatial-coordinate-systems/src/common.rs index e148017..7796a22 100644 --- a/spatial-coordinate-systems/src/common.rs +++ b/spatial-coordinate-systems/src/common.rs @@ -1,11 +1,11 @@ use nom::{ branch::alt, - bytes::complete::tag_no_case, + bytes::complete::{tag_no_case, take_while1}, character::{ complete::{space0, space1}, *, }, - combinator::{map, opt}, + combinator::{map, map_opt, opt}, sequence::tuple, IResult, }; @@ -34,3 +34,23 @@ pub fn parse_direction(i: &str) -> IResult<&str, Direction> { map(tag_no_case("w"), |_| Direction::West), ))(i) } + +pub fn parse_f64(i: &str) -> IResult<&str, f64> { + map_opt( + take_while1(|c: char| c.is_ascii_digit() || c == '-' || c == '.'), + |n: &str| n.parse::().ok(), + )(i) +} + +#[cfg(tests)] +mod tests { + use super::*; + + #[test] + fn test_parse_f64() { + assert_eq!(parse_f64("1"), Ok(("", 1_f64))); + assert_eq!(parse_f64("1.0"), Ok(("", 1.0_f64))); + assert_eq!(parse_f64("1.0e2"), Ok(("e2", 1.0_f64))); + assert_eq!(parse_f64("1.0x"), Ok(("x", 1.0_f64))); + } +} diff --git a/spatial-coordinate-systems/src/dd.rs b/spatial-coordinate-systems/src/dd.rs index 21df2bc..9ea101f 100644 --- a/spatial-coordinate-systems/src/dd.rs +++ b/spatial-coordinate-systems/src/dd.rs @@ -24,14 +24,26 @@ impl Coordinate { tuple((double, optional_separator(','), double)), |(lat, _, lon)| { // Ensure this is a north/south then east/west direction - if (-90_f64..=90_f64).contains(&lat) && (-180_f64..=180_f64).contains(&lon) { - Some(Coordinate { lat: lat, lon: lon }) - } else { - None - } + Self::from(lat, lon) }, )(i) } + + pub fn from(lat: f64, lon: f64) -> Option { + if (-90_f64..=90_f64).contains(&lat) && (-180_f64..=180_f64).contains(&lon) { + Some(Self { lat: lat, lon: lon }) + } else { + None + } + } +} + +impl TryFrom<(f64, f64)> for Coordinate { + type Error = (); + + fn try_from((lat, lon): (f64, f64)) -> Result { + Self::from(lat, lon).ok_or(()) + } } impl FromStr for Coordinate { diff --git a/spatial-coordinate-systems/src/dmm.rs b/spatial-coordinate-systems/src/dmm.rs index 1ef6ab8..1dc1ef8 100644 --- a/spatial-coordinate-systems/src/dmm.rs +++ b/spatial-coordinate-systems/src/dmm.rs @@ -1,6 +1,6 @@ use crate::{ common::{optional_separator, parse_direction}, - Direction, + Direction, LatLon, }; use nom::{ branch::alt, @@ -10,10 +10,11 @@ use nom::{ sequence::{pair, tuple}, IResult, }; -use std::str::FromStr; +use std::{convert::Infallible, str::FromStr}; #[derive(PartialEq, Debug)] pub struct Coordinate(pub DMM, pub DMM); + impl Coordinate { pub fn parse(i: &str) -> IResult<&str, Self> { map_opt( @@ -95,3 +96,34 @@ impl FromStr for DMM { DMM::parse(i).map_err(|_| ()).map(|(_, ret)| ret) } } + +impl TryInto for Coordinate { + type Error = (); + + fn try_into(self) -> Result { + LatLon::from( + self.0.degrees as f64 + self.0.minutes * 60.0_f64, + self.1.degrees as f64 + self.1.minutes * 60.0_f64, + ) + .ok_or(()) + } +} + +impl TryFrom for Coordinate { + type Error = Infallible; + + fn try_from(value: LatLon) -> Result { + Ok(Self( + DMM { + degrees: value.lat as i16, + minutes: value.lat.fract() * 60_f64, + direction: Direction::North, + }, + DMM { + degrees: value.lon as i16, + minutes: value.lon.fract() * 60_f64, + direction: Direction::North, + }, + )) + } +} diff --git a/spatial-coordinate-systems/src/dms.rs b/spatial-coordinate-systems/src/dms.rs index 74eb139..735d8ea 100644 --- a/spatial-coordinate-systems/src/dms.rs +++ b/spatial-coordinate-systems/src/dms.rs @@ -1,6 +1,6 @@ use crate::{ common::{optional_separator, parse_direction}, - Direction, + Direction, LatLon, }; use nom::{ branch::alt, @@ -103,3 +103,39 @@ impl FromStr for DMS { DMS::parse(i).map_err(|_| ()).map(|(_, ret)| ret) } } + +impl TryInto for Coordinate { + type Error = (); + + fn try_into(self) -> Result { + Err(()) + + // LatLon::from( + // self.0.degrees as f64 + self.0.minutes * 60.0_f64, + // self.1.degrees as f64 + self.1.minutes * 60.0_f64, + // ) + // .ok_or(()) + } +} + +impl TryFrom for Coordinate { + type Error = (); + + fn try_from(value: LatLon) -> Result { + Err(()) + // Ok(Self( + // DMS { + // degrees: value.lat as i16, + // minutes: value.lat.fract() * 60_f64 as i16, + // seconds: value.lat.fract() * 3600_f64, + // direction: Direction::North, + // }, + // DMS { + // degrees: value.lon as i16, + // minutes: value.lon.fract() * 60_f64 as i16, + // seconds: value.lon.fract() * 3600_f64, + // direction: Direction::North, + // }, + // )) + } +} diff --git a/spatial-coordinate-systems/src/plus.rs b/spatial-coordinate-systems/src/plus.rs index 5389536..7a4c90c 100644 --- a/spatial-coordinate-systems/src/plus.rs +++ b/spatial-coordinate-systems/src/plus.rs @@ -1,6 +1,6 @@ use crate::{ common::{optional_separator, parse_direction}, - Direction, + Direction, LatLon, }; use nom::{ branch::alt, @@ -13,20 +13,24 @@ use nom::{ }; use std::str::FromStr; -const PLUSCODE_CHARS: [char; 22] = [ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', 'F', 'G', 'H', 'J', 'M', 'P', 'Q', 'R', - 'V', 'W', 'X', +// TODO: Pick an approprite length +const PLUSCODE_LENGTH: usize = 8; +const PLUSCODE_CHARS: [char; 23] = [ + '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', 'F', 'G', 'H', 'J', 'M', 'P', 'Q', + 'R', 'V', 'W', 'X', ]; #[derive(PartialEq, Debug)] -pub struct Coordinate(pub f64, pub f64); +pub struct Coordinate(pub String); impl Coordinate { pub fn parse(i: &str) -> IResult<&str, Self> { map_opt(pluscode_chars, |c| { - pluscodes::decode(c) - .map(|coord| Coordinate(coord.latitude, coord.longitude)) - .ok() + // Check if it can be decoded first + pluscodes::decode(c).ok()?; + + // It can! Store it + Some(Coordinate(c.to_string())) })(i) } } @@ -42,3 +46,34 @@ impl FromStr for Coordinate { fn pluscode_chars(i: &str) -> IResult<&str, &str> { take_while(|c| PLUSCODE_CHARS.contains(&c))(i) } + +impl TryInto for Coordinate { + type Error = (); + + fn try_into(self) -> Result { + pluscodes::decode(&self.0) + .map( + |pluscodes::Coordinate { + latitude: lat, + longitude: lon, + }| { LatLon::from(lat, lon) }, + ) + .map_err(|_| ())? + .ok_or(()) + } +} + +impl TryFrom for Coordinate { + type Error = pluscodes::Error; + + fn try_from(value: LatLon) -> Result { + pluscodes::encode( + &pluscodes::Coordinate { + latitude: value.lat, + longitude: value.lon, + }, + PLUSCODE_LENGTH, + ) + .map(Coordinate) + } +} diff --git a/spatial-coordinate-systems/src/utm.rs b/spatial-coordinate-systems/src/utm.rs index e65a5a1..67051f3 100644 --- a/spatial-coordinate-systems/src/utm.rs +++ b/spatial-coordinate-systems/src/utm.rs @@ -1,6 +1,6 @@ use crate::{ - common::{optional_separator, parse_direction}, - Direction, + common::{optional_separator, parse_direction, parse_f64}, + Direction, LatLon, }; use nom::{ branch::alt, @@ -12,6 +12,7 @@ use nom::{ IResult, }; use std::str::FromStr; +use utm::WSG84ToLatLonError; #[derive(PartialEq, Debug)] pub struct Coordinate { @@ -26,6 +27,8 @@ impl Coordinate { /// use spatial_coordinate_systems::utm::Coordinate; /// /// assert!(Coordinate::parse("10S 706832mE 4344683mN").is_ok()); + /// assert!(Coordinate::parse("10S 706832mE 4344683N").is_ok()); + /// assert!(Coordinate::parse("10S 706832E 4344683N").is_ok()); /// assert!(Coordinate::parse("10S706832mE 4344683mN").is_err()); /// assert!(Coordinate::parse("10S 706832mE 4344683m").is_err()); /// ``` @@ -36,12 +39,12 @@ impl Coordinate { space0, take(1_usize), space1, - double, - //TODO: Can there be spaces around the m here or no? + parse_f64, + // TODO: Can there be spaces around the m here or no? opt(complete::char('m')), parse_direction, space1, - double, + parse_f64, opt(complete::char('m')), parse_direction, )), @@ -52,24 +55,12 @@ impl Coordinate { _, easting, _, - easting_direction, + _easting_direction, _, northing, _, - northing_direction, + _northing_direction, )| { - eprintln!( - "Debug: {:?}", - ( - zone_num, - zone_letter, - easting, - easting_direction, - northing, - northing_direction - ) - ); - let ret = Coordinate { easting, northing, @@ -93,3 +84,33 @@ impl FromStr for Coordinate { Self::parse(i).map_err(|_| ()).map(|(_, ret)| ret) } } + +impl TryInto for Coordinate { + type Error = (); + + fn try_into(self) -> Result { + LatLon::try_from( + utm::wsg84_utm_to_lat_lon(self.easting, self.northing, self.zone_num, self.zone_letter) + // TODO: Return the actual error + .map_err(|_| ())?, + ) + } +} + +impl TryFrom for Coordinate { + type Error = (); + + fn try_from(value: LatLon) -> Result { + // TODO: This does not feel right + let zone_num = utm::lat_lon_to_zone_number(value.lat, value.lon); + let zone_letter = utm::lat_to_zone_letter(value.lat).ok_or(())?; + let (easting, northing, _) = utm::to_utm_wgs84(value.lat, value.lon, zone_num); + + Ok(Self { + zone_num, + zone_letter, + easting, + northing, + }) + } +}