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 DMM, pub DMM); impl Coordinate { pub fn parse(i: &str) -> IResult<&str, Self> { map_opt( tuple((DMM::parse, optional_separator(','), DMM::parse)), |(ns, _, ew)| { // Ensure this is a north/south then east/west direction if ns.direction.is_lat() && ew.direction.is_lon() { Some(Coordinate(ns, ew)) } else { None } }, )(i) } } impl FromStr for Coordinate { type Err = (); fn from_str(i: &str) -> Result { Self::parse(i).map_err(|_| ()).map(|(_, ret)| ret) } } #[derive(PartialEq, Debug)] pub struct DMM { pub degrees: i16, pub minutes: f64, pub direction: Direction, } impl DMM { pub fn parse(i: &str) -> IResult<&str, DMM> { map( tuple(( // Degrees complete::i16, optional_separator('°'), // Minutes double, optional_separator('\''), // Direction parse_direction, )), |(degrees, (), minutes, (), direction)| DMM { degrees, minutes, direction, }, )(i) } } impl FromStr for DMM { 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::dmm::DMM; /// use spatial_coordinate_systems::Direction; /// use std::str::FromStr; /// /// assert_eq!(DMM::from_str("40 31.3 N").unwrap(), DMM { /// degrees: 40, /// minutes: 31.3_f64, /// direction: Direction::North, /// }); /// assert_eq!(DMM::from_str("40°31' N").unwrap(), DMM { /// degrees: 40, /// minutes: 31_f64, /// direction: Direction::North, /// }); /// ``` fn from_str(i: &str) -> Result { DMM::parse(i).map_err(|_| ()).map(|(_, ret)| ret) } }