直接用math.cos/sin算经纬度距离会出错,因地球是球面,欧氏距离不适用;须用Haversine公式——先转弧度,再按球面三角计算大圆距离,并防浮点越界、设半径6371km。

为什么直接用 math.cos 和 math.sin 算经纬度距离会出错
地球是球面,平面直角坐标系下的欧氏距离在经纬度上完全不适用。哪怕两点只差0.1度,在高纬度地区误差也能到几十公里。Haversine 公式本质是球面三角解法,它把地球近似为标准球体(半径取6371 km),用弧度制和正弦/余弦组合算大圆距离——这才是地理坐标间“最短路径”的数学基础。
常见错误现象:lat1, lon1 = 40.7128, -74.0060; lat2, lon2 = 35.6895, 139.6917,如果直接套用 sqrt((lat1-lat2)**2 + (lon1-lon2)**2),结果单位不明、数值荒谬(约167),根本没法换算成公里。
- 必须先把经纬度转为弧度:用
math.radians(),别手写* 3.14159 / 180,容易精度丢失 - 公式里所有角度输入都得是弧度,否则
math.sin和math.cos返回的是错的值 - 注意纬度(lat)和经度(lon)顺序:Haversine 对参数顺序敏感,别把 lon 当 lat 传
用纯 Python 写 Haversine 函数要注意哪几个参数细节
核心就是四步:转弧度 → 算纬度差和经度差的正弦平方 → 套公式 → 乘地球半径。但实际写时最容易漏掉两个关键点:一是 math.sqrt 前没做防负数处理,二是没限制 a 值范围导致 math.asin 报 ValueError: math domain error。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 计算中间变量
a = sin²(Δφ/2) + cos(φ1)⋅cos(φ2)⋅sin²(Δλ/2)后,加一句a = min(a, 1.0),避免浮点误差让a略大于1 - 地球半径推荐用
6371.0(km),若需英里就换3958.8,别硬编码6378(那是赤道半径,Haversine 默认球体模型) - 函数签名建议明确单位:
def haversine(lat1, lon1, lat2, lon2, unit='km'):,别默认返回“无单位数字”
简短示例:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
import math
def haversine(lat1, lon1, lat2, lon2):
R = 6371.0
φ1 = math.radians(lat1)
φ2 = math.radians(lat2)
Δφ = math.radians(lat2 - lat1)
Δλ = math.radians(lon2 - lon1)
a = math.sin(Δφ/2)**2 + math.cos(φ1)*math.cos(φ2)*math.sin(Δλ/2)**2
a = min(a, 1.0) # 防浮点越界
c = 2 * math.asin(math.sqrt(a))
return R * c用 geopy 的 distance 模块反而更慢?什么场景该自己写
geopy.distance.geodesic 确实精度更高(用 Vincenty 或 Geodesic 算椭球体),但它内部做了大量校验、单位转换和异常捕获,调用开销明显。如果你只是批量算几万对城市中心坐标(比如快递始发地-目的地),用纯 Haversine 能快 3–5 倍。
使用场景判断:
- 需要亚米级精度(如测绘、无人机航迹)→ 必须用
geopy.distance.geodesic或pyproj.Geod - 做粗略排序、聚类、邻近搜索(比如“找50km内门店”)→ 自己写的 Haversine 完全够用,且可向量化
- 输入含非法坐标(如
lat=95)→geopy会抛ValueError,而自制函数可提前if not (-90
NumPy 向量化 Haversine 时为什么结果全是 nan
用 np.sin、np.cos 替代 math.sin 是提速关键,但一旦数组里有 None、字符串或超范围经纬度(如 lon=200),np.sin 会静默返回 nan,且这个 nan 会污染整个计算链——最后 np.sqrt(nan) 还是 nan,很难定位源头。
排查要点:
- 先用
np.isfinite(arr).all()检查经纬度数组是否干净 - 确保所有输入已用
np.radians()转换,别混用math.radians(它不支持数组) - 计算
a后立刻用np.clip(a, 0, 1)截断,比min(a, 1.0)更适合数组 - 如果原始数据来自 CSV,注意
pandas.read_csv可能将空字段读成NaN,得先df.dropna(subset=['lat', 'lon'])
Haversine 看似简单,但真实数据里总有 lat 写成 latitude 字段名、坐标系用 WGS84 却误当 GCJ02、或者一批坐标批量导入时小数点后多了一个空格——这些不会报错,但会让距离结果系统性偏移。动手前,先用两组已知距离的点(比如北京到上海约1068km)跑一次验证值,比啥都管用。

















