use crate::{ common::{parse_direction, parse_f64}, LatLon, }; use nom::{ bytes::complete::take, character::complete::{self, space0, space1}, combinator::{map_opt, opt}, sequence::tuple, IResult, }; use std::{fmt, str::FromStr}; #[derive(PartialEq, Debug, Clone)] 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_ok()); /// ``` 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')), // TODO: Should I allow a direction here nor no opt(parse_direction), space1, parse_f64, opt(complete::char('m')), opt(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 fmt::Display for Coordinate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}{} {} {}", self.zone_num, self.zone_letter, self.easting, self.northing, ) } } 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, }) } }