import { useEffect, useMemo, useRef, useState } from 'react'; import type { AmapConfig, HeatmapMetric, HeatmapPoint } from './types'; type Props = { config: AmapConfig; points: HeatmapPoint[]; max: number; metric: HeatmapMetric; 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: HeatmapPoint[]) { if (points.length === 0) return null; let minLng = points[0].lng; let maxLng = points[0].lng; let minLat = points[0].lat; let maxLat = points[0].lat; for (let index = 1; index < points.length; index += 1) { const point = points[index]; minLng = Math.min(minLng, point.lng); maxLng = Math.max(maxLng, point.lng); minLat = Math.min(minLat, point.lat); maxLat = Math.max(maxLat, point.lat); } return { minLng, maxLng, minLat, maxLat }; } export default function AmapHeatmapCanvas({ 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 === 'locations' ? (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: 25, opacity: [0.12, 0.82], 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('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 === 0 || !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}
); }