161 lines
4.3 KiB
Rust
161 lines
4.3 KiB
Rust
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, Self::Err> {
|
|
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<LatLon> for Coordinate {
|
|
type Error = Error;
|
|
|
|
fn try_from(latlon: LatLon) -> Result<Self, Self::Error> {
|
|
// 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,
|
|
})
|
|
}
|
|
}
|
|
|
|
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,
|
|
|
|
// Allow nearby comparisons
|
|
latlon: LatLon::new(1.534972646100779e-9, 100.50027886695585).unwrap()
|
|
})
|
|
);
|
|
}
|
|
}
|