From 400c485833e369232de59f912f3e30cf67c46b58 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 7 May 2026 03:58:46 +0000 Subject: [PATCH] fix: contour overlay with pmtiles fork, absolute URL, extended zoom range - Switch to @acalcutt/maplibre-contour-pmtiles for PMTiles support - Use absolute URL for DemSource so Web Worker can resolve path - Extend contour thresholds from z3-z15 for full zoom coverage - Improve line styling with zoom-dependent width - Improve label styling with bold font and better halo Co-Authored-By: Claude --- package-lock.json | 14 ++++---- package.json | 2 +- src/components/MapView.jsx | 73 +++++++++++++++++++++++++------------- 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1fb2d33..3bd1ca0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,11 @@ "name": "navi", "version": "0.0.0", "dependencies": { + "@acalcutt/maplibre-contour-pmtiles": "^0.1.2", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "lucide-react": "^1.8.0", - "maplibre-contour": "^0.1.0", "maplibre-gl": "^5.23.0", "opening_hours": "^3.12.0", "pmtiles": "^4.4.1", @@ -38,6 +38,12 @@ "vite": "^8.0.9" } }, + "node_modules/@acalcutt/maplibre-contour-pmtiles": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@acalcutt/maplibre-contour-pmtiles/-/maplibre-contour-pmtiles-0.1.2.tgz", + "integrity": "sha512-dCyJFLLM4NomLoJ22McRp7yETFmzUuA6iEMVJS6+mFyHoNk7Sv6RI4Hn0DhGKeyjcJgan3YnfSnzsqRinnXSug==", + "license": "BSD-3-Clause" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -2686,12 +2692,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/maplibre-contour": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/maplibre-contour/-/maplibre-contour-0.1.0.tgz", - "integrity": "sha512-H8muT7JWYE4oLbFv7L2RSbIM1NOu5JxjA9P/TQqhODDnRChE8ENoDkQIWOKgfcKNU77ypLk2ggGoh4/pt4UPLA==", - "license": "BSD-3-Clause" - }, "node_modules/maplibre-gl": { "version": "5.23.0", "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.23.0.tgz", diff --git a/package.json b/package.json index b879244..ae0057b 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ "preview": "vite preview" }, "dependencies": { + "@acalcutt/maplibre-contour-pmtiles": "^0.1.2", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "lucide-react": "^1.8.0", - "maplibre-contour": "^0.1.0", "maplibre-gl": "^5.23.0", "opening_hours": "^3.12.0", "pmtiles": "^4.4.1", diff --git a/src/components/MapView.jsx b/src/components/MapView.jsx index 3599c5b..7ae3146 100644 --- a/src/components/MapView.jsx +++ b/src/components/MapView.jsx @@ -12,7 +12,7 @@ import { MapPin, Navigation, ArrowUpRight, ArrowDownLeft, Plus, Star, Ruler, X } import RadialMenu from './RadialMenu' import useContextMenu from '../hooks/useContextMenu' import toast from 'react-hot-toast' -import mlcontour from 'maplibre-contour' +import mlcontour from '@acalcutt/maplibre-contour-pmtiles' let demSourceInstance = null @@ -565,19 +565,34 @@ function removePublicLands(map) { /** Add topographic contours via maplibre-contour */ function addContours(map) { - console.log('[CONTOUR] addContours called, source exists:', !!map?.getSource(CONTOUR_SOURCE)) - if (!map || map.getSource(CONTOUR_SOURCE)) return + console.log('[CONTOUR] addContours called, source exists:', !!map?.getSource(CONTOUR_SOURCE), 'demSource:', !!demSourceInstance) + if (!map || !demSourceInstance || map.getSource(CONTOUR_SOURCE)) return + const contourThresholds = { + 3: [5000, 25000], + 4: [2500, 10000], + 5: [1000, 5000], + 6: [1000, 5000], + 7: [500, 2500], + 8: [500, 2500], + 9: [250, 1000], + 10: [200, 1000], + 11: [200, 1000], + 12: [100, 500], + 13: [100, 500], + 14: [50, 200], + 15: [20, 100], + } map.addSource(CONTOUR_SOURCE, { type: 'vector', tiles: [demSourceInstance.contourProtocolUrl({ multiplier: 3.28084, - thresholds: { 11: [200, 1000], 12: [100, 500], 13: [100, 500], 14: [50, 200] }, + thresholds: contourThresholds, })], - maxzoom: 15, + maxzoom: 16, }) console.log('[CONTOUR] protocol URL:', demSourceInstance.contourProtocolUrl({ multiplier: 3.28084, - thresholds: { 11: [200, 1000], 12: [100, 500], 13: [100, 500], 14: [50, 200] }, + thresholds: contourThresholds, })) console.log('[CONTOUR] source added:', !!map.getSource(CONTOUR_SOURCE)) let beforeId = undefined @@ -589,9 +604,13 @@ function addContours(map) { id: CONTOUR_LINE, type: 'line', source: CONTOUR_SOURCE, 'source-layer': 'contours', paint: { - 'line-color': isDark ? '#c0b898' : '#8b6f47', - 'line-opacity': 0.7, - 'line-width': ['match', ['get', 'level'], 1, 1.5, 0.5], + 'line-color': 'rgba(0,0,0,0.35)', + 'line-width': [ + 'interpolate', ['linear'], ['zoom'], + 7, ['match', ['get', 'level'], 1, 1, 0.3], + 11, ['match', ['get', 'level'], 1, 1.5, 0.6], + 14, ['match', ['get', 'level'], 1, 2, 0.8], + ], }, }, beforeId) map.addLayer({ @@ -599,13 +618,15 @@ function addContours(map) { 'source-layer': 'contours', filter: ['>', ['get', 'level'], 0], layout: { - 'symbol-placement': 'line', 'text-size': 10, + 'symbol-placement': 'line', + 'text-size': ['interpolate', ['linear'], ['zoom'], 7, 9, 11, 11, 14, 13], 'text-field': ['concat', ['number-format', ['get', 'ele'], {}], "'"], - 'text-font': ['Noto Sans Regular'], + 'text-font': ['Noto Sans Bold'], + 'text-max-angle': 25, }, paint: { - 'text-color': isDark ? '#c0b898' : '#5a4020', - 'text-halo-color': isDark ? '#1a1a1a' : '#ffffff', + 'text-color': 'rgba(0,0,0,0.7)', + 'text-halo-color': 'rgba(255,255,255,0.9)', 'text-halo-width': 1.5, }, }) @@ -2026,18 +2047,22 @@ const MapView = forwardRef(function MapView(_, ref) { const protocol = new Protocol() maplibregl.addProtocol('pmtiles', protocol.tile) - // Initialize DemSource for maplibre-contour - if (!demSourceInstance) { - demSourceInstance = new mlcontour.DemSource({ - url: `${window.location.origin}/tiles/terrain/{z}/{x}/{y}`, - encoding: 'terrarium', - maxzoom: 14, - worker: true, - }) - demSourceInstance.setupMaplibre(maplibregl) - } - const config = getConfig() + + // Initialize DemSource for maplibre-contour (uses same PMTiles as hillshade) + if (!demSourceInstance) { + const hs = config?.tileset_hillshade + if (hs?.url) { + demSourceInstance = new mlcontour.DemSource({ + url: `pmtiles://${window.location.origin}${hs.url}`, + encoding: hs.encoding || 'terrarium', + maxzoom: hs.max_zoom || 12, + worker: true, + cacheSize: 100, + }) + demSourceInstance.setupMaplibre(maplibregl) + } + } const DEFAULT_CENTER = config?.defaults?.center ? [config.defaults.center[1], config.defaults.center[0]] // config is [lat,lon], MapLibre wants [lon,lat] : [-114.6066, 42.5736]