mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-05-20 22:54:42 +02:00
fix(map): boundary fill opacity and highlight text duplication
Two fixes for rendering at close zoom (z14+):
1. Boundary rendering:
- Add subtle fill layer with 0.05 opacity (barely visible tint)
- Insert fill and outline layers BELOW symbol layers using
firstSymbolId so labels render on top
- Dashed outline remains primary indicator
2. Highlight text duplication:
- Remove separate hover-hl-* and selected-hl-* symbol layers
that were creating duplicate/ghost text
- Instead, modify ORIGINAL layer paint properties directly using
setPaintProperty on text-color, text-halo-color, text-halo-width
- Store original paint values for restoration on clear
- Single symbol layer per label = no duplication
Test at z14+ on small places like Rock Creek Park - no text ghosting.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
68f0a8dff7
commit
39996fdafe
1 changed files with 93 additions and 35 deletions
|
|
@ -43,44 +43,78 @@ const MEASURE_SOURCE = 'measure-source'
|
|||
const MEASURE_LINE_LAYER = 'measure-line-layer'
|
||||
const MEASURE_POINT_LAYER = 'measure-point-layer'
|
||||
|
||||
// Highlight layers (filter-based for PMTiles compatibility)
|
||||
const HIGHLIGHT_SOURCE_LAYERS = ['places', 'pois']
|
||||
const EMPTY_FILTER = ['==', ['get', 'name'], '___NOMATCH___']
|
||||
// Highlight state - modify original layer paint properties (no duplicate layers)
|
||||
const INTERACTIVE_LABEL_LAYERS = ['pois', 'places_subplace', 'places_locality', 'places_region', 'places_country']
|
||||
let originalPaintValues = {} // Store original paint values for restoration
|
||||
let currentHighlightedLayer = null
|
||||
let currentHoveredLayer = null
|
||||
|
||||
function setupHighlightLayers(map, isDark) {
|
||||
const accentColor = getComputedStyle(document.documentElement).getPropertyValue('--accent').trim() || '#7a9a6b'
|
||||
HIGHLIGHT_SOURCE_LAYERS.forEach(sl => {
|
||||
if (map.getLayer('hover-hl-' + sl)) map.removeLayer('hover-hl-' + sl)
|
||||
if (map.getLayer('selected-hl-' + sl)) map.removeLayer('selected-hl-' + sl)
|
||||
})
|
||||
HIGHLIGHT_SOURCE_LAYERS.forEach(sourceLayer => {
|
||||
map.addLayer({
|
||||
id: 'hover-hl-' + sourceLayer, type: 'symbol', source: 'protomaps', 'source-layer': sourceLayer,
|
||||
filter: EMPTY_FILTER,
|
||||
layout: { 'text-field': ['coalesce', ['get', 'name:en'], ['get', 'name']], 'text-font': ['Noto Sans Regular'], 'text-size': ['interpolate', ['linear'], ['zoom'], 4, 10, 10, 14, 16, 18], 'text-allow-overlap': true, 'text-ignore-placement': true },
|
||||
paint: { 'text-color': isDark ? '#ffffff' : '#000000', 'text-halo-color': isDark ? 'rgba(255,255,255,0.3)' : 'rgba(0,0,0,0.2)', 'text-halo-width': 2.5 },
|
||||
})
|
||||
map.addLayer({
|
||||
id: 'selected-hl-' + sourceLayer, type: 'symbol', source: 'protomaps', 'source-layer': sourceLayer,
|
||||
filter: EMPTY_FILTER,
|
||||
layout: { 'text-field': ['coalesce', ['get', 'name:en'], ['get', 'name']], 'text-font': ['Noto Sans Regular'], 'text-size': ['interpolate', ['linear'], ['zoom'], 4, 10, 10, 14, 16, 18], 'text-allow-overlap': true, 'text-ignore-placement': true },
|
||||
paint: { 'text-color': accentColor, 'text-halo-color': isDark ? 'rgba(122,154,107,0.5)' : 'rgba(122,154,107,0.3)', 'text-halo-width': 3 },
|
||||
})
|
||||
})
|
||||
function storeOriginalPaint(map, layerId) {
|
||||
if (originalPaintValues[layerId]) return // Already stored
|
||||
if (!map.getLayer(layerId)) return
|
||||
originalPaintValues[layerId] = {
|
||||
'text-color': map.getPaintProperty(layerId, 'text-color'),
|
||||
'text-halo-color': map.getPaintProperty(layerId, 'text-halo-color'),
|
||||
'text-halo-width': map.getPaintProperty(layerId, 'text-halo-width'),
|
||||
}
|
||||
}
|
||||
|
||||
function restoreOriginalPaint(map, layerId) {
|
||||
if (!originalPaintValues[layerId] || !map.getLayer(layerId)) return
|
||||
const orig = originalPaintValues[layerId]
|
||||
if (orig['text-color'] !== undefined) map.setPaintProperty(layerId, 'text-color', orig['text-color'])
|
||||
if (orig['text-halo-color'] !== undefined) map.setPaintProperty(layerId, 'text-halo-color', orig['text-halo-color'])
|
||||
if (orig['text-halo-width'] !== undefined) map.setPaintProperty(layerId, 'text-halo-width', orig['text-halo-width'])
|
||||
}
|
||||
|
||||
function setHoverHighlight(map, feature) {
|
||||
HIGHLIGHT_SOURCE_LAYERS.forEach(sl => { if (map.getLayer('hover-hl-' + sl)) map.setFilter('hover-hl-' + sl, EMPTY_FILTER) })
|
||||
// Restore previous hovered layer
|
||||
if (currentHoveredLayer && currentHoveredLayer !== currentHighlightedLayer) {
|
||||
restoreOriginalPaint(map, currentHoveredLayer)
|
||||
}
|
||||
currentHoveredLayer = null
|
||||
|
||||
if (!feature) return
|
||||
const name = feature.properties?.name, sourceLayer = feature.sourceLayer
|
||||
if (name && sourceLayer && map.getLayer('hover-hl-' + sourceLayer)) map.setFilter('hover-hl-' + sourceLayer, ['==', ['get', 'name'], name])
|
||||
const layerId = feature.layer?.id
|
||||
if (!layerId || !map.getLayer(layerId)) return
|
||||
if (layerId === currentHighlightedLayer) return // Don't hover over selected
|
||||
|
||||
storeOriginalPaint(map, layerId)
|
||||
currentHoveredLayer = layerId
|
||||
|
||||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
map.setPaintProperty(layerId, 'text-color', isDark ? '#ffffff' : '#000000')
|
||||
map.setPaintProperty(layerId, 'text-halo-color', isDark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.25)')
|
||||
map.setPaintProperty(layerId, 'text-halo-width', 2.5)
|
||||
}
|
||||
|
||||
function setSelectedHighlight(map, feature) {
|
||||
HIGHLIGHT_SOURCE_LAYERS.forEach(sl => { if (map.getLayer('selected-hl-' + sl)) map.setFilter('selected-hl-' + sl, EMPTY_FILTER) })
|
||||
// Restore previous highlighted layer
|
||||
if (currentHighlightedLayer) {
|
||||
restoreOriginalPaint(map, currentHighlightedLayer)
|
||||
}
|
||||
currentHighlightedLayer = null
|
||||
currentHoveredLayer = null // Also clear hover
|
||||
|
||||
if (!feature) return
|
||||
const name = feature.properties?.name, sourceLayer = feature.sourceLayer
|
||||
if (name && sourceLayer && map.getLayer('selected-hl-' + sourceLayer)) map.setFilter('selected-hl-' + sourceLayer, ['==', ['get', 'name'], name])
|
||||
const layerId = feature.layer?.id
|
||||
if (!layerId || !map.getLayer(layerId)) return
|
||||
|
||||
storeOriginalPaint(map, layerId)
|
||||
currentHighlightedLayer = layerId
|
||||
|
||||
const accentColor = getComputedStyle(document.documentElement).getPropertyValue('--accent').trim() || '#7a9a6b'
|
||||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
map.setPaintProperty(layerId, 'text-color', accentColor)
|
||||
map.setPaintProperty(layerId, 'text-halo-color', isDark ? 'rgba(122,154,107,0.6)' : 'rgba(122,154,107,0.4)')
|
||||
map.setPaintProperty(layerId, 'text-halo-width', 3)
|
||||
}
|
||||
|
||||
function clearAllHighlights(map) {
|
||||
if (currentHoveredLayer) restoreOriginalPaint(map, currentHoveredLayer)
|
||||
if (currentHighlightedLayer) restoreOriginalPaint(map, currentHighlightedLayer)
|
||||
currentHoveredLayer = null
|
||||
currentHighlightedLayer = null
|
||||
}
|
||||
|
||||
/** Build a full MapLibre style object for the given theme */
|
||||
|
|
@ -656,7 +690,9 @@ function removeContoursTest10ft(map) {
|
|||
if (map.getLayer(CONTOUR_TEST_10FT_MINOR)) map.removeLayer(CONTOUR_TEST_10FT_MINOR)
|
||||
if (map.getSource(CONTOUR_TEST_10FT_SOURCE)) map.removeSource(CONTOUR_TEST_10FT_SOURCE)
|
||||
}
|
||||
/** Add boundary polygon layer with computed accent color (MapLibre rejects CSS vars in paint) */
|
||||
/** Add boundary polygon layers with computed accent color (MapLibre rejects CSS vars in paint) */
|
||||
const BOUNDARY_FILL_LAYER = 'boundary-fill-layer'
|
||||
|
||||
function addBoundaryLayer(map) {
|
||||
if (!map || map.getLayer(BOUNDARY_LAYER)) return
|
||||
if (!map.getSource(BOUNDARY_SOURCE)) {
|
||||
|
|
@ -666,6 +702,29 @@ function addBoundaryLayer(map) {
|
|||
})
|
||||
}
|
||||
const accentColor = getComputedStyle(document.documentElement).getPropertyValue("--accent").trim() || "#7a9a6b"
|
||||
|
||||
// Find first symbol layer to insert boundary layers below labels
|
||||
const layers = map.getStyle().layers
|
||||
let firstSymbolId = null
|
||||
for (const layer of layers) {
|
||||
if (layer.type === 'symbol') {
|
||||
firstSymbolId = layer.id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Add subtle fill layer (barely visible tint)
|
||||
map.addLayer({
|
||||
id: BOUNDARY_FILL_LAYER,
|
||||
type: "fill",
|
||||
source: BOUNDARY_SOURCE,
|
||||
paint: {
|
||||
"fill-color": accentColor,
|
||||
"fill-opacity": 0.05,
|
||||
},
|
||||
}, firstSymbolId)
|
||||
|
||||
// Add dashed outline layer
|
||||
map.addLayer({
|
||||
id: BOUNDARY_LAYER,
|
||||
type: "line",
|
||||
|
|
@ -676,7 +735,7 @@ function addBoundaryLayer(map) {
|
|||
"line-opacity": 0.7,
|
||||
"line-dasharray": [3, 2],
|
||||
},
|
||||
})
|
||||
}, firstSymbolId)
|
||||
}
|
||||
|
||||
const MapView = forwardRef(function MapView(_, ref) {
|
||||
|
|
@ -1434,8 +1493,6 @@ const MapView = forwardRef(function MapView(_, ref) {
|
|||
}
|
||||
} catch {}
|
||||
|
||||
// Set up highlight layers
|
||||
setupHighlightLayers(map, document.documentElement.getAttribute('data-theme') === 'dark')
|
||||
|
||||
// Register updateBoundary function - called directly when boundary data arrives
|
||||
const updateBoundaryFn = (boundaryGeometry) => {
|
||||
|
|
@ -1620,8 +1677,9 @@ const MapView = forwardRef(function MapView(_, ref) {
|
|||
if (activeLayersRef.current.publicLands) addPublicLands(map)
|
||||
if (activeLayersRef.current.contours) addContours(map)
|
||||
|
||||
// Re-setup highlight layers
|
||||
setupHighlightLayers(map, theme === 'dark')
|
||||
// Clear highlights on theme change (paint values will be re-stored on next interaction)
|
||||
clearAllHighlights(map)
|
||||
originalPaintValues = {}
|
||||
|
||||
// Restore view
|
||||
map.jumpTo({ center, zoom, bearing, pitch })
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue