Add map area selection extraction
This commit is contained in:
@@ -6,11 +6,15 @@ interface GeoMapProps {
|
||||
data: GeoJSON.FeatureCollection | null
|
||||
areaData?: GeoJSON.FeatureCollection | null
|
||||
selectedFeature?: GeoJSON.Feature | null
|
||||
selectionData?: GeoJSON.FeatureCollection | null
|
||||
selectionBbox?: { min_x: number; min_y: number; max_x: number; max_y: number } | null
|
||||
bboxSelectionMode?: boolean
|
||||
visible?: boolean
|
||||
opacity?: number
|
||||
areaVisible?: boolean
|
||||
areaOpacity?: number
|
||||
onFeatureSelect?: (feature: GeoJSON.Feature | null) => void
|
||||
onMapCoordinateSelect?: (coordinate: [number, number]) => void
|
||||
}
|
||||
|
||||
const EMPTY_FEATURE_COLLECTION: GeoJSON.FeatureCollection = {
|
||||
@@ -57,25 +61,73 @@ function mergeFeatureCollections(collections: Array<GeoJSON.FeatureCollection |
|
||||
return features.length > 0 ? { type: 'FeatureCollection', features } : null
|
||||
}
|
||||
|
||||
function bboxToFeatureCollection(
|
||||
bbox: { min_x: number; min_y: number; max_x: number; max_y: number } | null | undefined,
|
||||
): GeoJSON.FeatureCollection {
|
||||
if (!bbox) {
|
||||
return EMPTY_FEATURE_COLLECTION
|
||||
}
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[bbox.min_x, bbox.min_y],
|
||||
[bbox.max_x, bbox.min_y],
|
||||
[bbox.max_x, bbox.max_y],
|
||||
[bbox.min_x, bbox.max_y],
|
||||
[bbox.min_x, bbox.min_y],
|
||||
],
|
||||
],
|
||||
},
|
||||
properties: {
|
||||
layer_type: 'selection_bbox',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
function GeoMap({
|
||||
data,
|
||||
areaData = null,
|
||||
selectedFeature = null,
|
||||
selectionData = null,
|
||||
selectionBbox = null,
|
||||
bboxSelectionMode = false,
|
||||
visible = true,
|
||||
opacity = 0.4,
|
||||
areaVisible = true,
|
||||
areaOpacity = 0.18,
|
||||
onFeatureSelect,
|
||||
onMapCoordinateSelect,
|
||||
}: 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 bboxSelectionModeRef = useRef(bboxSelectionMode)
|
||||
const [mapStyleReady, setMapStyleReady] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
onFeatureSelectRef.current = onFeatureSelect
|
||||
}, [onFeatureSelect])
|
||||
|
||||
useEffect(() => {
|
||||
onMapCoordinateSelectRef.current = onMapCoordinateSelect
|
||||
}, [onMapCoordinateSelect])
|
||||
|
||||
useEffect(() => {
|
||||
bboxSelectionModeRef.current = bboxSelectionMode
|
||||
if (mapRef.current) {
|
||||
mapRef.current.getCanvas().style.cursor = bboxSelectionMode ? 'crosshair' : ''
|
||||
}
|
||||
}, [bboxSelectionMode])
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current || mapRef.current) {
|
||||
return
|
||||
@@ -92,6 +144,10 @@ function GeoMap({
|
||||
setMapStyleReady(true)
|
||||
})
|
||||
map.on('click', (event) => {
|
||||
if (bboxSelectionModeRef.current) {
|
||||
onMapCoordinateSelectRef.current?.([event.lngLat.lng, event.lngLat.lat])
|
||||
return
|
||||
}
|
||||
const layers = ['dataset-fill', 'dataset-line', 'area-fill', 'area-line'].filter((layerId) => map.getLayer(layerId))
|
||||
if (layers.length === 0) {
|
||||
onFeatureSelectRef.current?.(null)
|
||||
@@ -327,6 +383,86 @@ function GeoMap({
|
||||
})
|
||||
}, [selectedFeature, mapStyleReady])
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
const bboxCollection = bboxToFeatureCollection(selectionBbox)
|
||||
if (map.getSource('selection-bbox')) {
|
||||
;(map.getSource('selection-bbox') as maplibregl.GeoJSONSource).setData(bboxCollection)
|
||||
} else {
|
||||
map.addSource('selection-bbox', { type: 'geojson', data: bboxCollection })
|
||||
map.addLayer({
|
||||
id: 'selection-bbox-fill',
|
||||
type: 'fill',
|
||||
source: 'selection-bbox',
|
||||
paint: {
|
||||
'fill-color': '#38bdf8',
|
||||
'fill-opacity': 0.12,
|
||||
},
|
||||
})
|
||||
map.addLayer({
|
||||
id: 'selection-bbox-line',
|
||||
type: 'line',
|
||||
source: 'selection-bbox',
|
||||
paint: {
|
||||
'line-color': '#0369a1',
|
||||
'line-width': 2,
|
||||
'line-dasharray': [2, 1],
|
||||
},
|
||||
})
|
||||
}
|
||||
}, [selectionBbox, mapStyleReady])
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
const resultCollection = selectionData ?? EMPTY_FEATURE_COLLECTION
|
||||
if (map.getSource('selection-result')) {
|
||||
;(map.getSource('selection-result') as maplibregl.GeoJSONSource).setData(resultCollection)
|
||||
return
|
||||
}
|
||||
|
||||
map.addSource('selection-result', { type: 'geojson', data: resultCollection })
|
||||
map.addLayer({
|
||||
id: 'selection-result-fill',
|
||||
type: 'fill',
|
||||
source: 'selection-result',
|
||||
filter: ['match', ['geometry-type'], ['Polygon', 'MultiPolygon'], true, false],
|
||||
paint: {
|
||||
'fill-color': '#7c3aed',
|
||||
'fill-opacity': 0.24,
|
||||
},
|
||||
})
|
||||
map.addLayer({
|
||||
id: 'selection-result-line',
|
||||
type: 'line',
|
||||
source: 'selection-result',
|
||||
filter: ['match', ['geometry-type'], ['Polygon', 'MultiPolygon', 'LineString', 'MultiLineString'], true, false],
|
||||
paint: {
|
||||
'line-color': '#5b21b6',
|
||||
'line-width': 3,
|
||||
},
|
||||
})
|
||||
map.addLayer({
|
||||
id: 'selection-result-circle',
|
||||
type: 'circle',
|
||||
source: 'selection-result',
|
||||
filter: ['match', ['geometry-type'], ['Point', 'MultiPoint'], true, false],
|
||||
paint: {
|
||||
'circle-color': '#7c3aed',
|
||||
'circle-radius': 6,
|
||||
'circle-stroke-color': '#ffffff',
|
||||
'circle-stroke-width': 2,
|
||||
},
|
||||
})
|
||||
}, [selectionData, mapStyleReady])
|
||||
|
||||
return <div className="map-container" ref={containerRef} />
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user