Improve spatial-coordinate-systems
This commit is contained in:
parent
9bea0077a7
commit
f687c45b6d
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1375,6 +1375,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"url",
|
||||
"utm",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -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}
|
||||
|
@ -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)?,
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)?),
|
||||
})
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user