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", "thiserror",
"url", "url",
"utm", "utm",
"wasm-bindgen",
] ]
[[package]] [[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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
wasm_bindgen = ["dep:wasm-bindgen"]
[dependencies] [dependencies]
# Excluding because rustc-serialize does not work with wasm # Excluding because rustc-serialize does not work with wasm
# dms-coordinates v1.1.0 # dms-coordinates v1.1.0
@ -23,3 +26,5 @@ pluscodes = "0.5.0"
utm = "0.1.6" utm = "0.1.6"
thiserror = "1.0.38" thiserror = "1.0.38"
url = "2.3.1" 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> { fn try_from(latlon: LatLon) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
latlon, latlon,
dd: dd::Coordinate::try_from(latlon)?, dd: dd::Coordinate::from(latlon),
dms: dms::Coordinate::try_from(latlon)?, dms: dms::Coordinate::from(latlon),
dmm: dmm::Coordinate::try_from(latlon)?, dmm: dmm::Coordinate::from(latlon),
utm: utm::Coordinate::try_from(latlon)?, utm: utm::Coordinate::try_from(latlon)?,
plus: plus::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 { impl From<LatLon> for Coordinate {
type Error = Error; fn from(value: LatLon) -> Self {
Self(value)
fn try_from(value: LatLon) -> Result<Self, Self::Error> {
Ok(Self(value))
} }
} }

View File

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

View File

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

View File

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

View File

@ -89,6 +89,10 @@ impl Coordinate {
map(dd::Coordinate::parse, Coordinate::DD), map(dd::Coordinate::parse, Coordinate::DD),
// map(xpin::Coordinate::parse, Cordinate::Xpin), // map(xpin::Coordinate::parse, Cordinate::Xpin),
map(plus::Coordinate::parse, Coordinate::Plus), 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, space0,
eof, eof,
@ -101,9 +105,9 @@ impl Coordinate {
let lat_lon = LatLon::from(self); let lat_lon = LatLon::from(self);
Ok(match coordinate_type { Ok(match coordinate_type {
CoordinateType::DD => Self::DD(dd::Coordinate::try_from(lat_lon)?), CoordinateType::DD => Self::DD(dd::Coordinate::from(lat_lon)),
CoordinateType::DMS => Self::DMS(dms::Coordinate::try_from(lat_lon)?), CoordinateType::DMS => Self::DMS(dms::Coordinate::from(lat_lon)),
CoordinateType::DMM => Self::DMM(dmm::Coordinate::try_from(lat_lon)?), CoordinateType::DMM => Self::DMM(dmm::Coordinate::from(lat_lon)),
CoordinateType::UTM => Self::UTM(utm::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)?), 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 { impl TryFrom<LatLon> for Coordinate {
type Error = pluscodes::Error; 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::encode(
&pluscodes::Coordinate { &pluscodes::Coordinate {
latitude: value.get_lat(), latitude: latlon.get_lat(),
longitude: value.get_lon(), longitude: latlon.get_lon(),
}, },
PLUSCODE_LENGTH, 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 std::str::FromStr;
use url::Url; use url::Url;
// TODO: Set a reasonable OSM zoom level // TODO: Set a reasonable OSM zoom level
const OPENSTREETMAP_ZOOM_LEVEL: u8 = 19; const OPENSTREETMAP_ZOOM_LEVEL: u8 = 19;
#[cfg_attr(feature = "wasm_bindgen", wasm_bindgen(getter_with_clone))]
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub struct CoordinateUrls { pub struct CoordinateUrls {
// TODO: These should be getters only // TODO: These should be getters only
pub google_maps: String, pub google_maps: String,
pub openstreetmap: String, pub openstreetmap: String,
pub waze: String,
pub apple_maps: String,
pub osmand: String,
pub bing_maps: String,
pub latlon: LatLon, pub latlon: LatLon,
} }
impl CoordinateUrls { impl CoordinateUrls {
pub fn parse(i: &str) -> Result<Self, Error> { pub fn parse(i: &str) -> IResult<&str, Self> {
let url = Url::parse(i.trim())?; // 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(); fn parse_url_full(i: &str) -> IResult<&str, Url> {
let mut possible_latlon = vec![]; 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" { if key == "query" {
possible_latlon.push(value.to_string()); if let Ok((_str, ret)) = LatLon::parse_full(value.as_ref()) {
} return Ok(("", ret));
}
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}"));
}
} }
} }
} }
// Try to parse a url from google maps context("latlon not found in url", fail)(i)
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()))?
} }
// fn parse_openstreetmap(i: Url) ->IResult<(), LatLon> { fn parse_openstreetmap(i: &str) -> IResult<&str, LatLon> {
// map_res(tuple(( let (_, url) = Self::parse_url_full(i)?;
// tag("map="),
// digit1,
// tag("/"),
// parse_f64 ,
// tag("/"),
// parse_f64 ,
// eof
// )), |(
// _,
// _,
// _,
// lat ,
// _,
// lon ,
// _,
// )| {
// LatLon::new(lat,lon)
// })(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 { impl From<&CoordinateUrls> for LatLon {
@ -78,29 +113,76 @@ impl From<&CoordinateUrls> for LatLon {
} }
} }
impl TryFrom<LatLon> for CoordinateUrls { impl From<LatLon> for CoordinateUrls {
type Error = Error; fn from(latlon: LatLon) -> Self {
fn try_from(latlon: LatLon) -> Result<Self, Self::Error> {
let (lat, lon) = (latlon.get_lat(), latlon.get_lon()); 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 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}"))); ret.set_fragment(Some(&format!("map={OPENSTREETMAP_ZOOM_LEVEL}/{lat}/{lon}")));
String::from(ret) String::from(ret)
}; };
Ok(Self { // https://developers.google.com/waze/deeplinks
google_maps: String::from(Url::parse_with_params( let waze = String::from(
"https://www.google.com/maps/search/", Url::parse_with_params(
&[("api", "1"), ("query", &latlon_str)], "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, openstreetmap,
latlon, latlon,
}) waze,
apple_maps,
osmand,
bing_maps,
}
} }
} }
@ -108,7 +190,7 @@ impl FromStr for CoordinateUrls {
type Err = Error; type Err = Error;
fn from_str(i: &str) -> Result<Self, Self::Err> { 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] [dependencies]
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
xpin = {path=".."} 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" console_error_panic_hook = "0.1.7"

View File

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