feat: map the complete municipality of Mol
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 05:35:15 +02:00
parent f757a527cf
commit 490325e0cd
20 changed files with 1027 additions and 62 deletions
+38 -46
View File
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'
import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import { PRIMARY_FOCUS_CENTER } from '../config/primaryFocus'
import { featureCollectionBounds } from '../lib/geojsonBounds'
interface GeoMapProps {
data: GeoJSON.FeatureCollection | null
@@ -49,36 +50,13 @@ function defaultMapStyle(): string | maplibregl.StyleSpecification {
}
function collectCoordinates(featureCollection: GeoJSON.FeatureCollection): maplibregl.LngLatBoundsLike | null {
const coordinates: [number, number][] = []
const walk = (coords: unknown) => {
if (!Array.isArray(coords)) {
return
}
if (coords.length === 2 && typeof coords[0] === 'number' && typeof coords[1] === 'number') {
coordinates.push([coords[0], coords[1]])
return
}
for (const item of coords) {
walk(item)
}
}
for (const feature of featureCollection.features) {
const geometry = feature.geometry as any
if (geometry && geometry.coordinates) {
walk(geometry.coordinates)
}
}
if (coordinates.length === 0) {
const bounds = featureCollectionBounds(featureCollection)
if (!bounds) {
return null
}
const xs = coordinates.map((point) => point[0])
const ys = coordinates.map((point) => point[1])
return [
[Math.min(...xs), Math.min(...ys)],
[Math.max(...xs), Math.max(...ys)],
[bounds.minX, bounds.minY],
[bounds.maxX, bounds.maxY],
]
}
@@ -244,15 +222,22 @@ function GeoMap({
source: 'dataset',
paint: {
'fill-color': [
'match',
['get', 'change_type'],
'added',
'#16a34a',
'removed',
'#dc2626',
'unchanged',
'#2563eb',
'#f97316',
'case',
['==', ['get', 'layer_type'], 'municipality_boundary'],
'#0f766e',
['==', ['get', 'layer_type'], 'building'],
'#0891b2',
[
'match',
['get', 'change_type'],
'added',
'#16a34a',
'removed',
'#dc2626',
'unchanged',
'#2563eb',
'#f97316',
],
],
'fill-opacity': 0.4,
},
@@ -263,17 +248,24 @@ function GeoMap({
source: 'dataset',
paint: {
'line-color': [
'match',
['get', 'change_type'],
'added',
'#15803d',
'removed',
'#b91c1c',
'unchanged',
'#1d4ed8',
'#ea580c',
'case',
['==', ['get', 'layer_type'], 'municipality_boundary'],
'#0f5f59',
['==', ['get', 'layer_type'], 'building'],
'#0e7490',
[
'match',
['get', 'change_type'],
'added',
'#15803d',
'removed',
'#b91c1c',
'unchanged',
'#1d4ed8',
'#ea580c',
],
],
'line-width': 2,
'line-width': ['interpolate', ['linear'], ['zoom'], 8, 0.25, 12, 0.8, 16, 2],
},
})
}
+11 -12
View File
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import GeoMap from '../GeoMap'
import type { AreaRead, DatasetCreateResponse, QaComparisonResult, VectorSelectionBBox, VectorSelectionResponse } from '../../types'
import { featureCollectionBounds } from '../../lib/geojsonBounds'
const DEFAULT_SELECTED_FEATURE_FILENAME = 'selected-feature.geojson'
const DEFAULT_AREA_SELECTION_FILENAME = 'area-selection.geojson'
@@ -55,17 +56,15 @@ function getFeatureGeometrySummary(feature: GeoJSON.Feature | null) {
}
function getFeatureCollectionBBox(collection: GeoJSON.FeatureCollection | null): VectorSelectionBBox | null {
const points = collection?.features.flatMap((feature) => collectGeometryPoints(feature.geometry)) ?? []
if (points.length === 0) {
const bounds = featureCollectionBounds(collection)
if (!bounds) {
return null
}
const xs = points.map((point) => point[0])
const ys = points.map((point) => point[1])
return {
min_x: Math.min(...xs),
min_y: Math.min(...ys),
max_x: Math.max(...xs),
max_y: Math.max(...ys),
min_x: bounds.minX,
min_y: bounds.minY,
max_x: bounds.maxX,
max_y: bounds.maxY,
crs: 'EPSG:4326',
}
}
@@ -303,9 +302,9 @@ export function MapWorkspace({
const featureExtractionEntries = featureProperties ? Object.entries(featureProperties).slice(0, 48) : []
const featureGeometrySummary = getFeatureGeometrySummary(selectedMapFeature)
const selectedFeatureGeoJson = selectedMapFeature ? selectedFeatureCollection(selectedMapFeature) : null
const selectedFeatureBbox = getFeatureBBox(selectedMapFeature)
const activeLayerBbox = getFeatureCollectionBBox(mapFeatureCollection)
const selectedAreaBbox = getFeatureCollectionBBox(areaFeatureCollection)
const selectedFeatureBbox = useMemo(() => getFeatureBBox(selectedMapFeature), [selectedMapFeature])
const activeLayerBbox = useMemo(() => getFeatureCollectionBBox(mapFeatureCollection), [mapFeatureCollection])
const selectedAreaBbox = useMemo(() => getFeatureCollectionBBox(areaFeatureCollection), [areaFeatureCollection])
const currentSelectionBbox = parseBboxInput(bboxInput)
const areaSelectionFeatures = mapSelectionResult?.geojson.features ?? []
const areaSelectionPreviewFeatures = areaSelectionFeatures.slice(0, 12)