地图可视化实践录:空间分析库Turf.js的学习
本文介绍空间分析库Turf.js的接口,并给出实例。
知识点
Turf .js是空间分析的 JavaScript 库。它包含传统的空间操作、用于创建 GeoJSON 数据的辅助函数以及数据分类和统计工具。其涵盖空间几何图形的测量、关系判断、坐标变换和偏移、辅助方法等具体场景,功能强大。
因为Turf.js使用GeoJSON格式处理所有地理数,因此其坐标顺序为经度,纬度,与leaflet刚好相反。
Turf.js 不仅能在后端的Node.js环境中使用,也可在浏览器环境中使用,比如与 Leaflet、MapBox GL JS 等地图库配合。本文基于原来的综合示例,学习一些入门级的接口,由于是在html中调用接口,所以给出实例和对应展示效果。详细接口说明建议参考官网。
实践
下载和使用
参考https://turf.nodejs.cn/docs/getting-started文档下载,编写本文时(2025年11月),最新版本为7.2.0,具体地址:https://unpkg.com/@turf/turf@7.2.0/turf.min.js。
在html文件引入:
<script src="mymap/maptool/turf-7.2.0.min.js"></script>
点、线、面
turf.js的点、线、面,与leaflet有较多相似之处。
点:turf.point([坐标], {可选属性});。
线:turf.lineString([坐标], {可选属性})。
面:turf.polygon([坐标], {可选属性})。
核心代码:
const points = [[108.397293,22.738823],[108.481064,22.724890]];// 点// 注:turf.point 返回点,第二个参数可指定properties参数,drawOneLayer 函数会自动判断name,故加之const point1 = turf.point(points[0], {name: "五象湖公园"});var pointLayer1 = drawOneLayer(point1);addToLayers(pointLayer1);const point2 = turf.point(points[1], {name: "南宁园博园"});var pointLayer2 = drawOneLayer(point2);addToLayers(pointLayer2);// 线const linePoints = [[108.316269, 22.838212],[108.326569, 22.807200],[108.347168, 22.779347],[108.352661, 22.759720],[108.379440, 22.738190]];var line = turf.lineString(// [// [108.316269, 22.838212],// [108.326569, 22.807200],// [108.347168, 22.779347],// [108.352661, 22.759720],// [108.379440, 22.738190]// ],linePoints,{color: "#FF00FF", name: "折线"});// 可指定为miles或kilometers,默认后者,可不写var totalDistance = turf.length(line, { units: "kilometers"});var lineLayer = drawOneLayer(line);addToLayers(lineLayer);// 面const polygon = turf.polygon([[[108.3, 22.86], [108.4, 22.86], [108.4, 22.9], [108.3, 22.9], [108.3, 22.86]]], {color: "#0000FF"});var polygonLayer = drawOneLayer(polygon);addToLayers(polygonLayer);
注意,drawOneLayer和addToLayers是为了显示绘制效果而调用的。效果如下图所示。

