这一篇主要讲讲如何在实际运用中编写dbscan算法。dbscan算法主要的目的就是找到最大密度相连点的集合。那么它必然涉及到3个子算法:
1) dbscan主流程
2) 如何确定两个GPS数据点的距离
3) 如何合并簇
我们逐一解决。首先说说GPS数据点的距离该如何确定。这个问题感觉看似简单(居然有人用勾股定理和经纬度与距离关系来计算,我只能脑洞大,但没法用)。如果要精确计算两点之间的关系,单纯利用球心,半径,点三者关系利用三角和反三角函数来计算也不大对的。毕竟地球不是那种半径一致的球体,而是个两极稍扁、赤道略鼓的不规则球体。所以,还是用专业的知识去求解把。这里提供两种解决方法。
方法一: 既然我们地图选用的是百度地图。那自然就可以选用他的计算工具。(http://developer.baidu.com/map/library.htm)如图:
方法二:利用球心,半径(假设地球是规则球体),点关系来求解。这个计算结果可想而知,没法太精准的。当然方法一是否精准我也无法保证。基于方法二的算法大致相同。这里提供个参考资料链接。http://www.movable-type.co.uk/scripts/latlong.html 这个提供的资料很详细了。直接用吧。
接下来,我们主要谈dbscan的具体实现。首先我们定义一个结构体用来存放数据类型。如下:
class DataObject:
def __init__(self, longitude, latitude):
self.latitude = latitude
self.longitude = longitude
self.isVisited = False
self.clusterId = 0 #簇序号 0表示未分类 -1 表示噪声点 正数为序号
def setVisited(self):
self.isVisited = True
def isVisited(self):
return self.isVisited
def getClusterId(self):
return self.clusterId
def getLongitude(self):
return self.longitude
def getLatitude(self):
return self.latitude
def setClusterId(self, id):
self.clusterId = id
这样设置的好处是合并簇的时候我只要通过设置cluseterId即可实现,简单吧。
dbscan算法实现如下:
def dbscan(self):
"""
@brief: dbscan核心函数 方法: 遍历每个点 判断是否访问,若未,则获取它的Eps邻域数据
若Neps的大小>=MinPts 则 进行扩张簇
:return:不返回数据
"""
self.clusterId = 0 #簇序号
for p in self.data:
if p.isVisited == False:
p.setVisited()
neighbors = self.getNeighbors(p)
if neighbors.__len__() <=0: #噪声点
p.setClusterId(-1)
elif neighbors.__len__() >= self.MinPts:#核心点 (边界点不做处理)
if p.getClusterId() <=0:
self.clusterId += 1
self.expandCluster(p, neighbors, self.clusterId)
else :
id = p.getClusterId() #如果p核心点已经分类了,那么返回它原本属于的簇序号 进行扩张
self.expandCluster(p, neighbors, id)
def getNeighbors(self, pData):
"""
@brief 找到点P的所有满足Eps邻域的点 注意这里采用浅复制 方便修改原数据
:param pData: 待查找的点
:return: 返回所有满足点
"""
DataRet = []
for tmp in self.data:
if self.getDistance(tmp, pData) <= self.Eps:
DataRet.append(tmp)
return DataRet
扩张簇的方法如下:
def expandCluster(self, pData, neighbors, clusterId):
"""
@brief 扩张簇的数据
:param pData: 核心点 需要设置簇ID
:param neighbors: 核心点p的所有Eps邻域点
:param clusterId: 簇的ID号
:return: 不返回数据
"""
pData.setClusterId(clusterId)
for qData in neighbors:
if qData.isVisited == False:
qData.setVisited()
#接下来查找 qData的所有Eps邻域的数据
DataRst = self.getNeighbors(qData)
if DataRst.__len__() >= self.MinPts:
for q in DataRst:
if q.getClusterId()<=0: #说明未分配 或者为噪声点
q.setClusterId(clusterId)
if qData.getClusterId()<=0:
qData.setClusterId(clusterId)