Turf.js多边形如何生成中线?三种GIS实战方法与代码详解(附:对比表)

编程与开发
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

引言:如果你正在做 WebGIS 路网提取、河道中心线表达、面状地物骨架分析,通常会遇到一个问题:Turf.js多边形如何生成中线?三种GIS实战方法与代码详解(附:对比表)这类需求看起来像一个简单的前端空间分析任务,但实际操作时会发现,Turf.js 本身并没有直接提供“多边形生成中线”或“面生成骨架线”的标准函数。

本文以 Turf.js 多边形生成中线为核心问题,结合 WebGIS 常见场景,讲清楚三种可落地的方法:基于边界点采样的近似中线、基于 Voronoi 的骨架线近似、以及借助后端 GIS 工具生成更稳定的中心线。你可以根据数据精度、浏览器性能和项目架构选择合适方案。

Turf.js多边形生成中线与Turf.js多边形中心线方法对比示意图
Turf.js 多边形生成中线的三种常见实现思路:前端近似、Voronoi 骨架和后端 GIS 处理。

背景:为什么 Turf.js 多边形生成中线不是一个内置函数?

Turf.js 是 WebGIS 前端常用的 JavaScript 空间分析库,适合处理 GeoJSON 的缓冲区、相交、裁剪、距离、面积、中心点等任务。但“多边形生成中线”并不是普通几何计算,而更接近骨架线提取中心线提取

所谓骨架线,可以理解为多边形内部距离边界相对均衡的一组线。对于河道、道路面、狭长行政边界、建筑轮廓等数据,中线结果往往受以下因素影响:

  • 多边形是否狭长、是否存在分叉;
  • 边界点密度是否足够;
  • 是否存在孔洞、多部件面或自相交几何;
  • 是否需要拓扑连通的线,而不是零散线段;
  • 运行环境是在浏览器前端,还是可以使用后端 GIS 服务。

所以,Turf.js 多边形生成中线通常不是“一行 API”能稳定解决的问题,而是需要根据业务目标做近似、筛选或后端预处理。

原理:多边形中线、中心线和骨架线有什么区别?

在实际项目中,很多人会把“中线”“中心线”“骨架线”混用,但它们的含义并不完全相同。

名称 常见含义 适用场景 注意点
中心点连线 通过若干横截面或采样点计算中心点,再连成线 狭长面、河道、道路面 实现简单,但对复杂分叉面不稳定
骨架线 多边形内部等距边界的拓扑骨架 河网、道路骨架、形态分析 算法复杂,通常需要专门库或后端处理
中心线 工程上表达多边形主方向的线 制图表达、WebGIS 可视化、路径近似 可能是简化后的业务线,不一定严格等距

本文讨论的 Turf.js 多边形生成中线,重点面向 WebGIS 工程实践:在 GeoJSON 多边形中生成一条或一组可用于展示、分析或后续计算的线。

步骤:方法一,用 Turf.js 边界采样生成近似中线

第一种方法适合狭长、规则、无明显分叉的多边形,例如河道面、道路缓冲面、廊道面。思路是:沿多边形外边界采样,估计主方向,然后按主方向生成若干截线,与多边形相交后取每段交线的中点,最后把中点连成线。

适用条件

  • 多边形整体呈狭长形;
  • 中线只需要近似表达,不要求严格骨架;
  • 希望尽量在浏览器前端完成;
  • 数据量不大,单个多边形顶点数可控。

安装 Turf.js

npm install @turf/turf

示例代码:基于截线中点生成近似中线

import * as turf from '@turf/turf';

/**
 * 使用截线中点法生成狭长多边形近似中线
 * @param {Feature<Polygon>} polygon GeoJSON Polygon
 * @param {number} count 截线数量
 * @returns {Feature<LineString>|null}
 */
