Improve coordinate ergonomics

This commit is contained in:
Austen Adler 2023-03-25 00:19:42 -04:00
parent 2fc2772be9
commit df0b688a3c
9 changed files with 258 additions and 196 deletions

View File

@ -1,25 +1,22 @@
use crate::{ use crate::{
common::{optional_separator, parse_direction, parse_f64}, common::{optional_separator, parse_direction, parse_f64},
Direction, Direction, Error, LatLon,
}; };
use nom::{ use nom::{
branch::alt, branch::alt,
character::complete::space0, character::complete::space0,
combinator::{map, map_opt}, combinator::{map, map_opt, map_res},
sequence::tuple, sequence::tuple,
IResult, IResult,
}; };
use std::{fmt, str::FromStr}; use std::{fmt, str::FromStr};
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub struct Coordinate { pub struct Coordinate(LatLon);
pub lat: f64,
pub lon: f64,
}
impl Coordinate { impl Coordinate {
pub fn parse(i: &str) -> IResult<&str, Self> { pub fn parse(i: &str) -> IResult<&str, Self> {
map_opt( map_res(
alt(( alt((
map_opt( map_opt(
alt(( alt((
@ -78,33 +75,28 @@ impl Coordinate {
|(lat, _, _, lon)| (lat, lon), |(lat, _, _, lon)| (lat, lon),
), ),
)), )),
|(lat, lon)| { |(lat, lon)| LatLon::new(lat, lon).map(Self),
// Ensure this is a north/south then east/west direction
Self::from(lat, lon)
},
)(i) )(i)
} }
pub fn from(lat: f64, lon: f64) -> Option<Self> {
if (-90_f64..=90_f64).contains(&lat) && (-180_f64..=180_f64).contains(&lon) {
Some(Self { lat, lon })
} else {
None
}
}
} }
impl fmt::Display for Coordinate { impl fmt::Display for Coordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, {}", self.lat, self.lon) write!(f, "{}", self.0)
} }
} }
impl TryFrom<(f64, f64)> for Coordinate { impl From<&Coordinate> for LatLon {
type Error = (); fn from(coordinate: &Coordinate) -> LatLon {
coordinate.0
}
}
fn try_from((lat, lon): (f64, f64)) -> Result<Self, Self::Error> { impl TryFrom<LatLon> for Coordinate {
Self::from(lat, lon).ok_or(()) type Error = Error;
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
Ok(Self(value))
} }
} }

View File

@ -1,33 +1,44 @@
use crate::{ use crate::{
common::{optional_separator, parse_direction, parse_f64}, common::{optional_separator, parse_direction, parse_f64},
Direction, LatLon, Direction, Error, LatLon,
}; };
use nom::{ use nom::{
branch::alt, branch::alt,
character::complete::{self, space0}, character::complete::{self, space0},
combinator::{map, map_opt}, combinator::{map, map_res},
sequence::tuple, sequence::tuple,
IResult, IResult,
}; };
use std::{fmt, str::FromStr}; use std::{fmt, str::FromStr};
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone, Copy)]
pub struct Coordinate(pub DMM, pub DMM); pub struct Coordinate(DMM, DMM, LatLon);
impl Coordinate { impl Coordinate {
pub fn parse(i: &str) -> IResult<&str, Self> { pub fn parse(i: &str) -> IResult<&str, Self> {
map_opt( map_res(
tuple((DMM::parse, optional_separator(','), space0, DMM::parse)), tuple((DMM::parse, optional_separator(','), space0, DMM::parse)),
|(lat, _, _, lon)| { |(lat, _, _, lon)| {
// Ensure this is a north/south then east/west direction // Ensure this is a north/south then east/west direction
if lat.direction.is_lat() && lon.direction.is_lon() { if !lat.direction.is_lat() {
Some(Coordinate(lat, lon)) Err(Error::InvalidLatitudeBearing(lat.direction))
} else if !lon.direction.is_lon() {
Err(Error::InvalidLongitudeBearing(lon.direction))
} else { } else {
None let latlon = LatLon::new(lat.to_decimal_degrees(), lon.to_decimal_degrees())?;
Ok(Coordinate(lat, lon, latlon))
} }
}, },
)(i) )(i)
} }
pub fn get_lat(&self) -> DMM {
self.0
}
pub fn get_lon(&self) -> DMM {
self.1
}
} }
impl FromStr for Coordinate { impl FromStr for Coordinate {
@ -38,7 +49,7 @@ impl FromStr for Coordinate {
} }
} }
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone, Copy)]
pub struct DMM { pub struct DMM {
pub degrees: i16, pub degrees: i16,
pub minutes: f64, pub minutes: f64,
@ -99,21 +110,11 @@ impl DMM {
)(i) )(i)
} }
pub fn try_from_decimal_degrees(d: f64, is_latitude: bool) -> Result<Self, ()> { pub fn from_decimal_degrees(d: f64, is_latitude: bool) -> Self {
let bounds = if is_latitude {
-90_f64..=90_f64
} else {
-180_f64..=180_f64
};
if !bounds.contains(&d) {
return Err(());
}
let degrees = d as i16; let degrees = d as i16;
let minutes = d.fract() * 60_f64; let minutes = d.fract() * 60_f64;
Ok(Self { Self {
degrees, degrees,
minutes, minutes,
direction: if is_latitude { direction: if is_latitude {
@ -121,7 +122,7 @@ impl DMM {
} else { } else {
Direction::East Direction::East
}, },
}) }
} }
pub fn to_decimal_degrees(&self) -> f64 { pub fn to_decimal_degrees(&self) -> f64 {
@ -141,21 +142,20 @@ impl fmt::Display for Coordinate {
} }
} }
impl TryInto<LatLon> for Coordinate { impl From<&Coordinate> for LatLon {
type Error = (); fn from(coordinate: &Coordinate) -> LatLon {
coordinate.2
fn try_into(self) -> Result<LatLon, Self::Error> {
LatLon::from(self.0.to_decimal_degrees(), self.1.to_decimal_degrees()).ok_or(())
} }
} }
impl TryFrom<LatLon> for Coordinate { impl TryFrom<LatLon> for Coordinate {
type Error = (); type Error = Error;
fn try_from(value: LatLon) -> Result<Self, Self::Error> { fn try_from(value: LatLon) -> Result<Self, Self::Error> {
Ok(Self( Ok(Self(
DMM::try_from_decimal_degrees(value.lat, true)?, DMM::from_decimal_degrees(value.get_lat(), true),
DMM::try_from_decimal_degrees(value.lon, false)?, DMM::from_decimal_degrees(value.get_lon(), false),
value,
)) ))
} }
} }

