使用H3与OpenLayers生成墨卡托投影六边形地图时±180°存在间隙
H3 + OpenLayers 墨卡托投影下±180°经度处六边形间隙问题解决
我在使用H3与OpenLayers生成墨卡托投影的六边形地图时,发现±180°经度位置存在间隙。
问题代码
const createHex = (lat: any, lng: any) => { const cell = latLngToCell(lng, lat, 1); const coords = cellToBoundary(cell).map((c) => [c[1], c[0]]); const polygon = new Polygon([coords]); const feature = new Feature(polygon); vectorSource.addFeature(feature); }; const map = new Map({ target: 'map', layers: [ new TileLayer({ source: new TileJSON({ url: 'https://api.maptiler.com/maps/satellite/256/tiles.json?key=yFxxWLuQf6QipClqNF0n', crossOrigin: 'anonymous' }) }) ], view: new View({ center: [0, 0], zoom: 2, projection: 'EPSG:4326' }) }); map.on('click', (e) => { createHex(e.coordinate[0], e.coordinate[1]); });
问题截图

解决方案
问题根源有两点:一是H3函数参数顺序错误,二是跨±180°经线的多边形未被正确处理,导致OpenLayers渲染时出现间隙。修复步骤如下:
- 纠正H3参数顺序:H3的
latLngToCell函数参数为(纬度, 经度, 分辨率),原代码传反了经纬度,导致六边形位置计算错误。 - 拆分跨日界线的多边形:编写工具函数检测并拆分跨±180°的多边形,将其分为东、西两部分,确保OpenLayers能正确渲染。
修复后的代码
// 拆分跨±180°经线的多边形 const splitCrossAntimeridianPolygon = (coords: number[][]) => { let hasCross = coords.some(coord => Math.abs(coord[0]) > 180); if (!hasCross) return [coords]; const westCoords: number[][] = []; const eastCoords: number[][] = []; coords.forEach((coord, idx) => { const lng = coord[0]; const prevCoord = coords[idx === 0 ? coords.length - 1 : idx - 1]; const prevLng = prevCoord[0]; if (lng > 180) { westCoords.push([lng - 360, coord[1]]); eastCoords.push([lng, coord[1]]); } else if (lng < -180) { westCoords.push([lng, coord[1]]); eastCoords.push([lng + 360, coord[1]]); } else { // 处理跨线的情况,添加180/-180分割点 if ((prevLng > 180 && lng < 180) || (prevLng < -180 && lng > -180)) { westCoords.push([-180, coord[1]]); eastCoords.push([180, coord[1]]); } westCoords.push(coord); eastCoords.push(coord); } }); // 闭合多边形 westCoords.push(westCoords[0]); eastCoords.push(eastCoords[0]); return [westCoords, eastCoords]; }; const createHex = (lat: number, lng: number) => { // 纠正H3参数顺序:纬度在前,经度在后 const cell = latLngToCell(lat, lng, 1); // H3返回的边界格式为[经度, 纬度],直接适配OpenLayers的EPSG:4326格式 const coords = cellToBoundary(cell); // 处理跨线多边形 const polygonCoordsList = splitCrossAntimeridianPolygon(coords); polygonCoordsList.forEach(polygonCoords => { const polygon = new Polygon([polygonCoords]); const feature = new Feature(polygon); vectorSource.addFeature(feature); }); }; // 地图初始化代码保持不变 const map = new Map({ target: 'map', layers: [ new TileLayer({ source: new TileJSON({ url: 'https://api.maptiler.com/maps/satellite/256/tiles.json?key=yFxxWLuQf6QipClqNF0n', crossOrigin: 'anonymous' }) }) ], view: new View({ center: [0, 0], zoom: 2, projection: 'EPSG:4326' }) }); map.on('click', (e) => { // OpenLayers的coordinate格式是[经度, 纬度],修正传参顺序 createHex(e.coordinate[1], e.coordinate[0]); });
额外说明:原代码中createHex的调用参数也传反了——OpenLayers的e.coordinate是[经度, 纬度],需把纬度(e.coordinate[1])传给createHex第一个参数,经度(e.coordinate[0])传给第二个参数,这也是之前位置错误的原因之一。
内容的提问来源于stack exchange,提问作者Omid
相关产品推荐
相关产品推荐

