First version of multiple coordinate formats

This commit is contained in:
Austen Adler 2023-03-20 21:09:14 -04:00
parent 97814e390a
commit 795a333624
12 changed files with 599 additions and 179 deletions

View File

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

View File

@ -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> {

View File

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

View File

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

View File

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

View File

@ -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 {

View File

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

View 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>

View File

@ -49,7 +49,7 @@
});
map.on('click', onMapClick);
map.on('locationfound', (e) => {
dispatch('locationfound', e);
dispatch('locationfound', e.detail);
});
// Add the location control

View File

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

View File

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

View File

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