mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-04-19 10:01:18 +00:00
Merge branch 'maplibre' into dev
This commit is contained in:
@@ -20,9 +20,8 @@
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { defaultBasemap, type CustomLayer } from '$lib/assets/layers';
|
||||
import { onMount } from 'svelte';
|
||||
import { customBasemapUpdate, isSelected, remove } from './utils';
|
||||
import { remove } from './utils';
|
||||
import { settings } from '$lib/logic/settings';
|
||||
import { map } from '$lib/components/map/map';
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
|
||||
const {
|
||||
@@ -42,13 +41,8 @@
|
||||
let maxZoom: number = $state(20);
|
||||
let layerType: 'basemap' | 'overlay' = $state('basemap');
|
||||
let resourceType: 'raster' | 'vector' = $derived.by(() => {
|
||||
if (tileUrls[0].length > 0) {
|
||||
if (
|
||||
tileUrls[0].includes('.json') ||
|
||||
(tileUrls[0].includes('api.mapbox.com/styles') && !tileUrls[0].includes('tiles'))
|
||||
) {
|
||||
return 'vector';
|
||||
}
|
||||
if (tileUrls[0].length > 0 && tileUrls[0].includes('.json')) {
|
||||
return 'vector';
|
||||
}
|
||||
return 'raster';
|
||||
});
|
||||
@@ -134,8 +128,8 @@
|
||||
],
|
||||
};
|
||||
}
|
||||
$customLayers[layerId] = layer;
|
||||
addLayer(layerId);
|
||||
$customLayers[layerId] = layer;
|
||||
selectedLayerId = undefined;
|
||||
setDataFromSelectedLayer();
|
||||
}
|
||||
@@ -158,9 +152,7 @@
|
||||
return $tree;
|
||||
});
|
||||
|
||||
if ($currentBasemap === layerId) {
|
||||
$customBasemapUpdate++;
|
||||
} else {
|
||||
if ($currentBasemap !== layerId) {
|
||||
$currentBasemap = layerId;
|
||||
}
|
||||
|
||||
@@ -176,14 +168,6 @@
|
||||
return $tree;
|
||||
});
|
||||
|
||||
if ($map && $currentOverlays && isSelected($currentOverlays, layerId)) {
|
||||
try {
|
||||
$map.removeImport(layerId);
|
||||
} catch (e) {
|
||||
// No reliable way to check if the map is ready to remove sources and layers
|
||||
}
|
||||
}
|
||||
|
||||
currentOverlays.update(($overlays) => {
|
||||
if (!$overlays.overlays.hasOwnProperty('custom')) {
|
||||
$overlays.overlays['custom'] = {};
|
||||
|
||||
@@ -5,12 +5,8 @@
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
|
||||
import { Layers } from '@lucide/svelte';
|
||||
import { basemaps, defaultBasemap, overlays } from '$lib/assets/layers';
|
||||
import { settings } from '$lib/logic/settings';
|
||||
import { map } from '$lib/components/map/map';
|
||||
import { customBasemapUpdate, getLayers } from './utils';
|
||||
import type { ImportSpecification, StyleSpecification } from 'mapbox-gl';
|
||||
import { untrack } from 'svelte';
|
||||
|
||||
let container: HTMLDivElement;
|
||||
let overpassLayer: OverpassLayer;
|
||||
@@ -23,125 +19,14 @@
|
||||
selectedBasemapTree,
|
||||
selectedOverlayTree,
|
||||
selectedOverpassTree,
|
||||
customLayers,
|
||||
opacities,
|
||||
} = settings;
|
||||
|
||||
function setStyle() {
|
||||
if (!$map) {
|
||||
return;
|
||||
}
|
||||
let basemap = basemaps.hasOwnProperty($currentBasemap)
|
||||
? basemaps[$currentBasemap]
|
||||
: ($customLayers[$currentBasemap]?.value ?? basemaps[defaultBasemap]);
|
||||
$map.removeImport('basemap');
|
||||
if (typeof basemap === 'string') {
|
||||
$map.addImport({ id: 'basemap', url: basemap }, 'overlays');
|
||||
} else {
|
||||
$map.addImport(
|
||||
{
|
||||
id: 'basemap',
|
||||
url: '',
|
||||
data: basemap as StyleSpecification,
|
||||
},
|
||||
'overlays'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if ($map && ($currentBasemap || $customBasemapUpdate)) {
|
||||
untrack(() => setStyle());
|
||||
}
|
||||
});
|
||||
|
||||
function addOverlay(id: string) {
|
||||
if (!$map) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let overlay = $customLayers.hasOwnProperty(id) ? $customLayers[id].value : overlays[id];
|
||||
if (typeof overlay === 'string') {
|
||||
$map.addImport({ id, url: overlay });
|
||||
} else {
|
||||
if ($opacities.hasOwnProperty(id)) {
|
||||
overlay = {
|
||||
...overlay,
|
||||
layers: (overlay as StyleSpecification).layers.map((layer) => {
|
||||
if (layer.type === 'raster') {
|
||||
if (!layer.paint) {
|
||||
layer.paint = {};
|
||||
}
|
||||
layer.paint['raster-opacity'] = $opacities[id];
|
||||
}
|
||||
return layer;
|
||||
}),
|
||||
};
|
||||
}
|
||||
$map.addImport({
|
||||
id,
|
||||
url: '',
|
||||
data: overlay as StyleSpecification,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// No reliable way to check if the map is ready to add sources and layers
|
||||
}
|
||||
}
|
||||
|
||||
function updateOverlays() {
|
||||
if ($map && $currentOverlays && $opacities) {
|
||||
let overlayLayers = getLayers($currentOverlays);
|
||||
try {
|
||||
let activeOverlays =
|
||||
$map
|
||||
.getStyle()
|
||||
.imports?.reduce(
|
||||
(
|
||||
acc: Record<string, ImportSpecification>,
|
||||
imprt: ImportSpecification
|
||||
) => {
|
||||
if (!['basemap', 'overlays'].includes(imprt.id)) {
|
||||
acc[imprt.id] = imprt;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
) || {};
|
||||
let toRemove = Object.keys(activeOverlays).filter((id) => !overlayLayers[id]);
|
||||
toRemove.forEach((id) => {
|
||||
$map?.removeImport(id);
|
||||
});
|
||||
let toAdd = Object.entries(overlayLayers)
|
||||
.filter(([id, selected]) => selected && !activeOverlays.hasOwnProperty(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if ($map && $currentOverlays && $opacities) {
|
||||
untrack(() => updateOverlays());
|
||||
}
|
||||
});
|
||||
|
||||
map.onLoad((_map: mapboxgl.Map) => {
|
||||
map.onLoad((_map: maplibregl.Map) => {
|
||||
if (overpassLayer) {
|
||||
overpassLayer.remove();
|
||||
}
|
||||
overpassLayer = new OverpassLayer(_map);
|
||||
overpassLayer = new OverpassLayer(_map, map.layerEventManager!);
|
||||
overpassLayer.add();
|
||||
let first = true;
|
||||
_map.on('style.import.load', () => {
|
||||
if (!first) return;
|
||||
first = false;
|
||||
updateOverlays();
|
||||
});
|
||||
});
|
||||
|
||||
let open = $state(false);
|
||||
|
||||
@@ -167,11 +167,11 @@
|
||||
{#if isSelected($selectedOverlayTree, selectedOverlay)}
|
||||
{#if $isLayerFromExtension(selectedOverlay)}
|
||||
{$getLayerName(selectedOverlay)}
|
||||
{:else if $customLayers.hasOwnProperty(selectedOverlay)}
|
||||
{$customLayers[selectedOverlay].name}
|
||||
{:else}
|
||||
{i18n._(`layers.label.${selectedOverlay}`)}
|
||||
{/if}
|
||||
{:else if $customLayers.hasOwnProperty(selectedOverlay)}
|
||||
{$customLayers[selectedOverlay].name}
|
||||
{/if}
|
||||
{/if}
|
||||
</Select.Trigger>
|
||||
@@ -213,7 +213,9 @@
|
||||
isSelected($currentOverlays, selectedOverlay)
|
||||
) {
|
||||
try {
|
||||
$map.removeImport(selectedOverlay);
|
||||
if ($map.getLayer(selectedOverlay)) {
|
||||
$map.removeLayer(selectedOverlay);
|
||||
}
|
||||
} catch (e) {
|
||||
// No reliable way to check if the map is ready to remove sources and layers
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export class ExtensionAPI {
|
||||
if (current && isSelected(current, overlay.id)) {
|
||||
show = true;
|
||||
try {
|
||||
get(map)?.removeImport(overlay.id);
|
||||
get(map)?.removeLayer(overlay.id);
|
||||
} catch (e) {
|
||||
// No reliable way to check if the map is ready to remove sources and layers
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ 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';
|
||||
import type { GeoJSONSource } from 'maplibre-gl';
|
||||
import { ANCHOR_LAYER_KEY } from '$lib/components/map/style';
|
||||
import type { MapLayerEventManager } from '$lib/components/map/map-layer-event-manager';
|
||||
import { loadSVGIcon } from '$lib/utils';
|
||||
|
||||
const { currentOverpassQueries } = settings;
|
||||
|
||||
@@ -25,7 +28,8 @@ export class OverpassLayer {
|
||||
minZoom = 12;
|
||||
queryZoom = 12;
|
||||
expirationTime = 7 * 24 * 3600 * 1000;
|
||||
map: mapboxgl.Map;
|
||||
map: maplibregl.Map;
|
||||
layerEventManager: MapLayerEventManager;
|
||||
popup: MapPopup;
|
||||
|
||||
currentQueries: Set<string> = new Set();
|
||||
@@ -36,8 +40,9 @@ export class OverpassLayer {
|
||||
updateBinded = this.update.bind(this);
|
||||
onHoverBinded = this.onHover.bind(this);
|
||||
|
||||
constructor(map: mapboxgl.Map) {
|
||||
constructor(map: maplibregl.Map, layerEventManager: MapLayerEventManager) {
|
||||
this.map = map;
|
||||
this.layerEventManager = layerEventManager;
|
||||
this.popup = new MapPopup(map, {
|
||||
closeButton: false,
|
||||
focusAfterOpen: false,
|
||||
@@ -48,7 +53,7 @@ export class OverpassLayer {
|
||||
|
||||
add() {
|
||||
this.map.on('moveend', this.queryIfNeededBinded);
|
||||
this.map.on('style.import.load', this.updateBinded);
|
||||
this.map.on('style.load', this.updateBinded);
|
||||
this.unsubscribes.push(data.subscribe(this.updateBinded));
|
||||
this.unsubscribes.push(
|
||||
currentOverpassQueries.subscribe(() => {
|
||||
@@ -72,10 +77,17 @@ 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 mapboxgl.GeoJSONSource | undefined;
|
||||
let source = this.map.getSource('overpass') as GeoJSONSource | undefined;
|
||||
if (source) {
|
||||
source.setData(d);
|
||||
} else {
|
||||
@@ -101,13 +113,9 @@ export class OverpassLayer {
|
||||
ANCHOR_LAYER_KEY.overpass
|
||||
);
|
||||
|
||||
this.map.on('mouseenter', 'overpass', this.onHoverBinded);
|
||||
this.map.on('click', 'overpass', this.onHoverBinded);
|
||||
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
|
||||
}
|
||||
@@ -115,7 +123,9 @@ export class OverpassLayer {
|
||||
|
||||
remove() {
|
||||
this.map.off('moveend', this.queryIfNeededBinded);
|
||||
this.map.off('style.import.load', this.updateBinded);
|
||||
this.map.off('style.load', this.updateBinded);
|
||||
this.layerEventManager.off('mouseenter', 'overpass', this.onHoverBinded);
|
||||
this.layerEventManager.off('click', 'overpass', this.onHoverBinded);
|
||||
this.unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||
|
||||
try {
|
||||
@@ -248,27 +258,16 @@ export class OverpassLayer {
|
||||
loadIcons() {
|
||||
let currentQueries = getCurrentQueries();
|
||||
currentQueries.forEach((query) => {
|
||||
if (!this.map.hasImage(`overpass-${query}`)) {
|
||||
let icon = new Image(100, 100);
|
||||
icon.onload = () => {
|
||||
if (!this.map.hasImage(`overpass-${query}`)) {
|
||||
this.map.addImage(`overpass-${query}`, icon);
|
||||
}
|
||||
};
|
||||
|
||||
// Lucide icons are SVG files with a 24x24 viewBox
|
||||
// Create a new SVG with a 32x32 viewBox and center the icon in a circle
|
||||
icon.src =
|
||||
'data:image/svg+xml,' +
|
||||
encodeURIComponent(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
|
||||
loadSVGIcon(
|
||||
this.map,
|
||||
`overpass-${query}`,
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" r="20" fill="${overpassQueryData[query].icon.color}" />
|
||||
<g transform="translate(8 8)">
|
||||
${overpassQueryData[query].icon.svg.replace('stroke="currentColor"', 'stroke="white"')}
|
||||
</g>
|
||||
</svg>
|
||||
`);
|
||||
}
|
||||
</svg>`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,5 +76,3 @@ export function removeAll(node: LayerTreeType, ids: string[]) {
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
export const customBasemapUpdate = writable(0);
|
||||
|
||||
Reference in New Issue
Block a user