2023-03-19 18:34:04 -04:00
|
|
|
use crate::{
|
|
|
|
common::{optional_separator, parse_direction},
|
2023-03-19 22:33:30 -04:00
|
|
|
Direction, LatLon,
|
2023-03-19 18:34:04 -04:00
|
|
|
};
|
|
|
|
use nom::{
|
|
|
|
branch::alt,
|
|
|
|
character::complete::{self, space0, space1},
|
|
|
|
combinator::{eof, map, map_opt, map_res, opt},
|
|
|
|
number::complete::double,
|
|
|
|
sequence::{pair, tuple},
|
|
|
|
IResult,
|
|
|
|
};
|
2023-03-19 22:33:30 -04:00
|
|
|
use std::{convert::Infallible, str::FromStr};
|
2023-03-19 18:34:04 -04:00
|
|
|
|
2023-03-19 18:53:22 -04:00
|
|
|
#[derive(PartialEq, Debug)]
|
2023-03-19 18:54:49 -04:00
|
|
|
pub struct Coordinate(pub DMM, pub DMM);
|
2023-03-19 22:33:30 -04:00
|
|
|
|
2023-03-19 18:59:25 -04:00
|
|
|
impl Coordinate {
|
2023-03-19 19:20:17 -04:00
|
|
|
pub fn parse(i: &str) -> IResult<&str, Self> {
|
2023-03-19 18:59:25 -04:00
|
|
|
map_opt(
|
|
|
|
tuple((DMM::parse, optional_separator(','), DMM::parse)),
|
|
|
|
|(ns, _, ew)| {
|
|
|
|
// Ensure this is a north/south then east/west direction
|
|
|
|
if ns.direction.is_lat() && ew.direction.is_lon() {
|
|
|
|
Some(Coordinate(ns, ew))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)(i)
|
|
|
|
}
|
|
|
|
}
|
2023-03-19 18:53:22 -04:00
|
|
|
|
2023-03-19 19:20:17 -04:00
|
|
|
impl FromStr for Coordinate {
|
|
|
|
type Err = ();
|
|
|
|
|
|
|
|
fn from_str(i: &str) -> Result<Self, Self::Err> {
|
|
|
|
Self::parse(i).map_err(|_| ()).map(|(_, ret)| ret)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-19 18:53:22 -04:00
|
|
|
#[derive(PartialEq, Debug)]
|
2023-03-19 18:54:49 -04:00
|
|
|
pub struct DMM {
|
2023-03-19 18:53:22 -04:00
|
|
|
pub degrees: i16,
|
|
|
|
pub minutes: f64,
|
|
|
|
pub direction: Direction,
|
|
|
|
}
|
|
|
|
|
2023-03-19 18:59:25 -04:00
|
|
|
impl DMM {
|
|
|
|
pub fn parse(i: &str) -> IResult<&str, DMM> {
|
|
|
|
map(
|
|
|
|
tuple((
|
|
|
|
// Degrees
|
|
|
|
complete::i16,
|
|
|
|
optional_separator('°'),
|
|
|
|
// Minutes
|
|
|
|
double,
|
|
|
|
optional_separator('\''),
|
|
|
|
// Direction
|
|
|
|
parse_direction,
|
|
|
|
)),
|
|
|
|
|(degrees, (), minutes, (), direction)| DMM {
|
|
|
|
degrees,
|
|
|
|
minutes,
|
|
|
|
direction,
|
|
|
|
},
|
|
|
|
)(i)
|
|
|
|
}
|
2023-03-19 18:34:04 -04:00
|
|
|
}
|
|
|
|
|
2023-03-19 18:54:49 -04:00
|
|
|
impl FromStr for DMM {
|
2023-03-19 18:34:04 -04:00
|
|
|
type Err = ();
|
|
|
|
|
|
|
|
/// Recognizes DMS in the formats:
|
|
|
|
///
|
|
|
|
/// * `40° 31' 21" N, 105° 5' 39" W`
|
|
|
|
/// * `40 31 21 N, 105 5 39 W`
|
|
|
|
///
|
|
|
|
/// ```rust
|
2023-03-19 18:54:49 -04:00
|
|
|
/// use spatial_coordinate_systems::dmm::DMM;
|
2023-03-19 18:34:04 -04:00
|
|
|
/// use spatial_coordinate_systems::Direction;
|
|
|
|
/// use std::str::FromStr;
|
|
|
|
///
|
2023-03-19 18:54:49 -04:00
|
|
|
/// assert_eq!(DMM::from_str("40 31.3 N").unwrap(), DMM {
|
2023-03-19 18:34:04 -04:00
|
|
|
/// degrees: 40,
|
|
|
|
/// minutes: 31.3_f64,
|
|
|
|
/// direction: Direction::North,
|
|
|
|
/// });
|
2023-03-19 18:54:49 -04:00
|
|
|
/// assert_eq!(DMM::from_str("40°31' N").unwrap(), DMM {
|
2023-03-19 18:34:04 -04:00
|
|
|
/// degrees: 40,
|
|
|
|
/// minutes: 31_f64,
|
|
|
|
/// direction: Direction::North,
|
|
|
|
/// });
|
|
|
|
/// ```
|
|
|
|
fn from_str(i: &str) -> Result<Self, Self::Err> {
|
2023-03-19 18:59:25 -04:00
|
|
|
DMM::parse(i).map_err(|_| ()).map(|(_, ret)| ret)
|
2023-03-19 18:34:04 -04:00
|
|
|
}
|
|
|
|
}
|
2023-03-19 22:33:30 -04:00
|
|
|
|
|
|
|
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,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|