Many improvements to wasm and web frontend

This commit is contained in:
Austen Adler 2023-03-21 23:22:29 -04:00
parent 795a333624
commit 025d0fcca3
12 changed files with 175 additions and 136 deletions

11
Cargo.lock generated
View File

@ -226,6 +226,16 @@ dependencies = [
"vec_map", "vec_map",
] ]
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]] [[package]]
name = "cookie" name = "cookie"
version = "0.16.2" version = "0.16.2"
@ -2110,6 +2120,7 @@ dependencies = [
name = "xpin-wasm" name = "xpin-wasm"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"spatial-coordinate-systems", "spatial-coordinate-systems",
"wasm-bindgen", "wasm-bindgen",
"xpin", "xpin",

View File

@ -23,7 +23,7 @@ impl Coordinate {
pub fn from(lat: f64, lon: f64) -> Option<Self> { pub fn from(lat: f64, lon: f64) -> Option<Self> {
if (-90_f64..=90_f64).contains(&lat) && (-180_f64..=180_f64).contains(&lon) { if (-90_f64..=90_f64).contains(&lat) && (-180_f64..=180_f64).contains(&lon) {
Some(Self { lat: lat, lon: lon }) Some(Self { lat, lon })
} else { } else {
None None
} }

View File

@ -22,12 +22,14 @@ pub struct Coordinate {
impl Coordinate { impl Coordinate {
/// ```rust /// ```rust
/// use spatial_coordinate_systems::utm::Coordinate; /// use spatial_coordinate_systems::utm::Coordinate;
/// use spatial_coordinate_systems::LatLon;
/// ///
/// assert!(Coordinate::parse("10S 706832mE 4344683mN").is_ok()); /// assert!(Coordinate::parse("10S 706832mE 4344683mN").is_ok());
/// assert!(Coordinate::parse("10S 706832mE 4344683N").is_ok()); /// assert!(Coordinate::parse("10S 706832mE 4344683N").is_ok());
/// assert!(Coordinate::parse("10S 706832E 4344683N").is_ok()); /// assert!(Coordinate::parse("10S 706832E 4344683N").is_ok());
/// assert!(Coordinate::parse("10S706832mE 4344683mN").is_err()); /// assert!(Coordinate::parse("10S706832mE 4344683mN").is_err());
/// assert!(Coordinate::parse("10S 706832mE 4344683m").is_ok()); /// assert!(Coordinate::parse("10S 706832mE 4344683m").is_ok());
/// assert!(Coordinate::parse("34H 261877.8163738246 6243185.589276327").is_ok());
/// ``` /// ```
pub fn parse(i: &str) -> IResult<&str, Self> { pub fn parse(i: &str) -> IResult<&str, Self> {
map_opt( map_opt(
@ -39,7 +41,7 @@ impl Coordinate {
parse_f64, parse_f64,
// TODO: Can there be spaces around the m here or no? // TODO: Can there be spaces around the m here or no?
opt(complete::char('m')), opt(complete::char('m')),
// TODO: Should I allow a direction here nor no // TODO: Should I allow a direction here or not
opt(parse_direction), opt(parse_direction),
space1, space1,
parse_f64, parse_f64,
@ -112,7 +114,7 @@ impl TryFrom<LatLon> for Coordinate {
// TODO: This does not feel right // TODO: This does not feel right
let zone_num = utm::lat_lon_to_zone_number(value.lat, value.lon); let zone_num = utm::lat_lon_to_zone_number(value.lat, value.lon);
let zone_letter = utm::lat_to_zone_letter(value.lat).ok_or(())?; let zone_letter = utm::lat_to_zone_letter(value.lat).ok_or(())?;
let (easting, northing, _) = utm::to_utm_wgs84(value.lat, value.lon, zone_num); let (northing, easting, _) = utm::to_utm_wgs84(value.lat, value.lon, zone_num);
Ok(Self { Ok(Self {
zone_num, zone_num,
@ -122,3 +124,28 @@ impl TryFrom<LatLon> for Coordinate {
}) })
} }
} }
#[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());
}};
}
dbg!(dbg!(Coordinate::try_from(
LatLon::from(-33.92487_f64, 18.42406_f64).unwrap()
))
.unwrap()
.to_string());
p!("34H 261877.8163738246 6243185.589276327");
}
}

View File

@ -11,9 +11,7 @@ use crate::Error;
pub fn lat_lon_to_cellid(lat: f64, lon: f64) -> Result<CellID, Error> { pub fn lat_lon_to_cellid(lat: f64, lon: f64) -> Result<CellID, Error> {
// Wrap the latitudes and longitudes // Wrap the latitudes and longitudes
eprintln!("Pre-lat: {lat},{lon}");
let (lat, lon) = wrap::wrap_latlon(lat, lon); let (lat, lon) = wrap::wrap_latlon(lat, lon);
eprintln!("Post-lat: {lat},{lon}");
if !lat.is_finite() || !lon.is_finite() { if !lat.is_finite() || !lon.is_finite() {
return Err(Error::NaNLatLng); return Err(Error::NaNLatLng);

View File

@ -291,6 +291,13 @@ mod tests {
} }
} }
#[test]
fn test_general() {
assert!(dbg!(Address::from_str("6532 BROADCAST TINY apple")).is_ok());
assert!(dbg!(Address::from_str("6532 BROADCAST TINY orange")).is_ok());
assert!(dbg!(Address::from_lat_lon(0.0, 0.0)).is_ok());
}
#[test] #[test]
fn test_parse_v0() { fn test_parse_v0() {
// Regular case // Regular case

View File

@ -30,6 +30,6 @@ placeholder="Enter address"></Svelecte>
type="text" type="text"
bind:value bind:value
placeholder="Address" placeholder="Address"
on:input on:change
/> />
</div> </div>

View File

@ -17,15 +17,14 @@
export let value; export let value;
</script> </script>
<select bind:value={selectedCoordinateType} on:change={selectedCoordinateTypeChange}>
{#each coordinateTypes as t}
<option value={t}>{t}</option>
{/each}
</select>
<div class="mb-4"> <div class="mb-4">
<label class="text-gray-700 text-sm font-bold mb-2" for="username">{selectedCoordinateType}</label <label class="text-gray-700 text-sm font-bold mb-2" for="username">
> <select bind:value={selectedCoordinateType} on:change={selectedCoordinateTypeChange}>
{#each coordinateTypes as t}
<option value={t}>{t}</option>
{/each}
</select>
</label>
<input <input
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-full" 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" type="text"

View File

@ -8,21 +8,6 @@ export const emptyxpin = {
srcCoordsRepr: '0.0, 0.0' 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(),
srcCoordsType: xpin.get_coords_repr_type(),
srcCoordsRepr: xpin.get_coords_repr(),
xpin: xpin
};
}
export const WasmStatus = { export const WasmStatus = {
NotLoaded: -1, NotLoaded: -1,
Loaded: 0, Loaded: 0,

View File

@ -6,17 +6,19 @@
import CoordinateInput from '$lib/CoordinateInput.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 } from '$lib/common.js';
let initSuccess = false;
let coordinateTypes = []; let coordinateTypes = [];
let selectedCoordinateType; 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 coordinateInputValue = '0,0';
// let coordinateInputValue = '16U 5737178.365943674 437486.0153131335';
let addr = emptyxpin; let addr;
let addrInputValue = ''; let addrInputValue = '';
let wasm = { let wasm = {
@ -25,41 +27,34 @@
call: undefined call: undefined
}; };
const updateEditedAddress = (latlng) => { // const updateEditedAddress = (latlng) => {
let popup = leaflet.popup(); // let popup = leaflet.popup();
try { // try {
addr = getxpin(wasm.call.EncodedAddress.from_lat_lon(latlng.lat, latlng.lng)); // addr = wasm.call.EncodedAddress.from_lat_lon(latlng.lat, latlng.lng);
addrInputValue = addr.address; // addrInputValue = addr.address;
coordinateInputValue = addr.decimalDegrees; // coordinateInputValue = addr.decimalDegrees;
popup // popup
.setLatLng({ // .setLatLng({
lat: addr.latLon[0], // lat: addr.latLon[0],
lng: addr.latLon[1] // lng: addr.latLon[1]
}) // })
.setContent(`${addr.address}`) // .setContent(`${addr.address}`)
.openOn(map); // .openOn(map);
} catch (err) { // } catch (err) {
console.error(err); // console.error(err);
addrInputValue = ''; // addrInputValue = '';
coordinateInputValue = '0, 0'; // 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);
// kupdateEditedAddress(e.detail.latlng); updateAddr(wasm.call.EncodedAddress.from_coordinate(`${e.latlng.lat}, ${e.latlng.lng}`), false);
updateAddr(
getxpin(wasm.call.EncodedAddress.from_coordinate(`${e.latlng.lat}, ${e.latlng.lng}`)),
false
);
}; };
const onMapClick = (e) => { const onMapClick = (e) => {
updateAddr( updateAddr(wasm.call.EncodedAddress.from_coordinate(`${e.latlng.lat}, ${e.latlng.lng}`), false);
getxpin(wasm.call.EncodedAddress.from_coordinate(`${e.latlng.lat}, ${e.latlng.lng}`)),
false
);
}; };
let init = async () => { let init = async () => {
@ -79,6 +74,13 @@
.then(async () => { .then(async () => {
leaflet = await import('leaflet'); leaflet = await import('leaflet');
}) })
.then(async () => {
// Initialize the app
updateAddr(wasm.call.EncodedAddress.from_coordinate(coordinateInputValue), true);
})
.then(async () => {
initSuccess = true;
})
.catch((err) => { .catch((err) => {
console.log('Erroring'); console.log('Erroring');
console.error(err); console.error(err);
@ -95,19 +97,20 @@
let outputValue = ' '; let outputValue = ' ';
const selectedCoordinateTypeChange = () => { const selectedCoordinateTypeChange = () => {
console.log('New type:', selectedCoordinateType); // Update everything to handle the case where the address is currently invalid
coordinateInputValue = addr.xpin.get_coords_repr_as( updateAddr(wasm.call.EncodedAddress.from_coordinate(coordinateInputValue), true);
coordinateTypes.indexOf(selectedCoordinateType)
); coordinateInputValue = addr.get_coords_repr_as(coordinateTypes.indexOf(selectedCoordinateType));
}; };
const coordinateInput = () => { const coordinateInput = () => {
try { try {
let parsed = wasm.call.EncodedAddress.from_coordinate(coordinateInputValue); let xpin = wasm.call.EncodedAddress.from_coordinate(coordinateInputValue);
updateAddr(getxpin(parsed), true); updateAddr(xpin, true);
console.log('parsed:', parsed); console.log('xpin:', xpin);
selectedCoordinateType = coordinateTypes[xpin.srcCoordsType] || selectedCoordinateType;
} catch (e) { } catch (e) {
console.error('Could not parse coordinate input:', e); console.error('Could not parse coordinate input:', e);
} }
@ -121,7 +124,9 @@
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);
leaflet.popup().setLatLng(latlng).setContent(`${addr.address}`).openOn(map); leaflet.popup().setLatLng(latlng).setContent(`${addr.address}`).openOn(map);
map.setView(latlng); if (fromTextInput) {
map.setView(latlng);
}
outputValue = ' '; outputValue = ' ';
addrInputValue = addr.address; addrInputValue = addr.address;
@ -130,13 +135,11 @@
if (fromTextInput) { if (fromTextInput) {
selectedCoordinateType = coordinateTypes[addr.srcCoordsType] || selectedCoordinateType; selectedCoordinateType = coordinateTypes[addr.srcCoordsType] || selectedCoordinateType;
coordinateInputValue = addr.srcCoordsRepr || coordinateInputValue; coordinateInputValue = addr.srcCoordsRepr || coordinateInputValue;
console.log('hi');
} else { } else {
console.log('Getting it in the format of', selectedCoordinateType); console.log('Getting it in the format of', selectedCoordinateType);
coordinateInputValue = addr.xpin.get_coords_repr_as( coordinateInputValue = addr.get_coords_repr_as(
coordinateTypes.indexOf(selectedCoordinateType) coordinateTypes.indexOf(selectedCoordinateType)
); );
console.log('Result:', coordinateInputValue);
} }
} }
} catch (e) { } catch (e) {
@ -147,7 +150,7 @@
const addressInput = () => { const addressInput = () => {
try { try {
updateAddr(getxpin(wasm.call.EncodedAddress.from_address(addrInputValue)), false); updateAddr(wasm.call.EncodedAddress.from_address(addrInputValue), false);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
outputValue = `${e}`; outputValue = `${e}`;
@ -174,7 +177,7 @@
<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.latLon})</p> <p class="text-sm">({addr.latLon}) => ({addr.latLon})</p>
<AddressInput bind:value={addrInputValue} on:input={addressInput} /> <AddressInput bind:value={addrInputValue} on:change={addressInput} />
<CoordinateInput <CoordinateInput
bind:value={coordinateInputValue} bind:value={coordinateInputValue}
@ -183,8 +186,10 @@
bind:coordinateTypes bind:coordinateTypes
bind:selectedCoordinateType bind:selectedCoordinateType
/> />
<Map bind:map {onMapClick} on:locationfound={locationfound} />
{:catch message} {:catch message}
<Error {message}>Could not start core module</Error> <Error {message}>Could not start core module</Error>
{/await} {/await}
<span class:invisible={!initSuccess}>
<Map bind:map {onMapClick} on:locationfound={locationfound} />
</span>

View File

@ -184,7 +184,7 @@ mod tests {
assert_ne!(w2e, w3e); assert_ne!(w2e, w3e);
assert_ne!(w1e, w3e); assert_ne!(w1e, w3e);
// Make sure the encoded owrds are not the same // Make sure the encoded words are not the same
assert_ne!(w1e.number, w2e.number); assert_ne!(w1e.number, w2e.number);
assert_ne!(w2e.number, w3e.number); assert_ne!(w2e.number, w3e.number);
assert_ne!(w1e.number, w3e.number); assert_ne!(w1e.number, w3e.number);

View File

@ -12,3 +12,4 @@ crate-type = ["cdylib"]
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/"}
console_error_panic_hook = "0.1.7"

View File

@ -41,61 +41,52 @@ impl From<spatial_coordinate_systems::CoordinateType> for CoordinateType {
} }
} }
#[wasm_bindgen] #[wasm_bindgen(getter_with_clone)]
pub struct EncodedAddress { pub struct EncodedAddress {
address: String, pub address: String,
/// The coordinates used to encode this address /// The coordinates used to encode this address
src_coords: Option<Coordinate>, src_coords: Coordinate,
// coords_repr: Option<String>, #[wasm_bindgen(js_name = srcCoordsRepr)]
pub lat: f64, pub src_coords_repr: String,
pub lon: f64, #[wasm_bindgen(js_name = srcCoordsType)]
pub src_coords_type: CoordinateType,
#[wasm_bindgen(js_name = latLon)]
pub lat_lon: Box<[f64]>,
#[wasm_bindgen(js_name = decimalDegrees)]
pub decimal_degrees: String,
} }
#[wasm_bindgen] #[wasm_bindgen]
impl EncodedAddress { impl EncodedAddress {
/// Get the current address as decimal degrees
#[wasm_bindgen]
pub fn get_decimal_degrees(&self) -> String {
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 /// Get the string representation of the encoded value
#[wasm_bindgen] #[wasm_bindgen]
// TODO: Do not return option
pub fn get_coords_repr_as(&self, coordinate_type: CoordinateType) -> Option<String> { pub fn get_coords_repr_as(&self, coordinate_type: CoordinateType) -> Option<String> {
self.src_coords self.src_coords
.clone()
.as_ref()
// TODO: Remove the clone here // TODO: Remove the clone here
.map(|c| c.clone().as_type(&coordinate_type.into()).ok()) .clone()
.flatten() .as_type(&coordinate_type.into())
.ok()
.map(|s| s.to_string()) .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> {
xpin::Address::from_lat_lon(lat, lon) Self::try_from(
.as_ref() xpin::Address::from_lat_lon(lat, lon)
.map(EncodedAddress::from) .as_ref()
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())?,
)
.map_err(|()| String::from("Could not convert xpin to address"))
} }
/// Get an encoded address from a latitude/longitude /// Get an encoded address from a latitude/longitude
#[wasm_bindgen] #[wasm_bindgen]
pub fn from_coordinate(i: &str) -> Result<EncodedAddress, String> { pub fn from_coordinate(i: &str) -> Result<EncodedAddress, String> {
console_error_panic_hook::set_once();
let src_coords = Coordinate::from_str(i) let src_coords = Coordinate::from_str(i)
.map_err(|()| format!("Could not parse str as a coordinate {i:?}"))?; .map_err(|()| format!("Could not parse str as a coordinate {i:?}"))?;
@ -103,43 +94,58 @@ impl EncodedAddress {
let latlon = LatLon::try_from(src_coords.clone()) let latlon = LatLon::try_from(src_coords.clone())
.map_err(|_| format!("Could not convert coordinate back to latlon"))?; .map_err(|_| format!("Could not convert coordinate back to latlon"))?;
let mut ret = xpin::Address::from_lat_lon(latlon.lat, latlon.lon) let mut ret = Self::try_from(
.as_ref() xpin::Address::from_lat_lon(latlon.lat, latlon.lon)
.map(EncodedAddress::from) .as_ref()
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?,
)
.map_err(|()| String::from("Could not convert xpin to address"))?;
ret.src_coords = Some(src_coords); ret.src_coords_repr = src_coords.to_string();
ret.src_coords_type = src_coords.get_type().into();
ret.src_coords = src_coords;
Ok(ret) 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) Self::try_from(
.as_ref() xpin::Address::from_str(addr_str)
.map(EncodedAddress::from) .as_ref()
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())?,
} )
.map_err(|()| String::from("Could not convert xpin to address"))
#[wasm_bindgen]
pub fn get_address(&self) -> String {
self.address.clone()
}
#[wasm_bindgen]
pub fn get_lat_lon(&self) -> Vec<f64> {
vec![self.lat, self.lon]
} }
} }
impl From<&'_ Address<'_>> for EncodedAddress { impl TryFrom<&'_ Address<'_>> for EncodedAddress {
fn from(addr: &Address) -> Self { type Error = ();
fn try_from(addr: &Address) -> Result<Self, Self::Error> {
console_error_panic_hook::set_once();
let (lat, lon) = addr.as_lat_lon(); let (lat, lon) = addr.as_lat_lon();
Self { let src_coords = Coordinate::from_str(&format!("{}, {}", lat, lon))?;
Ok(Self {
address: addr.to_string(), address: addr.to_string(),
src_coords: None, // TODO: Do not use formatting here
lat, lat_lon: Box::new([lat, lon]),
lon, src_coords_repr: src_coords.to_string(),
} src_coords_type: src_coords.get_type().into(),
src_coords,
decimal_degrees: format!("{}, {}", lat, lon),
})
} }
} }
// #[cfg(test)]
// mod tests {
// use super::*;
// #[test]
// fn test_general() {
// super::from_address("6532 BROADCAST TINY apple")
// }
// }