From 9c6e03f4a8d1200765ddd54ebe5f249ffcbd33fd Mon Sep 17 00:00:00 2001 From: vcoppe Date: Fri, 30 Jan 2026 21:30:37 +0100 Subject: [PATCH] improve layer stacking --- website/src/lib/components/map/Map.svelte | 2 +- .../map/gpx-layer/distance-markers.ts | 73 ++++++++++--------- .../lib/components/map/gpx-layer/gpx-layer.ts | 64 ++++++++-------- .../map/layer-control/overpass-layer.ts | 24 +++--- website/src/lib/components/map/map.ts | 41 +++++++---- .../map/street-view-control/mapillary.ts | 5 +- .../lib/components/toolbar/tools/Clean.svelte | 21 +++--- .../toolbar/tools/reduce/utils.svelte.ts | 23 +++--- .../toolbar/tools/scissors/split-controls.ts | 26 ++++--- 9 files changed, 155 insertions(+), 124 deletions(-) diff --git a/website/src/lib/components/map/Map.svelte b/website/src/lib/components/map/Map.svelte index d4b65e4a0..1a884dc7b 100644 --- a/website/src/lib/components/map/Map.svelte +++ b/website/src/lib/components/map/Map.svelte @@ -48,7 +48,7 @@ language = 'en'; } - map.init(PUBLIC_MAPBOX_TOKEN, language, hash, geocoder, geolocate); + map.init(language, hash, geocoder, geolocate); }); onDestroy(() => { diff --git a/website/src/lib/components/map/gpx-layer/distance-markers.ts b/website/src/lib/components/map/gpx-layer/distance-markers.ts index 4a7f9d5c7..e6edc4a83 100644 --- a/website/src/lib/components/map/gpx-layer/distance-markers.ts +++ b/website/src/lib/components/map/gpx-layer/distance-markers.ts @@ -3,7 +3,7 @@ import { gpxStatistics } from '$lib/logic/statistics'; import { getConvertedDistanceToKilometers } from '$lib/units'; import type { GeoJSONSource } from 'mapbox-gl'; import { get } from 'svelte/store'; -import { map } from '$lib/components/map/map'; +import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map'; import { allHidden } from '$lib/logic/hidden'; const { distanceMarkers, distanceUnits } = settings; @@ -44,44 +44,45 @@ export class DistanceMarkers { }); } if (!map_.getLayer('distance-markers')) { - map_.addLayer({ - id: 'distance-markers', - type: 'symbol', - source: 'distance-markers', - filter: [ - 'match', - ['get', 'level'], - 100, - ['>=', ['zoom'], 0], - 50, - ['>=', ['zoom'], 7], - 25, - [ - 'any', - ['all', ['>=', ['zoom'], 8], ['<=', ['zoom'], 9]], + map_.addLayer( + { + id: 'distance-markers', + type: 'symbol', + source: 'distance-markers', + filter: [ + 'match', + ['get', 'level'], + 100, + ['>=', ['zoom'], 0], + 50, + ['>=', ['zoom'], 7], + 25, + [ + 'any', + ['all', ['>=', ['zoom'], 8], ['<=', ['zoom'], 9]], + ['>=', ['zoom'], 11], + ], + 10, + ['>=', ['zoom'], 10], + 5, ['>=', ['zoom'], 11], + 1, + ['>=', ['zoom'], 13], + false, ], - 10, - ['>=', ['zoom'], 10], - 5, - ['>=', ['zoom'], 11], - 1, - ['>=', ['zoom'], 13], - false, - ], - layout: { - 'text-field': ['get', 'distance'], - 'text-size': 14, - 'text-font': ['Open Sans Bold'], + layout: { + 'text-field': ['get', 'distance'], + 'text-size': 14, + 'text-font': ['Open Sans Bold'], + }, + paint: { + 'text-color': 'black', + 'text-halo-width': 2, + 'text-halo-color': 'white', + }, }, - paint: { - 'text-color': 'black', - 'text-halo-width': 2, - 'text-halo-color': 'white', - }, - }); - } else { - map_.moveLayer('distance-markers'); + ANCHOR_LAYER_KEY.distanceMarkers + ); } } else { if (map_.getLayer('distance-markers')) { 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 ee81ca9ba..e38e8b4aa 100644 --- a/website/src/lib/components/map/gpx-layer/gpx-layer.ts +++ b/website/src/lib/components/map/gpx-layer/gpx-layer.ts @@ -1,6 +1,6 @@ import { get, type Readable } from 'svelte/store'; import mapboxgl, { type FilterSpecification } from 'mapbox-gl'; -import { map } from '$lib/components/map/map'; +import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map'; import { waypointPopup, trackpointPopup } from './gpx-layer-popup'; import { ListTrackSegmentItem, @@ -196,20 +196,23 @@ export class GPXLayer { } if (!_map.getLayer(this.fileId)) { - _map.addLayer({ - id: this.fileId, - type: 'line', - source: this.fileId, - layout: { - 'line-join': 'round', - 'line-cap': 'round', + _map.addLayer( + { + id: this.fileId, + type: 'line', + source: this.fileId, + layout: { + 'line-join': 'round', + 'line-cap': 'round', + }, + paint: { + 'line-color': ['get', 'color'], + 'line-width': ['get', 'width'], + 'line-opacity': ['get', 'opacity'], + }, }, - paint: { - 'line-color': ['get', 'color'], - 'line-width': ['get', 'width'], - 'line-opacity': ['get', 'opacity'], - }, - }); + ANCHOR_LAYER_KEY.tracks + ); _map.on('click', this.fileId, this.layerOnClickBinded); _map.on('contextmenu', this.fileId, this.layerOnContextMenuBinded); @@ -232,18 +235,21 @@ export class GPXLayer { } if (!_map.getLayer(this.fileId + '-waypoints')) { - _map.addLayer({ - id: this.fileId + '-waypoints', - type: 'symbol', - source: this.fileId + '-waypoints', - layout: { - 'icon-image': ['get', 'icon'], - 'icon-size': 0.3, - 'icon-anchor': 'bottom', - 'icon-padding': 0, - 'icon-allow-overlap': true, + _map.addLayer( + { + id: this.fileId + '-waypoints', + type: 'symbol', + source: this.fileId + '-waypoints', + layout: { + 'icon-image': ['get', 'icon'], + 'icon-size': 0.3, + 'icon-anchor': 'bottom', + 'icon-padding': 0, + 'icon-allow-overlap': true, + }, }, - }); + ANCHOR_LAYER_KEY.waypoints + ); _map.on( 'mouseenter', @@ -292,7 +298,7 @@ export class GPXLayer { 'text-halo-color': 'white', }, }, - _map.getLayer('distance-markers') ? 'distance-markers' : undefined + ANCHOR_LAYER_KEY.directionMarkers ); } } else { @@ -393,13 +399,13 @@ export class GPXLayer { return; } if (_map.getLayer(this.fileId)) { - _map.moveLayer(this.fileId); + _map.moveLayer(this.fileId, ANCHOR_LAYER_KEY.tracks); } if (_map.getLayer(this.fileId + '-waypoints')) { - _map.moveLayer(this.fileId + '-waypoints'); + _map.moveLayer(this.fileId + '-waypoints', ANCHOR_LAYER_KEY.waypoints); } if (_map.getLayer(this.fileId + '-direction')) { - _map.moveLayer(this.fileId + '-direction'); + _map.moveLayer(this.fileId + '-direction', ANCHOR_LAYER_KEY.directionMarkers); } } 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 8721e629b..09b09137c 100644 --- a/website/src/lib/components/map/layer-control/overpass-layer.ts +++ b/website/src/lib/components/map/layer-control/overpass-layer.ts @@ -6,6 +6,7 @@ import { overpassQueryData } from '$lib/assets/layers'; import { MapPopup } from '$lib/components/map/map-popup'; import { settings } from '$lib/logic/settings'; import { db } from '$lib/db'; +import { ANCHOR_LAYER_KEY } from '$lib/components/map/map'; const { currentOverpassQueries } = settings; @@ -85,17 +86,20 @@ export class OverpassLayer { } if (!this.map.getLayer('overpass')) { - this.map.addLayer({ - id: 'overpass', - type: 'symbol', - source: 'overpass', - layout: { - 'icon-image': ['get', 'icon'], - 'icon-size': 0.25, - 'icon-padding': 0, - 'icon-allow-overlap': ['step', ['zoom'], false, 14, true], + this.map.addLayer( + { + id: 'overpass', + type: 'symbol', + source: 'overpass', + layout: { + 'icon-image': ['get', 'icon'], + 'icon-size': 0.25, + 'icon-padding': 0, + 'icon-allow-overlap': ['step', ['zoom'], false, 14, true], + }, }, - }); + ANCHOR_LAYER_KEY.overpass + ); this.map.on('mouseenter', 'overpass', this.onHoverBinded); this.map.on('click', 'overpass', this.onHoverBinded); diff --git a/website/src/lib/components/map/map.ts b/website/src/lib/components/map/map.ts index c257af202..4e38afb28 100644 --- a/website/src/lib/components/map/map.ts +++ b/website/src/lib/components/map/map.ts @@ -20,6 +20,28 @@ let fitBoundsOptions: mapboxgl.MapOptions['fitBoundsOptions'] = { easing: () => 1, }; +const emptySource: mapboxgl.GeoJSONSourceSpecification = { + type: 'geojson', + data: { + type: 'FeatureCollection', + features: [], + }, +}; +export const ANCHOR_LAYER_KEY = { + mapillary: 'mapillary-end', + tracks: 'tracks-end', + directionMarkers: 'direction-markers-end', + distanceMarkers: 'distance-markers-end', + interactions: 'interactions-end', + overpass: 'overpass-end', + waypoints: 'waypoints-end', +}; +const anchorLayers: mapboxgl.LayerSpecification[] = Object.values(ANCHOR_LAYER_KEY).map((id) => ({ + id: id, + type: 'symbol', + source: 'empty-source', +})); + export class MapboxGLMap { private _map: Writable = writable(null); private _onLoadCallbacks: ((map: mapboxgl.Map) => void)[] = []; @@ -29,19 +51,15 @@ export class MapboxGLMap { return this._map.subscribe(run, invalidate); } - init( - accessToken: string, - language: string, - hash: boolean, - geocoder: boolean, - geolocate: boolean - ) { + init(language: string, hash: boolean, geocoder: boolean, geolocate: boolean) { const map = new mapboxgl.Map({ container: 'map', style: { version: 8, - sources: {}, - layers: [], + sources: { + 'empty-source': emptySource, + }, + layers: anchorLayers, imports: [ { id: 'basemap', @@ -50,11 +68,6 @@ export class MapboxGLMap { { id: 'overlays', url: '', - data: { - version: 8, - sources: {}, - layers: [], - }, }, ], }, diff --git a/website/src/lib/components/map/street-view-control/mapillary.ts b/website/src/lib/components/map/street-view-control/mapillary.ts index bce282512..9b68be06d 100644 --- a/website/src/lib/components/map/street-view-control/mapillary.ts +++ b/website/src/lib/components/map/street-view-control/mapillary.ts @@ -2,6 +2,7 @@ import mapboxgl, { type LayerSpecification, type VectorSourceSpecification } fro import { Viewer, type ViewerBearingEvent } from 'mapillary-js/dist/mapillary.module'; import 'mapillary-js/dist/mapillary.css'; import { mapCursor, MapCursorState } from '$lib/logic/map-cursor'; +import { ANCHOR_LAYER_KEY } from '$lib/components/map/map'; const mapillarySource: VectorSourceSpecification = { type: 'vector', @@ -99,10 +100,10 @@ export class MapillaryLayer { this.map.addSource('mapillary', mapillarySource); } if (!this.map.getLayer('mapillary-sequence')) { - this.map.addLayer(mapillarySequenceLayer); + this.map.addLayer(mapillarySequenceLayer, ANCHOR_LAYER_KEY.mapillary); } if (!this.map.getLayer('mapillary-image')) { - this.map.addLayer(mapillaryImageLayer); + this.map.addLayer(mapillaryImageLayer, ANCHOR_LAYER_KEY.mapillary); } this.map.on('style.load', this.addBinded); this.map.on('mouseenter', 'mapillary-image', this.onMouseEnterBinded); diff --git a/website/src/lib/components/toolbar/tools/Clean.svelte b/website/src/lib/components/toolbar/tools/Clean.svelte index 39a6d6aab..8c3adddb6 100644 --- a/website/src/lib/components/toolbar/tools/Clean.svelte +++ b/website/src/lib/components/toolbar/tools/Clean.svelte @@ -15,7 +15,7 @@ import { onDestroy, onMount } from 'svelte'; import { getURLForLanguage } from '$lib/utils'; import { Trash2 } from '@lucide/svelte'; - import { map } from '$lib/components/map/map'; + import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map'; import type { GeoJSONSource } from 'mapbox-gl'; import { selection } from '$lib/logic/selection'; import { fileActions } from '$lib/logic/file-actions'; @@ -63,15 +63,18 @@ }); } if (!$map.getLayer('rectangle')) { - $map.addLayer({ - id: 'rectangle', - type: 'fill', - source: 'rectangle', - paint: { - 'fill-color': 'SteelBlue', - 'fill-opacity': 0.5, + $map.addLayer( + { + id: 'rectangle', + type: 'fill', + source: 'rectangle', + paint: { + 'fill-color': 'SteelBlue', + 'fill-opacity': 0.5, + }, }, - }); + ANCHOR_LAYER_KEY.interactions + ); } } } diff --git a/website/src/lib/components/toolbar/tools/reduce/utils.svelte.ts b/website/src/lib/components/toolbar/tools/reduce/utils.svelte.ts index af11ca8c4..3f2f80375 100644 --- a/website/src/lib/components/toolbar/tools/reduce/utils.svelte.ts +++ b/website/src/lib/components/toolbar/tools/reduce/utils.svelte.ts @@ -1,5 +1,5 @@ import { ListItem, ListTrackSegmentItem } from '$lib/components/file-list/file-list'; -import { map } from '$lib/components/map/map'; +import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map'; import { fileActions } from '$lib/logic/file-actions'; import { GPXFileStateCollectionObserver, type GPXFileState } from '$lib/logic/file-state'; import { selection } from '$lib/logic/selection'; @@ -144,17 +144,18 @@ export class ReducedGPXLayerCollection { }); } if (!map_.getLayer('simplified')) { - map_.addLayer({ - id: 'simplified', - type: 'line', - source: 'simplified', - paint: { - 'line-color': 'white', - 'line-width': 3, + map_.addLayer( + { + id: 'simplified', + type: 'line', + source: 'simplified', + paint: { + 'line-color': 'white', + 'line-width': 3, + }, }, - }); - } else { - map_.moveLayer('simplified'); + ANCHOR_LAYER_KEY.interactions + ); } } diff --git a/website/src/lib/components/toolbar/tools/scissors/split-controls.ts b/website/src/lib/components/toolbar/tools/scissors/split-controls.ts index df0068ea6..5f3776dca 100644 --- a/website/src/lib/components/toolbar/tools/scissors/split-controls.ts +++ b/website/src/lib/components/toolbar/tools/scissors/split-controls.ts @@ -8,6 +8,7 @@ import { get } from 'svelte/store'; import { fileStateCollection } from '$lib/logic/file-state'; import { fileActions } from '$lib/logic/file-actions'; import { mapCursor, MapCursorState } from '$lib/logic/map-cursor'; +import { ANCHOR_LAYER_KEY } from '$lib/components/map/map'; export class SplitControls { map: mapboxgl.Map; @@ -108,24 +109,25 @@ export class SplitControls { } if (!this.map.getLayer('split-controls')) { - this.map.addLayer({ - id: 'split-controls', - type: 'symbol', - source: 'split-controls', - layout: { - 'icon-image': 'split-control', - 'icon-size': 0.25, - 'icon-padding': 0, + this.map.addLayer( + { + id: 'split-controls', + type: 'symbol', + source: 'split-controls', + layout: { + 'icon-image': 'split-control', + 'icon-size': 0.25, + 'icon-padding': 0, + }, + filter: ['<=', ['get', 'minZoom'], ['zoom']], }, - filter: ['<=', ['get', 'minZoom'], ['zoom']], - }); + ANCHOR_LAYER_KEY.interactions + ); this.map.on('mouseenter', 'split-controls', this.layerOnMouseEnterBinded); this.map.on('mouseleave', 'split-controls', this.layerOnMouseLeaveBinded); this.map.on('click', 'split-controls', this.layerOnClickBinded); } - - this.map.moveLayer('split-controls'); } catch (e) { // No reliable way to check if the map is ready to add sources and layers }