From 45d7e6f8b7f42aacdc7e60b8342250ca309ce555 Mon Sep 17 00:00:00 2001 From: Baptiste Prevot Date: Fri, 18 Oct 2024 19:06:03 +0200 Subject: [PATCH] wasm + html demo: add click to measure functionality Signed-off-by: Baptiste Prevot --- wasm/html_demo/index.js | 55 +++++++++++++++++++++++++++++++++++------ wasm/src/lib.rs | 52 +++++++++++++++++++++++++++++++++++--- 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/wasm/html_demo/index.js b/wasm/html_demo/index.js index 777afa1..56f0a31 100644 --- a/wasm/html_demo/index.js +++ b/wasm/html_demo/index.js @@ -1,11 +1,11 @@ 'use strict'; -import 'maplibre-gl/dist/maplibre-gl.css'; +import Bbox from '@turf/bbox'; +import * as turf from '@turf/helpers'; +import Alpine from 'alpinejs'; import * as maplibregl from 'maplibre-gl'; +import 'maplibre-gl/dist/maplibre-gl.css'; import { Protocol } from 'pmtiles'; -import { Lrs, LrmScaleMeasure, set_panic_hook } from '../pkg/liblrs_wasm'; -import * as turf from '@turf/helpers'; -import Bbox from '@turf/bbox' -import Alpine from 'alpinejs' +import { LrmScaleMeasure, Lrs, Point, set_panic_hook } from '../pkg/liblrs_wasm'; // For the rust bindings: this allows us to have nice error messages set_panic_hook() @@ -65,6 +65,17 @@ async function file_selected(el) { } }); + + map.addLayer({ + 'id': 'lrms-hitbox', + 'type': 'line', + 'source': 'lrms', + 'paint': { + 'line-width': 10, + 'line-opacity': 0 + } + }); + map.addLayer({ 'id': 'lrms-hover', 'type': 'line', @@ -156,6 +167,34 @@ async function file_selected(el) { } }); + + + map.on('mouseenter', 'lrms-hitbox', () => { + map.getCanvas().style.cursor = 'pointer' + }) + map.on('mouseleave', 'lrms-hitbox', () => { + map.getCanvas().style.cursor = '' + }) + + + map.on('click', 'lrms-hitbox', (e) => { + + let lrm_id = e.features[0].id; + let clicked_point = new Point(e.lngLat.lng, e.lngLat.lat); + + let projection = lrs.lookup(clicked_point, lrm_id)[0]; + + let window_lrms = window.Alpine.store('lrms') + + window_lrms.selectedFeature = curves_features[lrm_id]; + window_lrms.pkStart = projection.measure.anchor_name + '+' + Math.round(projection.measure.scale_offset); + + window_lrms.startMeasure = projection.measure; + let point = lrs.resolve(lrm_id, projection.measure) + window_lrms.pkStartPoint = turf.point([point.x, point.y]); + window_lrms.handlePks(false) + }); + return { features: curves_features, filename: file.name, @@ -271,12 +310,14 @@ Alpine.store('lrms', { this.endMeasure = null; } }, - handlePks() { + handlePks(move_window = true) { const points = [this.pkStartPoint, this.pkEndPoint].filter(p => p !== null) const geojson = turf.featureCollection(points); map.getSource('pr').setData(geojson); if (points.length === 1) { - map.flyTo({ center: points[0].geometry.coordinates, zoom: 15 }) + if (move_window) { + map.flyTo({ center: points[0].geometry.coordinates, zoom: 15 }) + } } else { map.fitBounds(Bbox(geojson), { padding: 30 }) const range = this.lrs.resolve_range(this.selectedFeature.id, this.startMeasure, this.endMeasure) diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 6987d02..16fde4d 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -1,7 +1,10 @@ //! High level extensions meant for an easy usage //! Those functions are exposed in wasm-bindings -use liblrs::lrs_ext::*; +use liblrs::{ + lrs::{LrmHandle, LrsBase}, + lrs_ext::*, +}; use wasm_bindgen::prelude::*; #[wasm_bindgen] @@ -20,6 +23,15 @@ pub struct Point { pub y: f64, } +#[wasm_bindgen] +impl Point { + /// Build a new [`LrmMeasure`] from an [`Anchor`] `name` and the `offset` on the [`LrmScale`]. + #[wasm_bindgen(constructor)] + pub fn new(x: f64, y: f64) -> Self { + Self { x, y } + } +} + impl From for Point { fn from(value: geo_types::Point) -> Self { Self { @@ -29,6 +41,12 @@ impl From for Point { } } +impl From for geo_types::Point { + fn from(value: Point) -> Self { + Self::new(value.x, value.y) + } +} + impl From for Point { fn from(value: geo_types::Coord) -> Self { Self { @@ -39,12 +57,13 @@ impl From for Point { } #[wasm_bindgen(getter_with_clone)] +#[derive(Clone)] /// Represent a position on an [`LrmScale`] relative as an `offset` to an [`Anchor`]. pub struct LrmScaleMeasure { /// `name` of the reference [`Anchor`]. - anchor_name: String, + pub anchor_name: String, /// `offset` to the reference [`Anchor`]. - scale_offset: f64, + pub scale_offset: f64, } #[wasm_bindgen] @@ -102,6 +121,15 @@ impl From<&liblrs::lrm_scale::Anchor> for Anchor { } } +#[wasm_bindgen(getter_with_clone)] +/// The result of a projection onto an [`LrmScale`]. +pub struct LrmProjection { + /// Contains `measure` ([`LrmScaleMeasure`]) and `lrm` ([`LrmHandle`]). + pub measure: LrmScaleMeasure, + /// How far from the [`Lrm`] is the [`Point`] that has been projected. + pub orthogonal_offset: f64, +} + #[wasm_bindgen] impl Lrs { /// Load the data. @@ -155,6 +183,24 @@ impl Lrs { .resolve_range(lrm_index, &from.into(), &to.into()) .map(|coords| coords.into_iter().map(|coord| coord.into()).collect()) } + + /// Projects a [`Point`] on all applicable [`Traversal`]s to a given [`Lrm`]. + /// The [`Point`] must be in the bounding box of the [`Curve`] of the [`Traversal`]. + /// The result is sorted by `orthogonal_offset`: the nearest [`Lrm`] to the [`Point`] is the first item. + pub fn lookup(&self, point: Point, lrm_handle: usize) -> Vec { + self.lrs + .lrs + .lookup(point.into(), LrmHandle(lrm_handle)) + .iter() + .map(|p| LrmProjection { + measure: LrmScaleMeasure { + anchor_name: p.measure.measure.anchor_name.to_owned(), + scale_offset: p.measure.measure.scale_offset, + }, + orthogonal_offset: p.orthogonal_offset, + }) + .collect() + } } #[wasm_bindgen]