diff --git a/README.adoc b/README.adoc index 78b2880..a64bce3 100644 --- a/README.adoc +++ b/README.adoc @@ -20,6 +20,10 @@ In order of dependency, the role of each of these directories in this repository |Asciidoctor documentation |Design decisions and algorithm definition documentation +|spatial-coordinate-systems +|Rust Crate +|Convert among latitude/longitude (DD), degree-minute (DM), degree-minute-second (DMS), UTM, and Pluscodes + |wordlist |Python/CSV |Wordlist sources and generator code diff --git a/spatial-coordinate-systems/src/dd.rs b/spatial-coordinate-systems/src/dd.rs new file mode 100644 index 0000000..4041c47 --- /dev/null +++ b/spatial-coordinate-systems/src/dd.rs @@ -0,0 +1,30 @@ +use crate::{ + common::{optional_separator, parse_direction}, + Direction, +}; +use nom::{ + branch::alt, + character::complete::{self, space0, space1}, + combinator::{eof, map, map_opt, map_res, opt}, + number::complete::double, + sequence::{pair, tuple}, + IResult, +}; +use std::str::FromStr; + +#[derive(PartialEq, Debug)] +pub struct Coordinate(pub f64, pub f64); + +pub fn parse_coordinate(i: &str) -> IResult<&str, Coordinate> { + map_opt( + tuple((double, optional_separator(','), double)), + |(ns, _, ew)| { + // Ensure this is a north/south then east/west direction + if (-90_f64..=90_f64).contains(&ns) && (-180_f64..=180_f64).contains(&ew) { + Some(Coordinate(ns, ew)) + } else { + None + } + }, + )(i) +} diff --git a/spatial-coordinate-systems/src/dm.rs b/spatial-coordinate-systems/src/dm.rs index 5f8bff2..652e14a 100644 --- a/spatial-coordinate-systems/src/dm.rs +++ b/spatial-coordinate-systems/src/dm.rs @@ -12,13 +12,23 @@ use nom::{ }; use std::str::FromStr; -pub fn parse_coordinate(i: &str) -> IResult<&str, (DegreeMinute, DegreeMinute)> { +#[derive(PartialEq, Debug)] +pub struct Coordinate(pub DM, pub DM); + +#[derive(PartialEq, Debug)] +pub struct DM { + pub degrees: i16, + pub minutes: f64, + pub direction: Direction, +} + +pub fn parse_coordinate(i: &str) -> IResult<&str, Coordinate> { map_opt( tuple((parse, optional_separator(','), parse)), |(ns, _, ew)| { // Ensure this is a north/south then east/west direction if ns.direction.is_lat() && ew.direction.is_lon() { - Some((ns, ew)) + Some(Coordinate(ns, ew)) } else { None } @@ -26,7 +36,7 @@ pub fn parse_coordinate(i: &str) -> IResult<&str, (DegreeMinute, DegreeMinute)> )(i) } -pub fn parse(i: &str) -> IResult<&str, DegreeMinute> { +pub fn parse(i: &str) -> IResult<&str, DM> { map( tuple(( // Degrees @@ -38,7 +48,7 @@ pub fn parse(i: &str) -> IResult<&str, DegreeMinute> { // Direction parse_direction, )), - |(degrees, (), minutes, (), direction)| DegreeMinute { + |(degrees, (), minutes, (), direction)| DM { degrees, minutes, direction, @@ -46,14 +56,7 @@ pub fn parse(i: &str) -> IResult<&str, DegreeMinute> { )(i) } -#[derive(PartialEq, Debug)] -pub struct DegreeMinute { - pub degrees: i16, - pub minutes: f64, - pub direction: Direction, -} - -impl FromStr for DegreeMinute { +impl FromStr for DM { type Err = (); /// Recognizes DMS in the formats: @@ -62,43 +65,22 @@ impl FromStr for DegreeMinute { /// * `40 31 21 N, 105 5 39 W` /// /// ```rust - /// use spatial_coordinate_systems::dm::DegreeMinute; + /// use spatial_coordinate_systems::dm::DM; /// use spatial_coordinate_systems::Direction; /// use std::str::FromStr; /// - /// assert_eq!(DegreeMinute::from_str("40 31.3 N").unwrap(), DegreeMinute { + /// assert_eq!(DM::from_str("40 31.3 N").unwrap(), DM { /// degrees: 40, /// minutes: 31.3_f64, /// direction: Direction::North, /// }); - /// assert_eq!(DegreeMinute::from_str("40°31' N").unwrap(), DegreeMinute { + /// assert_eq!(DM::from_str("40°31' N").unwrap(), DM { /// degrees: 40, /// minutes: 31_f64, /// direction: Direction::North, /// }); /// ``` fn from_str(i: &str) -> Result { - map( - tuple(( - space0, - // Degrees - complete::i16, - optional_separator('°'), - // Minutes - double, - optional_separator('\''), - // Direction - parse_direction, - // Ensure no other characters can be read - eof, - )), - |(_, degrees, (), minutes, (), direction, _)| DegreeMinute { - degrees, - minutes, - direction, - }, - )(i) - .map_err(|_| ()) - .map(|(_, ret)| ret) + parse(i).map_err(|_| ()).map(|(_, ret)| ret) } } diff --git a/spatial-coordinate-systems/src/dms.rs b/spatial-coordinate-systems/src/dms.rs index 01ce209..1d6fafc 100644 --- a/spatial-coordinate-systems/src/dms.rs +++ b/spatial-coordinate-systems/src/dms.rs @@ -12,13 +12,16 @@ use nom::{ }; use std::str::FromStr; -pub fn parse_coordinate(i: &str) -> IResult<&str, (DegreeMinuteSecond, DegreeMinuteSecond)> { +#[derive(PartialEq, Debug)] +pub struct Coordinate(pub DMS, pub DMS); + +pub fn parse_coordinate(i: &str) -> IResult<&str, Coordinate> { map_opt( tuple((parse, optional_separator(','), parse)), |(ns, _, ew)| { // Ensure this is a north/south then east/west direction if ns.direction.is_lat() && ew.direction.is_lon() { - Some((ns, ew)) + Some(Coordinate(ns, ew)) } else { None } @@ -26,7 +29,7 @@ pub fn parse_coordinate(i: &str) -> IResult<&str, (DegreeMinuteSecond, DegreeMin )(i) } -pub fn parse(i: &str) -> IResult<&str, DegreeMinuteSecond> { +pub fn parse(i: &str) -> IResult<&str, DMS> { map( tuple(( // Degrees @@ -41,7 +44,7 @@ pub fn parse(i: &str) -> IResult<&str, DegreeMinuteSecond> { // Direction parse_direction, )), - |(degrees, (), minutes, (), seconds, (), direction)| DegreeMinuteSecond { + |(degrees, (), minutes, (), seconds, (), direction)| DMS { degrees, minutes, seconds, @@ -51,14 +54,14 @@ pub fn parse(i: &str) -> IResult<&str, DegreeMinuteSecond> { } #[derive(PartialEq, Debug)] -pub struct DegreeMinuteSecond { +pub struct DMS { pub degrees: i16, pub minutes: i16, pub seconds: f64, pub direction: Direction, } -impl FromStr for DegreeMinuteSecond { +impl FromStr for DMS { type Err = (); /// Recognizes DMS in the formats: @@ -67,17 +70,17 @@ impl FromStr for DegreeMinuteSecond { /// * `40 31 21 N, 105 5 39 W` /// /// ```rust - /// use spatial_coordinate_systems::dms::DegreeMinuteSecond; + /// use spatial_coordinate_systems::dms::DMS; /// use spatial_coordinate_systems::Direction; /// use std::str::FromStr; /// - /// assert_eq!(DegreeMinuteSecond::from_str("40 31 21 N").unwrap(), DegreeMinuteSecond { + /// assert_eq!(DMS::from_str("40 31 21 N").unwrap(), DMS { /// degrees: 40, /// minutes: 31, /// seconds: 21_f64, /// direction: Direction::North, /// }); - /// assert_eq!(DegreeMinuteSecond::from_str("40°31' 21 \" N").unwrap(), DegreeMinuteSecond { + /// assert_eq!(DMS::from_str("40°31' 21 \" N").unwrap(), DMS { /// degrees: 40, /// minutes: 31, /// seconds: 21_f64, @@ -85,31 +88,6 @@ impl FromStr for DegreeMinuteSecond { /// }); /// ``` fn from_str(i: &str) -> Result { - map( - tuple(( - space0, - // Degrees - complete::i16, - optional_separator('°'), - // Minutes - complete::i16, - optional_separator('\''), - // Seconds - double, - optional_separator('"'), - // Direction - parse_direction, - // Ensure no other characters can be read - eof, - )), - |(_, degrees, (), minutes, (), seconds, (), direction, _)| DegreeMinuteSecond { - degrees, - minutes, - seconds, - direction, - }, - )(i) - .map_err(|_| ()) - .map(|(_, ret)| ret) + parse(i).map_err(|_| ()).map(|(_, ret)| ret) } } diff --git a/spatial-coordinate-systems/src/lib.rs b/spatial-coordinate-systems/src/lib.rs index 64b4740..922a45a 100644 --- a/spatial-coordinate-systems/src/lib.rs +++ b/spatial-coordinate-systems/src/lib.rs @@ -2,11 +2,12 @@ use std::str::FromStr; mod common; +pub mod dd; pub mod dm; pub mod dms; -use dm::DegreeMinute; -use dms::DegreeMinuteSecond; +use dm::DM; +use dms::DMS; use nom::{ branch::alt, character::complete::space0, @@ -46,9 +47,11 @@ impl Direction { } pub enum Coordinate { - LatLon((f64, f64)), - DegreeMinuteSecond((DegreeMinuteSecond, DegreeMinuteSecond)), - DegreeMinute((DegreeMinute, DegreeMinute)), + DD(dd::Coordinate), + DMS(dms::Coordinate), + DM(dm::Coordinate), + // UTM(utm::UTMCoordinate), + // Plus(plus::PlusCoordinate), } pub enum CoordinateSystem { @@ -61,7 +64,13 @@ impl FromStr for Coordinate { fn from_str(i: &str) -> Result { tuple(( space0, - alt((map(dms::parse_coordinate, Coordinate::DegreeMinuteSecond),)), + alt(( + map(dd::parse_coordinate, Coordinate::DD), + map(dms::parse_coordinate, Coordinate::DMS), + map(dm::parse_coordinate, Coordinate::DM), + // map(utm::parse_coordinate, Coordinate::UTM), + // map(plus::parse_coordinate, Coordinate::PLUS), + )), space0, eof, ))(i)