Improve spatial-coordinate-systems

This commit is contained in:
Austen Adler 2023-03-28 22:11:23 -04:00
parent 9bea0077a7
commit f687c45b6d
12 changed files with 202 additions and 115 deletions

1
Cargo.lock generated
View File

@ -1375,6 +1375,7 @@ dependencies = [
"thiserror",
"url",
"utm",
"wasm-bindgen",
]
[[package]]

View File

@ -5,6 +5,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
wasm_bindgen = ["dep:wasm-bindgen"]
[dependencies]
# Excluding because rustc-serialize does not work with wasm
# dms-coordinates v1.1.0
@ -23,3 +26,5 @@ pluscodes = "0.5.0"
utm = "0.1.6"
thiserror = "1.0.38"
url = "2.3.1"
wasm-bindgen={version="0.2",optional=true}

View File

@ -28,9 +28,9 @@ impl TryFrom<LatLon> for Coordinates {
fn try_from(latlon: LatLon) -> Result<Self, Self::Error> {
Ok(Self {
latlon,
dd: dd::Coordinate::try_from(latlon)?,
dms: dms::Coordinate::try_from(latlon)?,
dmm: dmm::Coordinate::try_from(latlon)?,
dd: dd::Coordinate::from(latlon),
dms: dms::Coordinate::from(latlon),
dmm: dmm::Coordinate::from(latlon),
utm: utm::Coordinate::try_from(latlon)?,
plus: plus::Coordinate::try_from(latlon)?,
})

View File

@ -92,11 +92,9 @@ impl From<&Coordinate> for LatLon {
}
}
impl TryFrom<LatLon> for Coordinate {
type Error = Error;
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
Ok(Self(value))
impl From<LatLon> for Coordinate {
fn from(value: LatLon) -> Self {
Self(value)
}
}

View File

@ -148,15 +148,13 @@ impl From<&Coordinate> for LatLon {
}
}
impl TryFrom<LatLon> for Coordinate {
type Error = Error;
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
Ok(Self(
DMM::from_decimal_degrees(value.get_lat(), true),
DMM::from_decimal_degrees(value.get_lon(), false),
value,
))
impl From<LatLon> for Coordinate {
fn from(latlon: LatLon) -> Self {
Self(
DMM::from_decimal_degrees(latlon.get_lat(), true),
DMM::from_decimal_degrees(latlon.get_lon(), false),
latlon,
)
}
}

View File

@ -164,15 +164,13 @@ impl From<&Coordinate> for LatLon {
}
}
impl TryFrom<LatLon> for Coordinate {
type Error = Error;
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
Ok(Self(
DMS::from_decimal_degrees(value.get_lat(), true),
DMS::from_decimal_degrees(value.get_lon(), false),
value,
))
impl From<LatLon> for Coordinate {
fn from(latlon: LatLon) -> Self {
Self(
DMS::from_decimal_degrees(latlon.get_lat(), true),
DMS::from_decimal_degrees(latlon.get_lon(), false),
latlon,
)
}
}

View File

@ -1,3 +1,5 @@
#[cfg(feature = "wasm_bindgen")]
use wasm_bindgen::prelude::*;
use std::fmt;
use nom::{
@ -12,6 +14,7 @@ use crate::{
Error,
};
#[cfg_attr(feature = "wasm_bindgen", wasm_bindgen(getter_with_clone))]
#[derive(PartialEq, Debug, Clone, Copy)]
pub struct LatLon {
lat: f64,

View File

@ -89,6 +89,10 @@ impl Coordinate {
map(dd::Coordinate::parse, Coordinate::DD),
// map(xpin::Coordinate::parse, Cordinate::Xpin),
map(plus::Coordinate::parse, Coordinate::Plus),
// Try to parse as a URL last
map(urls::CoordinateUrls::parse, |coordinate_urls| {
Coordinate::DD(dd::Coordinate::from(coordinate_urls.latlon))
}),
)),
space0,
eof,
@ -101,9 +105,9 @@ impl Coordinate {
let lat_lon = LatLon::from(self);
Ok(match coordinate_type {
CoordinateType::DD => Self::DD(dd::Coordinate::try_from(lat_lon)?),
CoordinateType::DMS => Self::DMS(dms::Coordinate::try_from(lat_lon)?),
CoordinateType::DMM => Self::DMM(dmm::Coordinate::try_from(lat_lon)?),
CoordinateType::DD => Self::DD(dd::Coordinate::from(lat_lon)),
CoordinateType::DMS => Self::DMS(dms::Coordinate::from(lat_lon)),
CoordinateType::DMM => Self::DMM(dmm::Coordinate::from(lat_lon)),
CoordinateType::UTM => Self::UTM(utm::Coordinate::try_from(lat_lon)?),
CoordinateType::Plus => Self::Plus(plus::Coordinate::try_from(lat_lon)?),
})

View File

@ -56,14 +56,14 @@ impl From<&Coordinate> for LatLon {
impl TryFrom<LatLon> for Coordinate {
type Error = pluscodes::Error;
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
fn try_from(latlon: LatLon) -> Result<Self, Self::Error> {
pluscodes::encode(
&pluscodes::Coordinate {
latitude: value.get_lat(),
longitude: value.get_lon(),
latitude: latlon.get_lat(),
longitude: latlon.get_lon(),
},
PLUSCODE_LENGTH,
)
.map(|pluscode| Coordinate(pluscode, value))
.map(|pluscode| Coordinate(pluscode, latlon))
}
}

View File

@ -1,75 +1,110 @@
use crate::{Error, LatLon};
#[cfg(feature = "wasm_bindgen")]
use wasm_bindgen::prelude::*;
use crate::{common::parse_f64, Error, LatLon};
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::digit1,
combinator::{eof, fail, map, map_res, rest},
error::context,
sequence::tuple,
IResult,
};
use std::str::FromStr;
use url::Url;
// TODO: Set a reasonable OSM zoom level
const OPENSTREETMAP_ZOOM_LEVEL: u8 = 19;
#[cfg_attr(feature = "wasm_bindgen", wasm_bindgen(getter_with_clone))]
#[derive(PartialEq, Debug, Clone)]
pub struct CoordinateUrls {
// TODO: These should be getters only
pub google_maps: String,
pub openstreetmap: String,
pub waze: String,
pub apple_maps: String,
pub osmand: String,
pub bing_maps: String,
pub latlon: LatLon,
}
impl CoordinateUrls {
pub fn parse(i: &str) -> Result<Self, Error> {
let url = Url::parse(i.trim())?;
pub fn parse(i: &str) -> IResult<&str, Self> {
// let (_, url) = Self::parse_url_full(i)?;
// TODO: Do we have to convert to url for each of these?
map(
alt((Self::parse_google_maps, Self::parse_openstreetmap)),
Self::from,
)(i)
}
let pairs = url.query_pairs();
let mut possible_latlon = vec![];
fn parse_url_full(i: &str) -> IResult<&str, Url> {
map_res(rest, |i: &str| Url::parse(i.trim()))(i)
}
for (key, value) in pairs {
fn parse_google_maps(i: &str) -> IResult<&str, LatLon> {
let (_, url) = Self::parse_url_full(i)?;
for (key, value) in url.query_pairs() {
if key == "query" {
possible_latlon.push(value.to_string());
}
}
if let Some(fragment) = url.fragment() {
if let Some(("map", v)) = fragment.split_once("=") {
if let Some((_, lat_slash_lon)) = v.split_once("/") {
if let Some((lat, lon)) = lat_slash_lon.split_once("/") {
possible_latlon.push(format!("{lat},{lon}"));
}
if let Ok((_str, ret)) = LatLon::parse_full(value.as_ref()) {
return Ok(("", ret));
}
}
}
// Try to parse a url from google maps
possible_latlon
.iter()
.find_map(|value| {
LatLon::parse_full(value.as_ref())
.map(|(_, ret)| ret)
.ok()
.map(Self::try_from)
})
.ok_or_else(|| Error::NoUrlLatLon(i.to_string()))?
context("latlon not found in url", fail)(i)
}
// fn parse_openstreetmap(i: Url) ->IResult<(), LatLon> {
// map_res(tuple((
// tag("map="),
// digit1,
// tag("/"),
// parse_f64 ,
// tag("/"),
// parse_f64 ,
// eof
// )), |(
// _,
// _,
// _,
// lat ,
// _,
// lon ,
// _,
// )| {
// LatLon::new(lat,lon)
fn parse_openstreetmap(i: &str) -> IResult<&str, LatLon> {
let (_, url) = Self::parse_url_full(i)?;
// })(i.fragment().ok_or((Err(()))))
// }
// First check if they are querying anything
// https://www.openstreetmap.org/query?lat=12.34&lon=56.78#...
{
let get_float = |k: &str| {
url.query_pairs()
.filter(|(key, _value)| key == k)
.find_map(|(_key, value)| parse_f64(&value).ok().map(|(_, ret)| ret))
};
let lat = get_float("lat");
let lon = get_float("lat");
if let Some((lat, lon)) = lat.zip(lon) {
if let Ok(ret) = LatLon::new(lat, lon) {
return Ok(("", ret));
}
}
}
// Now check the fragment
// https://www.openstreetmap.org/query?lat=12.34&lon=56.78#...map=19/12.34/56.78
{
// TODO: Do not unwrap
if let Some(fragment) = url.fragment() {
if let Ok((_str, ret)) = map_res(
tuple((
tag("map="),
// Zoom level
digit1,
tag("/"),
parse_f64,
tag("/"),
parse_f64,
eof,
)),
|(_map_equals, _zoom, _, lat, _, lon, _eof)| LatLon::new(lat, lon),
)(fragment)
{
return Ok(("", ret));
}
}
}
context("latlon not found in url", fail)(i)
}
}
impl From<&CoordinateUrls> for LatLon {
@ -78,29 +113,76 @@ impl From<&CoordinateUrls> for LatLon {
}
}
impl TryFrom<LatLon> for CoordinateUrls {
type Error = Error;
fn try_from(latlon: LatLon) -> Result<Self, Self::Error> {
impl From<LatLon> for CoordinateUrls {
fn from(latlon: LatLon) -> Self {
let (lat, lon) = (latlon.get_lat(), latlon.get_lon());
let latlon_str = format!("{lat},{lon}");
let (lat_str, lon_str) = (format!("{lat}"), format!("{lon}"));
let ll = format!("{lat},{lon}");
let google_maps = String::from(
Url::parse_with_params(
"https://www.google.com/maps/search/",
&[("api", "1"), ("query", ll.as_ref())],
)
.unwrap(),
);
let openstreetmap = {
let mut ret = Url::parse("https://www.openstreetmap.org/")?;
let mut ret = Url::parse_with_params(
"https://www.openstreetmap.org/query",
&[("lat", lat_str.as_str()), ("lon", lon_str.as_str())],
)
.unwrap();
ret.set_fragment(Some(&format!("map={OPENSTREETMAP_ZOOM_LEVEL}/{lat}/{lon}")));
String::from(ret)
};
Ok(Self {
google_maps: String::from(Url::parse_with_params(
"https://www.google.com/maps/search/",
&[("api", "1"), ("query", &latlon_str)],
)?),
// https://developers.google.com/waze/deeplinks
let waze = String::from(
Url::parse_with_params(
"https://waze.com/ul",
// TODO: Figure out what nagivate=yes does
&[("ll", ll.as_ref()), ("navigate", "yes")],
)
.unwrap(),
);
// https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html
// http://maps.apple.com/?ll=50.894967,4.341626
let apple_maps = String::from(
Url::parse_with_params("https://maps.apple.com/", &[("ll", ll.as_str())]).unwrap(),
);
let osmand = String::from(
Url::parse_with_params(
"https://osmand.net/go",
// TODO: Find a good zoom level
&[
("lat", lat_str.as_ref()),
("lon", lon_str.as_ref()),
("z", "17"),
],
)
.unwrap(),
);
// https://learn.microsoft.com/en-us/bingmaps/articles/create-a-custom-map-url
let bing_maps = String::from(
Url::parse_with_params("https://bing.com/maps/default.aspx", &[("cp", ll.as_str())])
.unwrap(),
);
Self {
google_maps,
openstreetmap,
latlon,
})
waze,
apple_maps,
osmand,
bing_maps,
}
}
}
@ -108,7 +190,7 @@ impl FromStr for CoordinateUrls {
type Err = Error;
fn from_str(i: &str) -> Result<Self, Self::Err> {
Self::parse(i).map_err(Into::into)
Self::parse(i).map(|(_, ret)| ret).map_err(Into::into)
}
}

View File

@ -11,5 +11,5 @@ crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
xpin = {path=".."}
spatial-coordinate-systems = {path="../spatial-coordinate-systems/"}
spatial-coordinate-systems = {path="../spatial-coordinate-systems/",features=["wasm_bindgen"]}
console_error_panic_hook = "0.1.7"

View File

@ -1,6 +1,6 @@
use std::str::FromStr;
use spatial_coordinate_systems::{Coordinate, LatLon};
use spatial_coordinate_systems::{urls::CoordinateUrls, Coordinate, LatLon};
use wasm_bindgen::prelude::*;
use xpin::Address;
@ -177,33 +177,31 @@ impl TryFrom<&'_ Address<'_>> for EncodedAddress {
address: addr.to_string(),
lat_lon: Box::new([lat, lon]),
src_coords,
coordinate_urls: spatial_coordinate_systems::urls::CoordinateUrls::try_from(
all_coordinates.latlon,
)
.map_err(|e| e.to_string())?
.into(),
coordinate_urls: CoordinateUrls::try_from(all_coordinates.latlon)
.map_err(|e| e.to_string())?
.into(),
decimal_degrees: format!("{}, {}", lat, lon),
all_coordinates: Coordinates::from(&all_coordinates),
})
}
}
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug, Clone)]
pub struct CoordinateUrls {
// TODO: These should be getters only
pub google_maps: String,
pub openstreetmap: String,
}
// #[wasm_bindgen(getter_with_clone)]
// #[derive(Debug, Clone)]
// pub struct CoordinateUrls {
// // TODO: These should be getters only
// pub google_maps: String,
// pub openstreetmap: String,
// }
impl From<spatial_coordinate_systems::urls::CoordinateUrls> for CoordinateUrls {
fn from(value: spatial_coordinate_systems::urls::CoordinateUrls) -> Self {
Self {
google_maps: value.google_maps,
openstreetmap: value.openstreetmap,
}
}
}
// impl From<spatial_coordinate_systems::urls::CoordinateUrls> for CoordinateUrls {
// fn from(value: spatial_coordinate_systems::urls::CoordinateUrls) -> Self {
// Self {
// google_maps: value.google_maps,
// openstreetmap: value.openstreetmap,
// }
// }
// }
#[cfg(test)]
mod tests {