use crate::{ common::{optional_separator, parse_direction, parse_f64}, Direction, LatLon, }; use nom::{ branch::alt, bytes::complete::{take, take_while}, character::complete::{self, space0, space1}, combinator::{eof, map, map_opt, map_res, opt}, number::complete::double, sequence::{pair, tuple}, IResult, }; use std::str::FromStr; use utm::WSG84ToLatLonError; #[derive(PartialEq, Debug)] pub struct Coordinate { pub zone_num: u8, pub zone_letter: char, pub easting: f64, pub northing: f64, } impl Coordinate { /// ```rust /// 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()); /// ``` pub fn parse(i: &str) -> IResult<&str, Self> { map_opt( tuple(( complete::u8, space0, take(1_usize), space1, parse_f64, // TODO: Can there be spaces around the m here or no? opt(complete::char('m')), parse_direction, space1, parse_f64, opt(complete::char('m')), parse_direction, )), |( zone_num, _, zone_letter, _, easting, _, _easting_direction, _, northing, _, _northing_direction, )| { let ret = Coordinate { easting, northing, zone_num, zone_letter: zone_letter.chars().next()?, }; // Ensure it can be parsed first utm::wsg84_utm_to_lat_lon(ret.easting, ret.northing, ret.zone_num, ret.zone_letter) .ok() .and(Some(ret)) }, )(i) } } impl FromStr for Coordinate { type Err = (); fn from_str(i: &str) -> Result { 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, }) } }