function polygonToApproxCenterline(polygon, count = 30) {
  const bbox = turf.bbox(polygon);
  const [minX, minY, maxX, maxY] = bbox;

  const width = maxX - minX;
  const height = maxY - minY;

  const points = [];

  for (let i = 0; i <= count; i++) {
    const t = i / count;

    let cutLine;

    if (width >= height) {
      const x = minX + width * t;
      cutLine = turf.lineString([
        [x, minY - height],
        [x, maxY + height]
      ]);
    } else {
      const y = minY + height * t;
      cutLine = turf.lineString([
        [minX - width, y],
        [maxX + width, y]
      ]);
    }

    const intersections = turf.lineIntersect(cutLine, turf.polygonToLine(polygon));

    if (intersections.features.length >= 2) {
      const coords = intersections.features.map(f => f.geometry.coordinates);

      coords.sort((a, b) => {
        return width >= height ? a[1] - b[1] : a[0] - b[0];
      });

      const first = coords[0];
      const last = coords[coords.length - 1];

      const mid = [
        (first[0] + last[0]) / 2,
        (first[1] + last[1]) / 2
      ];

      if (turf.booleanPointInPolygon(turf.point(mid), polygon)) {
        points.push(mid);
      }
    }
  }

  if (points.length < 2) {
    return null;
  }

  return turf.lineString(points);
}

使用示例

const polygon = turf.polygon([[
  [120.000, 30.000],
  [120.010, 30.002],
  [120.020, 30.001],
  [120.030, 30.004],
  [120.032, 30.008],
  [120.020, 30.006],
  [120.010, 30.007],
  [120.000, 30.004],
  [120.000, 30.000]
]]);

const centerline = polygonToApproxCenterline(polygon, 40);

console.log(centerline);

这个方法的优点是轻量、易懂、方便直接嵌入前端项目。缺点也很明显:如果多边形存在弯曲、分叉、孔洞或复杂凹陷,生成的 Turf.js 多边形中心线可能会偏离真实骨架。

步骤:方法二,用 Turf.js + Voronoi 近似生成多边形骨架线

第二种方法更接近“骨架线”的思路。基本流程是:对多边形边界进行密集采样,使用 Turf.js 生成 Voronoi 图,再保留落在多边形内部的 Voronoi 边作为候选骨架线。

Voronoi 图可以理解为一组点的空间分区结构。对于多边形边界上的采样点,其内部 Voronoi 边往往会接近多边形的中轴线,因此可以用于 Turf.js 多边形生成中线的近似方案。

实现流程

  1. 将多边形边界转为线;
  2. 沿边界按固定距离采样点;
  3. 用采样点生成 Voronoi 面;
  4. 提取 Voronoi 面的边界线;
  5. 筛选落在原多边形内部的线段;
  6. 必要时对线段进行合并、简化和去毛刺。

示例代码:生成内部 Voronoi 候选骨架

import * as turf from '@turf/turf';

/**
 * 使用边界采样 + Voronoi 近似生成多边形骨架线
 * @param {Feature<Polygon>} polygon GeoJSON Polygon
 * @param {number} sampleDistance 采样距离,单位为 kilometers
 * @returns {FeatureCollection<LineString>}
 */
function polygonToVoronoiSkeleton(polygon, sampleDistance = 0.05) {
  const boundary = turf.polygonToLine(polygon);
  const length = turf.length(boundary, { units: 'kilometers' });

  const samplePoints = [];

  for (let d = 0; d <= length; d += sampleDistance) {
    const p = turf.along(boundary, d, { units: 'kilometers' });
    samplePoints.push(p);
  }

  if (samplePoints.length < 4) {
    return turf.featureCollection([]);
  }

  const pointsFc = turf.featureCollection(samplePoints);
  const bbox = turf.bbox(polygon);
  const voronoi = turf.voronoi(pointsFc, { bbox });

  if (!voronoi) {
    return turf.featureCollection([]);
  }

  const candidateLines = [];

  voronoi.features.forEach(cell => {
    if (!cell || !cell.geometry) return;

    const cellLine = turf.polygonToLine(cell);

    const segments = turf.lineSegment(cellLine);

    segments.features.forEach(seg => {
      const mid = turf.midpoint(
        turf.point(seg.geometry.coordinates[0]),
        turf.point(seg.geometry.coordinates[1])
      );

      if (turf.booleanPointInPolygon(mid, polygon)) {
        candidateLines.push(seg);
      }
    });
  });

  return turf.featureCollection(candidateLines);
}

