use style imports instead of layers to allow stacking mapbox styles, closes #32

This commit is contained in:
vcoppe
2024-08-31 15:57:58 +02:00
parent 33f3b6cc32
commit 0cb781176e
9 changed files with 3053 additions and 981 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public';
import { TramFront, Utensils, ShoppingBasket, Droplet, ShowerHead, Fuel, CircleParking, Fence, FerrisWheel, Bed, Mountain, Pickaxe, Store, TrainFront, Bus, Ship, Croissant, House, Tent, Wrench, Binoculars } from 'lucide-static'; import { TramFront, Utensils, ShoppingBasket, Droplet, ShowerHead, Fuel, CircleParking, Fence, FerrisWheel, Bed, Mountain, Pickaxe, Store, TrainFront, Bus, Ship, Croissant, House, Tent, Wrench, Binoculars } from 'lucide-static';
import { type AnySourceData, type Style } from 'mapbox-gl'; import { type Style } from 'mapbox-gl';
import ignFrTopo from './custom/ign-fr-topo.json'; import ignFrTopo from './custom/ign-fr-topo.json';
import ignFrPlan from './custom/ign-fr-plan.json'; import ignFrPlan from './custom/ign-fr-plan.json';
import ignFrSatellite from './custom/ign-fr-satellite.json'; import ignFrSatellite from './custom/ign-fr-satellite.json';
import bikerouterGravel from './custom/bikerouter-gravel.json';
export const basemaps: { [key: string]: string | Style; } = { export const basemaps: { [key: string]: string | Style; } = {
mapboxOutdoors: 'mapbox://styles/mapbox/outdoors-v12', mapboxOutdoors: 'mapbox://styles/mapbox/outdoors-v12',
@@ -255,29 +255,28 @@ export const basemaps: { [key: string]: string | Style; } = {
}, },
}; };
export function extendBasemap(basemap: string | Style): string | Style { export const overlays: { [key: string]: string | Style; } = {
if (typeof basemap === 'object') { cyclOSMlite: {
basemap["glyphs"] = "mapbox://fonts/mapbox/{fontstack}/{range}.pbf"; version: 8,
basemap["sprite"] = `https://api.mapbox.com/styles/v1/mapbox/outdoors-v12/sprite?access_token=${PUBLIC_MAPBOX_TOKEN}`; sources: {
}
return basemap;
}
Object.values(basemaps).forEach(extendBasemap);
export const font: { [key: string]: string; } = {
swisstopoVector: 'Frutiger Neue Condensed Regular',
swisstopoSatellite: 'Frutiger Neue Condensed Regular',
};
export const overlays: { [key: string]: AnySourceData; } = {
cyclOSMlite: { cyclOSMlite: {
type: 'raster', type: 'raster',
tiles: ['https://a.tile-cyclosm.openstreetmap.fr/cyclosm-lite/{z}/{x}/{y}.png', 'https://b.tile-cyclosm.openstreetmap.fr/cyclosm-lite/{z}/{x}/{y}.png', 'https://c.tile-cyclosm.openstreetmap.fr/cyclosm-lite/{z}/{x}/{y}.png'], tiles: ['https://a.tile-cyclosm.openstreetmap.fr/cyclosm-lite/{z}/{x}/{y}.png', 'https://b.tile-cyclosm.openstreetmap.fr/cyclosm-lite/{z}/{x}/{y}.png', 'https://c.tile-cyclosm.openstreetmap.fr/cyclosm-lite/{z}/{x}/{y}.png'],
tileSize: 256, tileSize: 256,
maxzoom: 17, maxzoom: 17,
attribution: '&copy; <a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' attribution: '&copy; <a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}
}, },
layers: [{
id: 'cyclOSMlite',
type: 'raster',
source: 'cyclOSMlite',
}],
},
bikerouterGravel: bikerouterGravel,
swisstopoSlope: {
version: 8,
sources: {
swisstopoSlope: { swisstopoSlope: {
type: 'raster', type: 'raster',
tiles: ['https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.hangneigung-ueber_30/default/current/3857/{z}/{x}/{y}.png'], tiles: ['https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.hangneigung-ueber_30/default/current/3857/{z}/{x}/{y}.png'],
@@ -285,6 +284,16 @@ export const overlays: { [key: string]: AnySourceData; } = {
maxzoom: 17, maxzoom: 17,
attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>', attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>',
}, },
},
layers: [{
id: 'swisstopoSlope',
type: 'raster',
source: 'swisstopoSlope',
}],
},
swisstopoHiking: {
version: 8,
sources: {
swisstopoHiking: { swisstopoHiking: {
type: 'raster', type: 'raster',
tiles: ['https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.swisstlm3d-wanderwege/default/current/3857/{z}/{x}/{y}.png'], tiles: ['https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.swisstlm3d-wanderwege/default/current/3857/{z}/{x}/{y}.png'],
@@ -292,59 +301,148 @@ export const overlays: { [key: string]: AnySourceData; } = {
maxzoom: 18, maxzoom: 18,
attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>' attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>'
}, },
},
layers: [{
id: 'swisstopoHiking',
type: 'raster',
source: 'swisstopoHiking',
}],
},
swisstopoHikingClosures: {
version: 8,
sources: {
swisstopoHikingClosures: { swisstopoHikingClosures: {
type: 'raster', type: 'raster',
tiles: ['https://wms.geo.admin.ch/?version=1.3.0&service=WMS&request=GetMap&sld_version=1.1.0&layers=ch.astra.wanderland-sperrungen_umleitungen&format=image/png&STYLE=default&bbox={bbox-epsg-3857}&width=256&height=256&crs=EPSG:3857&transparent=true'], tiles: ['https://wms.geo.admin.ch/?version=1.3.0&service=WMS&request=GetMap&sld_version=1.1.0&layers=ch.astra.wanderland-sperrungen_umleitungen&format=image/png&STYLE=default&bbox={bbox-epsg-3857}&width=256&height=256&crs=EPSG:3857&transparent=true'],
tileSize: 256, tileSize: 256,
attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>' attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>'
}, },
},
layers: [{
id: 'swisstopoHikingClosures',
type: 'raster',
source: 'swisstopoHikingClosures',
}],
},
swisstopoCycling: {
version: 8,
sources: {
swisstopoCycling: { swisstopoCycling: {
type: 'raster', type: 'raster',
tiles: ['https://wmts.geo.admin.ch/1.0.0/ch.astra.veloland/default/current/3857/{z}/{x}/{y}.png'], tiles: ['https://wmts.geo.admin.ch/1.0.0/ch.astra.veloland/default/current/3857/{z}/{x}/{y}.png'],
tileSize: 256, tileSize: 256,
maxzoom: 18, maxzoom: 18,
attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>' attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>'
}
}, },
layers: [{
id: 'swisstopoCycling',
type: 'raster',
source: 'swisstopoCycling',
}],
},
swisstopoCyclingClosures: {
version: 8,
sources: {
swisstopoCyclingClosures: { swisstopoCyclingClosures: {
type: 'raster', type: 'raster',
tiles: ['https://wms.geo.admin.ch/?version=1.3.0&service=WMS&request=GetMap&sld_version=1.1.0&layers=ch.astra.veloland-sperrungen_umleitungen&format=image/png&STYLE=default&bbox={bbox-epsg-3857}&width=256&height=256&crs=EPSG:3857&transparent=true'], tiles: ['https://wms.geo.admin.ch/?version=1.3.0&service=WMS&request=GetMap&sld_version=1.1.0&layers=ch.astra.veloland-sperrungen_umleitungen&format=image/png&STYLE=default&bbox={bbox-epsg-3857}&width=256&height=256&crs=EPSG:3857&transparent=true'],
tileSize: 256, tileSize: 256,
attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>' attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>'
}
}, },
layers: [{
id: 'swisstopoCyclingClosures',
type: 'raster',
source: 'swisstopoCyclingClosures',
}],
},
swisstopoMountainBike: {
version: 8,
sources: {
swisstopoMountainBike: { swisstopoMountainBike: {
type: 'raster', type: 'raster',
tiles: ['https://wmts.geo.admin.ch/1.0.0/ch.astra.mountainbikeland/default/current/3857/{z}/{x}/{y}.png'], tiles: ['https://wmts.geo.admin.ch/1.0.0/ch.astra.mountainbikeland/default/current/3857/{z}/{x}/{y}.png'],
tileSize: 256, tileSize: 256,
maxzoom: 18, maxzoom: 18,
attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>' attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>'
}
}, },
layers: [{
id: 'swisstopoMountainBike',
type: 'raster',
source: 'swisstopoMountainBike',
}],
},
swisstopoMountainBikeClosures: {
version: 8,
sources: {
swisstopoMountainBikeClosures: { swisstopoMountainBikeClosures: {
type: 'raster', type: 'raster',
tiles: ['https://wms.geo.admin.ch/?version=1.3.0&service=WMS&request=GetMap&sld_version=1.1.0&layers=ch.astra.mountainbikeland-sperrungen_umleitungen&format=image/png&STYLE=default&bbox={bbox-epsg-3857}&width=256&height=256&crs=EPSG:3857&transparent=true'], tiles: ['https://wms.geo.admin.ch/?version=1.3.0&service=WMS&request=GetMap&sld_version=1.1.0&layers=ch.astra.mountainbikeland-sperrungen_umleitungen&format=image/png&STYLE=default&bbox={bbox-epsg-3857}&width=256&height=256&crs=EPSG:3857&transparent=true'],
tileSize: 256, tileSize: 256,
attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>' attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>'
}
}, },
layers: [{
id: 'swisstopoMountainBikeClosures',
type: 'raster',
source: 'swisstopoMountainBikeClosures',
}],
},
swisstopoSkiTouring: {
version: 8,
sources: {
swisstopoSkiTouring: { swisstopoSkiTouring: {
type: 'raster', type: 'raster',
tiles: ['https://wmts.geo.admin.ch/1.0.0/ch.swisstopo-karto.skitouren/default/current/3857/{z}/{x}/{y}.png'], tiles: ['https://wmts.geo.admin.ch/1.0.0/ch.swisstopo-karto.skitouren/default/current/3857/{z}/{x}/{y}.png'],
tileSize: 256, tileSize: 256,
maxzoom: 17, maxzoom: 17,
attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>' attribution: '&copy; <a href="https://www.swisstopo.admin.ch" target="_blank">swisstopo</a>'
}
}, },
layers: [{
id: 'swisstopoSkiTouring',
type: 'raster',
source: 'swisstopoSkiTouring',
}],
},
ignFrCadastre: {
version: 8,
sources: {
ignFrCadastre: { ignFrCadastre: {
type: 'raster', type: 'raster',
tiles: ['https://data.geopf.fr/wmts?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&TILEMATRIXSET=PM&TILEMATRIX={z}&TILECOL={x}&TILEROW={y}&LAYER=CADASTRALPARCELS.PARCELS&FORMAT=image/png&STYLE=normal'], tiles: ['https://data.geopf.fr/wmts?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&TILEMATRIXSET=PM&TILEMATRIX={z}&TILECOL={x}&TILEROW={y}&LAYER=CADASTRALPARCELS.PARCELS&FORMAT=image/png&STYLE=normal'],
tileSize: 256, tileSize: 256,
maxzoom: 20, maxzoom: 20,
attribution: 'IGN-F/Géoportail' attribution: 'IGN-F/Géoportail'
}
}, },
layers: [{
id: 'ignFrCadastre',
type: 'raster',
source: 'ignFrCadastre',
}],
},
ignSlope: {
version: 8,
sources: {
ignSlope: { ignSlope: {
type: 'raster', type: 'raster',
tiles: ['https://data.geopf.fr/wmts?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&TileMatrixSet=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&Layer=GEOGRAPHICALGRIDSYSTEMS.SLOPES.MOUNTAIN&FORMAT=image/png&Style=normal'], tiles: ['https://data.geopf.fr/wmts?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&TileMatrixSet=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&Layer=GEOGRAPHICALGRIDSYSTEMS.SLOPES.MOUNTAIN&FORMAT=image/png&Style=normal'],
tileSize: 256, tileSize: 256,
maxzoom: 17,
attribution: 'IGN-F/Géoportail' attribution: 'IGN-F/Géoportail'
}
}, },
layers: [{
id: 'ignSlope',
type: 'raster',
source: 'ignSlope',
}],
},
ignSkiTouring: {
version: 8,
sources: {
ignSkiTouring: { ignSkiTouring: {
type: 'raster', type: 'raster',
tiles: ['https://data.geopf.fr/wmts?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&TileMatrixSet=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&Layer=TRACES.RANDO.HIVERNALE&FORMAT=image/png&Style=normal'], tiles: ['https://data.geopf.fr/wmts?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&TileMatrixSet=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&Layer=TRACES.RANDO.HIVERNALE&FORMAT=image/png&Style=normal'],
@@ -352,47 +450,114 @@ export const overlays: { [key: string]: AnySourceData; } = {
maxzoom: 16, maxzoom: 16,
attribution: 'IGN-F/Géoportail' attribution: 'IGN-F/Géoportail'
}, },
},
layers: [{
id: 'ignSkiTouring',
type: 'raster',
source: 'ignSkiTouring',
}],
},
waymarkedTrailsHiking: {
version: 8,
sources: {
waymarkedTrailsHiking: { waymarkedTrailsHiking: {
type: 'raster', type: 'raster',
tiles: ['https://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png'], tiles: ['https://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png'],
tileSize: 256, tileSize: 256,
maxzoom: 18, maxzoom: 18,
attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>' attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>'
}
}, },
layers: [{
id: 'waymarkedTrailsHiking',
type: 'raster',
source: 'waymarkedTrailsHiking',
}],
},
waymarkedTrailsCycling: {
version: 8,
sources: {
waymarkedTrailsCycling: { waymarkedTrailsCycling: {
type: 'raster', type: 'raster',
tiles: ['https://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png'], tiles: ['https://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png'],
tileSize: 256, tileSize: 256,
maxzoom: 18, maxzoom: 18,
attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>' attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>'
}
}, },
layers: [{
id: 'waymarkedTrailsCycling',
type: 'raster',
source: 'waymarkedTrailsCycling',
}],
},
waymarkedTrailsMTB: {
version: 8,
sources: {
waymarkedTrailsMTB: { waymarkedTrailsMTB: {
type: 'raster', type: 'raster',
tiles: ['https://tile.waymarkedtrails.org/mtb/{z}/{x}/{y}.png'], tiles: ['https://tile.waymarkedtrails.org/mtb/{z}/{x}/{y}.png'],
tileSize: 256, tileSize: 256,
maxzoom: 18, maxzoom: 18,
attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>' attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>'
}
}, },
layers: [{
id: 'waymarkedTrailsMTB',
type: 'raster',
source: 'waymarkedTrailsMTB',
}],
},
waymarkedTrailsSkating: {
version: 8,
sources: {
waymarkedTrailsSkating: { waymarkedTrailsSkating: {
type: 'raster', type: 'raster',
tiles: ['https://tile.waymarkedtrails.org/skating/{z}/{x}/{y}.png'], tiles: ['https://tile.waymarkedtrails.org/skating/{z}/{x}/{y}.png'],
tileSize: 256, tileSize: 256,
maxzoom: 18, maxzoom: 18,
attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>' attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>'
}
}, },
layers: [{
id: 'waymarkedTrailsSkating',
type: 'raster',
source: 'waymarkedTrailsSkating',
}],
},
waymarkedTrailsHorseRiding: {
version: 8,
sources: {
waymarkedTrailsHorseRiding: { waymarkedTrailsHorseRiding: {
type: 'raster', type: 'raster',
tiles: ['https://tile.waymarkedtrails.org/riding/{z}/{x}/{y}.png'], tiles: ['https://tile.waymarkedtrails.org/riding/{z}/{x}/{y}.png'],
tileSize: 256, tileSize: 256,
maxzoom: 18, maxzoom: 18,
attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>' attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>'
}
}, },
layers: [{
id: 'waymarkedTrailsHorseRiding',
type: 'raster',
source: 'waymarkedTrailsHorseRiding',
}],
},
waymarkedTrailsWinter: {
version: 8,
sources: {
waymarkedTrailsWinter: { waymarkedTrailsWinter: {
type: 'raster', type: 'raster',
tiles: ['https://tile.waymarkedtrails.org/slopes/{z}/{x}/{y}.png'], tiles: ['https://tile.waymarkedtrails.org/slopes/{z}/{x}/{y}.png'],
tileSize: 256, tileSize: 256,
maxzoom: 18, maxzoom: 18,
attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>' attribution: '&copy; <a href="https://www.waymarkedtrails.org" target="_blank">Waymarked Trails</a>'
}
},
layers: [{
id: 'waymarkedTrailsWinter',
type: 'raster',
source: 'waymarkedTrailsWinter',
}],
}, },
}; };
@@ -463,9 +628,6 @@ export const basemapTree: LayerTreeType = {
export const overlayTree: LayerTreeType = { export const overlayTree: LayerTreeType = {
overlays: { overlays: {
world: { world: {
cyclOSM: {
cyclOSMlite: true,
},
waymarked_trails: { waymarked_trails: {
waymarkedTrailsHiking: true, waymarkedTrailsHiking: true,
waymarkedTrailsCycling: true, waymarkedTrailsCycling: true,
@@ -473,7 +635,9 @@ export const overlayTree: LayerTreeType = {
waymarkedTrailsSkating: true, waymarkedTrailsSkating: true,
waymarkedTrailsHorseRiding: true, waymarkedTrailsHorseRiding: true,
waymarkedTrailsWinter: true, waymarkedTrailsWinter: true,
} },
cyclOSMlite: true,
bikerouterGravel: true,
}, },
countries: { countries: {
france: { france: {
@@ -547,9 +711,6 @@ export const defaultBasemap = 'mapboxOutdoors';
export const defaultOverlays = { export const defaultOverlays = {
overlays: { overlays: {
world: { world: {
cyclOSM: {
cyclOSMlite: false,
},
waymarked_trails: { waymarked_trails: {
waymarkedTrailsHiking: false, waymarkedTrailsHiking: false,
waymarkedTrailsCycling: false, waymarkedTrailsCycling: false,
@@ -557,7 +718,9 @@ export const defaultOverlays = {
waymarkedTrailsSkating: false, waymarkedTrailsSkating: false,
waymarkedTrailsHorseRiding: false, waymarkedTrailsHorseRiding: false,
waymarkedTrailsWinter: false, waymarkedTrailsWinter: false,
} },
cyclOSMlite: false,
bikerouterGravel: false,
}, },
countries: { countries: {
france: { france: {
@@ -683,9 +846,6 @@ export const defaultBasemapTree: LayerTreeType = {
export const defaultOverlayTree: LayerTreeType = { export const defaultOverlayTree: LayerTreeType = {
overlays: { overlays: {
world: { world: {
cyclOSM: {
cyclOSMlite: false,
},
waymarked_trails: { waymarked_trails: {
waymarkedTrailsHiking: true, waymarkedTrailsHiking: true,
waymarkedTrailsCycling: true, waymarkedTrailsCycling: true,
@@ -693,7 +853,9 @@ export const defaultOverlayTree: LayerTreeType = {
waymarkedTrailsSkating: false, waymarkedTrailsSkating: false,
waymarkedTrailsHorseRiding: false, waymarkedTrailsHorseRiding: false,
waymarkedTrailsWinter: false, waymarkedTrailsWinter: false,
} },
cyclOSMlite: false,
bikerouterGravel: false,
}, },
countries: { countries: {
france: { france: {

View File

@@ -52,7 +52,37 @@
let newMap = new mapboxgl.Map({ let newMap = new mapboxgl.Map({
container: 'map', container: 'map',
style: { version: 8, sources: {}, layers: [] }, style: {
version: 8,
sources: {},
layers: [],
imports: [
{
id: 'glyphs-and-sprite', // make Mapbox glyphs and sprite available to other styles
url: '',
data: {
version: 8,
sources: {},
layers: [],
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
sprite: `https://api.mapbox.com/styles/v1/mapbox/outdoors-v12/sprite?access_token=${PUBLIC_MAPBOX_TOKEN}`
}
},
{
id: 'basemap',
url: ''
},
{
id: 'overlays',
url: '',
data: {
version: 8,
sources: {},
layers: []
}
}
]
},
zoom: 0, zoom: 0,
hash: hash, hash: hash,
language, language,
@@ -134,14 +164,6 @@
}); });
} }
}); });
// add dummy layer to place the overlay layers below
newMap.addLayer({
id: 'overlays',
type: 'background',
paint: {
'background-color': 'rgba(0, 0, 0, 0)'
}
});
}); });
}); });
@@ -163,7 +185,9 @@
<div {...$$restProps}> <div {...$$restProps}>
<div id="map" class="h-full {webgl2Supported ? '' : 'hidden'}"></div> <div id="map" class="h-full {webgl2Supported ? '' : 'hidden'}"></div>
<div <div
class="flex flex-col items-center justify-center gap-3 h-full {webgl2Supported ? 'hidden' : ''}" class="flex flex-col items-center justify-center gap-3 h-full {webgl2Supported
? 'hidden'
: ''}"
> >
<p>{$_('webgl2_required')}</p> <p>{$_('webgl2_required')}</p>
<Button href="https://get.webgl.org/webgl2/" target="_blank"> <Button href="https://get.webgl.org/webgl2/" target="_blank">

View File

@@ -1,10 +1,9 @@
import { font } from "$lib/assets/layers";
import { settings } from "$lib/db"; import { settings } from "$lib/db";
import { gpxStatistics } from "$lib/stores"; import { gpxStatistics } from "$lib/stores";
import { get } from "svelte/store"; import { get } from "svelte/store";
const { distanceMarkers, distanceUnits, currentBasemap } = settings; const { distanceMarkers, distanceUnits } = settings;
export class DistanceMarkers { export class DistanceMarkers {
map: mapboxgl.Map; map: mapboxgl.Map;
@@ -17,7 +16,7 @@ export class DistanceMarkers {
this.unsubscribes.push(gpxStatistics.subscribe(this.updateBinded)); this.unsubscribes.push(gpxStatistics.subscribe(this.updateBinded));
this.unsubscribes.push(distanceMarkers.subscribe(this.updateBinded)); this.unsubscribes.push(distanceMarkers.subscribe(this.updateBinded));
this.unsubscribes.push(distanceUnits.subscribe(this.updateBinded)); this.unsubscribes.push(distanceUnits.subscribe(this.updateBinded));
this.map.on('style.load', this.updateBinded); this.map.on('style.import.load', this.updateBinded);
} }
update() { update() {
@@ -40,7 +39,7 @@ export class DistanceMarkers {
layout: { layout: {
'text-field': ['get', 'distance'], 'text-field': ['get', 'distance'],
'text-size': 14, 'text-size': 14,
'text-font': [font[get(currentBasemap)] ?? 'Open Sans Bold'], 'text-font': ['Open Sans Bold'],
'text-padding': 20, 'text-padding': 20,
}, },
paint: { paint: {

View File

@@ -7,7 +7,6 @@ import { addSelectItem, selectItem, selection } from "$lib/components/file-list/
import { ListTrackSegmentItem, ListWaypointItem, ListWaypointsItem, ListTrackItem, ListFileItem, ListRootItem } from "$lib/components/file-list/FileList"; import { ListTrackSegmentItem, ListWaypointItem, ListWaypointsItem, ListTrackItem, ListFileItem, ListRootItem } from "$lib/components/file-list/FileList";
import type { Waypoint } from "gpx"; import type { Waypoint } from "gpx";
import { getElevation, resetCursor, setGrabbingCursor, setPointerCursor, setScissorsCursor } from "$lib/utils"; import { getElevation, resetCursor, setGrabbingCursor, setPointerCursor, setScissorsCursor } from "$lib/utils";
import { font } from "$lib/assets/layers";
import { selectedWaypoint } from "$lib/components/toolbar/tools/Waypoint.svelte"; import { selectedWaypoint } from "$lib/components/toolbar/tools/Waypoint.svelte";
import { MapPin, Square } from "lucide-static"; import { MapPin, Square } from "lucide-static";
import { getSymbolKey, symbols } from "$lib/assets/symbols"; import { getSymbolKey, symbols } from "$lib/assets/symbols";
@@ -66,7 +65,7 @@ function getMarkerForSymbol(symbol: string | undefined, layerColor: string) {
</svg>`; </svg>`;
} }
const { directionMarkers, verticalFileView, currentBasemap, defaultOpacity, defaultWeight } = settings; const { directionMarkers, verticalFileView, defaultOpacity, defaultWeight } = settings;
export class GPXLayer { export class GPXLayer {
map: mapboxgl.Map; map: mapboxgl.Map;
@@ -112,7 +111,7 @@ export class GPXLayer {
})); }));
this.draggable = get(currentTool) === Tool.WAYPOINT; this.draggable = get(currentTool) === Tool.WAYPOINT;
this.map.on('style.load', this.updateBinded); this.map.on('style.import.load', this.updateBinded);
} }
update() { update() {
@@ -170,7 +169,7 @@ export class GPXLayer {
'text-keep-upright': false, 'text-keep-upright': false,
'text-max-angle': 361, 'text-max-angle': 361,
'text-allow-overlap': true, 'text-allow-overlap': true,
'text-font': [font[get(currentBasemap)] ?? 'Open Sans Bold'], 'text-font': ['Open Sans Bold'],
'symbol-placement': 'line', 'symbol-placement': 'line',
'symbol-spacing': 20, 'symbol-spacing': 20,
}, },
@@ -294,7 +293,7 @@ export class GPXLayer {
updateMap(map: mapboxgl.Map) { updateMap(map: mapboxgl.Map) {
this.map = map; this.map = map;
this.map.on('style.load', this.updateBinded); this.map.on('style.import.load', this.updateBinded);
this.update(); this.update();
} }
@@ -303,7 +302,7 @@ export class GPXLayer {
this.map.off('click', this.fileId, this.layerOnClickBinded); this.map.off('click', this.fileId, this.layerOnClickBinded);
this.map.off('mouseenter', this.fileId, this.layerOnMouseEnterBinded); this.map.off('mouseenter', this.fileId, this.layerOnMouseEnterBinded);
this.map.off('mouseleave', this.fileId, this.layerOnMouseLeaveBinded); this.map.off('mouseleave', this.fileId, this.layerOnMouseLeaveBinded);
this.map.off('style.load', this.updateBinded); this.map.off('style.import.load', this.updateBinded);
if (this.map.getLayer(this.fileId + '-direction')) { if (this.map.getLayer(this.fileId + '-direction')) {
this.map.removeLayer(this.fileId + '-direction'); this.map.removeLayer(this.fileId + '-direction');

View File

@@ -19,7 +19,7 @@
} from 'lucide-svelte'; } from 'lucide-svelte';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import { settings } from '$lib/db'; import { settings } from '$lib/db';
import { defaultBasemap, extendBasemap, type CustomLayer } from '$lib/assets/layers'; import { defaultBasemap, type CustomLayer } from '$lib/assets/layers';
import { map } from '$lib/stores'; import { map } from '$lib/stores';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import Sortable from 'sortablejs/Sortable'; import Sortable from 'sortablejs/Sortable';
@@ -95,7 +95,6 @@
(tileUrls[0].includes('api.mapbox.com/styles') && !tileUrls[0].includes('tiles')) (tileUrls[0].includes('api.mapbox.com/styles') && !tileUrls[0].includes('tiles'))
) { ) {
resourceType = 'vector'; resourceType = 'vector';
layerType = 'basemap';
} else { } else {
resourceType = 'raster'; resourceType = 'raster';
} }
@@ -124,8 +123,7 @@
if (resourceType === 'vector') { if (resourceType === 'vector') {
layer.value = tileUrls[0]; layer.value = tileUrls[0];
} else { } else {
if (layerType === 'basemap') { layer.value = {
layer.value = extendBasemap({
version: 8, version: 8,
sources: { sources: {
[layerId]: { [layerId]: {
@@ -142,16 +140,8 @@
source: layerId source: layerId
} }
] ]
});
} else {
layer.value = {
type: 'raster',
tiles: tileUrls,
tileSize: 256,
maxzoom: maxZoom
}; };
} }
}
$customLayers[layerId] = layer; $customLayers[layerId] = layer;
addLayer(layerId); addLayer(layerId);
selectedLayerId = undefined; selectedLayerId = undefined;
@@ -194,12 +184,12 @@
return $tree; return $tree;
}); });
if ($map && $map.getSource(layerId)) { if ($map) {
// Reset source when updating an existing layer try {
if ($map.getLayer(layerId)) { $map.removeImport(layerId);
$map.removeLayer(layerId); } catch (e) {
// No reliable way to check if the map is ready to remove sources and layers
} }
$map.removeSource(layerId);
} }
if (!$currentOverlays.overlays.hasOwnProperty('custom')) { if (!$currentOverlays.overlays.hasOwnProperty('custom')) {
@@ -235,7 +225,10 @@
layerId layerId
); );
if (Object.keys($selectedBasemapTree.basemaps['custom']).length === 0) { if (Object.keys($selectedBasemapTree.basemaps['custom']).length === 0) {
$selectedBasemapTree.basemaps = tryDeleteLayer($selectedBasemapTree.basemaps, 'custom'); $selectedBasemapTree.basemaps = tryDeleteLayer(
$selectedBasemapTree.basemaps,
'custom'
);
} }
$customBasemapOrder = $customBasemapOrder.filter((id) => id !== layerId); $customBasemapOrder = $customBasemapOrder.filter((id) => id !== layerId);
} else { } else {
@@ -252,16 +245,18 @@
layerId layerId
); );
if (Object.keys($selectedOverlayTree.overlays['custom']).length === 0) { if (Object.keys($selectedOverlayTree.overlays['custom']).length === 0) {
$selectedOverlayTree.overlays = tryDeleteLayer($selectedOverlayTree.overlays, 'custom'); $selectedOverlayTree.overlays = tryDeleteLayer(
$selectedOverlayTree.overlays,
'custom'
);
} }
$customOverlayOrder = $customOverlayOrder.filter((id) => id !== layerId); $customOverlayOrder = $customOverlayOrder.filter((id) => id !== layerId);
if ($map) { if ($map) {
if ($map.getLayer(layerId)) { try {
$map.removeLayer(layerId); $map.removeImport(layerId);
} } catch (e) {
if ($map.getSource(layerId)) { // No reliable way to check if the map is ready to remove sources and layers
$map.removeSource(layerId);
} }
} }
} }
@@ -369,7 +364,8 @@
/> />
{#if tileUrls.length > 1} {#if tileUrls.length > 1}
<Button <Button
on:click={() => (tileUrls = tileUrls.filter((_, index) => index !== i))} on:click={() =>
(tileUrls = tileUrls.filter((_, index) => index !== i))}
variant="outline" variant="outline"
class="p-1 h-8" class="p-1 h-8"
> >
@@ -389,7 +385,14 @@
{/each} {/each}
{#if resourceType === 'raster'} {#if resourceType === 'raster'}
<Label for="maxZoom">{$_('layers.custom_layers.max_zoom')}</Label> <Label for="maxZoom">{$_('layers.custom_layers.max_zoom')}</Label>
<Input type="number" bind:value={maxZoom} id="maxZoom" min={0} max={22} class="h-8" /> <Input
type="number"
bind:value={maxZoom}
id="maxZoom"
min={0}
max={22}
class="h-8"
/>
{/if} {/if}
<Label>{$_('layers.custom_layers.layer_type')}</Label> <Label>{$_('layers.custom_layers.layer_type')}</Label>
<RadioGroup.Root bind:value={layerType} class="flex flex-row"> <RadioGroup.Root bind:value={layerType} class="flex flex-row">
@@ -398,7 +401,7 @@
<Label for="basemap">{$_('layers.custom_layers.basemap')}</Label> <Label for="basemap">{$_('layers.custom_layers.basemap')}</Label>
</div> </div>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<RadioGroup.Item value="overlay" id="overlay" disabled={resourceType === 'vector'} /> <RadioGroup.Item value="overlay" id="overlay" />
<Label for="overlay">{$_('layers.custom_layers.overlay')}</Label> <Label for="overlay">{$_('layers.custom_layers.overlay')}</Label>
</div> </div>
</RadioGroup.Root> </RadioGroup.Root>

View File

@@ -35,9 +35,18 @@
let basemap = basemaps.hasOwnProperty($currentBasemap) let basemap = basemaps.hasOwnProperty($currentBasemap)
? basemaps[$currentBasemap] ? basemaps[$currentBasemap]
: $customLayers[$currentBasemap]?.value ?? basemaps[defaultBasemap]; : $customLayers[$currentBasemap]?.value ?? basemaps[defaultBasemap];
$map.setStyle(basemap, { $map.removeImport('basemap');
diff: false if (typeof basemap === 'string') {
}); $map.addImport({ id: 'basemap', url: basemap }, 'overlays');
} else {
$map.addImport(
{
id: 'basemap',
data: basemap
},
'overlays'
);
}
} }
} }
@@ -45,24 +54,50 @@
setStyle(); setStyle();
} }
$: if ($map && $currentOverlays) { function addOverlay(id: string) {
// Add or remove overlay layers depending on the current overlays try {
let overlayLayers = getLayers($currentOverlays); let overlay = $customLayers.hasOwnProperty(id) ? $customLayers[id].value : overlays[id];
Object.keys(overlayLayers).forEach((id) => { if (typeof overlay === 'string') {
if (overlayLayers[id]) { $map.addImport({ id, url: overlay });
if (!addOverlayLayer.hasOwnProperty(id)) { } else {
addOverlayLayer[id] = addOverlayLayerForId(id); $map.addImport({
} id,
if (!$map.getLayer(id)) { data: overlay
addOverlayLayer[id]();
$map.on('style.load', addOverlayLayer[id]);
}
} else if ($map.getLayer(id)) {
$map.removeLayer(id);
$map.off('style.load', addOverlayLayer[id]);
}
}); });
} }
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
}
}
function updateOverlays() {
if ($map && $currentOverlays) {
let overlayLayers = getLayers($currentOverlays);
try {
let activeOverlays = $map
.getStyle()
.imports.filter((i) => i.id !== 'basemap' && i.id !== 'overlays');
let toRemove = activeOverlays.filter((i) => !overlayLayers[i.id]);
toRemove.forEach((i) => {
$map.removeImport(i.id);
});
let toAdd = Object.entries(overlayLayers)
.filter(
([id, selected]) => selected && !activeOverlays.some((j) => j.id === id)
)
.map(([id]) => id);
toAdd.forEach((id) => {
addOverlay(id);
});
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
}
}
}
$: if ($map && $currentOverlays) {
updateOverlays();
}
$: if ($map) { $: if ($map) {
if (overpassLayer) { if (overpassLayer) {
@@ -70,6 +105,7 @@
} }
overpassLayer = new OverpassLayer($map); overpassLayer = new OverpassLayer($map);
overpassLayer.add(); overpassLayer.add();
$map.on('style.import.load', updateOverlays);
} }
let selectedBasemap = writable(get(currentBasemap)); let selectedBasemap = writable(get(currentBasemap));
@@ -85,35 +121,15 @@
selectedBasemap.set(value); selectedBasemap.set(value);
}); });
let addOverlayLayer: { [key: string]: () => void } = {}; function removeOverlayLayer(id: string) {
function addOverlayLayerForId(id: string) {
return () => {
if ($map) { if ($map) {
try {
let overlay = $customLayers.hasOwnProperty(id) ? $customLayers[id].value : overlays[id]; let overlay = $customLayers.hasOwnProperty(id) ? $customLayers[id].value : overlays[id];
if (!$map.getSource(id)) { if (overlay.layers) {
$map.addSource(id, overlay); $map.removeImport(id);
} } else {
$map.addLayer( $map.removeLayer(id);
{
id,
type: overlay.type === 'raster' ? 'raster' : 'line',
source: id,
paint: {
...(id in $opacities
? overlay.type === 'raster'
? { 'raster-opacity': $opacities[id] }
: { 'line-opacity': $opacities[id] }
: {})
}
},
'overlays'
);
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
} }
} }
};
} }
let open = false; let open = false;

View File

@@ -50,7 +50,7 @@ export class OverpassLayer {
add() { add() {
this.map.on('moveend', this.queryIfNeededBinded); this.map.on('moveend', this.queryIfNeededBinded);
this.map.on('style.load', this.updateBinded); this.map.on('style.import.load', this.updateBinded);
this.unsubscribes.push(data.subscribe(this.updateBinded)); this.unsubscribes.push(data.subscribe(this.updateBinded));
this.unsubscribes.push(currentOverpassQueries.subscribe(() => { this.unsubscribes.push(currentOverpassQueries.subscribe(() => {
this.updateBinded(); this.updateBinded();
@@ -108,9 +108,10 @@ export class OverpassLayer {
remove() { remove() {
this.map.off('moveend', this.queryIfNeededBinded); this.map.off('moveend', this.queryIfNeededBinded);
this.map.off('style.load', this.updateBinded); this.map.off('style.import.load', this.updateBinded);
this.unsubscribes.forEach((unsubscribe) => unsubscribe()); this.unsubscribes.forEach((unsubscribe) => unsubscribe());
try {
if (this.map.getLayer('overpass')) { if (this.map.getLayer('overpass')) {
this.map.removeLayer('overpass'); this.map.removeLayer('overpass');
} }
@@ -118,6 +119,9 @@ export class OverpassLayer {
if (this.map.getSource('overpass')) { if (this.map.getSource('overpass')) {
this.map.removeSource('overpass'); this.map.removeSource('overpass');
} }
} catch (e) {
// No reliable way to check if the map is ready to remove sources and layers
}
} }
onHover(e: any) { onHover(e: any) {

View File

@@ -272,6 +272,7 @@
"finlandTopo": "Lantmäteriverket Terrängkarta", "finlandTopo": "Lantmäteriverket Terrängkarta",
"bgMountains": "BGMountains", "bgMountains": "BGMountains",
"usgs": "USGS", "usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite", "cyclOSMlite": "CyclOSM Lite",
"swisstopoSlope": "swisstopo Slope", "swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Hiking", "swisstopoHiking": "swisstopo Hiking",