Python:加快地理比较

时间:2011-07-11 20:58:14

标签: python optimization distance geography

我编写了一些包含嵌套循环的代码,其中内循环执行大约150万次。我在这个循环中有一个函数,我正在尝试优化。我做了一些工作,并得到了一些结果,但我需要一些输入来检查我所做的事情是否合理。

一些背景知识:

我有两个地理点集合(纬度,经度),一个相对较小的集合和一个相对庞大的集合。对于小集合中的每个点,我需要找到大集合中的最近点。

显而易见的方法是使用hasrsine公式。这里的好处是距离绝对准确。

from math import radians, sin, cos, asin, sqrt

def haversine(point1, point2):
    """Gives the distance between two points on earth.
    """
    earth_radius_miles = 3956
    lat1, lon1 = (radians(coord) for coord in point1)
    lat2, lon2 = (radians(coord) for coord in point2)
    dlat, dlon = (lat2 - lat1, lon2 - lon1)
    a = sin(dlat/2.0)**2 + cos(lat1) * cos(lat2) * sin(dlon/2.0)**2
    great_circle_distance = 2 * asin(min(1,sqrt(a)))
    d = earth_radius_miles * great_circle_distance
    return d

然而,在我的机器上运行这150万次大约需要9秒(根据timeit)。由于准确的距离并不重要,我只需要找到最近的点,我决定尝试其他一些功能。

毕达哥拉斯定理的一个简单实现给了我大约30%的加速。考虑到我可以做得更好,我写了以下内容:

def dumb(point1, point2):
    lat1, lon1 = point1
    lat2, lon2 = point2
    d = abs((lat2 - lat1) + (lon2 - lon1))

这让我有了10倍的改进。但是,现在我担心这不会保留三角不等式。

所以,我的最后一个问题是两个问题:我想要一个运行速度与dumb一样快但仍然正确的函数。 dumb会工作吗?如果没有,有关如何改善我的半身功能的任何建议?

6 个答案:

答案 0 :(得分:19)

这是numpy非常擅长的计算方式。您可以在单个计算中计算单个点与整个数据集之间的距离,而不是遍历整个大型坐标集。通过下面的测试,您可以获得一个数量级的速度增加。

以下是使用haversine方法进行的一些时序测试,dumb方法(不确定是什么)以及我的numpy hasrsine方法。它计算两点之间的距离 - 一个位于弗吉尼亚州,一个位于加利福尼亚州,位于2293英里之外。

from math import radians, sin, cos, asin, sqrt, pi, atan2
import numpy as np
import itertools

earth_radius_miles = 3956.0

def haversine(point1, point2):
    """Gives the distance between two points on earth.
    """
    lat1, lon1 = (radians(coord) for coord in point1)
    lat2, lon2 = (radians(coord) for coord in point2)
    dlat, dlon = (lat2 - lat1, lon2 - lon1)
    a = sin(dlat/2.0)**2 + cos(lat1) * cos(lat2) * sin(dlon/2.0)**2
    great_circle_distance = 2 * asin(min(1,sqrt(a)))
    d = earth_radius_miles * great_circle_distance
    return d

def dumb(point1, point2):
    lat1, lon1 = point1
    lat2, lon2 = point2
    d = abs((lat2 - lat1) + (lon2 - lon1))
    return d

def get_shortest_in(needle, haystack):
    """needle is a single (lat,long) tuple.
        haystack is a numpy array to find the point in
        that has the shortest distance to needle
    """
    dlat = np.radians(haystack[:,0]) - radians(needle[0])
    dlon = np.radians(haystack[:,1]) - radians(needle[1])
    a = np.square(np.sin(dlat/2.0)) + cos(radians(needle[0])) * np.cos(np.radians(haystack[:,0])) * np.square(np.sin(dlon/2.0))
    great_circle_distance = 2 * np.arcsin(np.minimum(np.sqrt(a), np.repeat(1, len(a))))
    d = earth_radius_miles * great_circle_distance
    return np.min(d)


x = (37.160316546736745, -78.75)
y = (39.095962936305476, -121.2890625)

def dohaversine():
    for i in xrange(100000):
        haversine(x,y)

