Leaflet中使用H3渲染印度全域六边形网格异常及优化
问题根因
- H3分辨率设置过高:H3 13级分辨率单六边形平均边长仅约44米,覆盖印度全境会生成超过2000万个单元格,浏览器无法承载这个量级的几何计算和渲染负载,必然出现长时间卡顿、无法加载的问题。
- 坐标顺序不符合接口规范:
h3.polyfill要求输入坐标遵循GeoJSON标准的[经度, 纬度]格式,代码传入的边界点全是[纬度, 经度]顺序,会导致生成的单元格位置完全偏移,不在印度境内。 - 渲染逻辑开销过大:每个六边形单独包裹
<div>、单独生成独立Polygon组件实例,哪怕仅几千个格子也会产生极高的渲染开销,进一步加剧卡顿。
优化实现方案
核心思路是按需加载、动态适配、批量渲染,从根源降低数据量和渲染开销:
1. 按地图缩放级别动态匹配H3分辨率
不要固定使用13级高分辨率,低缩放级别(全国视角)用低分辨率格子,高缩放级别(街道视角)再切换到高分辨率,参考映射规则:
- 地图zoom < 5:用H3 res3(单格边长约35km),印度全境仅需约1000个格子
- zoom 5-7:用H3 res4(边长约13km),全境约7000个格子
- zoom 7-9:用H3 res5(边长约5km),全境约5万个格子
- zoom 9-11:用H3 res6(边长约2km),全境约35万个格子
- zoom 11以上逐步提升分辨率,到res13时用户已经放大到极小的局部范围,仅需加载数百个格子,完全不会卡顿。
2. 仅加载当前地图视野内的格子
不要预生成印度全境所有格子,每次地图平移、缩放结束后,取当前可视区域的边界作为h3.polyfill的输入范围,视野外的格子直接不生成、不渲染,数据量能降低2-3个数量级。
3. 替换为高性能批量渲染
- 不要为每个六边形生成独立组件、单独包裹DOM节点,把所有格子批量转为GeoJSON FeatureCollection格式,用单个图层一次性渲染
- 用Canvas渲染器替代默认的SVG渲染器,同量级图形下渲染性能可以提升10倍以上,且不影响点击、hover等交互能力
- 给渲染逻辑加防抖,避免用户快速拖动/缩放地图时频繁触发计算
参考实现代码
// 缩放级别与H3分辨率映射表,可根据实际视觉效果调整 const ZOOM_TO_H3_RES = new Map([ [4, 3], [6, 4], [8, 5], [10, 6], [12, 7], [14, 9], [16, 11], [18, 13] ]); function setupHexGrid(map) { let currentHexLayer = null; const renderHexes = () => { // 移除旧图层 if (currentHexLayer) map.removeLayer(currentHexLayer); // 匹配当前缩放级别的H3分辨率 const currentZoom = map.getZoom(); let res = 3; for (const [zoomThreshold, h3Res] of ZOOM_TO_H3_RES) { if (currentZoom >= zoomThreshold) res = h3Res; } // 获取当前可视区域边界,转成H3要求的[lng, lat]坐标格式 const mapBounds = map.getBounds(); const viewRange = [ [mapBounds.getWest(), mapBounds.getNorth()], [mapBounds.getEast(), mapBounds.getNorth()], [mapBounds.getEast(), mapBounds.getSouth()], [mapBounds.getWest(), mapBounds.getSouth()], [mapBounds.getWest(), mapBounds.getNorth()] // 闭合多边形 ]; // 仅生成视野内的单元格 const cellIds = h3.polyfill(viewRange, res, true); // 批量构造GeoJSON数据 const hexGeoData = { type: "FeatureCollection", features: cellIds.map(cellId => { const boundary = h3.h3ToGeoBoundary(cellId, true); return { type: "Feature", properties: { cellId }, geometry: { type: "Polygon", coordinates: [[...boundary, boundary[0]].map(([lat, lng]) => [lng, lat])] } }; }) }; // 用Canvas渲染器批量绘制 currentHexLayer = L.geoJSON(hexGeoData, { renderer: L.canvas(), style: { color: "#2563eb", weight: 1, fillOpacity: 0.15 } }).addTo(map); }; // 平移、缩放结束后再触发渲染,避免频繁计算 map.on("moveend zoomend", renderHexes); // 首次加载渲染 renderHexes(); }
如果使用react-leaflet,直接将构造好的GeoJSON数据传入单个GeoJSON组件即可,不要嵌套循环生成独立的Polygon组件。
最终实现效果参考:
内容的提问来源于stack exchange,提问作者Premshankar Tiwari
相关产品推荐
相关产品推荐

