Turf.js多边形如何生成等距线?手把手教你GIS空间插值实战(附:代码示例)

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

Turf.js多边形如何生成等距线?手把手教你GIS空间插值实战(附:代码示例)这篇文章解决一个很常见的 WebGIS 问题:前端只有一批带数值属性的点,想在浏览器里生成等值线,并把结果裁剪到研究区多边形范围内,用于展示温度、降雨、污染浓度、地价、人口密度等连续空间现象。

这里先说明一个容易混淆的概念:Turf.js 本身更擅长做矢量空间分析,例如缓冲区、相交、裁剪、合并、距离计算等;严格意义上的“空间插值”不是 Turf.js 的核心强项。不过,在前端实战中,我们可以用“点数据栅格化 + Turf.js isolines 生成等值线 + 多边形裁剪”的方式,完成一套轻量级的 GIS 空间插值可视化流程。

引言:Turf.js多边形生成等距线适合什么场景

很多同学搜索“Turf.js多边形生成等距线”时,其实想做的是“在一个行政区、地块或研究区多边形内,根据采样点生成等值线”。例如:

  • 根据气象站点温度生成温度等值线;
  • 根据监测点污染浓度生成污染扩散示意图;
  • 根据高程点生成简单的等高线示意;
  • 根据商圈评分点生成热度分布线;
  • 在 WebGIS 页面中动态展示分析结果,而不是提前在桌面 GIS 中制图。

本文会用一个完整流程演示:准备点数据和多边形边界,生成插值网格,使用 Turf.js 生成等值线,再把等值线裁剪到多边形范围内。代码以浏览器环境为主,也适合和 Leaflet、Mapbox GL JS、OpenLayers 等 WebGIS 框架结合。

Turf.js多边形生成等距线与Turf.js空间插值流程示意图
Turf.js 前端生成等值线的典型流程:点数据、插值网格、等值线生成、多边形裁剪。

背景:为什么不能直接对多边形生成等距线

在 GIS 里,“等距线”这个说法经常有两种含义:

  • 等值线:连接相同数值的线,例如等高线、等温线、等降雨量线;
  • 等距离线:与某个边界保持固定距离的线,更接近缓冲区或内缩缓冲区。

本文讨论的是前一种,也就是常见的 Turf.js isolines 等值线。它需要输入一组带有数值属性的点或网格点,例如每个点都有一个 value 字段。Turf.js 不能凭空从一个普通多边形生成等值线,因为普通多边形只有边界,没有连续变化的数值面。

因此,正确的数据逻辑应该是:

  1. 研究区多边形负责限定分析范围;
  2. 采样点负责提供空间数值;
  3. 插值或格网化过程负责把离散点转换为可生成等值线的规则点集;
  4. Turf.js 的 isolines 根据数值字段生成等值线;
  5. 最后用多边形裁剪结果,只保留研究区内部的线。

原理:Turf.js空间插值生成等值线的核心思路

Turf.js空间插值在实际项目中通常分为两步:先构造规则网格,再为每个网格点计算一个数值。Turf.js 提供了 interpolate 方法,可以根据已知点的属性值,为网格点估算数值。常见参数包括:

  • gridType:生成的格网类型,例如 pointssquarehextriangle
  • property:用于插值的字段名,例如 value
  • units:网格间距单位,例如 kilometers
  • weight:距离权重,值越大,近距离点对结果影响越强。

得到插值网格点后,再使用 turf.isolines 根据指定分级值生成等值线。例如分级值为 [10, 20, 30, 40],结果会生成对应数值等级的线要素。

注意:Turf.js 的前端插值适合轻量级展示和交互分析。如果你要做严肃的地统计建模,例如克里金插值、半变异函数拟合、大范围高精度栅格分析,建议使用 QGIS、ArcGIS Pro、Python GIS 或 PostGIS Raster 等后端流程。

步骤:使用 Turf.js 生成多边形范围内等值线

步骤一:准备研究区多边形和采样点

下面示例使用 GeoJSON 数据。研究区是一个简单多边形,采样点带有 value 字段。实际项目中,你可以把它们替换为接口返回的数据、数据库查询结果或用户上传的 GeoJSON。

const boundary = {
  "type": "Feature",
  "properties": { "name": "研究区" },
  "geometry": {
    "type": "Polygon",
    "coordinates": [[
      [116.30, 39.85],
      [116.55, 39.85],
      [116.55, 40.05],
      [116.30, 40.05],
      [116.30, 39.85]
    ]]
  }
};

const samplePoints = {
  "type": "FeatureCollection",
  "features": [
    turf.point([116.34, 39.88], { value: 12 }),
    turf.point([116.40, 39.90], { value: 18 }),
    turf.point([116.48, 39.89], { value: 25 }),
    turf.point([116.36, 39.98], { value: 30 }),
    turf.point([116.45, 40.01], { value: 38 }),
    turf.point([116.52, 39.97], { value: 42 })
  ]
};

