使用 Turf.js 计算 GeoJSON 两点距离与多边形面积

作者:函数茶馆时间:2026-11-03 08:12:52浏览:344

本文系统讲解如何使用 Turf.js 基于标准 GeoJSON 数据计算两点间测地距离与多边形表面积,包含包引入、坐标层级规范、核心 API 详尽参数、面积单位换算以及常见避坑要点。

在 Web 地理信息系统(WebGIS)与各类涉及位置服务的业务开发中,计算两点间的物理地面距离以及封闭区域的表面积是最基本也最高频的几何分析任务。由于地球是近似的曲面椭球体,简单的平面几何勾股定理无法直接应用于经纬度坐标。Turf.js 是 JavaScript 领域功能最完备的开源空间分析库,原生基于标准 GeoJSON 格式构建。本文将直接从工程实践出发,全面讲解如何使用 Turf.js 精确计算两点间的大圆测地距离与多边形表面积,解析背后的计算规则与单位换算逻辑,并指出坐标翻转、孔洞扣除等常见实战陷阱。
使用 Turf.js 计算 GeoJSON 两点距离与多边形面积
图 0:Turf.js 空间几何分析与核心函数应用概览

一、准备工作:GeoJSON 规范与 Turf.js 模块引入

Turf.js 所有的空间运算均以标准的 GeoJSON(RFC 7946)数据结构为核心载体。在开始编写代码前,必须建立一个不可动摇的准则:在 GeoJSON 规范中,点的坐标数组格式永远是 [经度, 纬度](即 [Longitude, Latitude])。这一顺序与部分 Web 地图框架(如 Leaflet 或百度地图 API 内部使用的 [lat, lng])恰好相反。如果颠倒了这一顺序,所有距离与面积计算都将产生不可挽回的巨大偏差。

在现代前端工程(Vite、Webpack、Next.js)或 Node.js 服务端环境中,推荐采用模块化按需引入的方式,只加载当前需要的计算包,从而大幅削减打包产物体积。

1. 按需安装与导入

# 在终端中安装专用独立包(强烈推荐,体积仅数千字节)
npm install @turf/distance @turf/area @turf/helpers

# 或者全量安装(包含完整空间分析工具箱,约 600KB+)
npm install @turf/turf

在业务模块中,可以通过 ESM 语法导入对应的函数:

// 方案 A:按需导入(推荐做法)
import distance from '@turf/distance';
import area from '@turf/area';
import { point, polygon } from '@turf/helpers';

// 方案 B:全量导入(适用于快速原型开发或 Node 脚本)
import * as turf from '@turf/turf';
使用 Turf.js 计算 GeoJSON 两点距离与多边形面积
图 1:GeoJSON 坐标层级标准与 Turf.js 测地空间分析模型

二、计算两点间距离:turf.distance

在地球表面上,连接两点间的最短路径并非直线,而是一段被称为“大圆航线”(Great-circle)的测地线弧长。turf.distance() 基于经典测地学半正矢公式(Haversine Formula)进行高精度推算,能够自动消除因纬度不同导致的经线收敛变形。

1. 函数签名与参数说明

turf.distance(from, to, [options])
  • from:起点对象。可以是一个纯坐标数组 [经度, 纬度],也可以是一个标准的 GeoJSON Point 或 Feature<Point>。
  • to:终点对象。格式与起点相同。
  • options.units:计算结果的长度单位,可选值包括:'kilometers'(默认值)、'meters'、'miles'、'degrees'、'radians'。

2. 完整代码示例

以下代码演示如何计算北京天安门广场与上海人民广场之间的实际地理测地距离:

import distance from '@turf/distance';
import { point } from '@turf/helpers';

// 1. 定义两个地点的 [经度, 纬度] 坐标
const beijingCoords = [116.3974, 39.9093];  // 北京天安门
const shanghaiCoords = [121.4737, 31.2304]; // 上海人民广场

// 2.1 直接传入坐标数组(默认单位:千米)
const distKm = distance(beijingCoords, shanghaiCoords);
console.log(`两地直线测地距离:${distKm.toFixed(2)} 公里`);
// 示例输出:两地直线测地距离:1068.32 公里

// 2.2 通过配置 options 指定单位为 'meters' (米)
const distMeters = distance(beijingCoords, shanghaiCoords, { units: 'meters' });
console.log(`米制精确结果:${Math.round(distMeters)} 米`);
// 示例输出:米制精确结果:1068322 米

