基于经纬度的城市热点识别:如何正确设置DBSCAN的米级eps参数?
正确设置DBSCAN处理经纬度时的米级eps参数
这个问题我之前也碰到过,核心原因是你混淆了球面坐标和平面坐标下的距离计算逻辑——DBSCAN默认使用欧氏距离,但经纬度是球面坐标系下的坐标,直接用线性的欧氏距离来对应地面实际距离会出现误差,这就是为什么你设置了5米的eps,却还是有超过5米的点被聚类到一起。
问题出在哪?
你把5米除以地球半径得到的是弧度值,但DBSCAN默认用欧氏距离计算时,这个eps对应的是经纬度坐标之间的线性差值,不是球面上的大圆距离。比如在高纬度地区,相同的经纬度差值对应的地面距离会比赤道附近小很多,导致你的实际聚类阈值和预期的5米偏差很大。
两种正确的解决方案
方案1:使用Haversine距离(球面距离)
Haversine公式专门用于计算球面上两点间的大圆距离,scikit-learn的DBSCAN支持直接指定这个距离度量,步骤如下:
- 先把经纬度坐标转换成弧度制(Haversine函数要求输入是弧度)
- 把5米转换成千米(0.005km),再除以地球半径得到对应的弧度值作为eps
- 初始化DBSCAN时指定
metric='haversine'
示例代码:
import numpy as np from sklearn.cluster import DBSCAN # 假设你的坐标是[[纬度1, 经度1], [纬度2, 经度2], ...]的数组 coords = np.array(your_coordinate_list) # 转换为弧度制 coords_rad = np.radians(coords) earth_radius_km = 6371 # 5米对应的弧度值 epsilon_rad = 0.005 / earth_radius_km # 基于Haversine距离的DBSCAN聚类 clusterer = DBSCAN(eps=epsilon_rad, min_samples=10, metric='haversine') cluster_labels = clusterer.fit_predict(coords_rad)
⚠️ 注意:Haversine距离要求输入的坐标顺序是纬度在前,经度在后,别搞反了!
方案2:投影到平面坐标系(比如UTM)
把经纬度转换成平面米制坐标(比如UTM投影),这样DBSCAN默认的欧氏距离就直接对应地面的米距离,你可以直接设置eps=5。这种方法的精度更高,适合小范围(比如单个城市)的聚类。
示例代码(使用pyproj库):
import numpy as np from pyproj import Transformer from sklearn.cluster import DBSCAN # WGS84坐标系(EPSG:4326)转UTM投影,替换成你所在城市的UTM EPSG代码(比如北京是EPSG:32650) transformer = Transformer.from_crs("EPSG:4326", "EPSG:32633") # 转换坐标,得到(x, y),单位是米 coords_utm = np.array([transformer.transform(lat, lon) for lat, lon in your_coordinate_list]) # 直接用米作为eps单位 clusterer = DBSCAN(eps=5, min_samples=10) cluster_labels = clusterer.fit_predict(coords_utm)
⚠️ 注意:如果你的数据覆盖多个UTM带,UTM投影会产生断裂,这时候推荐用方案1。
怎么验证?
你可以挑几个被聚类到一起的点,用Haversine公式手动计算它们的地面距离,看看是否符合5米的阈值,这样就能确认参数设置是否正确了。
内容的提问来源于stack exchange,提问作者dkantor




