feat: stream municipality vectors by viewport
This commit is contained in:
@@ -12,6 +12,7 @@ import type {
|
||||
} from '../types'
|
||||
import { formatError } from '../lib/formatError'
|
||||
import { isPrimaryFocusMunicipalityBoundaryDataset } from '../config/primaryFocus'
|
||||
import { VECTOR_VIEWPORT_FEATURE_THRESHOLD } from '../config/vectorDelivery'
|
||||
|
||||
interface DatasetWorkflowOptions {
|
||||
selectedProjectId: string | null
|
||||
@@ -165,12 +166,13 @@ export function useDatasetWorkflow({
|
||||
setJobs([])
|
||||
try {
|
||||
if (isVectorDatasetType(dataset.dataset_type)) {
|
||||
const [content, summary] = await Promise.all([
|
||||
datasetsApi.getContent(projectId, dataset.id),
|
||||
datasetsApi.vectorSummary(projectId, dataset.id),
|
||||
])
|
||||
setDatasetContent(content)
|
||||
const summary = await datasetsApi.vectorSummary(projectId, dataset.id)
|
||||
setSelectedDatasetSummary(summary)
|
||||
const featureCount = summary.feature_count ?? dataset.feature_count
|
||||
if (featureCount == null || featureCount <= VECTOR_VIEWPORT_FEATURE_THRESHOLD) {
|
||||
const content = await datasetsApi.getContent(projectId, dataset.id)
|
||||
setDatasetContent(content)
|
||||
}
|
||||
} else if (dataset.dataset_type === 'raster') {
|
||||
try {
|
||||
const rasterInspection = await datasetsApi.rasterInspect(projectId, dataset.id)
|
||||
|
||||
@@ -8,6 +8,7 @@ interface MapWorkspaceStateOptions {
|
||||
detectionGeoJson: GeoJSON.FeatureCollection | null
|
||||
datasetContent: GeoJSON.FeatureCollection | null
|
||||
selectedDataset: DatasetCreateResponse | null
|
||||
datasetLayerActive?: boolean
|
||||
}
|
||||
|
||||
export function useMapWorkspaceState({
|
||||
@@ -17,6 +18,7 @@ export function useMapWorkspaceState({
|
||||
detectionGeoJson,
|
||||
datasetContent,
|
||||
selectedDataset,
|
||||
datasetLayerActive = Boolean(datasetContent),
|
||||
}: MapWorkspaceStateOptions) {
|
||||
const [mapLayerVisible, setMapLayerVisible] = useState(true)
|
||||
const [mapLayerOpacity, setMapLayerOpacity] = useState(0.4)
|
||||
@@ -72,11 +74,11 @@ export function useMapWorkspaceState({
|
||||
if (detectionGeoJson) {
|
||||
return 'Detection result'
|
||||
}
|
||||
if (datasetContent && selectedDataset) {
|
||||
if (datasetLayerActive && selectedDataset) {
|
||||
return selectedDataset.name
|
||||
}
|
||||
return 'No active vector layer'
|
||||
}, [changeDetectionGeoJson, datasetContent, detectionGeoJson, segmentationGeoJson, selectedDataset])
|
||||
}, [changeDetectionGeoJson, datasetLayerActive, detectionGeoJson, segmentationGeoJson, selectedDataset])
|
||||
const mapFeatureCount = mapFeatureCollection?.features.length ?? 0
|
||||
const areaFeatureCount = areaFeatureCollection?.features.length ?? 0
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
VECTOR_VIEWPORT_DEBOUNCE_MS,
|
||||
VECTOR_VIEWPORT_FEATURE_LIMIT,
|
||||
VECTOR_VIEWPORT_FEATURE_THRESHOLD,
|
||||
VECTOR_VIEWPORT_MIN_ZOOM,
|
||||
} from '../config/vectorDelivery'
|
||||
import { formatError } from '../lib/formatError'
|
||||
import { datasetsApi } from '../services/api/datasets'
|
||||
import type { DatasetCreateResponse, MapViewportState } from '../types'
|
||||
|
||||
interface ViewportVectorLayerOptions {
|
||||
selectedProjectId: string | null
|
||||
selectedDataset: DatasetCreateResponse | null
|
||||
featureCount: number | null
|
||||
isVectorDatasetType: (datasetType: string) => boolean
|
||||
}
|
||||
|
||||
export function useViewportVectorLayer({
|
||||
selectedProjectId,
|
||||
selectedDataset,
|
||||
featureCount,
|
||||
isVectorDatasetType,
|
||||
}: ViewportVectorLayerOptions) {
|
||||
const [viewport, setViewport] = useState<MapViewportState | null>(null)
|
||||
const [data, setData] = useState<GeoJSON.FeatureCollection | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [truncated, setTruncated] = useState(false)
|
||||
const [loadedFeatureCount, setLoadedFeatureCount] = useState(0)
|
||||
const requestSequence = useRef(0)
|
||||
|
||||
const enabled = Boolean(
|
||||
selectedProjectId &&
|
||||
selectedDataset &&
|
||||
isVectorDatasetType(selectedDataset.dataset_type) &&
|
||||
(featureCount ?? 0) > VECTOR_VIEWPORT_FEATURE_THRESHOLD,
|
||||
)
|
||||
const zoom = viewport?.zoom ?? null
|
||||
const zoomRequired = enabled && (zoom === null || zoom < VECTOR_VIEWPORT_MIN_ZOOM)
|
||||
|
||||
useEffect(() => {
|
||||
requestSequence.current += 1
|
||||
setData(null)
|
||||
setLoading(false)
|
||||
setError(null)
|
||||
setTruncated(false)
|
||||
setLoadedFeatureCount(0)
|
||||
}, [enabled, selectedDataset?.id, selectedProjectId])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !selectedProjectId || !selectedDataset || !viewport) {
|
||||
return
|
||||
}
|
||||
if (viewport.zoom < VECTOR_VIEWPORT_MIN_ZOOM) {
|
||||
requestSequence.current += 1
|
||||
setData(null)
|
||||
setLoading(false)
|
||||
setError(null)
|
||||
setTruncated(false)
|
||||
setLoadedFeatureCount(0)
|
||||
return
|
||||
}
|
||||
|
||||
const sequence = requestSequence.current + 1
|
||||
requestSequence.current = sequence
|
||||
const timer = window.setTimeout(async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await datasetsApi.selectVectorFeatures(selectedProjectId, selectedDataset.id, {
|
||||
bbox: viewport.bbox,
|
||||
limit: VECTOR_VIEWPORT_FEATURE_LIMIT,
|
||||
})
|
||||
if (requestSequence.current !== sequence) {
|
||||
return
|
||||
}
|
||||
setData(response.geojson)
|
||||
setLoadedFeatureCount(response.feature_count)
|
||||
setTruncated(response.truncated)
|
||||
} catch (requestError) {
|
||||
if (requestSequence.current !== sequence) {
|
||||
return
|
||||
}
|
||||
setData(null)
|
||||
setLoadedFeatureCount(0)
|
||||
setTruncated(false)
|
||||
setError(formatError(requestError, 'Unable to load visible vector features'))
|
||||
} finally {
|
||||
if (requestSequence.current === sequence) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}, VECTOR_VIEWPORT_DEBOUNCE_MS)
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timer)
|
||||
}
|
||||
}, [enabled, selectedDataset, selectedProjectId, viewport])
|
||||
|
||||
const statusMessage = useMemo(() => {
|
||||
if (!enabled) {
|
||||
return null
|
||||
}
|
||||
if (zoomRequired) {
|
||||
return `Zoom in to level ${VECTOR_VIEWPORT_MIN_ZOOM} to load buildings from PostGIS.`
|
||||
}
|
||||
if (loading) {
|
||||
return 'Loading visible features from PostGIS...'
|
||||
}
|
||||
if (error) {
|
||||
return error
|
||||
}
|
||||
if (truncated) {
|
||||
return `${loadedFeatureCount.toLocaleString()} visible features loaded; zoom in further because this view exceeds the ${VECTOR_VIEWPORT_FEATURE_LIMIT.toLocaleString()} feature limit.`
|
||||
}
|
||||
return `${loadedFeatureCount.toLocaleString()} visible of ${(featureCount ?? 0).toLocaleString()} total features loaded from PostGIS.`
|
||||
}, [enabled, error, featureCount, loadedFeatureCount, loading, truncated, zoomRequired])
|
||||
|
||||
return {
|
||||
enabled,
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
truncated,
|
||||
loadedFeatureCount,
|
||||
featureLimit: VECTOR_VIEWPORT_FEATURE_LIMIT,
|
||||
minZoom: VECTOR_VIEWPORT_MIN_ZOOM,
|
||||
zoom,
|
||||
zoomRequired,
|
||||
statusMessage,
|
||||
setViewport,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user