diff --git a/website/src/lib/components/map/gpx-layer/gpx-layer.ts b/website/src/lib/components/map/gpx-layer/gpx-layer.ts index 0a85b8a98..3a72ed700 100644 --- a/website/src/lib/components/map/gpx-layer/gpx-layer.ts +++ b/website/src/lib/components/map/gpx-layer/gpx-layer.ts @@ -227,6 +227,56 @@ export class GPXLayer { layerEventManager.on('mouseleave', this.fileId, this.layerOnMouseLeaveBinded); layerEventManager.on('mousemove', this.fileId, this.layerOnMouseMoveBinded); } + + let visibleTrackSegmentIds: string[] = []; + file.forEachSegment((segment, trackIndex, segmentIndex) => { + if (!segment._data.hidden) { + visibleTrackSegmentIds.push(`${trackIndex}-${segmentIndex}`); + } + }); + const segmentFilter: FilterSpecification = [ + 'in', + ['get', 'trackSegmentId'], + ['literal', visibleTrackSegmentIds], + ]; + + _map.setFilter(this.fileId, segmentFilter, { validate: false }); + + if (get(directionMarkers)) { + if (!_map.getLayer(this.fileId + '-direction')) { + _map.addLayer( + { + id: this.fileId + '-direction', + type: 'symbol', + source: this.fileId, + layout: { + 'text-field': '»', + 'text-offset': [0, -0.1], + 'text-keep-upright': false, + 'text-max-angle': 361, + 'text-allow-overlap': true, + 'text-font': ['Open Sans Bold'], + 'symbol-placement': 'line', + 'symbol-spacing': 20, + }, + paint: { + 'text-color': 'white', + 'text-opacity': 0.7, + 'text-halo-width': 0.2, + 'text-halo-color': 'white', + }, + }, + ANCHOR_LAYER_KEY.directionMarkers + ); + } + + _map.setFilter(this.fileId + '-direction', segmentFilter, { validate: false }); + } else { + if (_map.getLayer(this.fileId + '-direction')) { + _map.removeLayer(this.fileId + '-direction'); + } + } + let waypointSource = _map.getSource(this.fileId + '-waypoints') as | GeoJSONSource | undefined; @@ -284,57 +334,6 @@ export class GPXLayer { ); } - if (get(directionMarkers)) { - if (!_map.getLayer(this.fileId + '-direction')) { - _map.addLayer( - { - id: this.fileId + '-direction', - type: 'symbol', - source: this.fileId, - layout: { - 'text-field': '»', - 'text-offset': [0, -0.1], - 'text-keep-upright': false, - 'text-max-angle': 361, - 'text-allow-overlap': true, - 'text-font': ['Open Sans Bold'], - 'symbol-placement': 'line', - 'symbol-spacing': 20, - }, - paint: { - 'text-color': 'white', - 'text-opacity': 0.7, - 'text-halo-width': 0.2, - 'text-halo-color': 'white', - }, - }, - ANCHOR_LAYER_KEY.directionMarkers - ); - } - } else { - if (_map.getLayer(this.fileId + '-direction')) { - _map.removeLayer(this.fileId + '-direction'); - } - } - - let visibleTrackSegmentIds: string[] = []; - file.forEachSegment((segment, trackIndex, segmentIndex) => { - if (!segment._data.hidden) { - visibleTrackSegmentIds.push(`${trackIndex}-${segmentIndex}`); - } - }); - const segmentFilter: FilterSpecification = [ - 'in', - ['get', 'trackSegmentId'], - ['literal', visibleTrackSegmentIds], - ]; - - _map.setFilter(this.fileId, segmentFilter, { validate: false }); - - if (_map.getLayer(this.fileId + '-direction')) { - _map.setFilter(this.fileId + '-direction', segmentFilter, { validate: false }); - } - let visibleWaypoints: number[] = []; file.wpt.forEach((waypoint, waypointIndex) => { if (!waypoint._data.hidden) { diff --git a/website/src/lib/components/map/layer-control/overpass-layer.ts b/website/src/lib/components/map/layer-control/overpass-layer.ts index 78d7739d2..b14fdf8c6 100644 --- a/website/src/lib/components/map/layer-control/overpass-layer.ts +++ b/website/src/lib/components/map/layer-control/overpass-layer.ts @@ -76,7 +76,14 @@ export class OverpassLayer { update() { this.loadIcons(); - let d = get(data); + const fullData = get(data); + const queries = getCurrentQueries(); + const d: GeoJSON.FeatureCollection = { + type: 'FeatureCollection', + features: fullData.features.filter((feature) => + queries.includes(feature.properties!.query) + ), + }; try { let source = this.map.getSource('overpass') as GeoJSONSource | undefined; @@ -108,10 +115,6 @@ export class OverpassLayer { this.layerEventManager.on('mouseenter', 'overpass', this.onHoverBinded); this.layerEventManager.on('click', 'overpass', this.onHoverBinded); } - - this.map.setFilter('overpass', ['in', 'query', ...getCurrentQueries()], { - validate: false, - }); } catch (e) { // No reliable way to check if the map is ready to add sources and layers } diff --git a/website/src/lib/components/map/map-layer-event-manager.ts b/website/src/lib/components/map/map-layer-event-manager.ts index e5f14f3e0..95e7e70f0 100644 --- a/website/src/lib/components/map/map-layer-event-manager.ts +++ b/website/src/lib/components/map/map-layer-event-manager.ts @@ -1,3 +1,4 @@ +import { fileStateCollection } from '$lib/logic/file-state'; import maplibregl from 'maplibre-gl'; type MapLayerMouseEventListener = (e: maplibregl.MapLayerMouseEvent) => void; @@ -141,7 +142,10 @@ export class MapLayerEventManager { } private _handleMouseMove(e: maplibregl.MapMouseEvent) { - const layerIds = Object.keys(this._listeners); + const layerIds = this._filterLayersContainingCoordinate( + Object.keys(this._listeners), + e.lngLat + ); const features = layerIds.length > 0 ? this._map.queryRenderedFeatures(e.point, { layers: layerIds }) @@ -224,8 +228,11 @@ export class MapLayerEventManager { } private _handleTouchStart(e: maplibregl.MapTouchEvent) { - const layerIds = Object.keys(this._listeners).filter( - (layerId) => this._listeners[layerId].touchstarts.length > 0 + const layerIds = this._filterLayersContainingCoordinate( + Object.keys(this._listeners).filter( + (layerId) => this._listeners[layerId].touchstarts.length > 0 + ), + e.lngLat ); if (layerIds.length === 0) return; const features = this._map.queryRenderedFeatures(e.points[0], { layers: layerIds }); @@ -250,4 +257,19 @@ export class MapLayerEventManager { } }); } + + private _filterLayersContainingCoordinate( + layerIds: string[], + lngLat: maplibregl.LngLat + ): string[] { + let result = layerIds.filter((layerId) => { + const fileId = layerId.replace('-waypoints', ''); + if (fileId === layerId) { + return fileStateCollection.getStatistics(fileId)?.inBBox(lngLat) ?? true; + } else { + return fileStateCollection.getStatistics(fileId)?.inWaypointBBox(lngLat) ?? true; + } + }); + return result; + } } diff --git a/website/src/lib/components/map/style.ts b/website/src/lib/components/map/style.ts index 228943dc9..03a703279 100644 --- a/website/src/lib/components/map/style.ts +++ b/website/src/lib/components/map/style.ts @@ -157,6 +157,9 @@ export class StyleManager { const terrain = this.getCurrentTerrain(); if (JSON.stringify(mapTerrain) !== JSON.stringify(terrain)) { if (terrain.exaggeration > 0) { + if (!map_.getSource(terrain.source)) { + map_.addSource(terrain.source, terrainSources[terrain.source]); + } map_.setTerrain(terrain); } else { map_.setTerrain(null); diff --git a/website/src/lib/logic/statistics-tree.ts b/website/src/lib/logic/statistics-tree.ts index 6ef8041c3..972491192 100644 --- a/website/src/lib/logic/statistics-tree.ts +++ b/website/src/lib/logic/statistics-tree.ts @@ -1,18 +1,24 @@ import { ListItem, ListLevel } from '$lib/components/file-list/file-list'; import { GPXFile, GPXStatistics, GPXStatisticsGroup, type Track } from 'gpx'; +import maplibregl from 'maplibre-gl'; export class GPXStatisticsTree { level: ListLevel; statistics: { [key: string]: GPXStatisticsTree | GPXStatistics; } = {}; + wptBounds: maplibregl.LngLatBounds; constructor(element: GPXFile | Track) { + this.wptBounds = new maplibregl.LngLatBounds(); if (element instanceof GPXFile) { this.level = ListLevel.FILE; element.children.forEach((child, index) => { this.statistics[index] = new GPXStatisticsTree(child); }); + element.wpt.forEach((wpt) => { + this.wptBounds.extend(wpt.getCoordinates()); + }); } else { this.level = ListLevel.TRACK; element.children.forEach((child, index) => { @@ -42,5 +48,27 @@ export class GPXStatisticsTree { } return statistics; } + + inBBox(coordinates: { lat: number; lng: number }): boolean { + for (let key in this.statistics) { + const stats = this.statistics[key]; + if (stats instanceof GPXStatistics) { + const bbox = new maplibregl.LngLatBounds( + stats.global.bounds.southWest, + stats.global.bounds.northEast + ); + if (!bbox.isEmpty() && bbox.contains(coordinates)) { + return true; + } + } else if (stats.inBBox(coordinates)) { + return true; + } + } + return false; + } + + inWaypointBBox(coordinates: { lat: number; lng: number }): boolean { + return !this.wptBounds.isEmpty() && this.wptBounds.contains(coordinates); + } } export type GPXFileWithStatistics = { file: GPXFile; statistics: GPXStatisticsTree };