里程、面积计算
计算长度:turf.length(line),默认返回长度单位为千米。
计算里程:turf.distance(point1, point2),默认返回长度单位为千米。
计算面积:turf.area(polygon),返回面积单位为平方米。
核心代码:
// 计算两点间距离const disTurf = turf.distance(point1, point2, { units: 'kilometers' });info +=`2点间距离(turfs): ${disTurf.toFixed(3)} 公里\r\n`;const disLeaflet = calcDistance(points, "km")info +=`2点间距离(leaflet): ${disLeaflet.toFixed(3)} 公里\r\n`;// 可指定为miles或kilometers,默认后者,可不写var totalDistance = turf.length(line, { units: "kilometers"});info += `线段长度: ${totalDistance.toFixed(3)} 公里\r\n`const disLeaflet2 = calcDistance(linePoints, "km")info +=`线段长度(leaflet): ${disLeaflet2.toFixed(3)} 公里\r\n`;// 计算面积const area = turf.area(polygon); // 平方米info += `面积: ${area.toFixed(3)} 平方米`;
turf使用distance计算2个经纬度的里程,用length即可计算经纬度数组的里程。leaflet也有类似的接口,其使用distanceTo计算2个经纬度里程,但没有length功能接口,下面实现之:
// 计算经纬度数组points的总里程
function calcDistance(points, units = 'km') {let totalDistance = 0;for (let i = 0; i < points.length - 1; i++) {const [lng1, lat1] = points[i];const [lng2, lat2] = points[i + 1];const distance = L.latLng(lat1, lng1).distanceTo(L.latLng(lat2, lng2));totalDistance += distance;}// 单位转换const unitConvert = {'m': (d) => d,'km': (d) => d / 1000,'mile': (d) => d / 1609.34};const convert = unitConvert[units] || unitConvert['km'];return convert(totalDistance);
}
效果图如下:
可以看到,使用turfs和leaflet计算相同经纬度里程的结果是相同的。

外接矩形、中心点
计算最小外接矩形:turf.bbox。
计算中心点:turf.center。
核心代码:
function turf_demo2() {var info = ""// 最小外接矩形var bbox = turf.bbox(gxGeoJson); // gxGeoJson 在 450000-广西壮族自治区.js 中定义var polygon = turf.bboxPolygon(bbox, {}, {color: "#0000FF"});var polygonLayer = drawOneLayer(polygon);addToLayers(polygonLayer);info += `广西最小外接矩形为 ${bbox}\r\n`// 计算中心点const center = turf.center(gxGeoJson);const latlng = center.geometry.coordinates;const point = turf.point(latlng, {name: "中心点"});var pointLayer = drawOneLayer(point);addToLayers(pointLayer);info += `中心点为 ${latlng}\r\n`// 计算总面积var totalArea = turf.area(gxGeoJson);totalArea = totalArea / 10000000000;info += `广西总面积 ${totalArea.toFixed(3)} 万平方千米`return info
}
效果图:

根据GeoJSON使用turf.area计算总面积,为23.81万平方千米,与官方部门提供的数据(23.76万平方千米)非常接近。
贝塞尔曲线Bezier
调用turf.bezierSpline可以将一条线段变成平滑的塞尔曲线。
核心代码:
// 坐标点var points = [[108.390427,22.922982],[108.476257,22.929306],[108.524323,22.895786],[108.500290,22.841376]];// 原始线var line = turf.lineString(points,{color: "#0000FF", name: "折线"});addToLayers(drawOneLayer(line));var line1 = turf.lineString(points,);var curved = turf.bezierSpline(line1, {properties: {color: "#FF0000", name: "折线"}});addToLayers(drawOneLayer(curved));
效果图如下,其中蓝色为原始线段,红色为贝塞尔曲线。

根据官方手册,可带resolution和sharpness参数。值越高,效果越好,下面代码将参数调低数值。
var curved = turf.bezierSpline(line1, {resolution: 1000, sharpness: 0.5,properties: {color: "#FF0000", name: "折线"}});addToLayers(drawOneLayer(curved));
效果比默认的差一些,如图:

空间关系判断
点与线关系的判断:turf.booleanPointOnLine,点在线上,返回true。
点与面关系的判断:booleanPointInPolygon,点在面内,返回true。
面与面关系的判断:booleanOverlap,如有重叠,返回true。
为减少篇幅,只列出核心代码,完整代码参考文后工程仓库代码。
点线关系判断代码片段:
const points = [[108.481064,22.724890],[108.397293,22.738823]];const point1 = turf.point(points[0], {name: "南宁园博园"});addToLayers(drawOneLayer(point1));const point2 = turf.point(points[1], {name: "五象湖公园"});addToLayers(drawOneLayer(point2));var line1 = turf.lineString([[108.481064,22.724890],[108.397293,22.738823],[108.369827,22.774599]], {color: "#0000FF"});addToLayers(drawOneLayer(line1));// 判断点与线的关系const isPointOnLine1 = turf.booleanPointOnLine(point1, line1);// 不在线上的点const outPoint = turf.point([108.45,22.77], {name: "其它点"});addToLayers(drawOneLayer(outPoint));const isPointOnLine2 = turf.booleanPointOnLine(outPoint, line1);info += `点1与线1关系:${isPointOnLine1}, 其它与线1关系: ${isPointOnLine2}`;
效果:

