feat(map): group GL place markers into clusters on zoom-out (#1385)

MapLibre/Mapbox showed every place as its own rich HTML marker with no
grouping when zoomed out, unlike the Leaflet map. Feed the place points
through a clustered GeoJSON source: clustered points render as a dark
count bubble (click to zoom in and expand) while the rich HTML photo
markers are only drawn for the points the source reports as unclustered.
Always on, matching the Leaflet MarkerClusterGroup.
This commit is contained in:
Maurice
2026-06-30 19:09:16 +02:00
committed by Maurice
parent 4abb38b517
commit a54503f26d
2 changed files with 248 additions and 40 deletions
+39 -1
View File
@@ -26,6 +26,10 @@ const glMap = vi.hoisted(() => ({
getStyle: vi.fn().mockReturnValue({ layers: [] }),
isStyleLoaded: vi.fn().mockReturnValue(true),
getCanvasContainer: vi.fn(() => document.createElement('div')),
getLayer: vi.fn().mockReturnValue(null),
queryRenderedFeatures: vi.fn().mockReturnValue([]),
querySourceFeatures: vi.fn().mockReturnValue([]),
easeTo: vi.fn(),
}))
vi.mock('mapbox-gl', () => ({
@@ -101,7 +105,7 @@ vi.mock('./locationMarkerMapbox', () => ({
vi.mock('./reservationsMapbox', () => ({
ReservationMapboxOverlay: vi.fn(function () {
return { update: vi.fn() }
return { update: vi.fn(), destroy: vi.fn() }
}),
}))
@@ -136,6 +140,13 @@ function buildMapPlace(overrides: Record<string, any> = {}) {
}
beforeEach(() => {
glMap.on.mockImplementation(() => glMap)
glMap.off.mockImplementation(() => glMap)
glMap.once.mockImplementation(() => glMap)
glMap.getSource.mockReturnValue(null)
glMap.getLayer.mockReturnValue(null)
glMap.queryRenderedFeatures.mockReturnValue([])
glMap.querySourceFeatures.mockReturnValue([])
useSettingsStore.setState({
settings: {
...useSettingsStore.getState().settings,
@@ -227,4 +238,31 @@ describe('MapViewGL', () => {
expect(maplibregl.Map).toHaveBeenCalled()
expect(mapboxgl.Map).not.toHaveBeenCalled()
})
it('FE-COMP-MAPVIEWGL-005: adds the clustered place source + layers so markers group on zoom-out (#1385)', async () => {
glMap.on.mockImplementation((event: string, handlerOrLayer: unknown) => {
if (event === 'load' && typeof handlerOrLayer === 'function') (handlerOrLayer as () => void)()
return glMap
})
useSettingsStore.setState({
settings: {
...useSettingsStore.getState().settings,
map_provider: 'maplibre-gl',
mapbox_access_token: '',
maplibre_style: 'https://tiles.openfreemap.org/styles/liberty',
},
} as any)
render(<MapViewGL places={[buildMapPlace({ id: 1, lat: 48.8584, lng: 2.2945 })]} fitKey={1} glProvider="maplibre-gl" />)
await act(async () => {})
expect(glMap.addSource).toHaveBeenCalledWith('trip-place-clusters', expect.objectContaining({
type: 'geojson',
cluster: true,
clusterRadius: 30,
clusterMaxZoom: 10,
}))
expect(glMap.addLayer).toHaveBeenCalledWith(expect.objectContaining({ id: 'trip-place-clusters-circle' }))
expect(glMap.addLayer).toHaveBeenCalledWith(expect.objectContaining({ id: 'trip-place-clusters-count' }))
})
})