use crate::{ common::{parse_direction, parse_f64}, Error, LatLon, }; use nom::{ bytes::complete::take, character::complete::{self, space0, space1}, combinator::{map_opt, map_res, opt}, sequence::tuple, IResult, }; use std::{fmt, str::FromStr}; #[derive(PartialEq, Debug, Clone)] pub struct Coordinate { zone_num: u8, zone_letter: char, easting: f64, northing: f64, latlon: LatLon, } impl Coordinate { /// ```rust /// use spatial_coordinate_systems::utm::Coordinate; /// use spatial_coordinate_systems::LatLon; /// /// 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()); /// assert!(Coordinate::parse("34H 261877.8163738246 6243185.589276327").is_ok()); /// ``` pub fn parse(i: &str) -> IResult<&str, Self> { map_res( tuple(( complete::u8, space0, map_opt(take(1_usize), |z: &str| z.chars().next()), 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 or not opt(parse_direction), space1, parse_f64, opt(complete::char('m')), opt(parse_direction), )), |( zone_num, _, zone_letter, _, easting, _, _easting_direction, _, northing, _, _northing_direction, )| -> Result<_, Error> { let latlon = utm::wsg84_utm_to_lat_lon(easting, northing, zone_num, zone_letter)?; Ok(Coordinate { easting, northing, zone_num, zone_letter, latlon: LatLon::try_from(latlon)?, }) }, )(i) } } impl FromStr for Coordinate { type Err = Error; fn from_str(i: &str) -> Result { Self::parse(i).map(|(_, ret)| ret).map_err(Into::into) } } 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 TryFrom for Coordinate { type Error = Error; fn try_from(latlon: LatLon) -> Result { // TODO: This does not feel right let zone_num = utm::lat_lon_to_zone_number(latlon.get_lat(), latlon.get_lon()); let zone_letter = utm::lat_to_zone_letter(latlon.get_lat()).ok_or(Error::NoUtmZoneLetter)?; let (northing, easting, _) = utm::to_utm_wgs84(latlon.get_lat(), latlon.get_lon(), zone_num); Ok(Self { zone_num, zone_letter, easting, northing, latlon: latlon, }) } } impl From<&Coordinate> for LatLon { fn from(coordinate: &Coordinate) -> LatLon { coordinate.latlon } } #[cfg(test)] mod tests { use super::*; #[test] fn test_general() { macro_rules! p { ($tt:tt) => {{ let cvt = Coordinate::from_str($tt); eprintln!("Testing: {} => {:?}", $tt, cvt); assert!(cvt.is_ok()); }}; } dbg!(dbg!(Coordinate::try_from( LatLon::new(-33.92487_f64, 18.42406_f64).unwrap() )) .unwrap() .to_string()); p!("34H 261877.8163738246 6243185.589276327"); assert_eq!( Coordinate::from_str("47N 666962.588 0.000"), Ok(Coordinate { zone_num: 47, zone_letter: 'N', easting: 666962.588, northing: 0.0, latlon: LatLon::new(1.534972646100779e-9, 100.50027886695585).unwrap() }) ); } }