首页 > 解决方案 > 如何按距离对地理点列表进行聚类?

问题描述

我有一个点 P=[p1,...pN] 的列表,其中 pi=(latitudeI,longitudeI)。

使用 Python 3,我想找到一组最小的集群(P 的不相交子集),使得集群中的每个成员都在集群中每个其他成员的 20 公里范围内。

两点之间的距离是使用Vincenty 方法计算的。

为了更具体一点,假设我有一组点,例如

from numpy import *
points = array([[33.    , 41.    ],
       [33.9693, 41.3923],
       [33.6074, 41.277 ],
       [34.4823, 41.919 ],
       [34.3702, 41.1424],
       [34.3931, 41.078 ],
       [34.2377, 41.0576],
       [34.2395, 41.0211],
       [34.4443, 41.3499],
       [34.3812, 40.9793]])

然后我试图定义这个函数:

from geopy.distance import vincenty
def clusters(points, distance):
    """Returns smallest list of clusters [C1,C2...Cn] such that
       for x,y in Ci, vincenty(x,y).km <= distance """
    return [points]  # Incorrect but gives the form of the output

注意:许多问题集中在地理位置和属性上。我的问题仅针对位置。这是针对纬度/经度,而不是欧几里得距离。还有其他问题可以给出某种答案,但不能给出这个问题的答案(很多没有答案):

标签: pythoncluster-analysislatitude-longitudespatial-query

解决方案


这可能是一个开始。该算法尝试 k 意味着通过将 k 从 2 迭代到沿途验证每个解决方案的点数来对点进行聚类。你应该选择最小的数字。

它通过对点进行聚类然后检查每个聚类是否遵守约束来工作。如果任何集群不合规,解决方案将被标记为False,然后我们继续进行下一个集群数量。

因为 sklearn 中使用的 K-means 算法属于局部最小值,所以证明这是否是您正在寻找的解决方案是最好的还有待建立,但它可能是一个

import numpy as np
from sklearn.cluster import KMeans
from scipy.spatial.distance import cdist
import math

points = np.array([[33.    , 41.    ],
       [33.9693, 41.3923],
       [33.6074, 41.277 ],
       [34.4823, 41.919 ],
       [34.3702, 41.1424],
       [34.3931, 41.078 ],
       [34.2377, 41.0576],
       [34.2395, 41.0211],
       [34.4443, 41.3499],
       [34.3812, 40.9793]])


def distance(origin, destination): #found here https://gist.github.com/rochacbruno/2883505
    lat1, lon1 = origin[0],origin[1]
    lat2, lon2 = destination[0],destination[1]
    radius = 6371 # km
    dlat = math.radians(lat2-lat1)
    dlon = math.radians(lon2-lon1)
    a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \
        * math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2)
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
    d = radius * c

    return d

def create_clusters(number_of_clusters,points):
    kmeans = KMeans(n_clusters=number_of_clusters, random_state=0).fit(points)
    l_array = np.array([[label] for label in kmeans.labels_])
    clusters = np.append(points,l_array,axis=1)
    return clusters

def validate_solution(max_dist,clusters):
    _, __, n_clust = clusters.max(axis=0)
    n_clust = int(n_clust)
    for i in range(n_clust):
        two_d_cluster=clusters[clusters[:,2] == i][:,np.array([True, True, False])]
        if not validate_cluster(max_dist,two_d_cluster):
            return False
        else:
            continue
    return True

def validate_cluster(max_dist,cluster):
    distances = cdist(cluster,cluster, lambda ori,des: int(round(distance(ori,des))))
    print(distances)
    print(30*'-')
    for item in distances.flatten():
        if item > max_dist:
            return False
    return True

if __name__ == '__main__':
    for i in range(2,len(points)):
        print(i)
        print(validate_solution(20,create_clusters(i,points)))

一旦建立了一个基准,就必须在每个集群中集中更多一个,以确定它的点是否可以在不违反距离约束的情况下分配给其他人。

您可以用您选择的任何距离度量替换 cdist 中的 lambda 函数,我在我提到的 repo 中找到了大圆距离。


推荐阅读