Haversine公式最靠谱,因GPS坐标是球面经纬度,欧氏距离会严重失真;C#无内置地理距离函数,Haversine考虑地球曲率,误差通常

用 Haversine 公式算两点距离最靠谱
GPS 坐标是球面经纬度,直接套欧氏距离会错得离谱——赤道上 1 度经度 ≈ 111km,到了北极就趋近于 0。C# 没有内置地理距离函数,Haversine 是工业级通用解法:它考虑地球曲率,误差通常
实操建议:
-
Haversine输入单位必须是弧度,别忘了把degrees * Math.PI / 180转换 - 地球平均半径用
6371.0km(或3959.0英里),别硬套6378.1(赤道半径)——除非你专算赤道附近短距离 - 避免对
Math.Asin传入略大于 1 的值(浮点误差导致),加一层Math.Min(1.0, ...)防崩
double haversine(double lat1, double lon1, double lat2, double lon2) {
var R = 6371.0;
var dLat = (lat2 - lat1) * Math.PI / 180;
var dLon = (lon2 - lon1) * Math.PI / 180;
var a = Math.Sin(dLat/2) * Math.Sin(dLat/2) +
Math.Cos(lat1 * Math.PI / 180) * Math.Cos(lat2 * Math.PI / 180) *
Math.Sin(dLon/2) * Math.Sin(dLon/2);
var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1-a));
return R * c;
}别用 System.Device.Location.GeoCoordinate 算远距离
这个类的 GetDistanceTo 方法底层用的是球面余弦定理,不是 Haversine,在两点纬度差异大(比如跨极地)或距离极近(NaN。
使用场景限制明显:
- 仅适用于 .NET Framework 4.0+ 或 .NET Core 3.0+(旧版 UWP 项目才自带)
- 输入坐标若含
double.NaN或超范围(纬度 > 90),不抛异常而是静默返回错误结果 - 性能比手写
Haversine低约 15%——它做了额外的参数校验和单位转换
真要用,至少包一层校验:
if (coord1.Latitude is >= -90 and <= 90 &&
coord2.Latitude is >= -90 and <= 90) {
return coord1.GetDistanceTo(coord2);
}
Math.Acos 和 Math.Atan2 选哪个?
Haversine 有两种等效写法:一种用 Math.Acos,一种用 Math.Atan2。前者更直白,后者更稳——因为 Math.Acos(x) 在 x 略大于 1 时直接抛 ArgumentOutOfRangeException,而 Math.Atan2 对输入容忍度高,且能天然处理 a == 0 的边界情况。
所以实际编码中:
- 永远优先用
Math.Atan2(Math.Sqrt(a), Math.Sqrt(1-a)),别省那两行代码 - 如果硬要用
Math.Acos,必须前置截断:var x = Math.Min(1.0, Math.Max(-1.0, ...)) - 别信“
Math.Acos更快”——现代 CPU 上差异可忽略,稳定性才是关键
精度陷阱:WGS84 坐标系不能当平面用
有人把经纬度当 XY 直接套 Math.Sqrt(dx*dx + dy*dy) * 111.195(111.195 是赤道 1°≈km 的粗略换算),这在城市内几百米范围内误差可能不到 1%,但一旦跨纬度(比如北京到广州),误差立刻飙升到 5% 以上——因为经度缩放因子随纬度变化:cos(lat)。
简单验证方法:
- 拿已知距离的两个点(比如机场跑道两端坐标),跑一遍你的算法,再查公开航程数据对比
- 如果结果恒比真实值小,大概率用了线性近似;如果忽大忽小,可能是弧度没转或
cos里传了度数 - 别依赖地图 SDK 返回的距离——有些 Web API(如老版 Bing Maps)默认返回驾车距离,不是直线
球面计算没法绕开,该转弧度就转,该调 Atan2 就调。少一步,结果就偏一公里。


