如何清理 Voronoi 生成的毛刺线?

Voronoi 方法通常会生成很多短线和毛刺线。实际项目中需要做二次过滤:

  • 删除长度小于阈值的线段;
  • 保留距离多边形边界较远的线段;
  • 对线段进行拓扑合并;
  • 根据业务方向保留主干线;
  • 使用 simplify 简化坐标点。
function filterShortLines(lines, minLength = 0.02) {
  return turf.featureCollection(
    lines.features.filter(line => {
      return turf.length(line, { units: 'kilometers' }) >= minLength;
    })
  );
}

const skeleton = polygonToVoronoiSkeleton(polygon, 0.03);
const filtered = filterShortLines(skeleton, 0.01);

这种方法比截线中点法更适合弯曲面和局部分叉面,但仍然不是严格的 GIS 骨架线算法。它更适合 WebGIS 前端预览、交互分析和低精度制图表达。

步骤:方法三,后端 GIS 生成中线,前端 Turf.js 只负责展示和校验

如果你的项目对结果质量要求较高,例如河道中心线生产、道路面转中心线、批量空间数据处理,不建议完全依赖前端 Turf.js。更稳妥的做法是使用后端 GIS 工具生成中心线,然后在前端用 Turf.js 做展示、裁剪、长度计算或空间校验。

推荐架构

  1. 前端上传或选择多边形 GeoJSON;
  2. 后端接收 GeoJSON,并使用 Python GIS、PostGIS 或桌面 GIS 工具处理;
  3. 后端返回 LineString 或 MultiLineString;
  4. 前端用 Leaflet、OpenLayers 或 MapLibre 加载结果;
  5. 前端用 Turf.js 验证长度、是否落在面内、是否与边界异常相交。

Python 后端思路

在 Python GIS 环境中,可以使用 GeoPandas、Shapely、GDAL/OGR,或者专门的 skeletonize、centerline 类库来处理。不同库的维护状态和适用数据差异较大,工程中要先用样本数据验证。

# 示例:伪代码结构,实际骨架算法需根据项目库选择
import geopandas as gpd

polygons = gpd.read_file("polygon.geojson")

# 1. 修复无效几何
polygons["geometry"] = polygons.geometry.buffer(0)

# 2. 投影到米制坐标系,避免经纬度距离误差
polygons = polygons.to_crs(3857)

# 3. 调用中心线或骨架线算法
# centerlines = build_centerline(polygons)

# 4. 转回 WGS84 供 WebGIS 使用
# centerlines = centerlines.to_crs(4326)
# centerlines.to_file("centerline.geojson", driver="GeoJSON")

前端 Turf.js 校验示例

function validateCenterlineInPolygon(centerline, polygon) {
  const segments = turf.lineSegment(centerline);

  const outsideSegments = segments.features.filter(seg => {
    const mid = turf.midpoint(
      turf.point(seg.geometry.coordinates[0]),
      turf.point(seg.geometry.coordinates[1])
    );

    return !turf.booleanPointInPolygon(mid, polygon);
  });

  return {
    valid: outsideSegments.length === 0,
    outsideCount: outsideSegments.length
  };
}

这种模式的优势是结果更稳定,适合批处理和生产环境。Turf.js 在这里不再承担复杂骨架算法,而是发挥其前端空间分析、快速校验和交互展示的优势。

常见坑:Turf.js 多边形中心线生成失败的原因

1. 直接用 centroid 或 center 当中线

turf.centroidturf.center 只能生成点,不能表达多边形的线状形态。对于狭长多边形,一个中心点通常没有业务意义。

2. 在经纬度坐标下直接按米计算

GeoJSON 常用 WGS84 经纬度坐标,单位是度,不是米。若要用距离阈值、采样间距、线段长度,建议先投影到合适的平面坐标系,或者明确使用 Turf.js 的 kilometers、meters 等单位参数。

3. 多边形几何无效

自相交、重复点、未闭合环、孔洞异常都会影响 Turf.js 多边形生成中线。处理前应检查 GeoJSON 是否有效,并尽量清理重复点和异常环。