def dodumb():
    for i in xrange(100000):
        dumb(x,y)

lots = np.array(list(itertools.repeat(y, 100000)))
def donumpy():
    get_shortest_in(x, lots)

from timeit import Timer
print 'haversine distance =', haversine(x,y), 'time =',
print Timer("dohaversine()", "from __main__ import dohaversine").timeit(100)
print 'dumb distance =', dumb(x,y), 'time =',
print Timer("dodumb()", "from __main__ import dodumb").timeit(100)
print 'numpy distance =', get_shortest_in(x, lots), 'time =',
print Timer("donumpy()", "from __main__ import donumpy").timeit(100)

这就是打印的内容:

haversine distance = 2293.13242188 time = 44.2363960743
dumb distance = 40.6034161104 time = 5.58199882507
numpy distance = 2293.13242188 time = 1.54996609688

numpy方法需要 1.55 秒来计算相同数量的距离计算,因为使用函数方法计算需要 44.24 秒。通过将一些numpy函数组合到一个语句中,你可能会获得更多的加速,但它将成为一个冗长,难以阅读的行。

答案 1 :(得分:5)

您可以考虑某种图形散列,即快速找到最近的点,然后对它们进行计算。例如,您可以创建一个统一的网格,并将(大型集合的)点分配到网格创建的箱中。

现在,从小集合中得到一个点,你需要处理更少量的点(即仅在相关箱中的点)

答案 2 :(得分:2)

你写的公式(d = abs(lat2-lat1)+(lon2-lon1))不保留三角形不等式:如果你找到lat,lon for wich d是min,你找不到最近的点,但是最接近两条对角直线的点在你正在检查的点上交叉!

我认为你应该用lat和lon命令大量的点数(这意味着:(1,1),(1,2),(1,3)......(2,1),(2, 2)等 然后使用炮手方法找到纬度和经度方面的一些最接近的点(这应该非常快,它将使cpu时间与ln2(n)成比例,其中n是点数)。您可以轻松地执行此操作,例如:选择要检查的点周围10x10的正方形中的所有点,这意味着:找到lat(gunner方法)中-10到+10的所有点,然后再次在lon(gunner方法)中从-10到+10的那些。现在你有一个非常小的数据进行处理,它应该非常快!

答案 3 :(得分:2)

abs(lat2 - lat1) + abs(lon2 - lon1)是1范数或曼哈顿指标,因此三角不等式成立。

答案 4 :(得分:1)

我遇到了类似的问题并决定敲击一个Cython功能。 在我的2008 MBP上,它每秒可以进行大约1.2M的迭代。进行型式检查可进一步提高25%。毫无疑问,进一步的优化是可能的(以清晰度为代价)。

您可能还想查看scipy.spatial.distance.cdist功能。

from libc.math cimport sin, cos, acos

def distance(float lat1, float lng1, float lat2, float lng2):
    if lat1 is None or lat2 is None or lng1 is None or lng2 is None: return None
    cdef float phi1
    cdef float phi2
    cdef float theta1
    cdef float theta2
    cdef float c
    cdef float arc

    phi1 = (90.0 - lat1)*0.0174532925
    phi2 = (90.0 - lat2)*0.0174532925
    theta1 = lng1*0.0174532925
    theta2 = lng2*0.0174532925

    c = (sin(phi1)*sin(phi2)*cos(theta1 - theta2) + cos(phi1)*cos(phi2))
    arc = acos( c )
    return arc*6371

答案 5 :(得分:0)

最快的方法是避免为每对点计算一个函数,假设你的相对较小的集合不是很小。

有些数据库可以使用地理索引(mysql,oracle,mongodb ..),或者自己实现一些东西。

您可以使用python-geohash。对于较小集合中的每个文档,您需要快速查找较大集合中的文档集,这些文档共享来自geohash.neighbors的散列,以获得具有匹配项的最长散列大小。您需要使用适当的数据结构进行查找,否则这将很慢。

为了找到点之间的距离,简单方法的误差随着点之间的距离的增加而增加,并且还取决于纬度。例如,请参阅http://www.movable-type.co.uk/scripts/gis-faq-5.1.html