2023-03-19 21:03:10 -04:00
|
|
|
use crate::{
|
2023-03-19 23:19:19 -04:00
|
|
|
common::{parse_direction, parse_f64}, LatLon,
|
2023-03-19 21:03:10 -04:00
|
|
|
};
|
|
|
|
use nom::{
|
2023-03-19 23:19:19 -04:00
|
|
|
bytes::complete::{take},
|
2023-03-19 21:03:10 -04:00
|
|
|
character::complete::{self, space0, space1},
|
2023-03-19 23:19:19 -04:00
|
|
|
combinator::{map_opt, opt},
|
|
|
|
sequence::{tuple},
|
2023-03-19 21:03:10 -04:00
|
|
|
IResult,
|
|
|
|
};
|
|
|
|
use std::str::FromStr;
|
2023-03-19 23:19:19 -04:00
|
|
|
|
2023-03-19 21:03:10 -04:00
|
|
|
|
|
|
|
#[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());
|
2023-03-19 22:33:30 -04:00
|
|
|
/// assert!(Coordinate::parse("10S 706832mE 4344683N").is_ok());
|
|
|
|
/// assert!(Coordinate::parse("10S 706832E 4344683N").is_ok());
|
2023-03-19 21:03:10 -04:00
|
|
|
/// 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,
|
2023-03-19 22:33:30 -04:00
|
|
|
parse_f64,
|
|
|
|
// TODO: Can there be spaces around the m here or no?
|
2023-03-19 21:03:10 -04:00
|
|
|
opt(complete::char('m')),
|
|
|
|
parse_direction,
|
|
|
|
space1,
|
2023-03-19 22:33:30 -04:00
|
|
|
parse_f64,
|
2023-03-19 21:03:10 -04:00
|
|
|
opt(complete::char('m')),
|
|
|
|
parse_direction,
|
|
|
|
)),
|
|
|
|
|(
|
|
|
|
zone_num,
|
|
|
|
_,
|
|
|
|
zone_letter,
|
|
|
|
_,
|
|
|
|
easting,
|
|
|
|
_,
|
2023-03-19 22:33:30 -04:00
|
|
|
_easting_direction,
|
2023-03-19 21:03:10 -04:00
|
|
|
_,
|
|
|
|
northing,
|
|
|
|
_,
|
2023-03-19 22:33:30 -04:00
|
|
|
_northing_direction,
|
2023-03-19 21:03:10 -04:00
|
|
|
)| {
|
|
|
|
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, Self::Err> {
|
|
|
|
Self::parse(i).map_err(|_| ()).map(|(_, ret)| ret)
|
|
|
|
}
|
|
|
|
}
|
2023-03-19 22:33:30 -04:00
|
|
|
|
|
|
|
impl TryInto<LatLon> for Coordinate {
|
|
|
|
type Error = ();
|
|
|
|
|
|
|
|
fn try_into(self) -> Result<LatLon, Self::Error> {
|
|
|
|
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<LatLon> for Coordinate {
|
|
|
|
type Error = ();
|
|
|
|
|
|
|
|
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
|
|
|
|
// 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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|