Turf.js多边形如何生成航线?GIS自动规划实战技巧(含代码)
引言:如果你正在搜索“Turf.js多边形如何生成航线?GIS自动规划实战技巧(含代码)”,大概率是想把一个农田、湖面、巡检区域或无人机作业区的多边形,自动转换成一组平行的航线。这个需求在 WebGIS、无人机巡检、测绘外业规划和自动化空间分析中很常见,核心不是简单画几条线,而是要让航线覆盖多边形内部,并能控制间距、角度和边界裁剪。
本文用 Turf.js 演示一个可落地的方案:输入一个 GeoJSON 多边形,按指定方向生成平行扫描线,再用多边形裁剪得到内部航线。你可以把它接入 Leaflet、OpenLayers 或前端任务规划系统中。

背景:为什么 Turf.js 多边形生成航线不能只靠画线
在很多 GIS 项目中,多边形代表一个作业区域,例如:
- 无人机航测的农田地块。
- 水域巡检的湖面边界。
- 林区、矿区、园区的巡查范围。
- 机器人或船舶的自动扫描区域。
用户通常希望输入一个多边形后,系统自动输出类似“割草机路径”的航线。也就是多条平行线按照固定间距覆盖整个区域。这类路径在 GIS 中常被称为扫描线、覆盖路径或作业航线。
但直接在地图上按经纬度画平行线会遇到几个问题:
- 经纬度不是米,直接用度作为距离单位会导致间距不准。
- 多边形可能是斜的,默认东西向或南北向航线不一定合理。
- 航线必须被裁剪在多边形内部,不能飞出作业区。
- 凹多边形、窄边、洞、多部件区域会产生断裂线段。
- 前端 Turf.js 适合轻量规划,但不适合超大范围和高精度测量替代。
所以,Turf.js 多边形生成航线的正确思路是:先确定坐标与单位,再生成足够长的平行线,最后使用多边形裁剪得到有效航线。
原理:Turf.js 航线规划的核心流程
Turf.js 是一个常用的 JavaScript 空间分析库,可以在浏览器或 Node.js 中处理 GeoJSON 数据。它适合做缓冲区、距离、相交、裁剪、网格、面积等前端空间计算。
本文的 Turf.js 航线规划流程如下:
- 读取作业区 Polygon 或 MultiPolygon。
- 计算多边形的外包矩形 bbox。
- 根据 bbox 生成一组覆盖范围足够大的平行线。
- 将平行线按指定角度旋转。
- 用多边形裁剪平行线,只保留多边形内部线段。
- 输出 GeoJSON LineString 或 MultiLineString 作为航线。
这里有一个重要概念:航线间距必须使用真实距离单位,例如米。Turf.js 的很多距离函数支持 kilometers、meters、miles 等单位,但 GeoJSON 坐标通常仍然是 WGS84 经纬度。因此,中小范围项目可以使用 Turf.js 的测地计算;如果是大范围或高精度工程项目,建议先投影到本地米制坐标系后再生成航线。
简单理解:Turf.js 负责“空间计算”,GeoJSON 负责“数据表达”,而航线规划的关键是把距离、角度、裁剪三件事处理一致。
步骤:用 Turf.js 多边形生成航线的完整代码
1. 安装 Turf.js
如果你在前端项目中使用 npm,可以安装 Turf.js:
npm install @turf/turf
如果你只是做一个简单 HTML 页面,也可以通过 CDN 引入:
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js"></script>
下面的示例采用模块写法,更适合 Vue、React、Vite 或 Node.js 环境。
2. 准备一个 GeoJSON 多边形
示例多边形如下,坐标为 WGS84 经纬度。实际项目中,它可以来自用户在 WebGIS 上绘制的面,也可以来自后端接口、数据库或 GeoJSON 文件。
import * as turf from "@turf/turf";
const polygon = turf.polygon([[
[116.3800, 39.9000],
[116.3860, 39.9010],
[116.3880, 39.9060],
[116.3820, 39.9080],
[116.3770, 39.9040],
[116.3800, 39.9000]
]]);
注意:GeoJSON 多边形的首尾坐标必须闭合,也就是第一个点和最后一个点相同。如果没有闭合,很多空间分析函数会出现异常或结果不稳定。
3. 生成覆盖 bbox 的基础平行线
航线要覆盖整个多边形,所以不能只在多边形内部直接画线。更稳妥的方法是先用 bbox 扩大范围,生成一批足够长的线,再统一裁剪。
function createBaseLinesByBbox(polygon, spacingMeters) {
const bbox = turf.bbox(polygon);
const [minX, minY, maxX, maxY] = bbox;
const center = turf.center(polygon);
const widthKm = turf.distance([minX, minY], [maxX, minY], { units: "kilometers" });
const heightKm = turf.distance([minX, minY], [minX, maxY], { units: "kilometers" });
const diagonalKm = Math.sqrt(widthKm * widthKm + heightKm * heightKm);
const lineLengthKm = diagonalKm * 3;
const spacingKm = spacingMeters / 1000;
const lines = [];
const lineCount = Math.ceil((diagonalKm * 2) / spacingKm);
for (let i = -lineCount; i <= lineCount; i++) {
const offsetKm = i * spacingKm;
const start = turf.destination(center, lineLengthKm / 2, 270, { units: "kilometers" });
const end = turf.destination(center, lineLengthKm / 2, 90, { units: "kilometers" });
const movedStart = turf.destination(start, offsetKm, 0, { units: "kilometers" });
const movedEnd = turf.destination(end, offsetKm, 0, { units: "kilometers" });
lines.push(turf.lineString([
movedStart.geometry.coordinates,
movedEnd.geometry.coordinates
]));
}
return lines;
}
这段代码默认生成东西向线,再通过南北方向偏移形成多条平行线。后面我们会根据航线角度进行旋转。
4. 根据航线角度旋转平行线
很多作业区不是正南正北方向。比如一块农田整体倾斜 30 度,如果航线仍然水平生成,会产生更多短线段和转弯点。因此需要支持 angle 参数。
function rotateLines(lines, angle, pivot) {
return lines.map(line => turf.transformRotate(line, angle, {
pivot: pivot.geometry.coordinates
}));
}
这里的 angle 单位是度。通常可以让用户在界面输入航线角度,例如 0、30、45、90。也可以通过多边形最长边自动估算一个推荐角度,但这属于进一步优化。
5. 裁剪航线,只保留多边形内部部分
Turf.js 本身没有一个在所有复杂场景下都完美的“线按面裁剪”单函数。一个实用方法是:将每条线与多边形边界求交点,再按线段切分,判断每个小线段的中点是否在多边形内。
下面给出一个可读性较好的实现。它适合普通 Polygon、多数凹多边形和常见前端航线预规划场景。
function clipLineByPolygon(line, polygon) {
const boundary = turf.polygonToLine(polygon);
const intersections = turf.lineIntersect(line, boundary);
const coords = line.geometry.coordinates;
const points = [
turf.point(coords[0]),
turf.point(coords[coords.length - 1])
];
intersections.features.forEach(pt => points.push(pt));
const sortedPoints = points
.map(pt => {
const snapped = turf.nearestPointOnLine(line, pt, { units: "kilometers" });
return {
point: snapped,
location: snapped.properties.location
};
})
.sort((a, b) => a.location - b.location);
const segments = [];
for (let i = 0; i < sortedPoints.length - 1; i++) {
const p1 = sortedPoints[i].point;
const p2 = sortedPoints[i + 1].point;
const segment = turf.lineString([
p1.geometry.coordinates,
p2.geometry.coordinates
]);
const length = turf.length(segment, { units: "meters" });
if (length < 0.5) {
continue;
}
const midpoint = turf.along(segment, length / 2, { units: "meters" });
if (turf.booleanPointInPolygon(midpoint, polygon)) {
segments.push(segment);
}
}
return segments;
}
这一步是 Turf.js 多边形生成航线最容易出问题的地方。因为凹多边形可能让一条扫描线被切成多段,所以输出不是一条完整 LineString,而是一组航线线段。
6. 封装完整的航线生成函数
把上面的步骤组合起来,就可以得到一个完整函数:
function generateFlightLines(polygon, options = {}) {
const spacingMeters = options.spacingMeters || 30;
const angle = options.angle || 0;
const minLengthMeters = options.minLengthMeters || 2;
const center = turf.center(polygon);
const baseLines = createBaseLinesByBbox(polygon, spacingMeters);
const rotatedLines = rotateLines(baseLines, angle, center);
const clippedSegments = [];
rotatedLines.forEach(line => {
const segments = clipLineByPolygon(line, polygon);
segments.forEach(seg => {
const len = turf.length(seg, { units: "meters" });
if (len >= minLengthMeters) {
seg.properties = {
type: "flight-line",
length_m: Number(len.toFixed(2)),
spacing_m: spacingMeters,
angle: angle
};
clippedSegments.push(seg);
}
});
});
return turf.featureCollection(clippedSegments);
}
const flightLines = generateFlightLines(polygon, {
spacingMeters: 40,
angle: 25,
minLengthMeters: 5
});
console.log(JSON.stringify(flightLines, null, 2));
输出结果是一个 FeatureCollection,每个 Feature 是一条 LineString 航线。你可以直接把它添加到 Leaflet、OpenLayers 或 Mapbox GL JS 图层中。
7. 在 Leaflet 中显示航线
如果你使用 Leaflet,可以这样把结果加载到地图上:
L.geoJSON(polygon, {
style: {
color: "#2563eb",
weight: 2,
fillOpacity: 0.1
}
}).addTo(map);
L.geoJSON(flightLines, {
style: {
color: "#f97316",
weight: 2
},
onEachFeature: function (feature, layer) {
layer.bindPopup(
"长度:" + feature.properties.length_m + " 米<br>" +
"间距:" + feature.properties.spacing_m + " 米<br>" +
"角度:" + feature.properties.angle + "°"
);
}
}).addTo(map);
这样就完成了一个基础的 Turf.js 航线规划流程:多边形输入、航线生成、属性输出、地图展示。
常见坑:Turf.js 自动规划航线容易出错的地方
1. 经纬度坐标下直接按度计算间距
很多初学者会用 0.0001 度当作固定间距,这在 GIS 中不可靠。因为经度对应的实际距离会随纬度变化,同样的度数在北京和海南代表的米数不同。
建议使用 Turf.js 的 distance、destination、length 等函数,并明确设置 units 为 meters 或 kilometers。对于工程精度要求高的项目,建议后端使用 PostGIS、GDAL、GeoPandas 或 Shapely 在投影坐标系中处理。
2. 多边形没有闭合或几何无效
GeoJSON Polygon 的外环必须闭合。如果坐标顺序混乱、自相交或存在重复点,Turf.js 多边形生成航线可能得到空结果、断裂结果或异常结果。
处理前建议检查:
- 首尾点是否一致。
- 多边形是否自相交。
- 是否存在过短边和重复点。
- Polygon 与 MultiPolygon 是否被正确区分。
3. 线裁剪结果出现很多短碎线
凹多边形、锯齿状边界和复杂边界会切出很多短线。可以通过 minLengthMeters 过滤过短线段,也可以先对边界做适度简化。
const simplifiedPolygon = turf.simplify(polygon, {
tolerance: 0.00001,
highQuality: true
});
但简化会改变边界形状,不能用于权属边界、精确测绘成果等严肃场景。是否简化,要根据业务允许误差决定。
4. 航线角度和地图显示方向理解不一致
在 Turf.js 中,方位角通常以北为 0 度,顺时针增加。地图屏幕上的“水平线”“竖直线”与真实地理方向不一定完全一致,尤其在 Web Mercator 投影下更明显。
如果用户在界面上拖动方向线,建议把方向线的起点和终点转换成方位角,再传入航线生成函数。
5. MultiPolygon 没有拆分处理
如果作业区是 MultiPolygon,比如多个不相连地块,建议先拆分成多个 Polygon 分别生成航线,再合并结果。否则裁剪和排序可能变得复杂。
方法比较:前端 Turf.js、后端 PostGIS 与 Python GIS 怎么选
| 方法 | 适合场景 | 优点 | 限制 |
|---|---|---|---|
| Turf.js 前端生成 | WebGIS 交互预览、小范围航线规划、用户即时编辑 | 部署简单,反馈快,直接处理 GeoJSON | 复杂几何和大数据量时性能有限 |
| PostGIS 后端生成 | 生产系统、批量地块、数据库内空间计算 | 适合批处理,可结合空间索引和投影坐标系 | 开发和部署成本更高 |
| Python GeoPandas 或 Shapely | 离线处理、科研分析、批量任务规划 | 几何处理能力强,便于自动化脚本 | 不适合纯前端即时交互 |
| 专业无人机航线软件 | 真实飞行任务、相机参数、重叠率、航高控制 | 考虑飞行安全和设备参数 | 二次开发和 WebGIS 集成不一定方便 |
如果你的目标是“用户在网页上画一个面,立即看到航线效果”,Turf.js 是很合适的起点。如果目标是生产级无人机任务,还需要加入航高、相机视场角、旁向重叠率、航向重叠率、禁飞区、起降点和转弯半径等约束。
检查清单:上线前验证 Turf.js 多边形航线结果
- 输入几何:确认 Polygon 闭合、无明显自相交、坐标顺序正确。
- 坐标单位:确认间距、长度、偏移使用 meters 或 kilometers,而不是直接使用经纬度差值。
- 航线角度:测试 0、45、90、135 度等典型角度,确认方向符合业务理解。
- 边界裁剪:检查航线是否全部位于多边形内部,没有明显飞出边界。
- 短线过滤:设置合理的 minLengthMeters,避免输出无意义碎线。
- 复杂多边形:测试凹多边形、带窄边多边形、多地块场景。
- 性能:测试最大作业区面积和最小航线间距,避免浏览器卡死。
- 结果导出:确认输出 GeoJSON 属性中包含 spacing_m、angle、length_m 等必要字段。
- 地图展示:在 Leaflet、OpenLayers 或 Mapbox GL JS 中检查样式、顺序和弹窗属性。
- 业务复核:如果用于真实无人机飞行,必须交由专业飞控软件或安全规则再次校验。
FAQ:Turf.js 多边形生成航线常见问题
Turf.js 多边形如何生成航线最简单?
最简单的方法是:计算多边形 bbox,生成一组等间距平行线,按指定角度旋转,然后用多边形裁剪线段。本文代码就是这个思路,适合 WebGIS 中的航线预览和轻量自动规划。
Turf.js 航线规划能直接用于无人机飞行吗?
不建议直接用于真实飞行。Turf.js 可以生成空间几何意义上的航线,但真实无人机任务还需要考虑航高、速度、相机参数、重叠率、电池、地形、禁飞区、返航点和飞控格式。本文方案更适合作为 GIS 自动规划的基础模块。
为什么生成的航线间距看起来不均匀?
常见原因是直接用经纬度差值做间距,或者地图投影显示造成视觉差异。建议使用 Turf.js 的 destination 和 distance 按米计算。对于较大范围或高精度任务,应先转换到合适的投影坐标系。
凹多边形生成航线为什么会断成多段?
这是正常现象。一条扫描线穿过凹多边形时,可能进入、离开、再次进入多边形,因此会被裁剪成多条 LineString。后续如果要形成连续飞行路径,还需要做航线排序和连接。
如何让航线按照多边形长边方向生成?
可以提取多边形外环边界,计算每条边的长度和方位角,选择最长边的方位角作为航线角度。更高级的做法是计算最小外接矩形,并使用矩形长轴方向作为推荐航线方向。
Turf.js 可以处理带洞多边形吗?
可以处理 GeoJSON 中带内环的 Polygon,但线裁剪逻辑需要特别测试。对于带洞区域,航线不能穿过洞内区域。本文的中点判断方法在很多场景下可用,但复杂洞、多洞、多部件区域建议做更严格的几何验证。
生成航线后如何排序成连续作业路径?
本文重点是从多边形生成航线线段。若要形成连续路径,可以按照线段中心点在垂直方向上的位置排序,并采用蛇形顺序连接:第一条从左到右,第二条从右到左,依次交替。真实飞行还要考虑转弯半径和安全距离。
结论:Turf.js 适合做 WebGIS 航线自动规划的第一步
Turf.js 多边形生成航线的关键,不是简单把线画出来,而是处理好距离单位、航线角度、边界裁剪和结果验证。对于 WebGIS 中的前端交互预览,Turf.js 可以快速实现“画一个面,自动出航线”的效果。
实战中建议采用本文流程:先基于 bbox 生成足够长的平行线,再按角度旋转,最后按多边形裁剪并过滤短线。这样得到的 GeoJSON 航线结果结构清晰,便于在 Leaflet、OpenLayers 或后端服务中继续使用。
如果项目进入生产级无人机作业阶段,还需要把 Turf.js 航线规划结果与飞控参数、安全规则和专业航线格式结合起来。把 GIS 几何规划和飞行任务规划分开处理,系统会更可靠。