View File

@ -1,34 +1,44 @@
use crate::{ use crate::{
common::{optional_separator, parse_direction, parse_f64}, common::{optional_separator, parse_direction, parse_f64},
Direction, LatLon, Direction, Error, LatLon,
}; };
use nom::{ use nom::{
branch::alt, branch::alt,
character::complete::{self, space0}, character::complete::{self, space0},
combinator::{map, map_opt}, combinator::{map, map_res},
sequence::tuple, sequence::tuple,
IResult, IResult,
}; };
use std::{fmt, str::FromStr}; use std::{fmt, str::FromStr};
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone, Copy)]
pub struct Coordinate(pub DMS, pub DMS, LatLon); pub struct Coordinate(DMS, DMS, LatLon);
impl Coordinate { impl Coordinate {
pub fn parse(i: &str) -> IResult<&str, Self> { pub fn parse(i: &str) -> IResult<&str, Self> {
map_opt( map_res(
tuple((DMS::parse, optional_separator(','), space0, DMS::parse)), tuple((DMS::parse, optional_separator(','), space0, DMS::parse)),
|(lat, _, _, lon)| { |(lat, _, _, lon)| {
// Ensure this is a north/south then east/west direction // Ensure this is a north/south then east/west direction
if lat.direction.is_lat() && lon.direction.is_lon() { if !lat.direction.is_lat() {
let latlon = LatLon::from(lat.to_decimal_degrees(), lon.to_decimal_degrees())?; Err(Error::InvalidLatitudeBearing(lat.direction))
Some(Coordinate(lat, lon, latlon)) } else if !lon.direction.is_lon() {
Err(Error::InvalidLongitudeBearing(lon.direction))
} else { } else {
None let latlon = LatLon::new(lat.to_decimal_degrees(), lon.to_decimal_degrees())?;
Ok(Coordinate(lat, lon, latlon))
} }
}, },
)(i) )(i)
} }
pub fn get_lat(&self) -> DMS {
self.0
}
pub fn get_lon(&self) -> DMS {
self.1
}
} }
/// Parse only the numeric portion of the coordinate /// Parse only the numeric portion of the coordinate
@ -51,7 +61,7 @@ fn parse_dms_numeric(i: &str) -> IResult<&str, (i16, i16, f64)> {
)(i) )(i)
} }
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone, Copy)]
pub struct DMS { pub struct DMS {
pub degrees: i16, pub degrees: i16,
pub minutes: i16, pub minutes: i16,
@ -100,23 +110,13 @@ impl DMS {
)(i) )(i)
} }
pub fn try_from_decimal_degrees(d: f64, is_latitude: bool) -> Result<Self, ()> { pub fn from_decimal_degrees(d: f64, is_latitude: bool) -> Self {
let bounds = if is_latitude {
-90_f64..=90_f64
} else {
-180_f64..=180_f64
};
if !bounds.contains(&d) {
return Err(());
}
let degrees = d as i16; let degrees = d as i16;
let minutes = d.fract() * 60_f64; let minutes = d.fract() * 60_f64;
let seconds = minutes.fract() * 60_f64; let seconds = minutes.fract() * 60_f64;
let minutes = minutes as i16; let minutes = minutes as i16;
Ok(Self { Self {
degrees, degrees,
minutes, minutes,
seconds, seconds,
@ -125,7 +125,7 @@ impl DMS {
} else { } else {
Direction::East Direction::East
}, },
}) }
} }
pub fn to_decimal_degrees(&self) -> f64 { pub fn to_decimal_degrees(&self) -> f64 {
@ -158,21 +158,19 @@ impl fmt::Display for Coordinate {
} }
} }
impl TryInto<LatLon> for Coordinate { impl From<&Coordinate> for LatLon {
type Error = (); fn from(coordinate: &Coordinate) -> LatLon {
coordinate.2
fn try_into(self) -> Result<LatLon, Self::Error> {
LatLon::from(self.0.to_decimal_degrees(), self.1.to_decimal_degrees()).ok_or(())
} }
} }
impl TryFrom<LatLon> for Coordinate { impl TryFrom<LatLon> for Coordinate {
type Error = (); type Error = Error;
fn try_from(value: LatLon) -> Result<Self, Self::Error> { fn try_from(value: LatLon) -> Result<Self, Self::Error> {
Ok(Self( Ok(Self(
DMS::try_from_decimal_degrees(value.lat, true)?, DMS::from_decimal_degrees(value.get_lat(), true),
DMS::try_from_decimal_degrees(value.lon, false)?, DMS::from_decimal_degrees(value.get_lon(), false),
value, value,
)) ))
} }
@ -190,7 +188,7 @@ mod tests {
eprintln!("Testing: {} => {:?}", $tt, cvt); eprintln!("Testing: {} => {:?}", $tt, cvt);
assert!(cvt.is_ok()); assert!(cvt.is_ok());
eprintln!("Now converting to latlon"); eprintln!("Now converting to latlon");
assert!(dbg!(TryInto::<LatLon>::try_into(cvt.unwrap())).is_ok()); // assert!(dbg!(TryInto::<LatLon>::try_into(cvt.unwrap())).is_ok());
}}; }};
($tt:tt, DMS) => {{ ($tt:tt, DMS) => {{
let cvt = DMS::parse($tt); let cvt = DMS::parse($tt);

View File

@ -0,0 +1,42 @@
use thiserror::Error;
use crate::Direction;
#[derive(Error, Debug)]
pub enum Error {
#[error("Invalid latitude: {0}")]
InvalidLatitude(f64),
#[error("Invalid longitude: {0}")]
InvalidLongitude(f64),
#[error("Invalid latitude bearing: {0:?}")]
InvalidLatitudeBearing(Direction),
#[error("Invalid longitude bearing: {0:?}")]
InvalidLongitudeBearing(Direction),
#[error("Pluscode error: {0:?}")]
Pluscodes(pluscodes::Error),
#[error("WSG84ToLatLon error: {0:?}")]
WSG84ToLatLonError(utm::WSG84ToLatLonError),
// TODO: Does this one have to be a string?
#[error("Error parsing: {0}")]
NomErr(String),
#[error("No UTM zone letter")]
NoUtmZoneLetter
}
impl From<pluscodes::Error> for Error {
fn from(e: pluscodes::Error) -> Self {
Self::Pluscodes(e)
}
}
impl From<utm::WSG84ToLatLonError> for Error {
fn from(e: utm::WSG84ToLatLonError) -> Self {
Self::WSG84ToLatLonError(e)
}
}
impl<E: std::fmt::Debug> From<nom::Err<E>> for Error {
fn from(e: nom::Err<E>) -> Self {
Self::NomErr(format!("{e:?}"))
}
}

View File

@ -1,19 +1,13 @@
use thiserror::Error; use std::fmt;
#[derive(PartialEq, Debug, Clone)] use crate::Error;
#[derive(PartialEq, Debug, Clone, Copy)]
pub struct LatLon { pub struct LatLon {
lat: f64, lat: f64,
lon: f64, lon: f64,
} }
#[derive(Error, Debug)]
pub enum Error {
#[error("Invalid latitude: {0:?}")]
InvalidLatitude(f64),
#[error("Invalid longitude: {0:?}")]
InvalidLongitude(f64),
}
impl LatLon { impl LatLon {
pub fn new(lat: f64, lon: f64) -> Result<Self, Error> { pub fn new(lat: f64, lon: f64) -> Result<Self, Error> {
if !(-90_f64..=90_f64).contains(&lat) { if !(-90_f64..=90_f64).contains(&lat) {
@ -33,3 +27,36 @@ impl LatLon {
self.lat self.lat
} }
} }
impl fmt::Display for LatLon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, {}", self.lat, self.lon)
}
}
impl Into<(f64, f64)> for LatLon {
fn into(self) -> (f64, f64) {
(self.lat, self.lon)
}
}
impl TryFrom<(f64, f64)> for LatLon {
type Error = Error;
fn try_from((lat, lon): (f64, f64)) -> Result<Self, Self::Error> {
Self::new(lat, lon)
}
}
impl TryFrom<pluscodes::Coordinate> for LatLon {
type Error = Error;
fn try_from(
pluscodes::Coordinate {
latitude: lat,
longitude: lon,
}: pluscodes::Coordinate,
) -> Result<Self, Self::Error> {
Self::new(lat, lon)
}
}

View File

@ -3,9 +3,11 @@ mod common;
pub mod dd; pub mod dd;
pub mod dmm; pub mod dmm;
pub mod dms; pub mod dms;
mod error;
pub mod latlon; pub mod latlon;
pub mod plus; pub mod plus;
pub mod utm; pub mod utm;
pub use error::Error;
// pub mod xpin; // pub mod xpin;
use nom::{ use nom::{
@ -16,7 +18,7 @@ use nom::{
IResult, IResult,
}; };
pub type LatLon = dd::Coordinate; pub use latlon::LatLon;
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum Direction { pub enum Direction {
@ -94,15 +96,15 @@ impl Coordinate {
} }
// TODO: Make this `&self` // TODO: Make this `&self`
pub fn as_type(self, coordinate_type: &CoordinateType) -> Result<Self, ()> { pub fn as_type(&self, coordinate_type: &CoordinateType) -> Result<Self, Error> {
let lat_lon = LatLon::try_from(self)?; let lat_lon = LatLon::from(self);
Ok(match coordinate_type { Ok(match coordinate_type {
CoordinateType::DD => Self::DD(dd::Coordinate::try_from(lat_lon).or(Err(()))?), CoordinateType::DD => Self::DD(dd::Coordinate::try_from(lat_lon)?),
CoordinateType::DMS => Self::DMS(dms::Coordinate::try_from(lat_lon)?), CoordinateType::DMS => Self::DMS(dms::Coordinate::try_from(lat_lon)?),
CoordinateType::DMM => Self::DMM(dmm::Coordinate::try_from(lat_lon)?), CoordinateType::DMM => Self::DMM(dmm::Coordinate::try_from(lat_lon)?),
CoordinateType::UTM => Self::UTM(utm::Coordinate::try_from(lat_lon)?), CoordinateType::UTM => Self::UTM(utm::Coordinate::try_from(lat_lon)?),
CoordinateType::Plus => Self::Plus(plus::Coordinate::try_from(lat_lon).or(Err(()))?), CoordinateType::Plus => Self::Plus(plus::Coordinate::try_from(lat_lon)?),
}) })
} }
@ -118,20 +120,33 @@ impl Coordinate {
} }
} }
impl TryFrom<Coordinate> for LatLon { impl From<&Coordinate> for LatLon {
type Error = (); fn from(value: &Coordinate) -> LatLon {
fn try_from(value: Coordinate) -> Result<LatLon, Self::Error> {
match value { match value {
Coordinate::DD(dd) => dd.try_into().or(Err(())), Coordinate::DD(dd) => dd.into(),
Coordinate::DMM(dmm) => dmm.try_into(), Coordinate::DMS(dms) => dms.into(),
Coordinate::DMS(dms) => dms.try_into(), Coordinate::DMM(dmm) => dmm.into(),
Coordinate::UTM(utm) => utm.try_into(), Coordinate::UTM(utm) => utm.into(),
Coordinate::Plus(plus) => plus.try_into(), // Coordinate::Xpin(xpin) => xpin.into(),
Coordinate::Plus(plus) => plus.into(),
} }
} }
} }
// impl TryFrom<Coordinate> for LatLon {
// type Error = Error;
// fn try_from(value: Coordinate) -> Result<LatLon, Self::Error> {
// match value {
// Coordinate::DD(dd) => dd.try_into().or(Err(())),
// Coordinate::DMM(dmm) => dmm.try_into(),
// Coordinate::DMS(dms) => dms.try_into(),
// Coordinate::UTM(utm) => utm.try_into(),
// Coordinate::Plus(plus) => plus.try_into(),
// }
// }
// }
impl From<dd::Coordinate> for Coordinate { impl From<dd::Coordinate> for Coordinate {
fn from(c: dd::Coordinate) -> Self { fn from(c: dd::Coordinate) -> Self {
Self::DD(c) Self::DD(c)
@ -158,30 +173,30 @@ impl From<plus::Coordinate> for Coordinate {
} }
} }
impl TryFrom<Coordinate> for dms::Coordinate { // impl TryFrom<Coordinate> for dms::Coordinate {
type Error = (); // type Error = Error;
fn try_from(c: Coordinate) -> Result<Self, Self::Error> { // fn try_from(c: Coordinate) -> Result<Self, Self::Error> {
dms::Coordinate::try_from(LatLon::try_from(c)?) // dms::Coordinate::try_from(LatLon::try_from(c)?)
} // }
} // }
impl TryFrom<Coordinate> for dmm::Coordinate { // impl TryFrom<Coordinate> for dmm::Coordinate {
type Error = (); // type Error = Error;
fn try_from(c: Coordinate) -> Result<Self, Self::Error> { // fn try_from(c: Coordinate) -> Result<Self, Self::Error> {
dmm::Coordinate::try_from(LatLon::try_from(c)?).or(Err(())) // dmm::Coordinate::try_from(LatLon::try_from(c)?).or(Err(()))
} // }
} // }
impl TryFrom<Coordinate> for utm::Coordinate { // impl TryFrom<Coordinate> for utm::Coordinate {
type Error = (); // type Error = Error;
fn try_from(c: Coordinate) -> Result<Self, Self::Error> { // fn try_from(c: Coordinate) -> Result<Self, Self::Error> {
utm::Coordinate::try_from(LatLon::try_from(c)?) // utm::Coordinate::try_from(LatLon::try_from(c)?)
} // }
} // }
impl TryFrom<Coordinate> for plus::Coordinate { // impl TryFrom<Coordinate> for plus::Coordinate {
type Error = (); // type Error = Error;
fn try_from(c: Coordinate) -> Result<Self, Self::Error> { // fn try_from(c: Coordinate) -> Result<Self, Self::Error> {
plus::Coordinate::try_from(LatLon::try_from(c)?).or(Err(())) // plus::Coordinate::try_from(LatLon::try_from(c)?).or(Err(()))
} // }
} // }
impl fmt::Display for Coordinate { impl fmt::Display for Coordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -10,21 +10,22 @@ const PLUSCODE_CHARS: [char; 23] = [
]; ];
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub struct Coordinate(pub String, LatLon); pub struct Coordinate(String, LatLon);
impl Coordinate { impl Coordinate {
pub fn parse(i: &str) -> IResult<&str, Self> { pub fn parse(i: &str) -> IResult<&str, Self> {
map_opt(pluscode_chars, |c| { map_opt(pluscode_chars, |c| {
// Check if it can be decoded first // Check if it can be decoded first
let coordinate = pluscodes::decode(c).ok()?; let decoded = pluscodes::decode(c).ok()?;
// It can! Store it // It can! Store it
Some(Coordinate( Some(Coordinate(c.to_string(), LatLon::try_from(decoded).ok()?))
c.to_string(),
LatLon::from(coordinate.latitude, coordinate.longitude)?,
))
})(i) })(i)
} }
pub fn get(&self) -> &str {
&self.0
}
} }
impl FromStr for Coordinate { impl FromStr for Coordinate {
@ -45,19 +46,9 @@ impl fmt::Display for Coordinate {
} }
} }
impl TryInto<LatLon> for Coordinate { impl From<&Coordinate> for LatLon {
type Error = (); fn from(coordinate: &Coordinate) -> LatLon {
coordinate.1
fn try_into(self) -> Result<LatLon, Self::Error> {
pluscodes::decode(&self.0)
.map(
|pluscodes::Coordinate {
latitude: lat,
longitude: lon,
}| { LatLon::from(lat, lon) },
)
.map_err(|_| ())?
.ok_or(())
} }
} }
@ -67,8 +58,8 @@ impl TryFrom<LatLon> for Coordinate {
fn try_from(value: LatLon) -> Result<Self, Self::Error> { fn try_from(value: LatLon) -> Result<Self, Self::Error> {
pluscodes::encode( pluscodes::encode(
&pluscodes::Coordinate { &pluscodes::Coordinate {
latitude: value.lat, latitude: value.get_lat(),
longitude: value.lon, longitude: value.get_lon(),
}, },
PLUSCODE_LENGTH, PLUSCODE_LENGTH,
) )

View File

@ -1,11 +1,11 @@
use crate::{ use crate::{
common::{parse_direction, parse_f64}, common::{parse_direction, parse_f64},
LatLon, Error, LatLon,
}; };
use nom::{ use nom::{
bytes::complete::take, bytes::complete::take,
character::complete::{self, space0, space1}, character::complete::{self, space0, space1},
combinator::{map_opt, opt}, combinator::{map_opt, map_res, opt},
sequence::tuple, sequence::tuple,
IResult, IResult,
}; };
@ -13,10 +13,12 @@ use std::{fmt, str::FromStr};
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub struct Coordinate { pub struct Coordinate {
pub zone_num: u8, zone_num: u8,
pub zone_letter: char, zone_letter: char,
pub easting: f64, easting: f64,
pub northing: f64, northing: f64,
latlon: LatLon,
} }
impl Coordinate { impl Coordinate {
@ -32,11 +34,11 @@ impl Coordinate {
/// assert!(Coordinate::parse("34H 261877.8163738246 6243185.589276327").is_ok()); /// assert!(Coordinate::parse("34H 261877.8163738246 6243185.589276327").is_ok());
/// ``` /// ```
pub fn parse(i: &str) -> IResult<&str, Self> { pub fn parse(i: &str) -> IResult<&str, Self> {
map_opt( map_res(
tuple(( tuple((
complete::u8, complete::u8,
space0, space0,
take(1_usize), map_opt(take(1_usize), |z: &str| z.chars().next()),
space1, space1,
parse_f64, parse_f64,
// TODO: Can there be spaces around the m here or no? // TODO: Can there be spaces around the m here or no?
@ -60,28 +62,27 @@ impl Coordinate {
northing, northing,
_, _,
_northing_direction, _northing_direction,
)| { )|
let ret = Coordinate { -> Result<_, Error> {
let latlon = utm::wsg84_utm_to_lat_lon(easting, northing, zone_num, zone_letter)?;
Ok(Coordinate {
easting, easting,
northing, northing,
zone_num, zone_num,
zone_letter: zone_letter.chars().next()?, zone_letter,
}; latlon: LatLon::try_from(latlon)?,
})
// 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) )(i)
} }
} }
impl FromStr for Coordinate { impl FromStr for Coordinate {
type Err = (); type Err = Error;
fn from_str(i: &str) -> Result<Self, Self::Err> { fn from_str(i: &str) -> Result<Self, Self::Err> {
Self::parse(i).map_err(|_| ()).map(|(_, ret)| ret) Self::parse(i).map(|(_, ret)| ret).map_err(Into::into)
} }
} }
@ -95,36 +96,33 @@ impl fmt::Display for Coordinate {
} }
} }
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 { impl TryFrom<LatLon> for Coordinate {
type Error = (); type Error = Error;
fn try_from(value: LatLon) -> Result<Self, Self::Error> { fn try_from(latlon: LatLon) -> Result<Self, Self::Error> {
// TODO: This does not feel right // TODO: This does not feel right
let zone_num = utm::lat_lon_to_zone_number(value.lat, value.lon); let zone_num = utm::lat_lon_to_zone_number(latlon.get_lat(), latlon.get_lon());
let zone_letter = utm::lat_to_zone_letter(value.lat).ok_or(())?; let zone_letter =
let (northing, easting, _) = utm::to_utm_wgs84(value.lat, value.lon, zone_num); 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 { Ok(Self {
zone_num, zone_num,
zone_letter, zone_letter,
easting, easting,
northing, northing,
latlon: latlon,
}) })
} }
} }
impl From<&Coordinate> for LatLon {
fn from(coordinate: &Coordinate) -> LatLon {
coordinate.latlon
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -137,12 +135,12 @@ mod tests {
eprintln!("Testing: {} => {:?}", $tt, cvt); eprintln!("Testing: {} => {:?}", $tt, cvt);
assert!(cvt.is_ok()); assert!(cvt.is_ok());
eprintln!("Now converting to latlon"); eprintln!("Now converting to latlon");
assert!(dbg!(TryInto::<LatLon>::try_into(cvt.unwrap())).is_ok()); // assert!(dbg!(TryInto::<LatLon>::try_into(cvt.unwrap())).is_ok());
}}; }};
} }
dbg!(dbg!(Coordinate::try_from( dbg!(dbg!(Coordinate::try_from(
LatLon::from(-33.92487_f64, 18.42406_f64).unwrap() LatLon::new(-33.92487_f64, 18.42406_f64).unwrap()
)) ))
.unwrap() .unwrap()
.to_string()); .to_string());

View File

@ -91,11 +91,10 @@ impl EncodedAddress {
.map_err(|()| format!("Could not parse str as a coordinate {i:?}"))?; .map_err(|()| format!("Could not parse str as a coordinate {i:?}"))?;
// TODO: Remove the clone here // TODO: Remove the clone here
let latlon = LatLon::try_from(src_coords.clone()) let latlon = LatLon::from(&src_coords);
.map_err(|_| "Could not convert coordinate back to latlon".to_string())?;
let mut ret = Self::try_from( let mut ret = Self::try_from(
xpin::Address::from_lat_lon(latlon.lat, latlon.lon) xpin::Address::from_lat_lon(latlon.get_lat(), latlon.get_lon())
.as_ref() .as_ref()
.map_err(|e| e.to_string())?, .map_err(|e| e.to_string())?,
) )