这里的关键是:每个点必须有同一个数值字段,例如 value。如果字段名不统一,后面的 interpolateisolines 都无法得到正确结果。

步骤二:计算多边形外包矩形

插值通常需要一个范围。最简单的做法是使用研究区多边形的外包矩形作为计算范围,然后在最后一步裁剪到多边形内部。

const bbox = turf.bbox(boundary);
// bbox 格式:[minX, minY, maxX, maxY]
console.log(bbox);

如果你的研究区很不规则,外包矩形会包含大量无效区域。小范围项目一般可以接受;如果范围较大,建议先在后端做更精细的栅格化或分块处理。

步骤三:使用 Turf.js interpolate 生成插值点网格

下面使用 turf.interpolate 生成点类型网格。网格间距越小,等值线越平滑,但前端计算量也越大。

const cellSize = 2; // 单位:公里

const interpolatedGrid = turf.interpolate(samplePoints, cellSize, {
  gridType: "points",
  property: "value",
  units: "kilometers",
  weight: 2
});

console.log(interpolatedGrid);

参数选择建议如下:

  • 城市级小范围展示,可从 15 公里尝试;
  • 地块级或园区级展示,需要更小间距,但要注意经纬度坐标下的单位换算;
  • weight 可先使用 2,如果结果受近点影响过强,可适当降低;
  • 如果采样点太少,插值结果只适合示意,不适合严肃分析。

步骤四:生成 Turf.js isolines 等值线

接下来指定等值线分级。分级值应该根据数据最小值、最大值和业务含义来确定,不建议随便写。

const breaks = [10, 15, 20, 25, 30, 35, 40];

const isolines = turf.isolines(interpolatedGrid, breaks, {
  zProperty: "value"
});

console.log(isolines);

如果你发现等值线为空,优先检查三件事:

  • interpolatedGrid 中是否真的有 value 字段;
  • breaks 是否落在数据值域范围内;
  • 输入点是否足够覆盖研究区,而不是集中在一个角落。

步骤五:把等值线裁剪到多边形范围内

Turf.js 的线面裁剪能力不像桌面 GIS 那样完整稳定。对于简单场景,可以遍历等值线,判断线段与研究区的关系;对于复杂多边形,建议使用更专业的裁剪库或后端 GIS 服务。

一个实用的前端简化方案是:先保留与多边形相交的等值线,再在视觉上叠加多边形遮罩或边界。示例代码如下:

const clippedLineFeatures = [];

isolines.features.forEach((line) => {
  const intersects = turf.booleanIntersects(line, boundary);

  if (intersects) {
    clippedLineFeatures.push(line);
  }
});

const resultIsolines = turf.featureCollection(clippedLineFeatures);

console.log(resultIsolines);

这个方案是“筛选相交线”,不是严格意义上的几何裁剪。如果你必须把线精确切断在多边形边界上,可以考虑:

  • 在后端使用 PostGIS 的 ST_Intersection
  • 使用 QGIS 或 ArcGIS Pro 预处理等值线;
  • 将等值线结果提交到后端,由 GEOS、GDAL 或 Python Shapely 完成裁剪;
  • 前端只负责渲染最终 GeoJSON。

步骤六:在 WebGIS 地图中渲染结果

如果你使用 Leaflet,可以把结果直接加入地图。下面是一个简化示例:

L.geoJSON(resultIsolines, {
  style: function(feature) {
    const value = feature.properties.value;

    return {
      color: "#e65100",
      weight: 2,
      opacity: 0.9
    };
  },
  onEachFeature: function(feature, layer) {
    layer.bindPopup("等值线:" + feature.properties.value);
  }
}).addTo(map);

L.geoJSON(boundary, {
  style: {
    color: "#333333",
    weight: 2,
    fillOpacity: 0
  }
}).addTo(map);

如果你使用 OpenLayers 或 Mapbox GL JS,思路也是一样:把 resultIsolines 作为 GeoJSON 数据源,然后配置线样式即可。

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

1. 把普通多边形当成插值输入

普通多边形没有连续数值字段,不能直接生成等值线。你需要的是带数值属性的采样点,或者已经栅格化的点网格。

2. value 字段不是数字

接口返回的数据经常把数值写成字符串,例如 "25.6"。在插值前建议统一转换:

samplePoints.features.forEach((feature) => {
  feature.properties.value = Number(feature.properties.value);
});

如果转换后出现 NaN,说明原字段存在空值、中文、单位符号或其他非法字符。

3. 坐标顺序写反

