135 lines
4.2 KiB
TypeScript
135 lines
4.2 KiB
TypeScript
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,
|
|
}
|
|
}
|