diff --git a/website/src/lib/assets/layers.ts b/website/src/lib/assets/layers.ts
index 98780282..ffc6f2bc 100644
--- a/website/src/lib/assets/layers.ts
+++ b/website/src/lib/assets/layers.ts
@@ -266,12 +266,15 @@ export const basemaps: { [key: string]: string | Style; } = {
},
};
-Object.values(basemaps).forEach((basemap) => {
+export function extendBasemap(basemap: string | Style): string | Style {
if (typeof basemap === 'object') {
basemap["glyphs"] = "mapbox://fonts/mapbox/{fontstack}/{range}.pbf";
basemap["sprite"] = `https://api.mapbox.com/styles/v1/mapbox/outdoors-v12/sprite?access_token=${mapboxAccessToken}`;
}
-});
+ return basemap;
+}
+
+Object.values(basemaps).forEach(extendBasemap);
export const font: { [key: string]: string; } = {
swisstopo: 'Frutiger Neue Condensed Regular',
@@ -678,6 +681,16 @@ export const defaultOverlayTree: LayerTreeType = {
}
}
+export type CustomLayer = {
+ id: string,
+ name: string,
+ tileUrls: string[],
+ maxZoom: number,
+ layerType: 'basemap' | 'overlay',
+ resourceType: 'raster' | 'vector',
+ value: string | {},
+};
+
export const stravaHeatmapServers = ['https://heatmap-external-a.strava.com/tiles-auth', 'https://heatmap-external-b.strava.com/tiles-auth', 'https://heatmap-external-c.strava.com/tiles-auth'];
export const stravaHeatmapActivityIds: { [key: string]: string } = {
stravaHeatmapRun: 'sport_Run',
diff --git a/website/src/lib/components/Menu.svelte b/website/src/lib/components/Menu.svelte
index 2fe77239..3b19207a 100644
--- a/website/src/lib/components/Menu.svelte
+++ b/website/src/lib/components/Menu.svelte
@@ -395,8 +395,10 @@
cutSelection();
e.preventDefault();
} else if (e.key === 'v' && (e.metaKey || e.ctrlKey)) {
- pasteSelection();
- e.preventDefault();
+ if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
+ pasteSelection();
+ e.preventDefault();
+ }
} else if ((e.key === 's' || e.key == 'S') && (e.metaKey || e.ctrlKey)) {
if (e.shiftKey) {
exportAllFiles();
diff --git a/website/src/lib/components/layer-control/CustomLayers.svelte b/website/src/lib/components/layer-control/CustomLayers.svelte
new file mode 100644
index 00000000..846f3194
--- /dev/null
+++ b/website/src/lib/components/layer-control/CustomLayers.svelte
@@ -0,0 +1,264 @@
+
+
+{#if Object.keys($customLayers).length > 0}
+
+ {#each Object.entries($customLayers) as [id, layer] (id)}
+
+
{layer.name}
+
+
+
+ {/each}
+
+{/if}
+
+
+
+
+ {#if selectedLayerId}
+ {$_('layers.custom_layers.edit')}
+ {:else}
+ {$_('layers.custom_layers.new')}
+ {/if}
+
+
+
+
+
+
diff --git a/website/src/lib/components/layer-control/LayerControl.svelte b/website/src/lib/components/layer-control/LayerControl.svelte
index 3d041eac..e9210331 100644
--- a/website/src/lib/components/layer-control/LayerControl.svelte
+++ b/website/src/lib/components/layer-control/LayerControl.svelte
@@ -18,12 +18,16 @@
previousBasemap,
currentOverlays,
selectedBasemapTree,
- selectedOverlayTree
+ selectedOverlayTree,
+ customLayers
} = settings;
$: if ($map) {
// Set style depending on the current basemap
- $map.setStyle(basemaps[$currentBasemap], {
+ let basemap = basemaps.hasOwnProperty($currentBasemap)
+ ? basemaps[$currentBasemap]
+ : $customLayers[$currentBasemap].value;
+ $map.setStyle(basemap, {
diff: false
});
}
@@ -65,17 +69,18 @@
return () => {
if ($map) {
try {
+ let overlay = $customLayers.hasOwnProperty(id) ? $customLayers[id].value : overlays[id];
if (!$map.getSource(id)) {
- $map.addSource(id, overlays[id]);
+ $map.addSource(id, overlay);
}
$map.addLayer(
{
id,
- type: overlays[id].type === 'raster' ? 'raster' : 'line',
+ type: overlay.type === 'raster' ? 'raster' : 'line',
source: id,
paint: {
...(id in opacities
- ? overlays[id].type === 'raster'
+ ? overlay.type === 'raster'
? { 'raster-opacity': opacities[id] }
: { 'line-opacity': opacities[id] }
: {})
diff --git a/website/src/lib/components/layer-control/LayerControlSettings.svelte b/website/src/lib/components/layer-control/LayerControlSettings.svelte
index e15f338a..417886b9 100644
--- a/website/src/lib/components/layer-control/LayerControlSettings.svelte
+++ b/website/src/lib/components/layer-control/LayerControlSettings.svelte
@@ -15,6 +15,7 @@
import { writable, get } from 'svelte/store';
import { map, setStravaHeatmapURLs } from '$lib/stores';
import { browser } from '$app/environment';
+ import CustomLayers from './CustomLayers.svelte';
const { selectedBasemapTree, selectedOverlayTree, stravaHeatmapColor, currentOverlays } =
settings;
@@ -111,9 +112,11 @@
- {$_('layers.custom_layers')}
+ {$_('layers.custom_layers.title')}
-
+
+
+
diff --git a/website/src/lib/components/layer-control/LayerTreeNode.svelte b/website/src/lib/components/layer-control/LayerTreeNode.svelte
index a980a0fa..5f3d84db 100644
--- a/website/src/lib/components/layer-control/LayerTreeNode.svelte
+++ b/website/src/lib/components/layer-control/LayerTreeNode.svelte
@@ -7,6 +7,7 @@
import { anySelectedLayer } from './utils';
import { _ } from 'svelte-i18n';
+ import { settings } from '$lib/db';
export let name: string;
export let node: LayerTreeType;
@@ -15,15 +16,19 @@
export let checked: LayerTreeType;
- Object.keys(node).forEach((id) => {
- if (!checked.hasOwnProperty(id)) {
- if (typeof node[id] == 'boolean') {
- checked[id] = false;
- } else {
- checked[id] = {};
+ const { customLayers } = settings;
+
+ $: if (checked !== undefined) {
+ Object.keys(node).forEach((id) => {
+ if (!checked.hasOwnProperty(id)) {
+ if (typeof node[id] == 'boolean') {
+ checked[id] = false;
+ } else {
+ checked[id] = {};
+ }
}
- }
- });
+ });
+ }
@@ -42,9 +47,13 @@
{:else}
{/if}
-
+
{/if}
{:else if anySelectedLayer(node[id])}
diff --git a/website/src/lib/db.ts b/website/src/lib/db.ts
index c2ea20d5..e80b0cb1 100644
--- a/website/src/lib/db.ts
+++ b/website/src/lib/db.ts
@@ -4,7 +4,7 @@ import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDra
import { writable, get, derived, type Readable, type Writable } from 'svelte/store';
import { gpxStatistics, initTargetMapBounds, splitAs, updateTargetMapBounds } from './stores';
import { mode } from 'mode-watcher';
-import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays } from './assets/layers';
+import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays, type CustomLayer } from './assets/layers';
import { applyToOrderedItemsFromFile, applyToOrderedSelectedItemsFromFile, selection } from '$lib/components/file-list/Selection';
import { ListFileItem, ListItem, ListTrackItem, ListLevel, ListTrackSegmentItem, ListWaypointItem, ListRootItem } from '$lib/components/file-list/FileList';
import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simplify';
@@ -48,13 +48,13 @@ function dexieSettingStore(setting: string, initial: T): Writable {
return {
subscribe: store.subscribe,
set: (value: any) => {
- if (value !== get(store)) {
+ if (typeof value === 'object' || value !== get(store)) {
db.settings.put(value, setting);
}
},
update: (callback: (value: any) => any) => {
let newValue = callback(get(store));
- if (newValue !== get(store)) {
+ if (typeof newValue === 'object' || newValue !== get(store)) {
db.settings.put(newValue, setting);
}
}
@@ -104,6 +104,7 @@ export const settings = {
currentOverlays: dexieUninitializedSettingStore('currentOverlays', defaultOverlays),
previousOverlays: dexieSettingStore('previousOverlays', defaultOverlays),
selectedOverlayTree: dexieSettingStore('selectedOverlayTree', defaultOverlayTree),
+ customLayers: dexieSettingStore>('customLayers', {}),
directionMarkers: dexieSettingStore('directionMarkers', false),
distanceMarkers: dexieSettingStore('distanceMarkers', false),
stravaHeatmapColor: dexieSettingStore('stravaHeatmapColor', 'bluered'),
diff --git a/website/src/locales/en.json b/website/src/locales/en.json
index 8511c616..bb39b0d4 100644
--- a/website/src/locales/en.json
+++ b/website/src/locales/en.json
@@ -198,12 +198,24 @@
"settings": "Layer settings",
"settings_help": "Select the map layers you want to show in the interface, add custom ones, and adjust their settings.",
"selection": "Layer selection",
- "custom_layers": "Custom layers",
+ "custom_layers": {
+ "title": "Custom layers",
+ "new": "New custom layer",
+ "edit": "Edit custom layer",
+ "urls": "URL(s)",
+ "max_zoom": "Max zoom",
+ "layer_type": "Layer type",
+ "basemap": "Basemap",
+ "overlay": "Overlay",
+ "create": "Create layer",
+ "update": "Update layer"
+ },
"heatmap": "Strava Heatmap",
"pois": "Points of interest",
"label": {
"basemaps": "Basemaps",
"overlays": "Overlays",
+ "custom": "Custom",
"world": "World",
"countries": "Countries",
"belgium": "Belgium",