// 2.3 使用标准 GeoJSON Feature 对象调用
const pt1 = point(beijingCoords, { name: 'Beijing' });
const pt2 = point(shanghaiCoords, { name: 'Shanghai' });
const distNmi = distance(pt1, pt2, { units: 'nauticalmiles' });
console.log(`海里结果:${distNmi.toFixed(2)} nmi`);
// 示例输出:海里结果:576.85 nmi
使用 Turf.js 计算 GeoJSON 两点距离与多边形面积
图 2:两点距离与多边形面积计算的标准操作流程

三、计算多边形表面积:turf.area

与距离计算不同,多边形表面积的度量需要在 WGS84 椭球体表面进行曲面积分。turf.area() 实现了 Chamberlin-Duquette 算法,通过对多边形各边在球面上的张角与球面过剩(Spherical Excess)进行累加积分,能够精确输出地理表面的真实物理面积。

1. GeoJSON 多边形环路规则

在调用 turf.area 之前,必须了解 GeoJSON 规范中关于多边形 coordinates 的三层嵌套结构:

  • 外环(Exterior Ring):数组的第一个元素 coordinates[0] 必须代表外边界。环线的第一个坐标点和最后一个坐标点必须完全一致,以构成拓扑闭合。
  • 内环(Interior Rings / Holes):数组后续的元素 coordinates[1...n] 代表内部扣除的孔洞。若有孔洞,其坐标同样必须首尾闭合。
  • 最小顶点数:每个环(无论是外环还是内环)必须至少包含 4 个坐标点(即 3 个不同的多边形角点加上 1 个重复的闭合点)。
重要注意:turf.area 的固定输出单位
turf.area() 不接受任何单位配置参数(units)!该函数始终固定返回以 平方米(Square Meters, m²) 为基准的数值。如果业务需要平方公里、公顷或市亩,必须由开发者在得到结果后进行数学折算。

2. 基础多边形与带孔洞多边形示例代码

import area from '@turf/area';
import { polygon } from '@turf/helpers';

// 场景一:计算普通简单四边形地块的面积
// 注意:第 1 个坐标点与最后 1 个坐标点严格一致闭合
const simpleParcelCoords = [
  [
    [116.38, 39.90],
    [116.42, 39.90],
    [116.42, 39.93],
    [116.38, 39.93],
    [116.38, 39.90]  // 闭合点
  ]
];

const simplePoly = polygon(simpleParcelCoords);
const areaInSqMeters = area(simplePoly);

console.log(`地块面积:${areaInSqMeters.toFixed(2)} 平方米`);
console.log(`折合平方公里:${(areaInSqMeters / 1000000).toFixed(4)} km²`);
console.log(`折合市亩:${(areaInSqMeters * 0.0015).toFixed(2)} 亩`);
// 示例输出:
// 地块面积:11467520.18 平方米
// 折合平方公里:11.4675 km²
// 折合市亩:17201.28 亩

// 场景二:计算带内部镂空湖泊的多边形公园净面积
const parkWithLakeCoords = [
  // 外环:公园外部红线边界
  [
    [120.10, 30.20],
    [120.16, 30.20],
    [120.16, 30.26],
    [120.10, 30.26],
    [120.10, 30.20]
  ],
  // 内环:公园中央的湖泊(自动扣除面积)
  [
    [120.12, 30.22],
    [120.14, 30.22],
    [120.14, 30.24],
    [120.12, 30.24],
    [120.12, 30.22]
  ]
];

const parkPoly = polygon(parkWithLakeCoords);
const netParkArea = area(parkPoly);
console.log(`扣除中央湖泊后的公园陆地净面积:${(netParkArea / 10000).toFixed(2)} 公顷`);
使用 Turf.js 计算 GeoJSON 两点距离与多边形面积
图 3:GeoJSON 多边形多环嵌套拓扑与 turf.area() 计算原理

四、综合实践:地块周长边长量算与面积复核

在实际土地测绘或外卖配送网格划分中,往往需要同时获取多边形每一段边界的长度(周长)以及整体围合面积。下面的完整实用脚本展示了如何组合使用 distance 与 area,并对输入数据进行防御性校验:

import distance from '@turf/distance';
import area from '@turf/area';
import { polygon } from '@turf/helpers';

/**
 * 分析多边形地块特征:周长、各边长及净面积
 * @param {Array} rings GeoJSON 坐标环数组
 */
