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