diff --git a/src/Models/MapProperties.ts b/src/Models/MapProperties.ts index 69d016f5e8..c97adf0629 100644 --- a/src/Models/MapProperties.ts +++ b/src/Models/MapProperties.ts @@ -1,6 +1,7 @@ import { Store, UIEventSource } from "../Logic/UIEventSource" import { BBox } from "../Logic/BBox" import { RasterLayerPolygon } from "./RasterLayers" +import { Feature } from "geojson" export interface KeyNavigationEvent { date: Date @@ -19,7 +20,10 @@ export interface MapProperties { readonly allowRotating: UIEventSource readonly rotation: UIEventSource readonly pitch: UIEventSource - readonly lastClickLocation: Store<{ lon: number; lat: number }> + readonly lastClickLocation: Store<{ lon: number; lat: number ; /** + * The nearest feature from a MapComplete layer + */ + nearestFeature?: Feature}> readonly allowZooming: UIEventSource readonly useTerrain: Store readonly showScale: UIEventSource diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index ce21d1308f..f13ed90b1e 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -178,7 +178,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.map = new UIEventSource(undefined) const geolocationState = new GeoLocationState() const initial = new InitialMapPositioning(layout, geolocationState) - this.mapProperties = new MapLibreAdaptor(this.map, initial) + this.mapProperties = new MapLibreAdaptor(this.map, initial, {correctClick: 20}) this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin @@ -554,6 +554,16 @@ export default class ThemeViewState implements SpecialVisualizationState { }) } + private setSelectedElement(feature: Feature){ + const current = this.selectedElement.data + if(current?.properties?.id !== undefined && current.properties.id === feature.properties.id ){ + console.log("Not setting selected, same id", current, feature) + return // already set + } + // this.selectedElement.setData(undefined) + this.selectedElement.setData(feature) + } + /** * Selects the feature that is 'i' closest to the map center */ @@ -570,13 +580,11 @@ export default class ThemeViewState implements SpecialVisualizationState { if (!toSelect) { return } - this.selectedElement.setData(undefined) - this.selectedElement.setData(toSelect) + this.setSelectedElement(toSelect) }) return } - this.selectedElement.setData(undefined) - this.selectedElement.setData(toSelect) + this.setSelectedElement(toSelect) } private initHotkeys() { @@ -992,6 +1000,15 @@ export default class ThemeViewState implements SpecialVisualizationState { this.userRelatedState.recentlyVisitedSearch.add(r) }) + this.mapProperties.lastClickLocation.addCallbackD(lastClick => { + if(lastClick.mode !== "left" || !lastClick.nearestFeature){ + return + } + const f = lastClick.nearestFeature + this.setSelectedElement(f) + + }) + this.userRelatedState.showScale.addCallbackAndRun((showScale) => { this.mapProperties.showScale.set(showScale) }) diff --git a/src/UI/Map/MapLibreAdaptor.ts b/src/UI/Map/MapLibreAdaptor.ts index 654c67f5f8..3e8b201938 100644 --- a/src/UI/Map/MapLibreAdaptor.ts +++ b/src/UI/Map/MapLibreAdaptor.ts @@ -1,10 +1,5 @@ import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" -import maplibregl, { - Map as MLMap, - Map as MlMap, - ScaleControl, - SourceSpecification, -} from "maplibre-gl" +import maplibregl, { Map as MLMap, Map as MlMap, ScaleControl, SourceSpecification } from "maplibre-gl" import { RasterLayerPolygon } from "../../Models/RasterLayers" import { Utils } from "../../Utils" import { BBox } from "../../Logic/BBox" @@ -16,6 +11,8 @@ import * as htmltoimage from "html-to-image" import RasterLayerHandler from "./RasterLayerHandler" import Constants from "../../Models/Constants" import { Protocol } from "pmtiles" +import { GeoOperations } from "../../Logic/GeoOperations" +import { Feature, LineString } from "geojson" /** * The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties` @@ -46,7 +43,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { readonly allowRotating: UIEventSource readonly allowZooming: UIEventSource readonly lastClickLocation: Store< - undefined | { lon: number; lat: number; mode: "left" | "right" | "middle" } + undefined | { lon: number; lat: number; mode: "left" | "right" | "middle" , /** + * The nearest feature from a MapComplete layer + */ + nearestFeature?: Feature } > readonly minzoom: UIEventSource readonly maxzoom: UIEventSource @@ -64,7 +64,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { private readonly _maplibreMap: Store - constructor(maplibreMap: Store, state?: Partial) { + constructor(maplibreMap: Store, state?: Partial, options?:{ + correctClick?: number + }) { if (!MapLibreAdaptor.pmtilesInited) { maplibregl.addProtocol("pmtiles", new Protocol().tile) MapLibreAdaptor.pmtilesInited = true @@ -104,7 +106,8 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { const lastClickLocation = new UIEventSource<{ lat: number lon: number - mode: "left" | "right" | "middle" + mode: "left" | "right" | "middle", + nearestFeature?: Feature }>(undefined) this.lastClickLocation = lastClickLocation const self = this @@ -122,8 +125,40 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { const lat = e.lngLat.lat const mouseEvent: MouseEvent = e.originalEvent mode = mode ?? clickmodes[mouseEvent.button] + let nearestFeature: Feature = undefined + if(options?.correctClick && maplibreMap.data){ + const map = maplibreMap.data + const point = e.point + const buffer = options?.correctClick + const features = map.queryRenderedFeatures([ + [point.x - buffer, point.y - buffer], + [point.x + buffer, point.y + buffer] + ]).filter(f => f.source.startsWith("mapcomplete_")) + if(features.length === 1){ + nearestFeature = features[0] + }else{ + let nearestD: number = undefined + for (const feature of features) { + let d: number // in meter + if(feature.geometry.type === "LineString"){ + const way = > feature + const lngLat:[number,number] = [e.lngLat.lng, e.lngLat.lat] + const p = GeoOperations.nearestPoint(way, lngLat) + console.log(">>>",p, way, lngLat) + if(!p){ + continue + } + d = p.properties.dist * 1000 + if(nearestFeature === undefined || d < nearestD){ + nearestFeature = way + nearestD = d + } + } + } + } + } + lastClickLocation.setData({ lon, lat, mode, nearestFeature }) - lastClickLocation.setData({ lon, lat, mode }) } maplibreMap.addCallbackAndRunD((map) => { diff --git a/src/UI/Map/ShowDataLayer.ts b/src/UI/Map/ShowDataLayer.ts index e1e8a124bc..a1ce050f30 100644 --- a/src/UI/Map/ShowDataLayer.ts +++ b/src/UI/Map/ShowDataLayer.ts @@ -568,7 +568,6 @@ export default class ShowDataLayer { return } const bbox = BBox.bboxAroundAll(features.map(BBox.get)) - console.debug("Zooming to features", bbox.asGeoJson()) window.requestAnimationFrame(() => { map.resize() map.fitBounds(bbox.toLngLat(), { diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index 82f4f4062b..2930465843 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -54,7 +54,6 @@ let theme = state.theme let maplibremap: UIEventSource = state.map - let state_selectedElement = state.selectedElement let selectedElement: UIEventSource = new UIEventSource(undefined) let compass = Orientation.singleton.alpha let compassLoaded = Orientation.singleton.gotMeasurement @@ -99,7 +98,7 @@ state.mapProperties.installCustomKeyboardHandler(viewport) - let selectedLayer: Store = state.selectedElement.mapD((element) => { + let selectedLayer: Store = selectedElement.mapD((element) => { if (element.properties.id.startsWith("current_view")) { return currentViewLayer } @@ -458,7 +457,7 @@ }} >
- + {/if} @@ -472,7 +471,7 @@ }} > - + {:else} {/if}