点面关系判断代码:
polygon1 = turf.polygon([[[108.3, 22.86], [108.4, 22.86], [108.4, 22.9], [108.3, 22.9], [108.3, 22.86]]], {color: "#0000FF"});polygon2 = turf.polygon([[[108.349228,22.918555],[108.378067,22.963451],[108.476257,22.950806],[108.479004,22.893888],[108.440552,22.855929],//[108.417892,22.909701], // 删除此坐标,两多边形相交[108.349228,22.918555]]], {color: "#0000FF"});addToLayers(drawOneLayer(polygon1));addToLayers(drawOneLayer(polygon2));inPoint = turf.point([108.304596,22.895786], {name: "内点"})addToLayers(drawOneLayer(inPoint));const isPointInPoly = turf.booleanPointInPolygon(inPoint, polygon1);const isOverlap = turf.booleanOverlap(polygon1, polygon2);info += `点与面关系:${isPointInPoly}, 面与面的关系: ${isOverlap}\r\n`;
效果:

几何运算
交集:turf.intersect
并集:turf.union
差集:turf.difference
上述几个函数均返回GeoJson格式,可直接用之显示在地图上。
核心代码:
polygon1 = turf.polygon([[[108.3, 22.86], [108.4, 22.86], [108.4, 22.9], [108.3, 22.9], [108.3, 22.86]]], {color: "#0000FF"});polygon2 = turf.polygon([[[108.325195,22.887562],[108.327942,22.872379],[108.422012,22.864155],[108.446732,22.879971],[108.417892,22.891990],[108.325195,22.887562]]], {color: "#0000FF"});// 交集const intersection = turf.intersect(turf.featureCollection([polygon1, polygon2]), {properties: {color: "#FF0000"}});// 并集const union = turf.union(turf.featureCollection([polygon1, polygon2]), {properties: {color: "#008000"}});// 差集const difference = turf.difference(turf.featureCollection([polygon1, polygon2]), {properties: {color: "#FF0000"}});
效果如下所示,从上而下依次是原始2个多边形,交集、并集、差集。

需要说明的是,笔者之前使用的是turf6版本,当前使用turf7版本,两版本之间部分接口参数是不同的。以下交集接口的对比:
v6版本:
const intersection = turf.intersect(polygon1, polygon2);
v7版本:
const intersection = turf.intersect(turf.featureCollection([polygon1, polygon2]));
发现这些变化,也是经历了一些曲折的过程。由于笔者旧的工程有使用计算2个多边形交集的接口,更新turf版本后抛出异常,查了很久才发现是接口参数变化了。
小结
turf还有很多其它高阶功能,但GIS于笔者而言,只是工作的一小部分而已,目前使用到的函数,只限于其中一小部分,比如,计算外接矩形、多边形交集,等。其它接口,留待后续有需要时再研究了。
代码
文中列出了主要的代码片段,另有相关的工程demo,已上传到github仓库。因不定时更新,代码不一定与文中严格对应,以代码仓库为准。如使用,请自行根据实际情况修改。
仓库地址:https://github.com/latelee/mapdemo 。
本文涉及文件:100.综合示例.html,函数为turf_demo1、turf_demo2。
附
- turf官方API说明(英文版):https://turfjs.org/docs/api/along
- turf官方文档:http://turfjs.org/docs/getting-started
- turfAPI说明(中文版但较旧):https://turf.nodejs.cn/docs/api/along
