Add TryFrom and TryInto for LatLon
This commit is contained in:
parent
abc450a15b
commit
11e158b0e8
@ -1,11 +1,11 @@
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::tag_no_case,
|
||||
bytes::complete::{tag_no_case, take_while1},
|
||||
character::{
|
||||
complete::{space0, space1},
|
||||
*,
|
||||
},
|
||||
combinator::{map, opt},
|
||||
combinator::{map, map_opt, opt},
|
||||
sequence::tuple,
|
||||
IResult,
|
||||
};
|
||||
@ -34,3 +34,23 @@ pub fn parse_direction(i: &str) -> IResult<&str, Direction> {
|
||||
map(tag_no_case("w"), |_| Direction::West),
|
||||
))(i)
|
||||
}
|
||||
|
||||
pub fn parse_f64(i: &str) -> IResult<&str, f64> {
|
||||
map_opt(
|
||||
take_while1(|c: char| c.is_ascii_digit() || c == '-' || c == '.'),
|
||||
|n: &str| n.parse::<f64>().ok(),
|
||||
)(i)
|
||||
}
|
||||
|
||||
#[cfg(tests)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_f64() {
|
||||
assert_eq!(parse_f64("1"), Ok(("", 1_f64)));
|
||||
assert_eq!(parse_f64("1.0"), Ok(("", 1.0_f64)));
|
||||
assert_eq!(parse_f64("1.0e2"), Ok(("e2", 1.0_f64)));
|
||||
assert_eq!(parse_f64("1.0x"), Ok(("x", 1.0_f64)));
|
||||
}
|
||||
}
|
||||
|
@ -24,14 +24,26 @@ impl Coordinate {
|
||||
tuple((double, optional_separator(','), double)),
|
||||
|(lat, _, lon)| {
|
||||
// Ensure this is a north/south then east/west direction
|
||||
if (-90_f64..=90_f64).contains(&lat) && (-180_f64..=180_f64).contains(&lon) {
|
||||
Some(Coordinate { lat: lat, lon: lon })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Self::from(lat, lon)
|
||||
},
|
||||
)(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: lat, lon: lon })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(f64, f64)> for Coordinate {
|
||||
type Error = ();
|
||||
|
||||
fn try_from((lat, lon): (f64, f64)) -> Result<Self, Self::Error> {
|
||||
Self::from(lat, lon).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Coordinate {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
common::{optional_separator, parse_direction},
|
||||
Direction,
|
||||
Direction, LatLon,
|
||||
};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
@ -10,10 +10,11 @@ use nom::{
|
||||
sequence::{pair, tuple},
|
||||
IResult,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use std::{convert::Infallible, str::FromStr};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Coordinate(pub DMM, pub DMM);
|
||||
|
||||
impl Coordinate {
|
||||
pub fn parse(i: &str) -> IResult<&str, Self> {
|
||||
map_opt(
|
||||
@ -95,3 +96,34 @@ impl FromStr for DMM {
|
||||
DMM::parse(i).map_err(|_| ()).map(|(_, ret)| ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<LatLon> for Coordinate {
|
||||
type Error = ();
|
||||
|
||||
fn try_into(self) -> Result<LatLon, Self::Error> {
|
||||
LatLon::from(
|
||||
self.0.degrees as f64 + self.0.minutes * 60.0_f64,
|
||||
self.1.degrees as f64 + self.1.minutes * 60.0_f64,
|
||||
)
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<LatLon> for Coordinate {
|
||||
type Error = Infallible;
|
||||
|
||||
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
|
||||
Ok(Self(
|
||||
DMM {
|
||||
degrees: value.lat as i16,
|
||||
minutes: value.lat.fract() * 60_f64,
|
||||
direction: Direction::North,
|
||||
},
|
||||
DMM {
|
||||
degrees: value.lon as i16,
|
||||
minutes: value.lon.fract() * 60_f64,
|
||||
direction: Direction::North,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
common::{optional_separator, parse_direction},
|
||||
Direction,
|
||||
Direction, LatLon,
|
||||
};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
@ -103,3 +103,39 @@ impl FromStr for DMS {
|
||||
DMS::parse(i).map_err(|_| ()).map(|(_, ret)| ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<LatLon> for Coordinate {
|
||||
type Error = ();
|
||||
|
||||
fn try_into(self) -> Result<LatLon, Self::Error> {
|
||||
Err(())
|
||||
|
||||
// LatLon::from(
|
||||
// self.0.degrees as f64 + self.0.minutes * 60.0_f64,
|
||||
// self.1.degrees as f64 + self.1.minutes * 60.0_f64,
|
||||
// )
|
||||
// .ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<LatLon> for Coordinate {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
|
||||
Err(())
|
||||
// Ok(Self(
|
||||
// DMS {
|
||||
// degrees: value.lat as i16,
|
||||
// minutes: value.lat.fract() * 60_f64 as i16,
|
||||
// seconds: value.lat.fract() * 3600_f64,
|
||||
// direction: Direction::North,
|
||||
// },
|
||||
// DMS {
|
||||
// degrees: value.lon as i16,
|
||||
// minutes: value.lon.fract() * 60_f64 as i16,
|
||||
// seconds: value.lon.fract() * 3600_f64,
|
||||
// direction: Direction::North,
|
||||
// },
|
||||
// ))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
common::{optional_separator, parse_direction},
|
||||
Direction,
|
||||
Direction, LatLon,
|
||||
};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
@ -13,20 +13,24 @@ use nom::{
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
const PLUSCODE_CHARS: [char; 22] = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', 'F', 'G', 'H', 'J', 'M', 'P', 'Q', 'R',
|
||||
'V', 'W', 'X',
|
||||
// TODO: Pick an approprite length
|
||||
const PLUSCODE_LENGTH: usize = 8;
|
||||
const PLUSCODE_CHARS: [char; 23] = [
|
||||
'+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', 'F', 'G', 'H', 'J', 'M', 'P', 'Q',
|
||||
'R', 'V', 'W', 'X',
|
||||
];
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Coordinate(pub f64, pub f64);
|
||||
pub struct Coordinate(pub String);
|
||||
|
||||
impl Coordinate {
|
||||
pub fn parse(i: &str) -> IResult<&str, Self> {
|
||||
map_opt(pluscode_chars, |c| {
|
||||
pluscodes::decode(c)
|
||||
.map(|coord| Coordinate(coord.latitude, coord.longitude))
|
||||
.ok()
|
||||
// Check if it can be decoded first
|
||||
pluscodes::decode(c).ok()?;
|
||||
|
||||
// It can! Store it
|
||||
Some(Coordinate(c.to_string()))
|
||||
})(i)
|
||||
}
|
||||
}
|
||||
@ -42,3 +46,34 @@ impl FromStr for Coordinate {
|
||||
fn pluscode_chars(i: &str) -> IResult<&str, &str> {
|
||||
take_while(|c| PLUSCODE_CHARS.contains(&c))(i)
|
||||
}
|
||||
|
||||
impl TryInto<LatLon> for Coordinate {
|
||||
type Error = ();
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<LatLon> for Coordinate {
|
||||
type Error = pluscodes::Error;
|
||||
|
||||
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
|
||||
pluscodes::encode(
|
||||
&pluscodes::Coordinate {
|
||||
latitude: value.lat,
|
||||
longitude: value.lon,
|
||||
},
|
||||
PLUSCODE_LENGTH,
|
||||
)
|
||||
.map(Coordinate)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
common::{optional_separator, parse_direction},
|
||||
Direction,
|
||||
common::{optional_separator, parse_direction, parse_f64},
|
||||
Direction, LatLon,
|
||||
};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
@ -12,6 +12,7 @@ use nom::{
|
||||
IResult,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use utm::WSG84ToLatLonError;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Coordinate {
|
||||
@ -26,6 +27,8 @@ impl Coordinate {
|
||||
/// use spatial_coordinate_systems::utm::Coordinate;
|
||||
///
|
||||
/// 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_err());
|
||||
/// ```
|
||||
@ -36,12 +39,12 @@ impl Coordinate {
|
||||
space0,
|
||||
take(1_usize),
|
||||
space1,
|
||||
double,
|
||||
//TODO: Can there be spaces around the m here or no?
|
||||
parse_f64,
|
||||
// TODO: Can there be spaces around the m here or no?
|
||||
opt(complete::char('m')),
|
||||
parse_direction,
|
||||
space1,
|
||||
double,
|
||||
parse_f64,
|
||||
opt(complete::char('m')),
|
||||
parse_direction,
|
||||
)),
|
||||
@ -52,24 +55,12 @@ impl Coordinate {
|
||||
_,
|
||||
easting,
|
||||
_,
|
||||
easting_direction,
|
||||
_easting_direction,
|
||||
_,
|
||||
northing,
|
||||
_,
|
||||
northing_direction,
|
||||
_northing_direction,
|
||||
)| {
|
||||
eprintln!(
|
||||
"Debug: {:?}",
|
||||
(
|
||||
zone_num,
|
||||
zone_letter,
|
||||
easting,
|
||||
easting_direction,
|
||||
northing,
|
||||
northing_direction
|
||||
)
|
||||
);
|
||||
|
||||
let ret = Coordinate {
|
||||
easting,
|
||||
northing,
|
||||
@ -93,3 +84,33 @@ impl FromStr for Coordinate {
|
||||
Self::parse(i).map_err(|_| ()).map(|(_, ret)| ret)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user