Add TryFrom and TryInto for LatLon

This commit is contained in:
Austen Adler 2023-03-19 22:33:30 -04:00
parent abc450a15b
commit 11e158b0e8
6 changed files with 193 additions and 37 deletions

View File

@ -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)));
}
}

View File

@ -24,13 +24,25 @@ impl Coordinate {
tuple((double, optional_separator(','), double)),
|(lat, _, lon)| {
// 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) {
Some(Coordinate { lat: lat, lon: lon })
Some(Self { lat: lat, lon: lon })
} else {
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(())
}
}

View File

@ -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,
},
))
}
}

View File

@ -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,
// },
// ))
}
}

View File

@ -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)
}
}

View File

@ -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,
})
}
}