经纬志
首页 / 实战手册 / 样式与渲染

多边形的洞方向反了,中间的湖被填满

先看结论

一个带湖的行政区面,渲染后湖面也被涂成了行政区的颜色,卫星底图看不见了。

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

现象

一个 Polygon,外环是行政边界,内环是湖岸线,共 240 个顶点。 渲染后湖面与外环一样是半透明的蓝色,本应露出的卫星底图被挡住。

在 QGIS 里该要素的拓扑检查正常,面积计算也正确, 说明几何本身没问题,是渲染阶段没有识别出「洞」。

原因

Canvas 与 SVG 的填充规则有两种:

  • nonzero(非零环绕):按环的方向累计环绕数,方向相同则累加,洞不会被挖掉。
  • evenodd(奇偶):按射线穿越次数判断,与方向无关,洞一定被挖掉。

大多数地图库使用 nonzero,因此要求外环与内环方向相反: GeoJSON 规范建议外环逆时针、内环顺时针(右手法则)。 两份数据都按顺时针生成时,环绕数不抵消,洞就被填满。

排查过程

  1. 用鞋带公式算每个环的有向面积,外环与内环符号应相反。
  2. 若同号,说明方向一致,渲染器无法识别洞。
  3. 在 QGIS 里用「检查几何有效性」,无效时会提示 Ring Self-intersection 或 Hole lies outside shell。
  4. 把填充规则改为 evenodd 临时验证,若洞出现,即可确认是方向问题。
function signedArea(ring) {
  let s = 0;
  for (let i = 0, n = ring.length; i < n - 1; i++) {
    s += ring[i][0] * ring[i + 1][1] - ring[i + 1][0] * ring[i][1];
  }
  return s / 2;
}

signedArea(poly[0]);   // 外环  1.2e-3  正
signedArea(poly[1]);   // 内环  3.4e-5  正  -> 同号,洞失效

解决方案

渲染前统一绕向:外环逆时针(正面积),内环顺时针(负面积)。 不要依赖数据提供方已经处理好。

function rewindPolygon(rings) {
  const out = rings.map((ring, i) => {
    const positive = signedArea(ring) > 0;
    const wantPositive = i === 0;        // 外环为正,内环为负
    return positive === wantPositive ? ring : ring.slice().reverse();
  });
  return out;
}

fc.features.forEach(f => {
  if (f.geometry.type !== 'Polygon') return;
  f.geometry.coordinates = rewindPolygon(f.geometry.coordinates);
});

Leaflet 的 SVG 渲染器可以直接指定填充规则,作为兜底手段。

L.geoJSON(fc, {
  style: { fillOpacity: 0.5, fillColor: '#1a6fd4' },
  // SVG 路径默认 nonzero,改成 evenodd 后与绕向无关
  onEachFeature: (f, layer) => {
    if (layer._path) layer._path.setAttribute('fill-rule', 'evenodd');
  }
}).addTo(map);

验证

修正后外环面积为正、内环为负,湖面重新透出底图; 用 turf.area 计算要素面积时,洞会被扣除,结果比修正前小。

console.assert(signedArea(poly[0]) > 0, '外环方向不对');
console.assert(signedArea(poly[1]) < 0, '内环方向不对');
// 带洞面积应小于外环单独面积
console.assert(turf.area(feature) < turf.area({ type: 'Feature', properties: {},
  geometry: { type: 'Polygon', coordinates: [poly[0]] } }), '洞未被扣除');

适用版本

库 / 规范版本默认填充规则
GeoJSONRFC 7946建议外环逆时针、内环顺时针
Leaflet1.9.xSVG nonzero
Mapbox GL JS2.15 / 3.x内部按绕向处理,方向错时洞失效
Canvas 2D—fill('nonzero') 为默认
引用本文

经纬志(2026)。《多边形的洞方向反了,中间的湖被填满》。https://www.jingweizhi.com/cookbook/polygon-hole-winding.html(访问日期 2026-11-03)。