haversine公式是计算地球表面两点间球面距离最常用且精度够用的方法,它基于地球近似为半径6371公里的球体模型,通过经纬度差值和三角函数精确计算大圆距离,避免平面欧氏距离在长距离下高达上千公里的误差。

用 Haversine 公式算两点间球面距离最靠谱
地球是近似球体,平面欧氏距离在经纬度上误差极大(比如北京到东京,直线距离算出来可能偏差上千公里)。Haversine 是实际项目中最常用、精度够用(误差通常
注意:别直接用 math.Sin 和 math.Cos 对度数调用——必须先转弧度,否则结果完全错误。
-
lat1, lon1, lat2, lon2都要先用lat * math.Pi / 180转成弧度 - 地球平均半径取
6371km(若需英里,换为3959) - 公式核心是
a = sin²(Δφ/2) + cos(φ1)⋅cos(φ2)⋅sin²(Δλ/2),再套c = 2 ⋅ atan2(√a, √(1−a))
func HaversineDistance(lat1, lon1, lat2, lon2 float64) float64 {
φ1 := lat1 * math.Pi / 180
φ2 := lat2 * math.Pi / 180
Δφ := (lat2 - lat1) * math.Pi / 180
Δλ := (lon2 - lon1) * math.Pi / 180
<pre class="brush:php;toolbar:false;">a := math.Sin(Δφ/2)*math.Sin(Δφ/2) +
math.Cos(φ1)*math.Cos(φ2)*math.Sin(Δλ/2)*math.Sin(Δλ/2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return 6371 * c // 单位:公里}
为什么不用 math.Acos 直接算球面角距离
math.Acos 理论上也能推导球面距离(用点积),但实际中极易因浮点舍入导致输入超出 [-1, 1] 范围,触发 NaN 或 panic。尤其当两点几乎重合或几乎对跖时,acos(x) 对 x 的微小扰动极度敏感。
-
Haversine用atan2避开了反余弦的数值不稳定性 - 如果硬要用
Acos,必须手动 clamp 输入:math.Max(-1, math.Min(1, x)) - 实测中,
Haversine在lat1 == lat2 && lon1 == lon2时天然返回 0,无需额外判断
需要更高精度?考虑 Vincenty 但别自己手写
如果应用涉及航空导航、测绘级精度(亚米级),Haversine 的球体假设不够——地球是椭球体。此时该用 Vincenty 迭代算法,但它有收敛问题,且 Golang 标准库不提供。
- 直接用现成包更稳妥:
github.com/olegorp/geo或github.com/paulmach/go.geo都实现了Vincenty - 自己实现容易漏掉边界 case(如极点、经度跨越 180°)
- 性能上,
Vincenty比Haversine慢 3–5 倍,普通 LBS 场景没必要
单位、参数顺序和常见调用陷阱
函数签名里 lat, lon 顺序必须和输入数据一致——很多 GeoJSON 或 API 返回的是 [lon, lat](注意!),而人类习惯说“北纬、东经”,即 [lat, lon]。传反了距离会错得离谱。
- 确认输入源格式:Google Maps API 是
lat, lon;GeoJSON 是lon, lat - 返回单位默认是公里;若需米,乘以
1000,别改半径常量(避免精度损失) - 传入
NaN或Inf会导致NaN输出,建议加简单校验:!math.IsNaN(lat1) && !math.IsInf(lat1, 0)
最常被忽略的是:没检查输入是否在有效范围内——纬度必须 ∈ [-90, 90],经度 ∈ [-180, 180]。超限值会让 Haversine 计算出荒谬结果,而且不会报错。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











