feat(map): Add state/province boundary lines at z4-z7

Fix D: State and province administrative boundaries are now visible
at low zoom levels (z4-z7) with theme-aware styling.

- Added STATE_BOUNDARIES_LAYER constant
- Added addStateBoundaries() function that creates a line layer
  filtering on kind_detail = 4 (state/province level)
- Uses dashed line style with opacity interpolation
- Layer uses theme boundaries color for consistency
- Layer is re-added on theme change to update colors

The layer renders below labels and provides subtle but visible
state/province boundaries when zoomed out viewing country-level
maps. Line width and opacity increase as you zoom in from z4 to z7.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-05-02 19:06:33 +00:00
commit c14edb0e53

View file

@ -23,6 +23,7 @@ function isCurrentThemeDark() {
const ROUTE_SOURCE = 'route-source' const ROUTE_SOURCE = 'route-source'
const BOUNDARY_SOURCE = 'boundary-source' const BOUNDARY_SOURCE = 'boundary-source'
const BOUNDARY_LAYER = 'boundary-layer' const BOUNDARY_LAYER = 'boundary-layer'
const STATE_BOUNDARIES_LAYER = 'state-boundaries-z4-z7'
const ROUTE_LAYER_PREFIX = 'route-layer-' const ROUTE_LAYER_PREFIX = 'route-layer-'
const HILLSHADE_SOURCE = 'hillshade-dem' const HILLSHADE_SOURCE = 'hillshade-dem'
const HILLSHADE_LAYER = 'hillshade-layer' const HILLSHADE_LAYER = 'hillshade-layer'
@ -1481,6 +1482,63 @@ function addBoundaryLayer(map) {
}, firstSymbolId) }, firstSymbolId)
} }
/**
* FIX D: Add state/province boundary lines visible at z4-z7
* These are administrative boundaries with kind_detail = 4 (state/province level)
* Uses theme-aware styling from the boundaries color
*/
function addStateBoundaries(map, themeId) {
if (!map || map.getLayer(STATE_BOUNDARIES_LAYER)) return
// Get the boundaries color from the current theme
const theme = getTheme(themeId)
const boundaryColor = theme?.colors?.boundaries || '#808080'
// Find first symbol layer to insert below labels
const layers = map.getStyle().layers
let firstSymbolId = null
for (const layer of layers) {
if (layer.type === 'symbol') {
firstSymbolId = layer.id
break
}
}
// Add state/province boundaries layer for z4-z7
// kind_detail 4 = state/province level administrative boundaries
map.addLayer({
id: STATE_BOUNDARIES_LAYER,
type: 'line',
source: 'protomaps',
'source-layer': 'boundaries',
filter: ['==', 'kind_detail', 4],
minzoom: 4,
maxzoom: 8,
paint: {
'line-color': boundaryColor,
'line-width': [
'interpolate', ['linear'], ['zoom'],
4, 0.5,
7, 1.0
],
'line-opacity': [
'interpolate', ['linear'], ['zoom'],
4, 0.4,
7, 0.6
],
'line-dasharray': [4, 2],
},
}, firstSymbolId)
}
/** Remove state boundaries layer */
function removeStateBoundaries(map) {
if (!map) return
if (map.getLayer(STATE_BOUNDARIES_LAYER)) {
map.removeLayer(STATE_BOUNDARIES_LAYER)
}
}
const MapView = forwardRef(function MapView(_, ref) { const MapView = forwardRef(function MapView(_, ref) {
const mapRef = useRef(null) const mapRef = useRef(null)
const mapInstance = useRef(null) const mapInstance = useRef(null)
@ -2383,6 +2441,9 @@ const MapView = forwardRef(function MapView(_, ref) {
// Apply improved base label styling for readability // Apply improved base label styling for readability
applyBaseLabelStyling(map) applyBaseLabelStyling(map)
// FIX D: Add state/province boundary lines at z4-z7
addStateBoundaries(map, currentThemeRef.current)
// Restore overlay layers from localStorage prefs // Restore overlay layers from localStorage prefs
try { try {
const raw = localStorage.getItem('navi-layer-prefs') const raw = localStorage.getItem('navi-layer-prefs')
@ -2599,6 +2660,9 @@ const MapView = forwardRef(function MapView(_, ref) {
// Apply improved base label styling for readability // Apply improved base label styling for readability
applyBaseLabelStyling(map) applyBaseLabelStyling(map)
// FIX D: Re-add state boundaries with new theme colors
addStateBoundaries(map, currentThemeRef.current)
// Re-add active overlay layers // Re-add active overlay layers
if (activeLayersRef.current.hillshade) addHillshade(map, currentThemeRef.current) if (activeLayersRef.current.hillshade) addHillshade(map, currentThemeRef.current)
if (activeLayersRef.current.traffic) addTraffic(map, currentThemeRef.current) if (activeLayersRef.current.traffic) addTraffic(map, currentThemeRef.current)