经纬志
首页 / 实战手册 / 数据加载

跨 180° 经线的多边形,被画成横穿整个地球的一条带

先看结论

斐济的专属经济区,在地图上变成一条横跨太平洋的细长条,中间还穿透了非洲。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文

现象

一份包含斐济海域的 GeoJSON,坐标从 177.3 一直排到 -178.2。 渲染后得到一个覆盖全球宽度的带状面,面积计算结果为 4.9e14 平方米, 而实际海域面积约 1.1e12 平方米,差了两个数量级。

原因

GeoJSON 的坐标是平面的经纬度数组,没有「环绕」概念。 当一个面的顶点从 179.5 直接跳到 -179.5 时, 渲染器按直线连接,这条线会从东经 179.5 一路向西画到西经 179.5, 在投影平面上横穿整个地图。

同理,turf.area 用鞋带公式在平面或球面上积分, 也会把这段跨越算成地球另一半的面积。

排查过程

  1. 找同一环内相邻两点的经度差,绝对值大于 180 就说明跨了反经线。
  2. 看要素 bbox 的宽度,接近 360 度必然是跨线引起的。
  3. 用 turf.area 算面积,与已知面积比对,差一个数量级即可确认。
  4. 检查数据来源是否由 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, '面积仍异常');

适用版本

库版本跨反经线处理
Leaflet1.9.x按平面直线连接,需要手工拆分
Mapbox GL JS2.15 / 3.x同样按平面处理
turf.js7.xturf.area 会算错跨越的环
QGIS3.28 及以上可用「拆分要素」或 v.clean 处理
引用本文

经纬志(2026)。《跨 180° 经线的多边形,被画成横穿整个地球的一条带》。https://www.jingweizhi.com/cookbook/antimeridian-polygon.html(访问日期 2026-11-03)。