diff --git a/frontend/src/components/Map/MapLibreMap.tsx b/frontend/src/components/Map/MapLibreMap.tsx index 6d2966e..cc7f223 100644 --- a/frontend/src/components/Map/MapLibreMap.tsx +++ b/frontend/src/components/Map/MapLibreMap.tsx @@ -31,6 +31,7 @@ import { MAP_STYLE, SEVEN_DAYS_MS, TERRAIN_CONFIG, + TERRAIN_DEM_SOURCE, } from './mapConfig.js'; import { buildClashLinesGeoJSON, @@ -74,6 +75,7 @@ export function MapLibreMap({ const linkMetricsRef = useRef(linkStateStore.getState().linkMetrics); const inferredNodesRef = useRef(inferredNodes); const showLinksRef = useRef(showLinks); + const showTerrainRef = useRef(showTerrain); const showClientNodesRef = useRef(showClientNodes); const showHexClashesRef = useRef(showHexClashes); const maxHexClashHopsRef = useRef(maxHexClashHops); @@ -208,29 +210,13 @@ export function MapLibreMap({ center: [DEFAULT_CENTER[1], DEFAULT_CENTER[0]], // [lon, lat] zoom: DEFAULT_ZOOM, maxPitch: 0, + minZoom: 6, attributionControl: false, }); map.on('load', () => { mapLoadedRef.current = true; - // ── Sky layer (for 3D terrain mode) ──────────────────────────────────── - // eslint-disable-next-line @typescript-eslint/no-explicit-any - map.addLayer({ id: 'sky', type: 'sky', paint: { 'sky-type': 'atmosphere', 'sky-atmosphere-sun': [0, 90], 'sky-atmosphere-sun-intensity': 15 } } as any); - - // ── Hillshade layer ──────────────────────────────────────────────────── - map.addLayer({ - id: 'hillshade', - type: 'hillshade', - source: 'terrain-dem', - paint: { - 'hillshade-exaggeration': 0.7, - 'hillshade-shadow-color': '#000000', - 'hillshade-highlight-color': '#ffffff', - 'hillshade-illumination-anchor': 'viewport', - }, - }); - // ── Node dots source + layer ─────────────────────────────────────────── map.addSource('nodes', { type: 'geojson', data: EMPTY_FC }); @@ -365,6 +351,20 @@ export function MapLibreMap({ mapRef.current = map; onMapReady?.(map); refreshMapSources(); + + // Restore terrain if it was saved in preferences + if (showTerrainRef.current) { + map.addSource('terrain-dem', TERRAIN_DEM_SOURCE); + map.addLayer({ + id: 'hillshade', type: 'hillshade', source: 'terrain-dem', minzoom: 7, + paint: { 'hillshade-exaggeration': 0.7, 'hillshade-shadow-color': '#000000', 'hillshade-highlight-color': '#ffffff', 'hillshade-illumination-anchor': 'viewport' }, + }, 'node-dots'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + map.addLayer({ id: 'sky', type: 'sky', paint: { 'sky-type': 'atmosphere', 'sky-atmosphere-sun': [0, 90], 'sky-atmosphere-sun-intensity': 15 } } as any); + map.setMaxPitch(85); + if (map.getZoom() >= 7) map.setTerrain(TERRAIN_CONFIG); + map.easeTo({ pitch: 45, duration: 600 }); + } }); return () => { @@ -387,16 +387,27 @@ export function MapLibreMap({ }, [showLinks, scheduleRefresh]); useEffect(() => { + showTerrainRef.current = showTerrain; const map = mapRef.current; if (!map || !mapLoadedRef.current) return; if (showTerrain) { + if (!map.getSource('terrain-dem')) map.addSource('terrain-dem', TERRAIN_DEM_SOURCE); + if (!map.getLayer('hillshade')) map.addLayer({ + id: 'hillshade', type: 'hillshade', source: 'terrain-dem', minzoom: 7, + paint: { 'hillshade-exaggeration': 0.7, 'hillshade-shadow-color': '#000000', 'hillshade-highlight-color': '#ffffff', 'hillshade-illumination-anchor': 'viewport' }, + }, 'node-dots'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (!map.getLayer('sky')) map.addLayer({ id: 'sky', type: 'sky', paint: { 'sky-type': 'atmosphere', 'sky-atmosphere-sun': [0, 90], 'sky-atmosphere-sun-intensity': 15 } } as any); map.setMaxPitch(85); - map.setTerrain(TERRAIN_CONFIG); + if (map.getZoom() >= 7) map.setTerrain(TERRAIN_CONFIG); map.easeTo({ pitch: 45, duration: 600 }); } else { + map.setTerrain(null); + if (map.getLayer('hillshade')) map.removeLayer('hillshade'); + if (map.getLayer('sky')) map.removeLayer('sky'); + if (map.getSource('terrain-dem')) map.removeSource('terrain-dem'); map.easeTo({ pitch: 0, duration: 400 }); setTimeout(() => map.setMaxPitch(0), 400); - map.setTerrain(null); } }, [showTerrain]); diff --git a/frontend/src/components/Map/mapConfig.ts b/frontend/src/components/Map/mapConfig.ts index 7d81a3b..78314d9 100644 --- a/frontend/src/components/Map/mapConfig.ts +++ b/frontend/src/components/Map/mapConfig.ts @@ -14,7 +14,15 @@ export const EMPTY_FC: GeoJSON.FeatureCollection = { features: [], }; -export const TERRAIN_CONFIG = { source: 'terrain-dem', exaggeration: 4 }; +export const TERRAIN_CONFIG = { source: 'terrain-dem', exaggeration: 3 }; + +export const TERRAIN_DEM_SOURCE: maplibregl.RasterDEMSourceSpecification = { + type: 'raster-dem', + tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'], + encoding: 'terrarium', + tileSize: 256, + maxzoom: 15, +}; export const MAP_STYLE: maplibregl.StyleSpecification = { version: 8, @@ -32,13 +40,6 @@ export const MAP_STYLE: maplibregl.StyleSpecification = { '© OpenStreetMap © CARTO', maxzoom: 19, }, - 'terrain-dem': { - type: 'raster-dem', - tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'], - encoding: 'terrarium', - tileSize: 256, - maxzoom: 15, - }, }, layers: [ { id: 'background', type: 'raster', source: 'carto-dark' },