import { useEffect, useMemo, useRef, useState } from 'react'; import type { AmapConfig, HydrogenHeatmapMetric, HydrogenHeatmapPoint } from './types'; type Props = { config: AmapConfig; points: HydrogenHeatmapPoint[]; max: number; metric: HydrogenHeatmapMetric; focusQuery: string; onMapClick: (longitude: number, latitude: number) => void; }; type AmapInstance = { Map: new (container: HTMLElement, options: Record) => any; HeatMap: new (map: any, options: Record) => any; ToolBar: new (options?: Record) => any; Scale: new (options?: Record) => any; Bounds: new (southWest: [number, number], northEast: [number, number]) => any; }; declare global { interface Window { _AMapSecurityConfig?: { securityJsCode: string }; } } function getBounds(points: HydrogenHeatmapPoint[]) { if (!points.length) return null; return points.reduce((bounds, point) => ({ minLng: Math.min(bounds.minLng, point.lng), maxLng: Math.max(bounds.maxLng, point.lng), minLat: Math.min(bounds.minLat, point.lat), maxLat: Math.max(bounds.maxLat, point.lat), }), { minLng: points[0].lng, maxLng: points[0].lng, minLat: points[0].lat, maxLat: points[0].lat }); } export default function HydrogenAmapCanvas({ config, points, max, metric, focusQuery, onMapClick }: Props) { const containerRef = useRef(null); const mapRef = useRef(null); const heatmapRef = useRef(null); const amapRef = useRef(null); const clickHandlerRef = useRef(onMapClick); const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading'); const intensity = useMemo(() => { const transform = metric === 'kg' ? (value: number) => Math.log1p(value) : (value: number) => Math.sqrt(value); return { points: points.map((point) => ({ ...point, count: transform(point.count) })), max: transform(Math.max(1, max)), }; }, [max, metric, points]); useEffect(() => { clickHandlerRef.current = onMapClick; }, [onMapClick]); useEffect(() => { let cancelled = false; const container = containerRef.current; if (!container) return undefined; async function initialize() { try { window._AMapSecurityConfig = { securityJsCode: config.securityCode }; const loaderModule = await import('@amap/amap-jsapi-loader'); const AMap = await loaderModule.default.load({ key: config.key, version: '2.0', plugins: ['AMap.HeatMap', 'AMap.ToolBar', 'AMap.Scale'], }) as unknown as AmapInstance; if (cancelled || !container) return; const map = new AMap.Map(container, { viewMode: '2D', zoom: 5, center: [105.4, 34.4], mapStyle: 'amap://styles/whitesmoke', resizeEnable: true, showLabel: true, }); map.addControl(new AMap.ToolBar({ position: { right: '20px', bottom: '76px' } })); map.addControl(new AMap.Scale({ position: { right: '18px', bottom: '24px' } })); const heatmap = new AMap.HeatMap(map, { radius: 34, opacity: [0.14, 0.84], gradient: { 0.1: '#2563eb', 0.3: '#0891b2', 0.5: '#16a34a', 0.68: '#eab308', 0.84: '#f97316', 1: '#dc2626', }, }); map.on('click', (event: any) => clickHandlerRef.current(event.lnglat.getLng(), event.lnglat.getLat())); mapRef.current = map; heatmapRef.current = heatmap; amapRef.current = AMap; setStatus('ready'); } catch (error) { console.error('Hydrogen heatmap AMap initialization failed', error); if (!cancelled) setStatus('error'); } } initialize(); return () => { cancelled = true; heatmapRef.current?.setMap?.(null); mapRef.current?.destroy?.(); heatmapRef.current = null; mapRef.current = null; amapRef.current = null; }; }, [config.key, config.securityCode]); useEffect(() => { if (status !== 'ready' || !heatmapRef.current) return; heatmapRef.current.setDataSet({ data: intensity.points, max: intensity.max }); if (!focusQuery || !points.length || !mapRef.current || !amapRef.current) return; const bounds = getBounds(points); if (!bounds) return; if (points.length === 1) { mapRef.current.setZoomAndCenter(12, [points[0].lng, points[0].lat]); return; } mapRef.current.setBounds(new amapRef.current.Bounds( [bounds.minLng, bounds.minLat], [bounds.maxLng, bounds.maxLat], ), false, [80, 80, 80, 80]); }, [focusQuery, intensity, points, status]); return (
{status === 'loading' ? (
正在加载高德地图
) : null} {status === 'error' ? (

地图加载失败

请检查高德 Key、域名白名单与网络连接

) : null}
); }