diff --git a/Cargo.lock b/Cargo.lock index dd6b67d..6c71a0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,30 @@ dependencies = [ "subtle", ] +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + [[package]] name = "approx" version = "0.4.0" @@ -65,7 +89,7 @@ checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -76,7 +100,7 @@ checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -199,6 +223,21 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "cookie" version = "0.16.2" @@ -298,7 +337,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -502,6 +541,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -716,6 +764,12 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "0.8.6" @@ -748,6 +802,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -859,7 +923,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -918,6 +982,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pluscodes" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d3288e02604f234a7071cba77560e6a6e47395268bf80611f7e2347b082efa" +dependencies = [ + "anyhow", + "regex", + "structopt", +] + [[package]] name = "polyval" version = "0.6.0" @@ -937,10 +1012,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] -name = "proc-macro2" -version = "1.0.50" +name = "proc-macro-error" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.107", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" dependencies = [ "unicode-ident", ] @@ -953,16 +1052,16 @@ checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "version_check", "yansi", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1023,7 +1122,7 @@ checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1032,6 +1131,8 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -1110,7 +1211,7 @@ dependencies = [ "proc-macro2", "quote", "rocket_http", - "syn", + "syn 1.0.107", "unicode-xid", ] @@ -1162,7 +1263,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn", + "syn 1.0.107", "walkdir", ] @@ -1238,7 +1339,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1312,6 +1413,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "spatial-coordinate-systems" +version = "0.1.0" +dependencies = [ + "nom", + "pluscodes", + "thiserror", + "utm", +] + [[package]] name = "spin" version = "0.9.5" @@ -1336,6 +1447,36 @@ dependencies = [ "loom", ] +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.107", +] + [[package]] name = "subtle" version = "2.4.1" @@ -1353,6 +1494,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d3276aee1fa0c33612917969b5172b5be2db051232a6e4826f1a1a9191b045" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -1368,23 +1520,32 @@ dependencies = [ ] [[package]] -name = "thiserror" -version = "1.0.38" +name = "textwrap" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.2", ] [[package]] @@ -1451,7 +1612,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1514,7 +1675,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1595,7 +1756,7 @@ checksum = "d337d3be617449165cb4633c8dece429afd83f84051024079f97ad32a9663716" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1620,6 +1781,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -1636,12 +1809,24 @@ dependencies = [ "subtle", ] +[[package]] +name = "utm" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09e3b3a0abd1ccb77673a6b7b8875d9d1c80626154add451cf18392dc4c3c" + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.4" @@ -1696,7 +1881,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-shared", ] @@ -1718,7 +1903,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 7e5e44a..29734f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "./xpin-wasm/", "./web", "./web-frontend/", + "./spatial-coordinate-systems/", ] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/spatial-coordinate-systems/Cargo.toml b/spatial-coordinate-systems/Cargo.toml new file mode 100644 index 0000000..248b085 --- /dev/null +++ b/spatial-coordinate-systems/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "spatial-coordinate-systems" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom = "7.1.3" +pluscodes = "0.5.0" +thiserror = "1.0.40" +utm = "0.1.6" diff --git a/spatial-coordinate-systems/src/common.rs b/spatial-coordinate-systems/src/common.rs new file mode 100644 index 0000000..e148017 --- /dev/null +++ b/spatial-coordinate-systems/src/common.rs @@ -0,0 +1,36 @@ +use nom::{ + branch::alt, + bytes::complete::tag_no_case, + character::{ + complete::{space0, space1}, + *, + }, + combinator::{map, opt}, + sequence::tuple, + IResult, +}; + +use crate::Direction; + +pub fn optional_separator<'a>(sep: char) -> impl FnMut(&'a str) -> IResult<&'a str, ()> { + map( + alt(( + map(tuple((space0, complete::char(sep), space0)), std::mem::drop), + map(space1, std::mem::drop), + )), + std::mem::drop, + ) +} + +pub fn parse_direction(i: &str) -> IResult<&str, Direction> { + alt(( + map(tag_no_case("north"), |_| Direction::North), + map(tag_no_case("south"), |_| Direction::South), + map(tag_no_case("east"), |_| Direction::East), + map(tag_no_case("west"), |_| Direction::West), + map(tag_no_case("n"), |_| Direction::North), + map(tag_no_case("s"), |_| Direction::South), + map(tag_no_case("e"), |_| Direction::East), + map(tag_no_case("w"), |_| Direction::West), + ))(i) +} diff --git a/spatial-coordinate-systems/src/dm.rs b/spatial-coordinate-systems/src/dm.rs new file mode 100644 index 0000000..5f8bff2 --- /dev/null +++ b/spatial-coordinate-systems/src/dm.rs @@ -0,0 +1,104 @@ +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; + +pub fn parse_coordinate(i: &str) -> IResult<&str, (DegreeMinute, DegreeMinute)> { + 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)) + } else { + None + } + }, + )(i) +} + +pub fn parse(i: &str) -> IResult<&str, DegreeMinute> { + map( + tuple(( + // Degrees + complete::i16, + optional_separator('°'), + // Minutes + double, + optional_separator('\''), + // Direction + parse_direction, + )), + |(degrees, (), minutes, (), direction)| DegreeMinute { + degrees, + minutes, + direction, + }, + )(i) +} + +#[derive(PartialEq, Debug)] +pub struct DegreeMinute { + pub degrees: i16, + pub minutes: f64, + pub direction: Direction, +} + +impl FromStr for DegreeMinute { + type Err = (); + + /// Recognizes DMS in the formats: + /// + /// * `40° 31' 21" N, 105° 5' 39" W` + /// * `40 31 21 N, 105 5 39 W` + /// + /// ```rust + /// use spatial_coordinate_systems::dm::DegreeMinute; + /// use spatial_coordinate_systems::Direction; + /// use std::str::FromStr; + /// + /// assert_eq!(DegreeMinute::from_str("40 31.3 N").unwrap(), DegreeMinute { + /// degrees: 40, + /// minutes: 31.3_f64, + /// direction: Direction::North, + /// }); + /// assert_eq!(DegreeMinute::from_str("40°31' N").unwrap(), DegreeMinute { + /// 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) + } +} diff --git a/spatial-coordinate-systems/src/dms.rs b/spatial-coordinate-systems/src/dms.rs new file mode 100644 index 0000000..01ce209 --- /dev/null +++ b/spatial-coordinate-systems/src/dms.rs @@ -0,0 +1,115 @@ +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; + +pub fn parse_coordinate(i: &str) -> IResult<&str, (DegreeMinuteSecond, DegreeMinuteSecond)> { + 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)) + } else { + None + } + }, + )(i) +} + +pub fn parse(i: &str) -> IResult<&str, DegreeMinuteSecond> { + map( + tuple(( + // Degrees + complete::i16, + optional_separator('°'), + // Minutes + complete::i16, + optional_separator('\''), + // Seconds + double, + optional_separator('"'), + // Direction + parse_direction, + )), + |(degrees, (), minutes, (), seconds, (), direction)| DegreeMinuteSecond { + degrees, + minutes, + seconds, + direction, + }, + )(i) +} + +#[derive(PartialEq, Debug)] +pub struct DegreeMinuteSecond { + pub degrees: i16, + pub minutes: i16, + pub seconds: f64, + pub direction: Direction, +} + +impl FromStr for DegreeMinuteSecond { + type Err = (); + + /// Recognizes DMS in the formats: + /// + /// * `40° 31' 21" N, 105° 5' 39" W` + /// * `40 31 21 N, 105 5 39 W` + /// + /// ```rust + /// use spatial_coordinate_systems::dms::DegreeMinuteSecond; + /// use spatial_coordinate_systems::Direction; + /// use std::str::FromStr; + /// + /// assert_eq!(DegreeMinuteSecond::from_str("40 31 21 N").unwrap(), DegreeMinuteSecond { + /// degrees: 40, + /// minutes: 31, + /// seconds: 21_f64, + /// direction: Direction::North, + /// }); + /// assert_eq!(DegreeMinuteSecond::from_str("40°31' 21 \" N").unwrap(), DegreeMinuteSecond { + /// degrees: 40, + /// minutes: 31, + /// seconds: 21_f64, + /// direction: Direction::North, + /// }); + /// ``` + 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) + } +} diff --git a/spatial-coordinate-systems/src/lib.rs b/spatial-coordinate-systems/src/lib.rs new file mode 100644 index 0000000..64b4740 --- /dev/null +++ b/spatial-coordinate-systems/src/lib.rs @@ -0,0 +1,71 @@ +#![allow(unused_imports)] + +use std::str::FromStr; +mod common; +pub mod dm; +pub mod dms; + +use dm::DegreeMinute; +use dms::DegreeMinuteSecond; +use nom::{ + branch::alt, + character::complete::space0, + combinator::{eof, map}, + sequence::tuple, + IResult, +}; + +pub struct LatLon { + pub lat: f64, + pub lon: f64, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Unknown")] + Unknown, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Direction { + North, + South, + East, + West, +} + +impl Direction { + /// True if this is north/south + 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 + } +} + +pub enum Coordinate { + LatLon((f64, f64)), + DegreeMinuteSecond((DegreeMinuteSecond, DegreeMinuteSecond)), + DegreeMinute((DegreeMinute, DegreeMinute)), +} + +pub enum CoordinateSystem { + LatLon, +} + +impl FromStr for Coordinate { + type Err = (); + + fn from_str(i: &str) -> Result { + tuple(( + space0, + alt((map(dms::parse_coordinate, Coordinate::DegreeMinuteSecond),)), + space0, + eof, + ))(i) + .map(|(_, (_, coordinate, _, _))| coordinate) + .map_err(|_| ()) + } +}