最省事的是用geopy.distance.geodesic(),但默认单位是米而非公里,易误用;需先用空间索引(如PostGIS ST_DWithin)或经纬度矩形过滤缩小候选集,再精算距离,避免全量遍历。

用 geopy 算两点距离最省事,但别直接信默认单位
默认返回的是米,但很多人调用 geopy.distance.geodesic() 后没注意单位,拿结果当公里用,导致推荐半径错一倍。它底层用的是 WGS-84 椭球模型,精度够日常用(误差通常
- 必须显式指定单位:
distance.geodesic((lat1, lon1), (lat2, lon2)).km或.m - 如果传入的坐标顺序是
(lon, lat)(比如从 GeoJSON 来的),会算错——geodesic要求(lat, lon) - 批量计算时别在循环里反复初始化
geodesic对象,直接传元组对就行,没额外开销
GeoDjango 的 distance_lte 查附近的人,得先确认 SRID
查“5 公里内用户”,写 User.objects.filter(location__distance_lte=(center, D(km=5))) 看似简单,但实际跑出来可能漏人或拉回远超 5km 的点——大概率是数据库中 PointField 的 srid 和你传入的 center 不一致。
- PostGIS 默认用 SRID 4326(WGS-84),但如果你用
Point(x=lon, y=lat, srid=4326)创建中心点,而数据库字段定义漏写了srid=4326,Django 会按平面坐标算欧氏距离,单位是“度”,完全失真 -
D(km=5)必须和字段 SRID 匹配;若用的是 Web Mercator(SRID 3857),得换算成米,且不能直接用km - 加
order_by('location__distance')会触发 PostGIS 的ST_Distance,但要注意:没空间索引的话,全表扫,千万级表直接卡死
自己写 Haversine 公式?小心浮点和反三角函数边界
不用第三方库时,Haversine 是常见选择,但它在极点附近或经度跨越 180° 时容易出 nan 或偏差变大。Python 标准库 math 里的 sin/cos 输入是弧度,不是度——这是最常被跳过的转换。
- 必须先转:
lat1_rad = math.radians(lat1),否则结果毫无意义 - 用
math.atan2(math.sqrt(a), math.sqrt(1-a))算中心角,比直接math.asin更稳,能避开a > 1的浮点误差溢出 - 地球半径取 6371 km 是折中值;若业务集中在某区域(如只在中国),用本地椭球参数(如 CGCS2000)会更准,但 Haversine 本身不支持
性能差不是算法问题,是没预过滤
哪怕用了 GeoDjango 或 geopy,如果对全量用户逐个算距离,10 万用户就要 10 万次计算——这不是慢,是不可扩展。真实场景下,“附近的人”必须先靠空间索引或经纬度范围框定候选集,再精排。
立即学习“Python免费学习笔记(深入)”;
- 用 PostGIS 的
ST_DWithin(配合 GIST 索引)能秒级筛出地理邻近候选,比distance_lte更底层、更快 - 自己实现可先用简单的矩形过滤:
lat ± (5/111.3)(纬度每度约 111.3km),lon ± (5/(111.3 * cos(lat_rad))),再对这个小集合用 Haversine 精算 - Redis 的
GEO命令适合轻量级场景,但只支持球面距离,且数据结构固定,没法存额外属性做复合筛选
真正难的从来不是“怎么算一个距离”,而是“怎么让十万个人的实时距离查询不拖垮数据库”。索引、缓存、分片、降精度——这些和公式本身无关,但线上出问题,八成栽在这儿。










