跨 180° 经线的多边形,被画成横穿整个地球的一条带
斐济的专属经济区,在地图上变成一条横跨太平洋的细长条,中间还穿透了非洲。
现象
一份包含斐济海域的 GeoJSON,坐标从 177.3 一直排到 -178.2。
渲染后得到一个覆盖全球宽度的带状面,面积计算结果为 4.9e14 平方米,
而实际海域面积约 1.1e12 平方米,差了两个数量级。
原因
GeoJSON 的坐标是平面的经纬度数组,没有「环绕」概念。
当一个面的顶点从 179.5 直接跳到 -179.5 时,
渲染器按直线连接,这条线会从东经 179.5 一路向西画到西经 179.5,
在投影平面上横穿整个地图。
同理,turf.area 用鞋带公式在平面或球面上积分,
也会把这段跨越算成地球另一半的面积。
排查过程
- 找同一环内相邻两点的经度差,绝对值大于 180 就说明跨了反经线。
- 看要素 bbox 的宽度,接近 360 度必然是跨线引起的。
- 用
turf.area算面积,与已知面积比对,差一个数量级即可确认。 - 检查数据来源是否由 Shapefile 转来,Shapefile 同样不支持跨线。
const ring = f.geometry.coordinates[0];
for (let i = 1; i < ring.length; i++) {
const d = Math.abs(ring[i][0] - ring[i - 1][0]);
if (d > 180) console.log('跨线点对', ring[i - 1], ring[i], d);
}
// [179.5, -16.8] [ -179.5, -16.9] d = 359
解决方案
在反经线处把多边形切成两块,一块放在西经侧,一块放在东经侧。 GeoJSON 里没有「日期变更线裁剪」的原生属性,必须显式拆分。
// 简易拆分:把所有经度规范到 0~360,切完再平移回来
function unwrapRing(ring) {
let prev = ring[0][0];
return ring.map(([x, y], i) => {
let nx = x;
if (i > 0) {
while (nx - prev > 180) nx -= 360;
while (nx - prev < -180) nx += 360;
}
prev = nx;
return [nx, y];
});
}
// 拆分后得到两块,分别平移 -360 与 0
const u = unwrapRing(ring);
const left = u.map(([x, y]) => [x < 0 ? x + 360 : x, y]);
const right = u.map(([x, y]) => [x > 180 ? x - 360 : x, y]);
const polys = [left, right].map(r => ({
type: 'Feature',
properties: f.properties,
geometry: { type: 'Polygon', coordinates: [r] }
}));
Mapbox GL JS 有内置的 mercator 处理,但仍需把几何裁成两块,
否则跨越部分会被渲染到视口之外。
验证
拆分后两块面积之和应接近 1.1e12 平方米,与公开的斐济海域面积吻合; 地图上不再出现横穿全图的带状面。
const total = polys.reduce((s, p) => s + turf.area(p), 0);
console.log(total.toExponential(2)); // 约 1.1e+12
console.assert(total < 2e12, '面积仍异常');
适用版本
| 库 | 版本 | 跨反经线处理 |
|---|---|---|
| Leaflet | 1.9.x | 按平面直线连接,需要手工拆分 |
| Mapbox GL JS | 2.15 / 3.x | 同样按平面处理 |
| turf.js | 7.x | turf.area 会算错跨越的环 |
| QGIS | 3.28 及以上 | 可用「拆分要素」或 v.clean 处理 |
经纬志(2026)。《跨 180° 经线的多边形,被画成横穿整个地球的一条带》。https://www.jingweizhi.com/cookbook/antimeridian-polygon.html(访问日期 2026-11-03)。