Add TryFrom and TryInto for LatLon
This commit is contained in:
parent
abc450a15b
commit
11e158b0e8
@ -1,11 +1,11 @@
|
|||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::tag_no_case,
|
bytes::complete::{tag_no_case, take_while1},
|
||||||
character::{
|
character::{
|
||||||
complete::{space0, space1},
|
complete::{space0, space1},
|
||||||
*,
|
*,
|
||||||
},
|
},
|
||||||
combinator::{map, opt},
|
combinator::{map, map_opt, opt},
|
||||||
sequence::tuple,
|
sequence::tuple,
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
@ -34,3 +34,23 @@ pub fn parse_direction(i: &str) -> IResult<&str, Direction> {
|
|||||||
map(tag_no_case("w"), |_| Direction::West),
|
map(tag_no_case("w"), |_| Direction::West),
|
||||||
))(i)
|
))(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,13 +24,25 @@ impl Coordinate {
|
|||||||
tuple((double, optional_separator(','), double)),
|
tuple((double, optional_separator(','), double)),
|
||||||
|(lat, _, lon)| {
|
|(lat, _, lon)| {
|
||||||
// Ensure this is a north/south then east/west direction
|
// Ensure this is a north/south then east/west direction
|
||||||
|
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) {
|
if (-90_f64..=90_f64).contains(&lat) && (-180_f64..=180_f64).contains(&lon) {
|
||||||
Some(Coordinate { lat: lat, lon: lon })
|
Some(Self { lat: lat, lon: lon })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)(i)
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
common::{optional_separator, parse_direction},
|
common::{optional_separator, parse_direction},
|
||||||
Direction,
|
Direction, LatLon,
|
||||||
};
|
};
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
@ -10,10 +10,11 @@ use nom::{
|
|||||||
sequence::{pair, tuple},
|
sequence::{pair, tuple},
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
use std::str::FromStr;
|
use std::{convert::Infallible, str::FromStr};
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct Coordinate(pub DMM, pub DMM);
|
pub struct Coordinate(pub DMM, pub DMM);
|
||||||
|
|
||||||
impl Coordinate {
|
impl Coordinate {
|
||||||
pub fn parse(i: &str) -> IResult<&str, Self> {
|
pub fn parse(i: &str) -> IResult<&str, Self> {
|
||||||
map_opt(
|
map_opt(
|
||||||
@ -95,3 +96,34 @@ impl FromStr for DMM {
|
|||||||
DMM::parse(i).map_err(|_| ()).map(|(_, ret)| ret)
|
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::{
|
use crate::{
|
||||||
common::{optional_separator, parse_direction},
|
common::{optional_separator, parse_direction},
|
||||||
Direction,
|
Direction, LatLon,
|
||||||
};
|
};
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
@ -103,3 +103,39 @@ impl FromStr for DMS {
|
|||||||
DMS::parse(i).map_err(|_| ()).map(|(_, ret)| ret)
|
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::{
|
use crate::{
|
||||||
common::{optional_separator, parse_direction},
|
common::{optional_separator, parse_direction},
|
||||||
Direction,
|
Direction, LatLon,
|
||||||
};
|
};
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
@ -13,20 +13,24 @@ use nom::{
|
|||||||
};
|
};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
const PLUSCODE_CHARS: [char; 22] = [
|
// TODO: Pick an approprite length
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', 'F', 'G', 'H', 'J', 'M', 'P', 'Q', 'R',
|
const PLUSCODE_LENGTH: usize = 8;
|
||||||
'V', 'W', 'X',
|
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)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct Coordinate(pub f64, pub f64);
|
pub struct Coordinate(pub String);
|
||||||
|
|
||||||
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| {
|
||||||
pluscodes::decode(c)
|
// Check if it can be decoded first
|
||||||
.map(|coord| Coordinate(coord.latitude, coord.longitude))
|
pluscodes::decode(c).ok()?;
|
||||||
.ok()
|
|
||||||
|
// It can! Store it
|
||||||
|
Some(Coordinate(c.to_string()))
|
||||||
})(i)
|
})(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,3 +46,34 @@ impl FromStr for Coordinate {
|
|||||||
fn pluscode_chars(i: &str) -> IResult<&str, &str> {
|
fn pluscode_chars(i: &str) -> IResult<&str, &str> {
|
||||||
take_while(|c| PLUSCODE_CHARS.contains(&c))(i)
|
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::{
|
use crate::{
|
||||||
common::{optional_separator, parse_direction},
|
common::{optional_separator, parse_direction, parse_f64},
|
||||||
Direction,
|
Direction, LatLon,
|
||||||
};
|
};
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
@ -12,6 +12,7 @@ use nom::{
|
|||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use utm::WSG84ToLatLonError;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct Coordinate {
|
pub struct Coordinate {
|
||||||
@ -26,6 +27,8 @@ impl Coordinate {
|
|||||||
/// use spatial_coordinate_systems::utm::Coordinate;
|
/// use spatial_coordinate_systems::utm::Coordinate;
|
||||||
///
|
///
|
||||||
/// assert!(Coordinate::parse("10S 706832mE 4344683mN").is_ok());
|
/// 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("10S706832mE 4344683mN").is_err());
|
||||||
/// assert!(Coordinate::parse("10S 706832mE 4344683m").is_err());
|
/// assert!(Coordinate::parse("10S 706832mE 4344683m").is_err());
|
||||||
/// ```
|
/// ```
|
||||||
@ -36,12 +39,12 @@ impl Coordinate {
|
|||||||
space0,
|
space0,
|
||||||
take(1_usize),
|
take(1_usize),
|
||||||
space1,
|
space1,
|
||||||
double,
|
parse_f64,
|
||||||
//TODO: Can there be spaces around the m here or no?
|
// TODO: Can there be spaces around the m here or no?
|
||||||
opt(complete::char('m')),
|
opt(complete::char('m')),
|
||||||
parse_direction,
|
parse_direction,
|
||||||
space1,
|
space1,
|
||||||
double,
|
parse_f64,
|
||||||
opt(complete::char('m')),
|
opt(complete::char('m')),
|
||||||
parse_direction,
|
parse_direction,
|
||||||
)),
|
)),
|
||||||
@ -52,24 +55,12 @@ impl Coordinate {
|
|||||||
_,
|
_,
|
||||||
easting,
|
easting,
|
||||||
_,
|
_,
|
||||||
easting_direction,
|
_easting_direction,
|
||||||
_,
|
_,
|
||||||
northing,
|
northing,
|
||||||
_,
|
_,
|
||||||
northing_direction,
|
_northing_direction,
|
||||||
)| {
|
)| {
|
||||||
eprintln!(
|
|
||||||
"Debug: {:?}",
|
|
||||||
(
|
|
||||||
zone_num,
|
|
||||||
zone_letter,
|
|
||||||
easting,
|
|
||||||
easting_direction,
|
|
||||||
northing,
|
|
||||||
northing_direction
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let ret = Coordinate {
|
let ret = Coordinate {
|
||||||
easting,
|
easting,
|
||||||
northing,
|
northing,
|
||||||
@ -93,3 +84,33 @@ impl FromStr for Coordinate {
|
|||||||
Self::parse(i).map_err(|_| ()).map(|(_, ret)| ret)
|
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