this_algorithm/spatial-coordinate-systems/src/dmm.rs
2023-03-20 00:19:18 -04:00

138 lines
3.7 KiB
Rust

use crate::{
common::{optional_separator, parse_direction, parse_f64},
Direction, LatLon,
};
use dms_coordinates::{Bearing, DMS};
use nom::{
character::complete::{self},
combinator::{map, map_opt},
sequence::tuple,
IResult,
};
use std::{convert::Infallible, fmt, str::FromStr};
#[derive(PartialEq, Debug)]
pub struct Coordinate(pub DMM, pub DMM);
impl Coordinate {
pub fn parse(i: &str) -> IResult<&str, Self> {
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)
}
}
impl FromStr for Coordinate {
type Err = ();
fn from_str(i: &str) -> Result<Self, Self::Err> {
Self::parse(i).map_err(|_| ()).map(|(_, ret)| ret)
}
}
#[derive(PartialEq, Debug)]
pub struct DMM {
pub degrees: i32,
pub minutes: f64,
pub direction: Direction,
}
impl DMM {
pub fn parse(i: &str) -> IResult<&str, DMM> {
map(
tuple((
// Degrees
complete::i32,
optional_separator('°'),
// Minutes
parse_f64,
optional_separator('\''),
// Direction
parse_direction,
)),
|(degrees, (), minutes, (), direction)| DMM {
degrees,
minutes,
direction,
},
)(i)
}
}
impl fmt::Display for DMM {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}° {}'", self.degrees, self.minutes)
}
}
impl fmt::Display for Coordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, {}", self.0, self.1)
}
}
impl TryInto<LatLon> for Coordinate {
type Error = ();
fn try_into(self) -> Result<LatLon, Self::Error> {
LatLon::from(
DMS::new(
self.0.degrees,
self.0.minutes as i32,
self.0.minutes.fract() * 60_f64,
match self.0.direction {
Direction::North => Bearing::North,
Direction::South => Bearing::South,
Direction::East => Bearing::East,
Direction::West => Bearing::West,
},
)
.to_decimal_degrees(),
DMS::new(
self.1.degrees,
self.1.minutes as i32,
self.1.minutes.fract() * 60_f64,
match self.1.direction {
Direction::North => Bearing::North,
Direction::South => Bearing::South,
Direction::East => Bearing::East,
Direction::West => Bearing::West,
},
)
.to_decimal_degrees(),
)
.ok_or(())
}
}
impl TryFrom<LatLon> for Coordinate {
type Error = Infallible;
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
let lat = DMS::from_decimal_degrees(value.lat, true);
let lon = DMS::from_decimal_degrees(value.lon, false);
Ok(Self(
DMM {
degrees: lat.degrees,
minutes: lat.minutes as f64 + lat.seconds / 60_f64,
// TODO: Fix the bearing information for these
direction: Direction::North,
},
DMM {
degrees: lon.degrees,
minutes: lon.minutes as f64 + lon.seconds / 60_f64,
direction: Direction::North,
},
))
}
}