fix(geoportal): fix basemap switching + OpenTopoMap maxzoom 17

- Basemap switching now recreates the map (basemap in useEffect deps)
  instead of buggy remove/re-add that swallowed errors
- OpenTopoMap maxzoom set to 17 (was requesting z19 → 400 errors)
- Basemap maxzoom applied to both source and map maxZoom

Note: Martin vector tiles return 404 — PostGIS views or Martin container
need to be checked on the server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-23 18:24:47 +02:00
parent fff20e0cb9
commit 2248ecc5d3
@@ -53,7 +53,7 @@ const LAYER_IDS = {
} as const;
/** Basemap tile definitions */
const BASEMAP_TILES: Record<BasemapId, { tiles: string[]; attribution: string; tileSize: number }> = {
const BASEMAP_TILES: Record<BasemapId, { tiles: string[]; attribution: string; tileSize: number; maxzoom?: number }> = {
osm: {
tiles: [
"https://a.tile.openstreetmap.org/{z}/{x}/{y}.png",
@@ -79,6 +79,7 @@ const BASEMAP_TILES: Record<BasemapId, { tiles: string[]; attribution: string; t
attribution:
'&copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)',
tileSize: 256,
maxzoom: 17,
},
};
@@ -231,47 +232,7 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
}
}, [mapReady, layerVisibility, applyLayerVisibility]);
/* ---- Basemap switching ---- */
useEffect(() => {
const map = mapRef.current;
if (!map || !mapReady) return;
const source = map.getSource("basemap") as maplibregl.RasterTileSource | undefined;
if (!source) return;
const def = BASEMAP_TILES[basemap];
// Update tiles by re-adding the source
// MapLibre doesn't support changing tiles on existing source, so we rebuild
try {
// Remove all layers that depend on basemap source, then remove source
if (map.getLayer("basemap-tiles")) map.removeLayer("basemap-tiles");
map.removeSource("basemap");
map.addSource("basemap", {
type: "raster",
tiles: def.tiles,
tileSize: def.tileSize,
attribution: def.attribution,
});
// Re-add basemap layer at bottom
const firstLayerId = map.getStyle().layers[0]?.id;
map.addLayer(
{
id: "basemap-tiles",
type: "raster",
source: "basemap",
minzoom: 0,
maxzoom: 19,
},
firstLayerId // insert before first existing layer
);
} catch {
// Fallback: if anything fails, the map still works
}
}, [basemap, mapReady]);
/* ---- Map initialization ---- */
/* ---- Map initialization (recreates on basemap change) ---- */
useEffect(() => {
if (!containerRef.current) return;
@@ -295,13 +256,13 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
type: "raster",
source: "basemap",
minzoom: 0,
maxzoom: 19,
maxzoom: initialBasemap.maxzoom ?? 19,
},
],
},
center: center ?? DEFAULT_CENTER,
zoom: zoom ?? DEFAULT_ZOOM,
maxZoom: 20,
maxZoom: initialBasemap.maxzoom ?? 20,
});
mapRef.current = map;
@@ -555,7 +516,7 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
setMapReady(false);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [resolvedMartinUrl]);
}, [resolvedMartinUrl, basemap]);
/* ---- Sync center/zoom prop changes ---- */
useEffect(() => {