feat: stream municipality vectors by viewport
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-14 10:47:23 +02:00
parent 4933284890
commit 1ba479b50b
19 changed files with 465 additions and 38 deletions
+33 -5
View File
@@ -3,6 +3,7 @@ import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import { PRIMARY_FOCUS_CENTER } from '../config/primaryFocus'
import { featureCollectionBounds } from '../lib/geojsonBounds'
import type { MapViewportState } from '../types'
interface GeoMapProps {
data: GeoJSON.FeatureCollection | null
@@ -16,8 +17,10 @@ interface GeoMapProps {
opacity?: number
areaVisible?: boolean
areaOpacity?: number
fitDataOnChange?: boolean
onFeatureSelect?: (feature: GeoJSON.Feature | null) => void
onMapCoordinateSelect?: (coordinate: [number, number]) => void
onViewportChange?: (viewport: MapViewportState) => void
}
const EMPTY_FEATURE_COLLECTION: GeoJSON.FeatureCollection = {
@@ -108,14 +111,18 @@ function GeoMap({
opacity = 0.4,
areaVisible = true,
areaOpacity = 0.18,
fitDataOnChange = true,
onFeatureSelect,
onMapCoordinateSelect,
onViewportChange,
}: GeoMapProps): JSX.Element {
const containerRef = useRef<HTMLDivElement | null>(null)
const mapRef = useRef<maplibregl.Map | null>(null)
const onFeatureSelectRef = useRef<GeoMapProps['onFeatureSelect']>(onFeatureSelect)
const onMapCoordinateSelectRef = useRef<GeoMapProps['onMapCoordinateSelect']>(onMapCoordinateSelect)
const onViewportChangeRef = useRef<GeoMapProps['onViewportChange']>(onViewportChange)
const bboxSelectionModeRef = useRef(bboxSelectionMode)
const lastFittedAreaRef = useRef<GeoJSON.FeatureCollection | null>(null)
const [mapStyleReady, setMapStyleReady] = useState(false)
useEffect(() => {
@@ -126,6 +133,10 @@ function GeoMap({
onMapCoordinateSelectRef.current = onMapCoordinateSelect
}, [onMapCoordinateSelect])
useEffect(() => {
onViewportChangeRef.current = onViewportChange
}, [onViewportChange])
useEffect(() => {
bboxSelectionModeRef.current = bboxSelectionMode
if (mapRef.current) {
@@ -152,9 +163,24 @@ function GeoMap({
}),
'bottom-right',
)
const emitViewport = () => {
const bounds = map.getBounds()
onViewportChangeRef.current?.({
bbox: {
min_x: bounds.getWest(),
min_y: bounds.getSouth(),
max_x: bounds.getEast(),
max_y: bounds.getNorth(),
crs: 'EPSG:4326',
},
zoom: map.getZoom(),
})
}
map.on('load', () => {
setMapStyleReady(true)
emitViewport()
})
map.on('moveend', emitViewport)
map.on('click', (event) => {
if (bboxSelectionModeRef.current) {
onMapCoordinateSelectRef.current?.([event.lngLat.lng, event.lngLat.lat])
@@ -270,7 +296,7 @@ function GeoMap({
})
}
if (data) {
if (data && fitDataOnChange) {
const collection = data
if (collection.type === 'FeatureCollection' && collection.features.length > 0) {
const bounds = collectCoordinates(collection)
@@ -279,7 +305,7 @@ function GeoMap({
}
}
}
}, [data, mapStyleReady])
}, [data, fitDataOnChange, mapStyleReady])
useEffect(() => {
const map = mapRef.current
@@ -329,14 +355,16 @@ function GeoMap({
)
}
const activeCollection = mergeFeatureCollections([areaData, data])
if (activeCollection) {
const activeCollection = fitDataOnChange ? mergeFeatureCollections([areaData, data]) : areaData
const shouldFitArea = fitDataOnChange || lastFittedAreaRef.current !== areaData
if (activeCollection && shouldFitArea) {
const bounds = collectCoordinates(activeCollection)
if (bounds) {
map.fitBounds(bounds, { padding: 40 })
}
}
}, [areaData, data, mapStyleReady])
lastFittedAreaRef.current = areaData
}, [areaData, data, fitDataOnChange, mapStyleReady])
useEffect(() => {
const map = mapRef.current