Improve coordinate ergonomics
This commit is contained in:
parent
2fc2772be9
commit
df0b688a3c
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
42
spatial-coordinate-systems/src/error.rs
Normal file
42
spatial-coordinate-systems/src/error.rs
Normal 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:?}"))
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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());
|
||||||
|
@ -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())?,
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user