经纬度两点距离计算:Haversine 与 Vincenty 对比
计算两个经纬度坐标之间的距离,是 GIS 开发中的高频需求。一个常见误区是直接用欧氏距离公式计算经纬度差值——这是错误的,因为经纬度是球面坐标,不同纬度上单位经度对应的实际距离并不相同。本文对比两种主流算法。
为什么不能用欧氏距离
地球近似球体,经线在两极汇聚,纬线圈长度随纬度升高而减小。在赤道附近,1 度经度约 111 公里;而在纬度 60 度处,1 度经度仅约 55 公里。因此必须使用球面或椭球面距离公式。
Haversine 公式
Haversine 假设地球为完美球体(半径取约 6371km),基于球面三角学的半正矢函数推导而来。
推导思路
球面余弦定理在两点距离较近时会出现大数相消问题,精度下降。Haversine 用半正矢函数 hav(θ) = sin²(θ/2) 改写,避免了这一问题:
function haversine(p1, p2) {
const R = 6371008.8; // 地球平均半径(米)
const dLat = toRad(p2.lat - p1.lat);
const dLng = toRad(p2.lng - p1.lng);
const a = Math.sin(dLat / 2) ** 2 +
Math.cos(toRad(p1.lat)) * Math.cos(toRad(p2.lat)) *
Math.sin(dLng / 2) ** 2;
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // 返回米
}Haversine 计算简单、速度快,是大多数应用的首选。
Vincenty 公式
Vincenty 基于地球为旋转椭球体的假设,采用 WGS84 椭球参数:
- 长半轴
a = 6378137 m - 扁率
f = 1/298.257223563
该公式通过迭代法求解大地线方程,收敛后即可得到两点间的大地线距离:
function vincenty(p1, p2) {
const a = 6378137.0;
const f = 1 / 298.257223563;
const b = (1 - f) * a;
let lambda = (p2.lng - p1.lng) * DEG;
let iter = 0;
do {
// 迭代修正 lambda 直至收敛
// ...
} while (Math.abs(lambda - lambdaP) > 1e-12 && ++iter < 200);
// 根据收敛后的参数计算最终距离
return b * A_ * (sigma - deltaSigma);
}注意:Vincenty 在某些近乎对跖点的情况下可能不收敛,实际实现需加入兜底处理,通常回退到 Haversine。
精度与适用场景对比
- 模型:Haversine 假设球体,Vincenty 采用 WGS84 椭球。
- 相对误差:Haversine 约 0.3%-0.5%,Vincenty 精度可达毫米级。
- 计算量:Haversine 较小,Vincenty 较大(需迭代)。
- 适用场景:Haversine 适合快速估算、短距离;Vincenty 适合高精度测量。
总结来说,若只是估算距离,Haversine 足够;若涉及精确测量,应选 Vincenty。如需在线计算,可使用 两点距离计算工具。