GeoJSON 坐标顺序是 [经度, 纬度],不是 [纬度, 经度]。坐标写反后,数据可能飞到其他国家,导致等值线无法显示。

4. 等值线分级不在数据范围内

如果数据值范围是 1242,而你设置的分级是 [100, 200, 300],自然不会生成有效等值线。建议先计算最小值和最大值。

const values = samplePoints.features.map(f => f.properties.value);
const minValue = Math.min(...values);
const maxValue = Math.max(...values);

console.log(minValue, maxValue);

5. 网格太密导致浏览器卡顿

前端 Turf.js 空间插值会占用浏览器 CPU。范围越大、网格越密、点越多,计算越慢。实际项目中要控制 cellSize,不要一开始就设置得很小。

6. 误以为 Turf.js 可以替代专业插值模型

Turf.js 适合前端快速分析和可视化,不适合替代完整 GIS 插值建模。如果结果用于规划审批、工程设计、环境评估等严肃场景,建议使用经过验证的专业流程。

方法比较:Turf.js、QGIS、PostGIS 和 Python GIS 怎么选

方法 适合场景 优点 限制
Turf.js 前端轻量级等值线展示、交互分析、原型验证 部署简单,直接在浏览器运行,适合 WebGIS 页面集成 复杂插值和精确裁剪能力有限,大数据量容易卡顿
QGIS 桌面端制图、教学实验、一次性数据处理 工具丰富,适合检查数据质量和人工调参 不适合高并发在线服务,自动化能力取决于流程设计
PostGIS 服务端空间查询、结果裁剪、在线接口输出 适合和业务数据库结合,空间裁剪和索引能力强 栅格插值流程需要额外设计,前端仍需渲染结果
Python GIS 批处理、科学计算、复杂空间插值 可结合 GeoPandas、Rasterio、SciPy、PyKrige 等库 需要后端环境,部署和性能优化成本更高

如果你的目标是“在网页上快速看到效果”,可以先用 Turf.js。如果你的目标是“得到可审查、可复现、可解释的插值成果”,建议把插值放到 QGIS、Python GIS 或后端服务中完成,前端只负责展示。

检查清单:发布前确认这些问题

  • 采样点是否都在合理空间范围内;
  • GeoJSON 坐标是否使用 [经度, 纬度]
  • 每个采样点是否都有数值型 value 字段;
  • 等值线分级是否落在数据最小值和最大值之间;
  • 研究区多边形是否闭合,是否存在自相交;
  • 网格间距是否适合当前地图尺度;
  • 前端计算是否会阻塞页面交互;
  • 等值线是否需要严格裁剪到多边形边界;
  • 结果是否只是展示用途,还是需要用于正式分析报告;
  • 是否需要在服务端缓存插值结果,避免每次打开页面都重新计算。

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

Q1:Turf.js 可以直接根据多边形生成等值线吗?

不可以。等值线需要连续数值或带数值属性的点网格。多边形只能作为分析范围或裁剪边界,不能单独生成等值线。

Q2:Turf.js isolines 和 interpolate 是什么关系?

interpolate 用来根据采样点估算网格点数值,isolines 用来根据这些网格点生成等值线。前者准备数据面,后者生成线结果。

Q3:为什么我生成的等值线很乱?

常见原因包括采样点太少、点分布不均、异常值没有处理、网格间距不合适、分级设置不合理。建议先在地图上显示采样点,并检查 value 字段是否可信。

Q4:Turf.js 空间插值结果可以用于正式 GIS 分析吗?

一般不建议直接用于正式分析。Turf.js 更适合前端快速展示。正式项目应根据数据特征选择 IDW、克里金、样条函数等合适模型,并记录参数和精度验证过程。

Q5:如何让 Turf.js 等值线只显示在多边形内部?

简单做法是用 booleanIntersects 筛选与多边形相交的线,再通过地图样式突出多边形范围。严格做法是在服务端使用 PostGIS、Shapely 或 GDAL 做几何裁剪。

Q6:前端生成等值线太慢怎么办?

可以增大网格间距、减少采样点数量、限制分析范围、使用 Web Worker 后台计算,或者把插值和裁剪放到后端完成,并缓存结果 GeoJSON。

结论:前端可以做等值线,但要理解边界

用 Turf.js 生成多边形范围内等值线的关键,不是“直接对多边形生成线”,而是建立正确流程:采样点提供数值,多边形限定范围,插值网格承接连续面,isolines 生成等值线,最后再筛选或裁剪到研究区。

对于 WebGIS 原型、数据预览和轻量级专题图,Turf.js多边形生成等距线方案足够实用;但对于精度要求高、数据量大、需要正式交付的 GIS 空间插值任务,建议将核心计算放到 QGIS、Python GIS 或 PostGIS 后端流程中完成。这样既能保证结果质量,也能让前端地图保持流畅。