2026-06-05 02:27:10 +08:00
|
|
|
import { useEffect, useRef, useState, useCallback, memo } from 'react';
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
import L from 'leaflet';
|
|
|
|
|
import { useRiskStore } from '@/stores';
|
|
|
|
|
import { LodGridLayer } from '@/components/LodGridLayer';
|
|
|
|
|
import { GridStatsOverlay } from '@/components/GridStatsOverlay';
|
|
|
|
|
import { useLodGrid } from '@/hooks/useLodGrid';
|
2026-06-05 02:27:10 +08:00
|
|
|
import type { Alert, GridRisk } from '@/types';
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
|
|
|
|
|
export interface CellInfo {
|
|
|
|
|
lat: number;
|
|
|
|
|
lon: number;
|
|
|
|
|
risk: number;
|
|
|
|
|
nearestAlertId: string | null;
|
|
|
|
|
nearestAlertDist: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface AlertMapProps {
|
|
|
|
|
selectedGridId: string | null;
|
|
|
|
|
onGridClick: (id: string) => void;
|
|
|
|
|
onCellInfo?: (info: CellInfo) => void;
|
|
|
|
|
forecastDay?: 1 | 3 | 7;
|
|
|
|
|
showAlertMarkers?: boolean;
|
|
|
|
|
showGrid?: boolean;
|
|
|
|
|
filteredAlerts?: Alert[];
|
|
|
|
|
riskRange?: [number, number];
|
|
|
|
|
isFullscreen?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const WUHAN_CENTER: [number, number] = [30.59, 114.31];
|
|
|
|
|
|
|
|
|
|
const RISK_COLORS: [number, number, string][] = [
|
|
|
|
|
[0.0, 0.2, '#22c55e'],
|
|
|
|
|
[0.2, 0.4, '#3b82f6'],
|
|
|
|
|
[0.4, 0.6, '#eab308'],
|
|
|
|
|
[0.6, 0.8, '#f97316'],
|
|
|
|
|
[0.8, 1.0, '#ef4444'],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
function getRiskLabel(value: number): string {
|
|
|
|
|
if (value >= 0.8) return '高风险';
|
|
|
|
|
if (value >= 0.6) return '中高';
|
|
|
|
|
if (value >= 0.4) return '中风险';
|
|
|
|
|
if (value >= 0.2) return '中低';
|
|
|
|
|
return '低风险';
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 02:27:10 +08:00
|
|
|
const EMPTY_GRIDS: GridRisk[] = [];
|
|
|
|
|
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
function AlertMapComponent({
|
|
|
|
|
selectedGridId,
|
|
|
|
|
onGridClick,
|
|
|
|
|
onCellInfo,
|
|
|
|
|
forecastDay = 1,
|
|
|
|
|
showAlertMarkers = true,
|
|
|
|
|
showGrid = true,
|
|
|
|
|
filteredAlerts = [],
|
|
|
|
|
riskRange,
|
|
|
|
|
isFullscreen = false,
|
|
|
|
|
}: AlertMapProps) {
|
|
|
|
|
const mapRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const mapInstanceRef = useRef<L.Map | null>(null);
|
|
|
|
|
const alertLayerRef = useRef<L.LayerGroup | null>(null);
|
|
|
|
|
const selectedMarkerRef = useRef<L.Rectangle | null>(null);
|
|
|
|
|
const clickHandlerRef = useRef(onGridClick);
|
|
|
|
|
const [currentZoom, setCurrentZoom] = useState(10);
|
|
|
|
|
|
2026-06-05 02:27:10 +08:00
|
|
|
const grids = useRiskStore((s) => s.grids ?? EMPTY_GRIDS);
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
|
|
|
|
|
// LOD grid data for stats overlay
|
|
|
|
|
const { count, avgRisk, maxRisk, loading } = useLodGrid(currentZoom, forecastDay);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
clickHandlerRef.current = onGridClick;
|
|
|
|
|
}, [onGridClick]);
|
|
|
|
|
|
|
|
|
|
// Initialize map
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!mapRef.current || mapInstanceRef.current) return;
|
|
|
|
|
|
|
|
|
|
const map = L.map(mapRef.current, {
|
|
|
|
|
center: WUHAN_CENTER,
|
|
|
|
|
zoom: 9,
|
|
|
|
|
zoomControl: true,
|
|
|
|
|
preferCanvas: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
|
|
|
|
|
maxZoom: 19,
|
|
|
|
|
}).addTo(map);
|
|
|
|
|
|
|
|
|
|
map.on('zoomend', () => {
|
|
|
|
|
setCurrentZoom(map.getZoom());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
mapInstanceRef.current = map;
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
map.remove();
|
|
|
|
|
mapInstanceRef.current = null;
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Render alert markers overlay
|
|
|
|
|
const renderAlertMarkers = useCallback(() => {
|
|
|
|
|
const map = mapInstanceRef.current;
|
|
|
|
|
if (!map) return;
|
|
|
|
|
|
|
|
|
|
if (alertLayerRef.current) {
|
|
|
|
|
try { map.removeLayer(alertLayerRef.current); } catch { /* ok */ }
|
|
|
|
|
alertLayerRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!showAlertMarkers || !filteredAlerts || filteredAlerts.length === 0) return;
|
|
|
|
|
|
|
|
|
|
const layer = L.layerGroup();
|
|
|
|
|
const mapBounds = map.getBounds();
|
|
|
|
|
const maxMarkers = 500;
|
|
|
|
|
const step = Math.max(1, Math.floor(filteredAlerts.length / maxMarkers));
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < filteredAlerts.length; i += step) {
|
|
|
|
|
const alert = filteredAlerts[i];
|
|
|
|
|
if (!alert.latitude || !alert.longitude) continue;
|
|
|
|
|
|
|
|
|
|
// Skip if outside viewport
|
|
|
|
|
if (
|
|
|
|
|
alert.latitude < mapBounds.getSouth() ||
|
|
|
|
|
alert.latitude > mapBounds.getNorth() ||
|
|
|
|
|
alert.longitude < mapBounds.getWest() ||
|
|
|
|
|
alert.longitude > mapBounds.getEast()
|
|
|
|
|
) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isP1 = alert.priority === 'P1';
|
2026-06-05 02:37:03 +08:00
|
|
|
|
|
|
|
|
const marker = L.circleMarker(
|
|
|
|
|
[alert.latitude, alert.longitude],
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
{
|
2026-06-05 02:37:03 +08:00
|
|
|
radius: isP1 ? 6 : 4,
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
fillColor: isP1 ? '#ef4444' : '#f97316',
|
2026-06-05 02:37:03 +08:00
|
|
|
fillOpacity: 0.7,
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
color: isP1 ? '#ef4444' : '#f97316',
|
|
|
|
|
weight: 2,
|
|
|
|
|
dashArray: isP1 ? undefined : '4 2',
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2026-06-05 02:37:03 +08:00
|
|
|
marker.bindTooltip(
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
`<div style="font-size:12px;">
|
|
|
|
|
<strong>${alert.priority}</strong> · ${(alert.risk_value * 100).toFixed(0)}%<br/>
|
|
|
|
|
${alert.region || ''} ${alert.street || ''}
|
|
|
|
|
</div>`,
|
|
|
|
|
{ direction: 'top', offset: [0, -5] }
|
|
|
|
|
);
|
|
|
|
|
|
2026-06-05 02:37:03 +08:00
|
|
|
marker.on('click', () => {
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
if (alert.grid_id) clickHandlerRef.current(alert.grid_id);
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-05 02:37:03 +08:00
|
|
|
marker.addTo(layer);
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
layer.addTo(map);
|
|
|
|
|
alertLayerRef.current = layer;
|
|
|
|
|
}, [filteredAlerts, showAlertMarkers]);
|
|
|
|
|
|
|
|
|
|
// Re-render alert markers when data changes
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
renderAlertMarkers();
|
|
|
|
|
}, [renderAlertMarkers]);
|
|
|
|
|
|
|
|
|
|
// Also re-render on map zoom/pan
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const map = mapInstanceRef.current;
|
|
|
|
|
if (!map) return;
|
|
|
|
|
|
|
|
|
|
const handleMove = () => renderAlertMarkers();
|
|
|
|
|
map.on('moveend', handleMove);
|
|
|
|
|
return () => { map.off('moveend', handleMove); };
|
|
|
|
|
}, [renderAlertMarkers]);
|
|
|
|
|
|
|
|
|
|
// Selected grid highlight
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const map = mapInstanceRef.current;
|
|
|
|
|
if (!map) return;
|
|
|
|
|
|
|
|
|
|
if (selectedMarkerRef.current) {
|
|
|
|
|
try { map.removeLayer(selectedMarkerRef.current); } catch { /* ok */ }
|
|
|
|
|
selectedMarkerRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selectedGridId) {
|
|
|
|
|
let grid = grids.find((g) => g.grid_id === selectedGridId);
|
|
|
|
|
if (!grid) {
|
|
|
|
|
const selectedAlertObj = filteredAlerts.find((a) => a.grid_id === selectedGridId);
|
|
|
|
|
if (selectedAlertObj) {
|
|
|
|
|
grid = grids.find((g) =>
|
|
|
|
|
Math.abs(g.latitude - selectedAlertObj.latitude) < 0.001 &&
|
|
|
|
|
Math.abs(g.longitude - selectedAlertObj.longitude) < 0.001
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (grid) {
|
|
|
|
|
const latHalf = 0.00045;
|
|
|
|
|
const lonHalf = 0.00052;
|
|
|
|
|
const marker = L.rectangle(
|
|
|
|
|
[
|
|
|
|
|
[grid.latitude - latHalf, grid.longitude - lonHalf],
|
|
|
|
|
[grid.latitude + latHalf, grid.longitude + lonHalf],
|
|
|
|
|
],
|
|
|
|
|
{
|
|
|
|
|
fillColor: '#3b82f6',
|
|
|
|
|
fillOpacity: 0.3,
|
|
|
|
|
color: '#3b82f6',
|
|
|
|
|
weight: 3,
|
|
|
|
|
}
|
|
|
|
|
).addTo(map);
|
|
|
|
|
selectedMarkerRef.current = marker;
|
|
|
|
|
|
|
|
|
|
map.flyTo([grid.latitude, grid.longitude], Math.max(map.getZoom(), 12), { duration: 0.5 });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [selectedGridId, grids]);
|
|
|
|
|
|
|
|
|
|
// Handle LOD grid cell click → find nearest alert
|
|
|
|
|
const handleCellClick = useCallback(
|
|
|
|
|
(lat: number, lon: number, risk: number) => {
|
|
|
|
|
let nearestId: string | null = null;
|
|
|
|
|
let minDist = Infinity;
|
|
|
|
|
|
|
|
|
|
if (filteredAlerts) {
|
|
|
|
|
for (const a of filteredAlerts) {
|
|
|
|
|
const d = Math.sqrt((a.latitude - lat) ** 2 + (a.longitude - lon) ** 2);
|
|
|
|
|
if (d < minDist) {
|
|
|
|
|
minDist = d;
|
|
|
|
|
nearestId = a.grid_id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nearestId && minDist < 0.01) {
|
|
|
|
|
clickHandlerRef.current(nearestId);
|
|
|
|
|
} else if (onCellInfo) {
|
|
|
|
|
onCellInfo({ lat, lon, risk, nearestAlertId: nearestId, nearestAlertDist: minDist });
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[filteredAlerts, onCellInfo]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Invalidate Leaflet size after fullscreen toggle
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const map = mapInstanceRef.current;
|
|
|
|
|
if (!map) return;
|
|
|
|
|
const timer = setTimeout(() => map.invalidateSize({ animate: true }), 100);
|
|
|
|
|
return () => clearTimeout(timer);
|
|
|
|
|
}, [isFullscreen]);
|
|
|
|
|
|
|
|
|
|
const containerHeight = isFullscreen ? 'calc(100vh - 120px)' : 'calc(100vh - 280px)';
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<div ref={mapRef} className="w-full rounded-lg overflow-hidden" style={{ height: containerHeight }} />
|
|
|
|
|
|
|
|
|
|
{/* LOD Grid Layer */}
|
|
|
|
|
<LodGridLayer
|
|
|
|
|
map={mapInstanceRef.current}
|
|
|
|
|
forecastDay={forecastDay}
|
|
|
|
|
visible={showGrid}
|
|
|
|
|
riskRange={riskRange}
|
|
|
|
|
onCellClick={handleCellClick}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Stats overlay */}
|
|
|
|
|
<GridStatsOverlay
|
|
|
|
|
count={count}
|
|
|
|
|
avgRisk={avgRisk}
|
|
|
|
|
maxRisk={maxRisk}
|
|
|
|
|
loading={loading}
|
|
|
|
|
forecastDay={forecastDay}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Legend */}
|
|
|
|
|
<div className="absolute bottom-4 right-4 bg-bg-card/90 backdrop-blur rounded-lg border border-border-light shadow-sm z-[1000] px-4 py-3">
|
|
|
|
|
<div className="text-[11px] font-semibold text-text-secondary mb-2">风险等级</div>
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
{RISK_COLORS.slice().reverse().map(([min, max, color]) => (
|
|
|
|
|
<div key={color} className="flex items-center gap-2">
|
|
|
|
|
<div className="w-4 h-4 rounded" style={{ backgroundColor: color }} />
|
|
|
|
|
<span className="text-[11px] text-text-secondary">
|
|
|
|
|
{getRiskLabel((min + max) / 2)} ({(min * 100).toFixed(0)}-{(max * 100).toFixed(0)}%)
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 02:27:10 +08:00
|
|
|
export const AlertMap = memo(AlertMapComponent);
|