Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions providers/maplibre/src/defaults.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const defaults = {
animationDuration: 400 // Must be less than core debounce time (500ms)
export const DEFAULTS = {
animationDuration: 400, // Must be less than core debounce time (500ms)
coordinatePrecision: 7
}

export const supportedShortcuts = [
Expand Down
16 changes: 8 additions & 8 deletions providers/maplibre/src/maplibreProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @typedef {import('../../../src/types.js').MapProviderConfig} MapProviderConfig
*/

import { defaults, supportedShortcuts } from './defaults.js'
import { DEFAULTS, supportedShortcuts } from './defaults.js'
import { cleanCanvas, applyPreventDefaultFix } from './utils/maplibreFixes.js'
import { attachMapEvents } from './mapEvents.js'
import { attachAppEvents } from './appEvents.js'
Expand Down Expand Up @@ -129,7 +129,7 @@ export default class MapLibreProvider {
this.map.flyTo({
center: center || this.getCenter(),
zoom: zoom || this.getZoom(),
duration: defaults.animationDuration
duration: DEFAULTS.animationDuration
})
}

Expand All @@ -141,7 +141,7 @@ export default class MapLibreProvider {
zoomIn (zoomDelta) {
this.map.easeTo({
zoom: this.getZoom() + zoomDelta,
duration: defaults.animationDuration
duration: DEFAULTS.animationDuration
})
}

Expand All @@ -153,7 +153,7 @@ export default class MapLibreProvider {
zoomOut (zoomDelta) {
this.map.easeTo({
zoom: this.getZoom() - zoomDelta,
duration: defaults.animationDuration
duration: DEFAULTS.animationDuration
})
}

Expand All @@ -163,7 +163,7 @@ export default class MapLibreProvider {
* @param {[number, number]} offset - Pixel offset [x, y].
*/
panBy (offset) {
this.map.panBy(offset, { duration: defaults.animationDuration })
this.map.panBy(offset, { duration: DEFAULTS.animationDuration })
}

/**
Expand All @@ -172,7 +172,7 @@ export default class MapLibreProvider {
* @param {[number, number, number, number]} bounds - Bounds as [west, south, east, north].
*/
fitToBounds (bounds) {
this.map.fitBounds(bounds, { duration: defaults.animationDuration })
this.map.fitBounds(bounds, { duration: DEFAULTS.animationDuration })
}

/**
Expand Down Expand Up @@ -241,7 +241,7 @@ export default class MapLibreProvider {
*/
getCenter () {
const coord = this.map.getCenter()
return [Number(coord.lng.toFixed(7)), Number(coord.lat.toFixed(7))]
return [Number(coord.lng.toFixed(DEFAULTS.coordinatePrecision)), Number(coord.lat.toFixed(DEFAULTS.coordinatePrecision))]
}

/**
Expand All @@ -250,7 +250,7 @@ export default class MapLibreProvider {
* @returns {number}
*/
getZoom () {
return Number(this.map.getZoom().toFixed(7))
return Number(this.map.getZoom().toFixed(DEFAULTS.coordinatePrecision))
}

/**
Expand Down
122 changes: 58 additions & 64 deletions providers/maplibre/src/utils/highlightFeatures.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
/**
* Update highlighted features using pure filters.
* Supports fill + line geometry, multi-source, cleanup, and bounds.
*/
function updateHighlightedFeatures({ LngLatBounds, map, selectedFeatures, stylesMap }) {
if (!map) {
return null
}

const groupFeaturesBySource = (map, selectedFeatures) => {
const featuresBySource = {}
const renderedFeatures = []

// Group features by source
selectedFeatures?.forEach(({ featureId, layerId, idProperty, geometry }) => {
const layer = map.getLayer(layerId)

Expand All @@ -37,10 +27,10 @@ function updateHighlightedFeatures({ LngLatBounds, map, selectedFeatures, styles
featuresBySource[sourceId].ids.add(featureId)
})

const currentSources = new Set(Object.keys(featuresBySource))
const previousSources = map._highlightedSources || new Set()
return featuresBySource
}

// Cleanup for sources no longer selected
const cleanupStaleSources = (map, previousSources, currentSources) => {
previousSources.forEach(src => {
if (!currentSources.has(src)) {
const base = `highlight-${src}`
Expand All @@ -52,7 +42,55 @@ function updateHighlightedFeatures({ LngLatBounds, map, selectedFeatures, styles
})
}
})
}

const applyHighlightLayer = (map, id, type, sourceId, srcLayer, paint, filter) => {
if (!map.getLayer(id)) {
map.addLayer({
id,
type,
source: sourceId,
...(srcLayer && { 'source-layer': srcLayer }),
paint
})
}
Object.entries(paint).forEach(([prop, value]) => {
map.setPaintProperty(id, prop, value)
})
map.setFilter(id, filter)
}

const calculateBounds = (LngLatBounds, renderedFeatures) => {
if (!renderedFeatures.length) {
return null
}

const bounds = new LngLatBounds()

renderedFeatures.forEach(f => {
const add = (c) => typeof c[0] === 'number' ? bounds.extend(c) : c.forEach(add)
add(f.geometry.coordinates)
})

return [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()]
}

/**
* Update highlighted features using pure filters.
* Supports fill + line geometry, multi-source, cleanup, and bounds.
*/
export function updateHighlightedFeatures({ LngLatBounds, map, selectedFeatures, stylesMap }) {
if (!map) {
return null
}

const featuresBySource = groupFeaturesBySource(map, selectedFeatures)
const renderedFeatures = []

const currentSources = new Set(Object.keys(featuresBySource))
const previousSources = map._highlightedSources || new Set()

cleanupStaleSources(map, previousSources, currentSources)
map._highlightedSources = currentSources

// Apply highlights for current sources
Expand All @@ -70,50 +108,19 @@ function updateHighlightedFeatures({ LngLatBounds, map, selectedFeatures, styles
const idExpression = idProperty ? ['get', idProperty] : ['id']
const filter = ['in', idExpression, ['literal', [...ids]]]

// Ensure layers
const linePaint = { 'line-color': stroke, 'line-width': strokeWidth }

if (geom === 'fill') {
if (!map.getLayer(`${base}-fill`)) {
map.addLayer({
id: `${base}-fill`,
type: 'fill',
source: sourceId,
...(srcLayer && { 'source-layer': srcLayer }),
paint: { 'fill-color': fill }
})
}
map.setPaintProperty(`${base}-fill`, 'fill-color', fill)
map.setFilter(`${base}-fill`, filter)
if (!map.getLayer(`${base}-line`)) {
map.addLayer({
id: `${base}-line`,
type: 'line',
source: sourceId,
...(srcLayer && { 'source-layer': srcLayer }),
paint: { 'line-color': stroke, 'line-width': strokeWidth }
})
}
map.setPaintProperty(`${base}-line`, 'line-color', stroke)
map.setPaintProperty(`${base}-line`, 'line-width', strokeWidth)
map.setFilter(`${base}-line`, filter)
applyHighlightLayer(map, `${base}-fill`, 'fill', sourceId, srcLayer, { 'fill-color': fill }, filter)
applyHighlightLayer(map, `${base}-line`, 'line', sourceId, srcLayer, linePaint, filter)
}

if (geom === 'line') {
// Clear any fill highlight from a previous polygon selection on the same source
if (map.getLayer(`${base}-fill`)) {
map.setFilter(`${base}-fill`, ['==', 'id', ''])
}
if (!map.getLayer(`${base}-line`)) {
map.addLayer({
id: `${base}-line`,
type: 'line',
source: sourceId,
...(srcLayer && { 'source-layer': srcLayer }),
paint: { 'line-color': stroke, 'line-width': strokeWidth }
})
}
map.setPaintProperty(`${base}-line`, 'line-color', stroke)
map.setPaintProperty(`${base}-line`, 'line-width', strokeWidth)
map.setFilter(`${base}-line`, filter)
applyHighlightLayer(map, `${base}-line`, 'line', sourceId, srcLayer, linePaint, filter)
}

// Bounds only from rendered tiles
Expand All @@ -124,18 +131,5 @@ function updateHighlightedFeatures({ LngLatBounds, map, selectedFeatures, styles
)
})

// Calculate bounds
if (!renderedFeatures.length) {
return null
}

let bounds = new LngLatBounds()
renderedFeatures.forEach(f => {
const add = (c) => typeof c[0] === 'number' ? bounds.extend(c) : c.forEach(add)
add(f.geometry.coordinates)
})

return [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()]
return calculateBounds(LngLatBounds, renderedFeatures)
}

export { updateHighlightedFeatures }
4 changes: 2 additions & 2 deletions providers/maplibre/src/utils/maplibreFixes.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ export function applyPreventDefaultFix (map) {
const originalPreventDefault = Event.prototype.preventDefault

// Override preventDefault only for events targeting our map
Event.prototype.preventDefault = function () {
Event.prototype.preventDefault = function () { // NOSONAR: intentional monkey-patch to fix MapLibre touch event bug
if ((this.type === 'touchmove' || this.type === 'touchstart') && !this.cancelable) {
// Check if the event target is within our map container
const canvas = map.getCanvas()
if (canvas && (this.target === canvas || canvas.contains(this.target))) {
return
}
}
return originalPreventDefault.call(this)
originalPreventDefault.call(this)
}
}
6 changes: 4 additions & 2 deletions src/App/hooks/useKeyboardShortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function useKeyboardShortcuts (containerRef) {
useEffect(() => {
const el = containerRef.current
if (!el || interfaceType !== 'keyboard') {
return
return undefined
}

const actions = createKeyboardActions(mapProvider, announce, {
Expand All @@ -31,7 +31,7 @@ export function useKeyboardShortcuts (containerRef) {

// Use e.code for letters to avoid 'dead' keys with Alt/AltGr
if (/^Key[A-Z]$/.test(e.code)) {
key = e.code.slice(3) // "KeyI" -> "I"
key = e.code.slice(3) // NOSONAR: strip "Key" prefix, e.g. "KeyI" -> "I"
} else {
key = e.key // works for arrows, numpad, punctuation
}
Expand All @@ -41,6 +41,8 @@ export function useKeyboardShortcuts (containerRef) {
key = '+'
} else if (key === 'Subtract' || key === 'NumpadSubtract') {
key = '-'
} else {
// No action
}

return e.altKey ? `Alt+${key}` : key
Expand Down
6 changes: 3 additions & 3 deletions src/App/hooks/useMapProviderOverrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export const useMapProviderOverrides = () => {

useEffect(() => {
if (!mapProvider) {
return
return undefined
}

const originalFitToBounds = mapProvider.fitToBounds

mapProvider.fitToBounds = (bounds, skipPaddingCalc = false) => {
if (!bounds) {
return
return undefined
}

// Calculate and set safe zone padding unless explicitly skipped
Expand All @@ -54,7 +54,7 @@ export const useMapProviderOverrides = () => {

mapProvider.setView = ({ center, zoom }) => {
if (!center) {
return
return undefined
}

updatePadding()
Expand Down
Loading