161 lines
4.3 KiB
Rust
Raw Normal View History

2023-03-19 21:03:10 -04:00
use crate::{
2023-03-19 23:52:27 -04:00
common::{parse_direction, parse_f64},
2023-03-25 00:19:42 -04:00
Error, LatLon,
2023-03-19 21:03:10 -04:00
};
use nom::{
2023-03-19 23:52:27 -04:00
bytes::complete::take,
2023-03-19 21:03:10 -04:00
character::complete::{self, space0, space1},
2023-03-25 00:19:42 -04:00
combinator::{map_opt, map_res, opt},
2023-03-19 23:52:27 -04:00
sequence::tuple,
2023-03-19 21:03:10 -04:00
IResult,
};
2023-03-20 00:19:18 -04:00
use std::{fmt, str::FromStr};
2023-03-19 23:19:19 -04:00
#[derive(PartialEq, Debug, Clone)]
2023-03-19 21:03:10 -04:00
pub struct Coordinate {
2023-03-25 00:19:42 -04:00
zone_num: u8,
zone_letter: char,
easting: f64,
northing: f64,
latlon: LatLon,
2023-03-19 21:03:10 -04:00
}
impl Coordinate {
/// ```rust
/// use spatial_coordinate_systems::utm::Coordinate;
/// use spatial_coordinate_systems::LatLon;
2023-03-19 21:03:10 -04:00
///
/// 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());
2023-03-19 23:52:27 -04:00
/// assert!(Coordinate::parse("10S 706832mE 4344683m").is_ok());
/// assert!(Coordinate::parse("34H 261877.8163738246 6243185.589276327").is_ok());
2023-03-19 21:03:10 -04:00
/// ```
pub fn parse(i: &str) -> IResult<&str, Self> {
2023-03-25 00:19:42 -04:00
map_res(
2023-03-19 21:03:10 -04:00
tuple((
complete::u8,
space0,
2023-03-25 00:19:42 -04:00
map_opt(take(1_usize), |z: &str| z.chars().next()),
2023-03-19 21:03:10 -04:00
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')),
// TODO: Should I allow a direction here or not
2023-03-19 23:52:27 -04:00
opt(parse_direction),
2023-03-19 21:03:10 -04:00
space1,
2023-03-19 22:33:30 -04:00
parse_f64,
2023-03-19 21:03:10 -04:00
opt(complete::char('m')),
2023-03-19 23:52:27 -04:00
opt(parse_direction),
2023-03-19 21:03:10 -04:00
)),
|(
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-25 00:19:42 -04:00
)|
-> Result<_, Error> {
let latlon = utm::wsg84_utm_to_lat_lon(easting, northing, zone_num, zone_letter)?;
Ok(Coordinate {
2023-03-19 21:03:10 -04:00
easting,
northing,
zone_num,
2023-03-25 00:19:42 -04:00
zone_letter,
latlon: LatLon::try_from(latlon)?,
})
2023-03-19 21:03:10 -04:00
},
)(i)
}
}
impl FromStr for Coordinate {
2023-03-25 00:19:42 -04:00
type Err = Error;
2023-03-19 21:03:10 -04:00
fn from_str(i: &str) -> Result<Self, Self::Err> {
2023-03-25 00:19:42 -04:00
Self::parse(i).map(|(_, ret)| ret).map_err(Into::into)
2023-03-19 21:03:10 -04:00
}
}
2023-03-19 22:33:30 -04:00
2023-03-20 00:19:18 -04:00
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,
)
}
}
2023-03-19 22:33:30 -04:00
impl TryFrom<LatLon> for Coordinate {
2023-03-25 00:19:42 -04:00
type Error = Error;
2023-03-19 22:33:30 -04:00
2023-03-25 00:19:42 -04:00
fn try_from(latlon: LatLon) -> Result<Self, Self::Error> {
2023-03-19 22:33:30 -04:00
// TODO: This does not feel right
2023-03-25 00:19:42 -04:00
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);
2023-03-19 22:33:30 -04:00
Ok(Self {
zone_num,
zone_letter,
easting,
northing,
2023-04-18 13:41:56 -04:00
latlon,
2023-03-19 22:33:30 -04:00
})
}
}
2023-03-25 00:19:42 -04:00
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(
2023-03-25 00:19:42 -04:00
LatLon::new(-33.92487_f64, 18.42406_f64).unwrap()
))
.unwrap()
.to_string());
p!("34H 261877.8163738246 6243185.589276327");
2023-03-25 00:50:47 -04:00
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
2023-03-25 00:50:47 -04:00
latlon: LatLon::new(1.534972646100779e-9, 100.50027886695585).unwrap()
})
);
}
}