use crate::{ common::{optional_separator, parse_direction, parse_f64}, Direction, LatLon, }; use dms_coordinates::{Bearing, DMS}; use nom::{ character::complete::{self}, combinator::{map, map_opt}, sequence::tuple, IResult, }; use std::{convert::Infallible, fmt, 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: i32, pub minutes: f64, pub direction: Direction, } impl DMM { pub fn parse(i: &str) -> IResult<&str, DMM> { map( tuple(( // Degrees complete::i32, optional_separator('°'), // Minutes parse_f64, optional_separator('\''), // Direction parse_direction, )), |(degrees, (), minutes, (), direction)| DMM { degrees, minutes, direction, }, )(i) } } impl fmt::Display for DMM { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}° {}'", self.degrees, self.minutes) } } impl fmt::Display for Coordinate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}, {}", self.0, self.1) } } impl TryInto for Coordinate { type Error = (); fn try_into(self) -> Result { LatLon::from( DMS::new( self.0.degrees, self.0.minutes as i32, self.0.minutes.fract() * 60_f64, match self.0.direction { Direction::North => Bearing::North, Direction::South => Bearing::South, Direction::East => Bearing::East, Direction::West => Bearing::West, }, ) .to_decimal_degrees(), DMS::new( self.1.degrees, self.1.minutes as i32, self.1.minutes.fract() * 60_f64, match self.1.direction { Direction::North => Bearing::North, Direction::South => Bearing::South, Direction::East => Bearing::East, Direction::West => Bearing::West, }, ) .to_decimal_degrees(), ) .ok_or(()) } } impl TryFrom for Coordinate { type Error = Infallible; fn try_from(value: LatLon) -> Result { let lat = DMS::from_decimal_degrees(value.lat, true); let lon = DMS::from_decimal_degrees(value.lon, false); Ok(Self( DMM { degrees: lat.degrees, minutes: lat.minutes as f64 + lat.seconds / 60_f64, // TODO: Fix the bearing information for these direction: Direction::North, }, DMM { degrees: lon.degrees, minutes: lon.minutes as f64 + lon.seconds / 60_f64, direction: Direction::North, }, )) } }