First version of multiple coordinate formats
This commit is contained in:
parent
97814e390a
commit
795a333624
@ -1,4 +1,3 @@
|
||||
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{tag_no_case, take_while1},
|
||||
@ -6,7 +5,7 @@ use nom::{
|
||||
complete::{space0, space1},
|
||||
*,
|
||||
},
|
||||
combinator::{map, map_opt, opt},
|
||||
combinator::{eof, map, map_opt, opt, peek},
|
||||
sequence::tuple,
|
||||
IResult,
|
||||
};
|
||||
@ -16,8 +15,9 @@ use crate::Direction;
|
||||
pub fn optional_separator<'a>(sep: char) -> impl FnMut(&'a str) -> IResult<&'a str, ()> {
|
||||
map(
|
||||
alt((
|
||||
map(tuple((space0, complete::char(sep), space0)), std::mem::drop),
|
||||
map(space1, std::mem::drop),
|
||||
map(tuple((space0, complete::char(sep))), std::mem::drop),
|
||||
map(peek(space1), std::mem::drop),
|
||||
map(eof, std::mem::drop),
|
||||
)),
|
||||
std::mem::drop,
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::common::optional_separator;
|
||||
use nom::{combinator::map_opt, number::complete::double, sequence::tuple, IResult};
|
||||
use crate::common::{optional_separator, parse_f64};
|
||||
use nom::{character::complete::space0, combinator::map_opt, sequence::tuple, IResult};
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct Coordinate {
|
||||
pub lat: f64,
|
||||
pub lon: f64,
|
||||
@ -10,13 +10,15 @@ pub struct Coordinate {
|
||||
|
||||
impl Coordinate {
|
||||
pub fn parse(i: &str) -> IResult<&str, Self> {
|
||||
map_opt(
|
||||
tuple((double, optional_separator(','), double)),
|
||||
|(lat, _, lon)| {
|
||||
let ret = map_opt(
|
||||
tuple((parse_f64, optional_separator(','), space0, parse_f64)),
|
||||
|(lat, _, _, lon)| {
|
||||
// Ensure this is a north/south then east/west direction
|
||||
Self::from(lat, lon)
|
||||
},
|
||||
)(i)
|
||||
)(i);
|
||||
eprintln!("{:?}", ret);
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn from(lat: f64, lon: f64) -> Option<Self> {
|
||||
|
@ -2,26 +2,26 @@ use crate::{
|
||||
common::{optional_separator, parse_direction, parse_f64},
|
||||
Direction, LatLon,
|
||||
};
|
||||
use dms_coordinates::{Bearing, DMS};
|
||||
use nom::{
|
||||
character::complete::{self},
|
||||
branch::alt,
|
||||
character::complete::{self, space0},
|
||||
combinator::{map, map_opt},
|
||||
sequence::tuple,
|
||||
IResult,
|
||||
};
|
||||
use std::{convert::Infallible, fmt, str::FromStr};
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
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)| {
|
||||
tuple((DMM::parse, optional_separator(','), space0, DMM::parse)),
|
||||
|(lat, _, _, lon)| {
|
||||
// Ensure this is a north/south then east/west direction
|
||||
if ns.direction.is_lat() && ew.direction.is_lon() {
|
||||
Some(Coordinate(ns, ew))
|
||||
if lat.direction.is_lat() && lon.direction.is_lon() {
|
||||
Some(Coordinate(lat, lon))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -38,38 +38,100 @@ impl FromStr for Coordinate {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct DMM {
|
||||
pub degrees: i32,
|
||||
pub degrees: i16,
|
||||
pub minutes: f64,
|
||||
pub direction: Direction,
|
||||
}
|
||||
|
||||
/// Parse only the numeric portion of the coordinate
|
||||
fn parse_dmm_numeric(i: &str) -> IResult<&str, (i16, f64)> {
|
||||
map(
|
||||
tuple((
|
||||
// Degrees
|
||||
complete::i16,
|
||||
optional_separator('°'),
|
||||
space0,
|
||||
// Minutes
|
||||
parse_f64,
|
||||
optional_separator('\''),
|
||||
)),
|
||||
|(degrees, _, _, minutes, _)| (degrees, minutes),
|
||||
)(i)
|
||||
}
|
||||
|
||||
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,
|
||||
alt((
|
||||
tuple((
|
||||
// Degrees/Minutes
|
||||
parse_dmm_numeric,
|
||||
space0,
|
||||
// Direction
|
||||
parse_direction,
|
||||
)),
|
||||
map(
|
||||
tuple((
|
||||
// Direction
|
||||
parse_direction,
|
||||
space0,
|
||||
// Degrees/Minutes
|
||||
parse_dmm_numeric,
|
||||
)),
|
||||
|(direction, space, dmm)| (dmm, space, direction),
|
||||
),
|
||||
)),
|
||||
|(degrees, (), minutes, (), direction)| DMM {
|
||||
degrees,
|
||||
minutes,
|
||||
direction,
|
||||
|((degrees, minutes), _, direction)| {
|
||||
let negate = direction == Direction::West || direction == Direction::South;
|
||||
|
||||
Self {
|
||||
degrees: degrees * if negate { -1 } else { 1 },
|
||||
minutes,
|
||||
direction: if direction.is_lat() {
|
||||
Direction::North
|
||||
} else {
|
||||
Direction::East
|
||||
},
|
||||
}
|
||||
},
|
||||
)(i)
|
||||
}
|
||||
|
||||
pub fn try_from_decimal_degrees(d: f64, is_latitude: bool) -> Result<Self, ()> {
|
||||
let bounds = if is_latitude {
|
||||
-90_f64..=90_f64
|
||||
} else {
|
||||
-180_f64..=180_f64
|
||||
};
|
||||
|
||||
if !bounds.contains(&d) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let degrees = d as i16;
|
||||
let minutes = d.fract() * 60_f64;
|
||||
|
||||
Ok(Self {
|
||||
degrees,
|
||||
minutes,
|
||||
direction: if is_latitude {
|
||||
Direction::North
|
||||
} else {
|
||||
Direction::East
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_decimal_degrees(&self) -> f64 {
|
||||
self.degrees as f64 + self.minutes / 60_f64
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DMM {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}° {}'", self.degrees, self.minutes)
|
||||
write!(f, "{}° {}' {}", self.degrees, self.minutes, self.direction)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,55 +145,60 @@ 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(())
|
||||
LatLon::from(self.0.to_decimal_degrees(), self.1.to_decimal_degrees()).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<LatLon> for Coordinate {
|
||||
type Error = Infallible;
|
||||
type Error = ();
|
||||
|
||||
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,
|
||||
},
|
||||
DMM::try_from_decimal_degrees(value.lat, true)?,
|
||||
DMM::try_from_decimal_degrees(value.lon, false)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_general() {
|
||||
macro_rules! p {
|
||||
($tt:tt) => {{
|
||||
let cvt = Coordinate::from_str($tt);
|
||||
eprintln!("Testing: {} => {:?}", $tt, cvt);
|
||||
assert!(cvt.is_ok());
|
||||
}};
|
||||
($tt:tt, DMM) => {{
|
||||
let cvt = DMM::parse($tt);
|
||||
eprintln!("Testing: {} => {:?}", $tt, cvt);
|
||||
assert!(cvt.is_ok());
|
||||
}};
|
||||
}
|
||||
// p!(r#"0° 0' 0" N 100° 30' 1" W"#);
|
||||
// p!(r#"0 0 0 N 100 30 1 W"#);
|
||||
|
||||
p!("0° 0' N", DMM);
|
||||
p!("0° 0'N", DMM);
|
||||
p!("N 0° 0'", DMM);
|
||||
|
||||
p!("0° 0' N", DMM);
|
||||
p!("0° 0'N", DMM);
|
||||
p!("N 0° 0' 0", DMM);
|
||||
|
||||
p!(r#"E 100° 30'"#, DMM);
|
||||
p!(r#"N 0° 0' E 100° 30'"#);
|
||||
|
||||
// parse_dmm_numeric(r#"38 53.2148425"#).unwrap();
|
||||
|
||||
// p!(r#"38 53.2148425"#, DMM);
|
||||
// p!(r#"38 53.2148425'"#, DMM);
|
||||
// p!(r#"38° 53.2148425"#, DMM);
|
||||
// p!(r#"38° 53.2148425'"#, DMM);
|
||||
// p!(r#"-77° -1.7611312866219464'"#, DMM);
|
||||
// p!(r#"38° 53.2148425', -77° -1.7611312866219464'"#);
|
||||
}
|
||||
}
|
||||
|
@ -2,30 +2,26 @@ use crate::{
|
||||
common::{optional_separator, parse_direction, parse_f64},
|
||||
Direction, LatLon,
|
||||
};
|
||||
use dms_coordinates::Bearing;
|
||||
pub use dms_coordinates::DMS;
|
||||
use nom::{
|
||||
character::complete::{self},
|
||||
branch::alt,
|
||||
character::complete::{self, space0},
|
||||
combinator::{map, map_opt},
|
||||
sequence::tuple,
|
||||
IResult,
|
||||
};
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct Coordinate(pub DMS, pub DMS);
|
||||
|
||||
impl Coordinate {
|
||||
pub fn parse(i: &str) -> IResult<&str, Self> {
|
||||
map_opt(
|
||||
tuple((parse_dms, optional_separator(','), parse_dms)),
|
||||
|(ns, _, ew)| {
|
||||
tuple((DMS::parse, optional_separator(','), space0, DMS::parse)),
|
||||
|(lat, _, _, lon)| {
|
||||
// Ensure this is a north/south then east/west direction
|
||||
if ns.bearing.is_northern()
|
||||
|| ns.bearing.is_southern() && ew.bearing.is_eastern()
|
||||
|| ew.bearing.is_western()
|
||||
{
|
||||
Some(Coordinate(ns, ew))
|
||||
if lat.direction.is_lat() && lon.direction.is_lon() {
|
||||
Some(Coordinate(lat, lon))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -34,37 +30,108 @@ impl Coordinate {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_dms(i: &str) -> IResult<&str, DMS> {
|
||||
/// Parse only the numeric portion of the coordinate
|
||||
fn parse_dms_numeric(i: &str) -> IResult<&str, (i16, i16, f64)> {
|
||||
map(
|
||||
tuple((
|
||||
// Degrees
|
||||
complete::i32,
|
||||
complete::i16,
|
||||
optional_separator('°'),
|
||||
space0,
|
||||
// Minutes
|
||||
complete::i32,
|
||||
complete::i16,
|
||||
optional_separator('\''),
|
||||
space0,
|
||||
// Seconds
|
||||
parse_f64,
|
||||
optional_separator('"'),
|
||||
// Direction
|
||||
parse_direction,
|
||||
)),
|
||||
|(degrees, (), minutes, (), seconds, (), direction)| {
|
||||
DMS::new(
|
||||
degrees,
|
||||
minutes,
|
||||
seconds,
|
||||
match direction {
|
||||
Direction::North => Bearing::North,
|
||||
Direction::South => Bearing::South,
|
||||
Direction::East => Bearing::East,
|
||||
Direction::West => Bearing::West,
|
||||
},
|
||||
)
|
||||
},
|
||||
|(degrees, _, _, minutes, _, _, seconds, _)| (degrees, minutes, seconds),
|
||||
)(i)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct DMS {
|
||||
pub degrees: i16,
|
||||
pub minutes: i16,
|
||||
pub seconds: f64,
|
||||
pub direction: Direction,
|
||||
}
|
||||
|
||||
impl DMS {
|
||||
pub fn parse(i: &str) -> IResult<&str, DMS> {
|
||||
map(
|
||||
alt((
|
||||
// 0° 0' 0 N 100° 30' 1 E
|
||||
tuple((
|
||||
// Degrees/Minutes/Seconds
|
||||
parse_dms_numeric,
|
||||
space0,
|
||||
// Direction
|
||||
parse_direction,
|
||||
)),
|
||||
// N 0° 0' 0 E 100° 30' 1
|
||||
map(
|
||||
tuple((
|
||||
// Direction
|
||||
parse_direction,
|
||||
space0,
|
||||
// Degrees/Minutes/Seconds
|
||||
parse_dms_numeric,
|
||||
)),
|
||||
|(direction, space, dms)| (dms, space, direction),
|
||||
),
|
||||
)),
|
||||
|((degrees, minutes, seconds), _, direction)| {
|
||||
let negate = direction == Direction::West || direction == Direction::South;
|
||||
|
||||
Self {
|
||||
degrees: degrees * if negate { -1 } else { 1 },
|
||||
minutes,
|
||||
seconds,
|
||||
direction: if direction.is_lat() {
|
||||
Direction::North
|
||||
} else {
|
||||
Direction::East
|
||||
},
|
||||
}
|
||||
},
|
||||
)(i)
|
||||
}
|
||||
|
||||
pub fn try_from_decimal_degrees(d: f64, is_latitude: bool) -> Result<Self, ()> {
|
||||
let bounds = if is_latitude {
|
||||
-90_f64..=90_f64
|
||||
} else {
|
||||
-180_f64..=180_f64
|
||||
};
|
||||
|
||||
if !bounds.contains(&d) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let degrees = d as i16;
|
||||
let minutes = d.fract() * 60_f64;
|
||||
let seconds = minutes.fract() * 60_f64;
|
||||
let minutes = minutes as i16;
|
||||
|
||||
Ok(Self {
|
||||
degrees,
|
||||
minutes,
|
||||
seconds,
|
||||
direction: if is_latitude {
|
||||
Direction::North
|
||||
} else {
|
||||
Direction::East
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_decimal_degrees(&self) -> f64 {
|
||||
self.degrees as f64 + self.minutes as f64 / 60_f64 + self.seconds / 3_600_f64
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Coordinate {
|
||||
type Err = ();
|
||||
|
||||
@ -77,14 +144,14 @@ impl fmt::Display for Coordinate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}° {}' {}\"",
|
||||
self.0.degrees, self.0.minutes, self.0.seconds
|
||||
"{}° {}' {}\" {}",
|
||||
self.0.degrees, self.0.minutes, self.0.seconds, self.0.direction
|
||||
)?;
|
||||
write!(f, ", ")?;
|
||||
write!(
|
||||
f,
|
||||
"{}° {}' {}\"",
|
||||
self.1.degrees, self.1.minutes, self.1.seconds
|
||||
"{}° {}' {}\" {}",
|
||||
self.1.degrees, self.1.minutes, self.1.seconds, self.1.direction
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -103,8 +170,46 @@ impl TryFrom<LatLon> for Coordinate {
|
||||
|
||||
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
|
||||
Ok(Self(
|
||||
DMS::from_decimal_degrees(value.lat, true),
|
||||
DMS::from_decimal_degrees(value.lon, false),
|
||||
DMS::try_from_decimal_degrees(value.lat, true)?,
|
||||
DMS::try_from_decimal_degrees(value.lon, false)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_general() {
|
||||
macro_rules! p {
|
||||
($tt:tt) => {{
|
||||
let cvt = Coordinate::from_str($tt);
|
||||
eprintln!("Testing: {} => {:?}", $tt, cvt);
|
||||
assert!(cvt.is_ok());
|
||||
eprintln!("Now converting to latlon");
|
||||
assert!(dbg!(TryInto::<LatLon>::try_into(cvt.unwrap())).is_ok());
|
||||
}};
|
||||
($tt:tt, DMS) => {{
|
||||
let cvt = DMS::parse($tt);
|
||||
eprintln!("Testing: {} => {:?}", $tt, cvt);
|
||||
assert!(cvt.is_ok());
|
||||
}};
|
||||
}
|
||||
// p!(r#"0° 0' 0" N 100° 30' 1" W"#);
|
||||
// p!(r#"0 0 0 N 100 30 1 W"#);
|
||||
|
||||
p!("0° 0' 0\" N", DMS);
|
||||
p!("0° 0' 0\"N", DMS);
|
||||
p!("N 0° 0' 0\"", DMS);
|
||||
|
||||
p!("0° 0' 0\" N", DMS);
|
||||
p!("0° 0' 0\"N", DMS);
|
||||
p!("N 0° 0' 0", DMS);
|
||||
|
||||
p!(r#"E 100° 30' 1""#, DMS);
|
||||
p!(r#"N 0° 0' 0" E 100° 30' 1""#);
|
||||
|
||||
p!(r#"N 51° 38' 55.3308 E 10° 34' 47.4024"#);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
use std::{fmt, str::FromStr};
|
||||
mod common;
|
||||
pub mod dd;
|
||||
// pub mod dmm;
|
||||
// pub mod dms;
|
||||
pub mod dmm;
|
||||
pub mod dms;
|
||||
pub mod plus;
|
||||
pub mod utm;
|
||||
// pub mod xpin;
|
||||
|
||||
use nom::{
|
||||
branch::alt,
|
||||
@ -24,6 +25,17 @@ pub enum Direction {
|
||||
West,
|
||||
}
|
||||
|
||||
impl fmt::Display for Direction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::North => write!(f, "N"),
|
||||
Self::South => write!(f, "S"),
|
||||
Self::East => write!(f, "E"),
|
||||
Self::West => write!(f, "W"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
/// True if this is north/south
|
||||
pub fn is_lat(&self) -> bool {
|
||||
@ -35,25 +47,42 @@ impl Direction {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Coordinate {
|
||||
DD(dd::Coordinate),
|
||||
// DMS(dms::Coordinate),
|
||||
// DMM(dmm::Coordinate),
|
||||
DMS(dms::Coordinate),
|
||||
DMM(dmm::Coordinate),
|
||||
UTM(utm::Coordinate),
|
||||
// Xpin(xpin::Xpin),
|
||||
Plus(plus::Coordinate),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum CoordinateType {
|
||||
DD,
|
||||
DMS,
|
||||
DMM,
|
||||
UTM,
|
||||
// Xpin,
|
||||
Plus,
|
||||
}
|
||||
|
||||
impl Coordinate {
|
||||
pub fn parse(i: &str) -> IResult<&str, Self> {
|
||||
map(
|
||||
tuple((
|
||||
space0,
|
||||
// Order matters here!
|
||||
// The parser will match on the first successful one
|
||||
alt((
|
||||
map(dd::Coordinate::parse, Coordinate::DD),
|
||||
// map(dms::Coordinate::parse, Coordinate::DMS),
|
||||
// map(dmm::Coordinate::parse, Coordinate::DMM),
|
||||
map(dms::Coordinate::parse, Coordinate::DMS),
|
||||
map(dmm::Coordinate::parse, Coordinate::DMM),
|
||||
map(utm::Coordinate::parse, Coordinate::UTM),
|
||||
// DD needs to be after DMS because the first two digits of
|
||||
// 0 0 0 N 100 30 1 W
|
||||
// can be parsed as a DD
|
||||
map(dd::Coordinate::parse, Coordinate::DD),
|
||||
// map(xpin::Coordinate::parse, Cordinate::Xpin),
|
||||
map(plus::Coordinate::parse, Coordinate::Plus),
|
||||
)),
|
||||
space0,
|
||||
@ -62,6 +91,30 @@ impl Coordinate {
|
||||
|(_, coordinate, _, _)| coordinate,
|
||||
)(i)
|
||||
}
|
||||
|
||||
// TODO: Make this `&self`
|
||||
pub fn as_type(self, coordinate_type: &CoordinateType) -> Result<Self, ()> {
|
||||
let lat_lon = LatLon::try_from(self)?;
|
||||
|
||||
Ok(match coordinate_type {
|
||||
CoordinateType::DD => Self::DD(dd::Coordinate::try_from(lat_lon).or(Err(()))?),
|
||||
CoordinateType::DMS => Self::DMS(dms::Coordinate::try_from(lat_lon)?),
|
||||
CoordinateType::DMM => Self::DMM(dmm::Coordinate::try_from(lat_lon)?),
|
||||
CoordinateType::UTM => Self::UTM(utm::Coordinate::try_from(lat_lon)?),
|
||||
CoordinateType::Plus => Self::Plus(plus::Coordinate::try_from(lat_lon).or(Err(()))?),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> CoordinateType {
|
||||
match self {
|
||||
Self::DD(_) => CoordinateType::DD,
|
||||
Self::DMS(_) => CoordinateType::DMS,
|
||||
Self::DMM(_) => CoordinateType::DMM,
|
||||
Self::UTM(_) => CoordinateType::UTM,
|
||||
// Self::Xpin(_) => CoordinateType::Xpin,
|
||||
Self::Plus(_) => CoordinateType::Plus,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Coordinate> for LatLon {
|
||||
@ -70,8 +123,8 @@ impl TryFrom<Coordinate> for LatLon {
|
||||
fn try_from(value: Coordinate) -> Result<LatLon, Self::Error> {
|
||||
match value {
|
||||
Coordinate::DD(dd) => dd.try_into().or(Err(())),
|
||||
// Coordinate::DMM(dmm) => dmm.try_into(),
|
||||
// Coordinate::DMS(dms) => dms.try_into(),
|
||||
Coordinate::DMM(dmm) => dmm.try_into(),
|
||||
Coordinate::DMS(dms) => dms.try_into(),
|
||||
Coordinate::UTM(utm) => utm.try_into(),
|
||||
Coordinate::Plus(plus) => plus.try_into(),
|
||||
}
|
||||
@ -83,16 +136,16 @@ impl From<dd::Coordinate> for Coordinate {
|
||||
Self::DD(c)
|
||||
}
|
||||
}
|
||||
// impl From<dms::Coordinate> for Coordinate {
|
||||
// fn from(c: dms::Coordinate) -> Self {
|
||||
// Self::DMS(c)
|
||||
// }
|
||||
// }
|
||||
// impl From<dmm::Coordinate> for Coordinate {
|
||||
// fn from(c: dmm::Coordinate) -> Self {
|
||||
// Self::DMM(c)
|
||||
// }
|
||||
// }
|
||||
impl From<dms::Coordinate> for Coordinate {
|
||||
fn from(c: dms::Coordinate) -> Self {
|
||||
Self::DMS(c)
|
||||
}
|
||||
}
|
||||
impl From<dmm::Coordinate> for Coordinate {
|
||||
fn from(c: dmm::Coordinate) -> Self {
|
||||
Self::DMM(c)
|
||||
}
|
||||
}
|
||||
impl From<utm::Coordinate> for Coordinate {
|
||||
fn from(c: utm::Coordinate) -> Self {
|
||||
Self::UTM(c)
|
||||
@ -104,18 +157,18 @@ impl From<plus::Coordinate> for Coordinate {
|
||||
}
|
||||
}
|
||||
|
||||
// impl TryFrom<Coordinate> for dms::Coordinate {
|
||||
// type Error = ();
|
||||
// fn try_from(c: Coordinate) -> Result<Self, Self::Error> {
|
||||
// dms::Coordinate::try_from(LatLon::try_from(c)?)
|
||||
// }
|
||||
// }
|
||||
// impl TryFrom<Coordinate> for dmm::Coordinate {
|
||||
// type Error = ();
|
||||
// fn try_from(c: Coordinate) -> Result<Self, Self::Error> {
|
||||
// dmm::Coordinate::try_from(LatLon::try_from(c)?).or(Err(()))
|
||||
// }
|
||||
// }
|
||||
impl TryFrom<Coordinate> for dms::Coordinate {
|
||||
type Error = ();
|
||||
fn try_from(c: Coordinate) -> Result<Self, Self::Error> {
|
||||
dms::Coordinate::try_from(LatLon::try_from(c)?)
|
||||
}
|
||||
}
|
||||
impl TryFrom<Coordinate> for dmm::Coordinate {
|
||||
type Error = ();
|
||||
fn try_from(c: Coordinate) -> Result<Self, Self::Error> {
|
||||
dmm::Coordinate::try_from(LatLon::try_from(c)?).or(Err(()))
|
||||
}
|
||||
}
|
||||
impl TryFrom<Coordinate> for utm::Coordinate {
|
||||
type Error = ();
|
||||
fn try_from(c: Coordinate) -> Result<Self, Self::Error> {
|
||||
@ -133,8 +186,8 @@ impl fmt::Display for Coordinate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Coordinate::DD(dd) => write!(f, "{}", dd),
|
||||
// Coordinate::DMM(dmm) => write!(f, "{}", dmm),
|
||||
// Coordinate::DMS(dms) => write!(f, "{}", dms),
|
||||
Coordinate::DMM(dmm) => write!(f, "{}", dmm),
|
||||
Coordinate::DMS(dms) => write!(f, "{}", dms),
|
||||
Coordinate::UTM(utm) => write!(f, "{}", utm),
|
||||
Coordinate::Plus(plus) => write!(f, "{}", plus),
|
||||
}
|
||||
@ -154,6 +207,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_general() {
|
||||
macro_rules! p {
|
||||
($tt:tt) => {{
|
||||
let cvt = Coordinate::from_str($tt);
|
||||
eprintln!("Testing: {} => {:?}", $tt, cvt);
|
||||
assert!(cvt.is_ok());
|
||||
}};
|
||||
}
|
||||
assert_eq!(
|
||||
Coordinate::from_str("0.000000 100.500278"),
|
||||
Ok(Coordinate::DD(dd::Coordinate {
|
||||
@ -172,6 +232,12 @@ mod tests {
|
||||
}))
|
||||
);
|
||||
|
||||
p!("1 2");
|
||||
p!(r#"0° 0' 0" N 100° 30' 1" W"#);
|
||||
p!(r#"0 0 0 N 100 30 1 W"#);
|
||||
// TODO: Handle no bearings
|
||||
// p!(r#"0° 0' 0" 100° 30' 1""#);
|
||||
|
||||
// assert_eq!(
|
||||
// dms::Coordinate::try_from(Coordinate::from_str("0.000000 100.500278").unwrap()),
|
||||
// dms::Coordinate::try_from(Coordinate::from_str("0.000000 100.500278").unwrap())
|
||||
|
@ -9,7 +9,7 @@ const PLUSCODE_CHARS: [char; 23] = [
|
||||
'R', 'V', 'W', 'X',
|
||||
];
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct Coordinate(pub String);
|
||||
|
||||
impl Coordinate {
|
||||
|
@ -11,7 +11,7 @@ use nom::{
|
||||
};
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct Coordinate {
|
||||
pub zone_num: u8,
|
||||
pub zone_letter: char,
|
||||
|
36
web-frontend/src/lib/CoordinateInput.svelte
Normal file
36
web-frontend/src/lib/CoordinateInput.svelte
Normal file
@ -0,0 +1,36 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const selectedCoordinateTypeChange = (event) => {
|
||||
dispatch('type-change', event.detail);
|
||||
};
|
||||
|
||||
const change = (event) => {
|
||||
dispatch('type-change', event.detail);
|
||||
};
|
||||
|
||||
export let selectedCoordinateType;
|
||||
export let coordinateTypes;
|
||||
|
||||
export let value;
|
||||
</script>
|
||||
|
||||
<select bind:value={selectedCoordinateType} on:change={selectedCoordinateTypeChange}>
|
||||
{#each coordinateTypes as t}
|
||||
<option value={t}>{t}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="text-gray-700 text-sm font-bold mb-2" for="username">{selectedCoordinateType}</label
|
||||
>
|
||||
<input
|
||||
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-full"
|
||||
type="text"
|
||||
bind:value
|
||||
placeholder="0.00"
|
||||
on:change
|
||||
/>
|
||||
</div>
|
@ -49,7 +49,7 @@
|
||||
});
|
||||
map.on('click', onMapClick);
|
||||
map.on('locationfound', (e) => {
|
||||
dispatch('locationfound', e);
|
||||
dispatch('locationfound', e.detail);
|
||||
});
|
||||
|
||||
// Add the location control
|
||||
|
@ -4,17 +4,22 @@ import * as xpinWasm from 'xpin-wasm';
|
||||
export const emptyxpin = {
|
||||
address: '',
|
||||
latLon: [0.0, 0.0],
|
||||
decimalDegrees: ''
|
||||
decimalDegrees: '',
|
||||
srcCoordsRepr: '0.0, 0.0'
|
||||
};
|
||||
|
||||
export function getxpin(xpin) {
|
||||
if (!xpin) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
address: xpin.get_address(),
|
||||
latLon: xpin.get_lat_lon(),
|
||||
decimalDegrees: xpin.get_decimal_degrees()
|
||||
decimalDegrees: xpin.get_decimal_degrees(),
|
||||
srcCoordsType: xpin.get_coords_repr_type(),
|
||||
srcCoordsRepr: xpin.get_coords_repr(),
|
||||
xpin: xpin
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3,14 +3,18 @@
|
||||
import { WasmStatus } from '$lib/common.js';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import AddressInput from '$lib/AddressInput.svelte';
|
||||
import CoordinateInput from '$lib/CoordinateInput.svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import Error from './Error.svelte';
|
||||
import { emptyxpin, getxpin } from '$lib/common.js';
|
||||
|
||||
let coordinateTypes = [];
|
||||
let selectedCoordinateType;
|
||||
let leaflet;
|
||||
import Map from '$lib/Map.svelte';
|
||||
|
||||
let map;
|
||||
let coordinateInputValue = '0, 0';
|
||||
|
||||
let addr = emptyxpin;
|
||||
let addrInputValue = '';
|
||||
@ -26,6 +30,7 @@
|
||||
try {
|
||||
addr = getxpin(wasm.call.EncodedAddress.from_lat_lon(latlng.lat, latlng.lng));
|
||||
addrInputValue = addr.address;
|
||||
coordinateInputValue = addr.decimalDegrees;
|
||||
popup
|
||||
.setLatLng({
|
||||
lat: addr.latLon[0],
|
||||
@ -36,22 +41,41 @@
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
addrInputValue = '';
|
||||
coordinateInputValue = '0, 0';
|
||||
popup.setLatLng(latlng).setContent(`You clicked at ${latlng}`).openOn(map);
|
||||
}
|
||||
};
|
||||
|
||||
const locationfound = (e) => {
|
||||
console.log('Updating current location event', e);
|
||||
updateEditedAddress(e.detail.latlng);
|
||||
// kupdateEditedAddress(e.detail.latlng);
|
||||
updateAddr(
|
||||
getxpin(wasm.call.EncodedAddress.from_coordinate(`${e.latlng.lat}, ${e.latlng.lng}`)),
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
const onMapClick = (e) => {
|
||||
updateEditedAddress(e.latlng);
|
||||
updateAddr(
|
||||
getxpin(wasm.call.EncodedAddress.from_coordinate(`${e.latlng.lat}, ${e.latlng.lng}`)),
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
let init = async () => {
|
||||
await getWasm()
|
||||
.then((w) => (wasm = w))
|
||||
.then((w) => {
|
||||
wasm = w;
|
||||
for (let k in wasm.call.CoordinateType) {
|
||||
// Add the string coordinate types
|
||||
if (!isNaN(k * 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
coordinateTypes.push(k);
|
||||
}
|
||||
selectedCoordinateType = coordinateTypes[0];
|
||||
})
|
||||
.then(async () => {
|
||||
leaflet = await import('leaflet');
|
||||
})
|
||||
@ -62,10 +86,6 @@
|
||||
});
|
||||
};
|
||||
|
||||
// onMount(async () => {
|
||||
// leaflet = await import('leaflet');
|
||||
// });
|
||||
|
||||
onDestroy(async () => {
|
||||
if (map) {
|
||||
console.log('Unloading Leaflet map.');
|
||||
@ -74,10 +94,29 @@
|
||||
});
|
||||
|
||||
let outputValue = ' ';
|
||||
const input = () => {
|
||||
const selectedCoordinateTypeChange = () => {
|
||||
console.log('New type:', selectedCoordinateType);
|
||||
coordinateInputValue = addr.xpin.get_coords_repr_as(
|
||||
coordinateTypes.indexOf(selectedCoordinateType)
|
||||
);
|
||||
};
|
||||
|
||||
const coordinateInput = () => {
|
||||
try {
|
||||
let parsed = wasm.call.EncodedAddress.from_coordinate(coordinateInputValue);
|
||||
|
||||
updateAddr(getxpin(parsed), true);
|
||||
|
||||
console.log('parsed:', parsed);
|
||||
} catch (e) {
|
||||
console.error('Could not parse coordinate input:', e);
|
||||
}
|
||||
};
|
||||
|
||||
const updateAddr = (newAddr, fromTextInput) => {
|
||||
try {
|
||||
addr = newAddr;
|
||||
if (browser) {
|
||||
addr = getxpin(wasm.call.EncodedAddress.from_address(addrInputValue));
|
||||
console.log('New addr', addr);
|
||||
let latlng = new leaflet.LatLng(addr.latLon[0], addr.latLon[1]);
|
||||
map.panTo(latlng, 20);
|
||||
@ -85,12 +124,35 @@
|
||||
map.setView(latlng);
|
||||
|
||||
outputValue = ' ';
|
||||
addrInputValue = addr.address;
|
||||
|
||||
// Update coordinate display
|
||||
if (fromTextInput) {
|
||||
selectedCoordinateType = coordinateTypes[addr.srcCoordsType] || selectedCoordinateType;
|
||||
coordinateInputValue = addr.srcCoordsRepr || coordinateInputValue;
|
||||
console.log('hi');
|
||||
} else {
|
||||
console.log('Getting it in the format of', selectedCoordinateType);
|
||||
coordinateInputValue = addr.xpin.get_coords_repr_as(
|
||||
coordinateTypes.indexOf(selectedCoordinateType)
|
||||
);
|
||||
console.log('Result:', coordinateInputValue);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
outputValue = `${e}`;
|
||||
}
|
||||
};
|
||||
|
||||
const addressInput = () => {
|
||||
try {
|
||||
updateAddr(getxpin(wasm.call.EncodedAddress.from_address(addrInputValue)), false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
outputValue = `${e}`;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<h1 class="font-bold text-2xl pb-4">App</h1>
|
||||
@ -110,20 +172,17 @@
|
||||
<p class="text-lg js-only">Loading WebAssembly module...</p>
|
||||
{:then}
|
||||
<p>Address: <span class="font-bold">{addr.address}</span></p>
|
||||
<p class="text-sm">({addr.latLon}) => ({addr.decimalDegrees})</p>
|
||||
<p class="text-sm">({addr.latLon}) => ({addr.latLon})</p>
|
||||
|
||||
<AddressInput bind:value={addrInputValue} on:input={input} />
|
||||
<AddressInput bind:value={addrInputValue} on:input={addressInput} />
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="text-gray-700 text-sm font-bold mb-2" for="username">Lat/Lon</label>
|
||||
<input
|
||||
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-full"
|
||||
disabled
|
||||
type="text"
|
||||
bind:value={addr.decimalDegrees}
|
||||
placeholder="0.00"
|
||||
/>
|
||||
</div>
|
||||
<CoordinateInput
|
||||
bind:value={coordinateInputValue}
|
||||
on:type-change={selectedCoordinateTypeChange}
|
||||
on:change={coordinateInput}
|
||||
bind:coordinateTypes
|
||||
bind:selectedCoordinateType
|
||||
/>
|
||||
|
||||
<Map bind:map {onMapClick} on:locationfound={locationfound} />
|
||||
{:catch message}
|
||||
|
@ -1,11 +1,52 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use spatial_coordinate_systems::{Coordinate, LatLon};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use xpin::Address;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum CoordinateType {
|
||||
DD,
|
||||
DMS,
|
||||
DMM,
|
||||
UTM,
|
||||
// Xpin,
|
||||
Plus,
|
||||
}
|
||||
|
||||
impl Into<spatial_coordinate_systems::CoordinateType> for CoordinateType {
|
||||
fn into(self) -> spatial_coordinate_systems::CoordinateType {
|
||||
match self {
|
||||
Self::DD => spatial_coordinate_systems::CoordinateType::DD,
|
||||
Self::DMS => spatial_coordinate_systems::CoordinateType::DMS,
|
||||
Self::DMM => spatial_coordinate_systems::CoordinateType::DMM,
|
||||
Self::UTM => spatial_coordinate_systems::CoordinateType::UTM,
|
||||
// Self::Xpin => spatial_coordinate_systems::CoordinateType::Xpin,
|
||||
Self::Plus => spatial_coordinate_systems::CoordinateType::Plus,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<spatial_coordinate_systems::CoordinateType> for CoordinateType {
|
||||
fn from(value: spatial_coordinate_systems::CoordinateType) -> CoordinateType {
|
||||
match value {
|
||||
spatial_coordinate_systems::CoordinateType::DD => Self::DD,
|
||||
spatial_coordinate_systems::CoordinateType::DMS => Self::DMS,
|
||||
spatial_coordinate_systems::CoordinateType::DMM => Self::DMM,
|
||||
spatial_coordinate_systems::CoordinateType::UTM => Self::UTM,
|
||||
// spatial_coordinate_systems::CoordinateType::Xpin => Self::Xpin ,
|
||||
spatial_coordinate_systems::CoordinateType::Plus => Self::Plus,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct EncodedAddress {
|
||||
address: String,
|
||||
/// The coordinates used to encode this address
|
||||
src_coords: Option<Coordinate>,
|
||||
// coords_repr: Option<String>,
|
||||
pub lat: f64,
|
||||
pub lon: f64,
|
||||
}
|
||||
@ -18,6 +59,31 @@ impl EncodedAddress {
|
||||
format!("{}, {}", self.lat, self.lon)
|
||||
}
|
||||
|
||||
pub fn get_coords_repr_type(&self) -> Option<CoordinateType> {
|
||||
self.src_coords
|
||||
.as_ref()
|
||||
.map(|c| c.get_type())
|
||||
.map(From::from)
|
||||
}
|
||||
|
||||
/// Get the string representation of the encoded value
|
||||
#[wasm_bindgen]
|
||||
pub fn get_coords_repr(&self) -> Option<String> {
|
||||
self.src_coords.as_ref().map(|s| s.to_string())
|
||||
}
|
||||
|
||||
/// Get the string representation of the encoded value
|
||||
#[wasm_bindgen]
|
||||
pub fn get_coords_repr_as(&self, coordinate_type: CoordinateType) -> Option<String> {
|
||||
self.src_coords
|
||||
.clone()
|
||||
.as_ref()
|
||||
// TODO: Remove the clone here
|
||||
.map(|c| c.clone().as_type(&coordinate_type.into()).ok())
|
||||
.flatten()
|
||||
.map(|s| s.to_string())
|
||||
}
|
||||
|
||||
/// Get an encoded address from a latitude/longitude
|
||||
#[wasm_bindgen]
|
||||
pub fn from_lat_lon(lat: f64, lon: f64) -> Result<EncodedAddress, String> {
|
||||
@ -27,6 +93,26 @@ impl EncodedAddress {
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Get an encoded address from a latitude/longitude
|
||||
#[wasm_bindgen]
|
||||
pub fn from_coordinate(i: &str) -> Result<EncodedAddress, String> {
|
||||
let src_coords = Coordinate::from_str(i)
|
||||
.map_err(|()| format!("Could not parse str as a coordinate {i:?}"))?;
|
||||
|
||||
// TODO: Remove the clone here
|
||||
let latlon = LatLon::try_from(src_coords.clone())
|
||||
.map_err(|_| format!("Could not convert coordinate back to latlon"))?;
|
||||
|
||||
let mut ret = xpin::Address::from_lat_lon(latlon.lat, latlon.lon)
|
||||
.as_ref()
|
||||
.map(EncodedAddress::from)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
ret.src_coords = Some(src_coords);
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn from_address(addr_str: &str) -> Result<EncodedAddress, String> {
|
||||
xpin::Address::from_str(addr_str)
|
||||
@ -46,18 +132,12 @@ impl EncodedAddress {
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn parse(i: &str) -> Result<String, String> {
|
||||
spatial_coordinate_systems::Coordinate::from_str(i)
|
||||
.map(|c| format!("{c:?}"))
|
||||
.map_err(|()| format!("Could not parse str as a coordinate {i:?}"))
|
||||
}
|
||||
|
||||
impl From<&'_ Address<'_>> for EncodedAddress {
|
||||
fn from(addr: &Address) -> Self {
|
||||
let (lat, lon) = addr.as_lat_lon();
|
||||
Self {
|
||||
address: addr.to_string(),
|
||||
src_coords: None,
|
||||
lat,
|
||||
lon,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user