4. 采样距离设置不合理

采样过稀,中线会粗糙甚至断裂;采样过密,浏览器性能会明显下降。前端项目应根据地图比例尺和数据大小设置动态阈值。

5. 复杂分叉面只生成一条线

如果多边形本身像 Y 字形、树枝状河网或复杂道路面,它的合理结果往往是多条骨架线,而不是一条 LineString。此时应接受 MultiLineString 或 FeatureCollection 结果。

方法比较:三种 Turf.js 多边形生成中线方案怎么选?

方法 核心思路 优点 缺点 推荐场景
截线中点法 按主方向切割多边形,取交线中点连线 代码简单,前端可直接运行 不适合弯曲和分叉多边形 狭长道路面、简单河道面、快速预览
Voronoi 近似骨架 边界采样后生成内部 Voronoi 边 更接近骨架线,可处理部分弯曲面 毛刺多,需要过滤和合并 WebGIS 交互分析、近似中心线表达
后端 GIS 处理 用 Python、PostGIS 或专业 GIS 工具生成中心线 稳定性好,适合批量生产 架构更复杂,不能纯前端完成 生产级数据处理、河道中心线、道路中心线

如果只是前端可视化,优先尝试截线中点法;如果多边形弯曲较明显,可以尝试 Voronoi 近似;如果结果要进入正式数据库或用于空间分析,建议采用后端 GIS 生成,再由 Turf.js 做展示和校验。

检查清单:生成中线前先确认这些问题

  • 数据类型:输入是否为 Polygon 或 MultiPolygon,而不是 LineString?
  • 坐标系:是否明确当前 GeoJSON 是经纬度还是投影坐标?
  • 几何有效性:是否存在自相交、重复点、空几何或异常孔洞?
  • 业务目标:需要严格骨架线,还是只要制图用近似中线?
  • 结果类型:是否允许输出 MultiLineString 或多条线段?
  • 性能限制:是否需要在浏览器中实时生成?数据量是否可控?
  • 后续用途:中线是否要用于长度统计、拓扑分析、路径计算或数据库入库?

FAQ:Turf.js 多边形生成中线常见问题

Turf.js 有没有 polygonToCenterline 这样的函数?

没有。Turf.js 提供了很多常用空间分析函数,但没有稳定的多边形转中心线 API。一般需要自己组合 polygonToLinelineIntersectvoronoibooleanPointInPolygon 等函数实现近似方案。

Turf.js 多边形中心线可以用于正式数据生产吗?

如果只是地图展示或交互预览,可以使用 Turf.js 前端近似生成。若用于正式数据生产、工程测绘、河道中心线或道路中心线入库,建议使用后端 GIS 算法,并进行人工抽检和拓扑校验。

为什么 Voronoi 方法生成很多碎线?

因为 Voronoi 图是由边界采样点生成的空间分割结构,它并不知道哪些边是你业务上需要的主中线。生成结果通常包含短线、分支和毛刺,需要按长度、位置、连通性和业务规则过滤。

MultiPolygon 可以直接生成中线吗?

不建议直接整体处理。更稳妥的做法是先把 MultiPolygon 拆成多个 Polygon,分别生成中线,再合并结果。否则不同面之间可能产生不合理的连接关系。

采样距离应该怎么设置?

没有固定值。可以按多边形尺寸设置,例如边界长度越长,采样距离越大;地图比例尺越大,采样距离越小。前端实时计算时,要避免过密采样导致浏览器卡顿。

结论:Turf.js 适合做近似中线,生产级骨架线建议后端处理

Turf.js 多边形生成中线的关键,不是寻找一个不存在的万能函数,而是根据数据形态选择合适方法。简单狭长面可以用截线中点法快速生成近似中线;弯曲或轻微分叉面可以用 Voronoi 方法提取候选骨架;如果是生产级河道中心线、道路中心线或批量面转线任务,应优先放到后端 GIS 流程中完成。

实践中最稳妥的方案是:前端 Turf.js 负责交互、预览和校验,后端 GIS 负责复杂中心线生成。这样既能保持 WebGIS 系统响应速度,也能保证空间数据成果的可靠性。