function analyzeCadastralParcel(rings) {
  const outerRing = rings[0];
  
  // 1. 防御性检查:至少 4 个点
  if (!outerRing || outerRing.length < 4) {
    throw new Error('无效的 GeoJSON 多边形:外环顶点数少于 4');
  }

  // 2. 防御性检查:首尾闭合
  const firstPt = outerRing[0];
  const lastPt = outerRing[outerRing.length - 1];
  if (firstPt[0] !== lastPt[0] || firstPt[1] !== lastPt[1]) {
    throw new Error('多边形环路未闭合,首尾坐标点必须相同');
  }

  // 3. 逐段计算外环各边长与总周长
  let totalPerimeterMeters = 0;
  const edgeDistances = [];

  for (let i = 0; i < outerRing.length - 1; i++) {
    const edgeLen = distance(outerRing[i], outerRing[i + 1], { units: 'meters' });
    edgeDistances.push(edgeLen);
    totalPerimeterMeters += edgeLen;
  }

  // 4. 计算多边形净面积
  const polyFeature = polygon(rings);
  const parcelAreaSqMeters = area(polyFeature);

  return {
    vertexCount: outerRing.length - 1,
    edgeDistancesMeters: edgeDistances.map(d => Number(d.toFixed(2))),
    perimeterMeters: Number(totalPerimeterMeters.toFixed(2)),
    areaSqMeters: Number(parcelAreaSqMeters.toFixed(2)),
    areaHectares: Number((parcelAreaSqMeters / 10000).toFixed(4))
  };
}

// 执行测试地块数据
const testRings = [[
  [121.400, 31.200],
  [121.405, 31.200],
  [121.405, 31.205],
  [121.400, 31.205],
  [121.400, 31.200]
]];

const report = analyzeCadastralParcel(testRings);
console.log('地块量算分析报告:', report);
// 示例输出:
// 地块量算分析报告: {
//   vertexCount: 4,
//   edgeDistancesMeters: [ 476.47, 555.97, 476.47, 555.97 ],
//   perimeterMeters: 2064.88,
//   areaSqMeters: 264906.12,
//   areaHectares: 26.4906
// }
使用 Turf.js 计算 GeoJSON 两点距离与多边形面积
图 4:turf.distance 与 turf.area 特性对比及避坑速查表

五、工程避坑与常见问题排查

在实际开发中,几何计算相关的 Bug 往往由于坐标系、数组维度或投影混乱导致。遇到异常数值时,可按以下清单进行快速排查:

1. 坐标顺序倒置(高危问题)

现象:计算出来的距离比预期小几百倍,或者算出的多边形面积出现离奇的巨大数值,甚至在地图上标注时点落在了南极洲或大洋深处。
原因:错误地传入了 [lat, lng] 纬度在前。我国大部分区域纬度在 18°N ~ 53°N,经度在 73°E ~ 135°E。颠倒后,坐标 [31.2, 121.4] 变成了经度 31.2°(位于东非或中东沙漠),纬度 121.4°(属于超出 -90°~90° 的非法纬度)。
对策:在调用 Turf 函数前,通过断言核实 coord[0](经度)是否在 -180 到 180 之间,coord[1](纬度)是否在 -90 到 90 之间。

2. 投影坐标系与地理坐标系混淆

现象:传入点坐标后,Turf 报错 RangeError 或计算结果为 NaN。
原因:数据源采用了 EPSG:3857(Web 墨卡托投影坐标,数值类似 [13524580.12, 3658921.44],单位为米),而 Turf 的大部分测地函数(包括 distance 与 area)要求必须是 EPSG:4326(WGS84 经纬度,单位为十进制度数)。
对策:若手头数据为投影平面坐标,应先通过 proj4js 等库转换为经纬度,再传入 Turf.js 处理。

3. 多边形未严格闭合

现象:部分版本的 turf.area 对未闭合环路会静默计算错误面积,或者抛出无法解析的异常。
对策:在解析由用户手工在前端界面绘制的多边形时,若最后一个点不等于第一个点,务必在进入计算前执行 ring.push(ring[0]) 补齐闭合点。

4. 常用面积换算系数速查

目标单位 转换公式(基于 turf.area 返回的平方米) 实际应用场景
平方千米 / 平方公里 (km²) sqMeters / 1,000,000 行政区划、流域面积、大尺度地理分析
公顷 (ha) sqMeters / 10,000 森林公园、大型农场、工业园区规划
市亩 sqMeters * 0.0015 或 sqMeters / 666.6667 国内土地流转、农田承包、不动产地籍
平方英尺 (sq ft) sqMeters * 10.7639 跨国地产交易、海外建筑测量

掌握 turf.distance 与 turf.area 的基本机制后,即可在前端页面或后端 Node.js 管道中快速构建出可靠、高精度的空间分析功能。遵循“经度在前、确保闭合、明确单位”这三大准则,能够规避绝大多数地理几何计算中的隐蔽故障。

作者最新文章
相关文章
更多