Python中给定经纬度数据计算距离矩阵的有效方法
我有纬度和经度的数据,我需要计算包含位置的两个数组之间的距离矩阵。我用它来计算给定纬度和经度的两个位置之间的距离 下面是我的代码示例:Python中给定经纬度数据计算距离矩阵的有效方法,python,numpy,scipy,distance,Python,Numpy,Scipy,Distance,我有纬度和经度的数据,我需要计算包含位置的两个数组之间的距离矩阵。我用它来计算给定纬度和经度的两个位置之间的距离 下面是我的代码示例: import numpy as np import math def get_distances(locs_1, locs_2): n_rows_1 = locs_1.shape[0] n_rows_2 = locs_2.shape[0] dists = np.empty((n_rows_1, n_rows_2)) # The
import numpy as np
import math
def get_distances(locs_1, locs_2):
n_rows_1 = locs_1.shape[0]
n_rows_2 = locs_2.shape[0]
dists = np.empty((n_rows_1, n_rows_2))
# The loops here are inefficient
for i in xrange(n_rows_1):
for j in xrange(n_rows_2):
dists[i, j] = get_distance_from_lat_long(locs_1[i], locs_2[j])
return dists
def get_distance_from_lat_long(loc_1, loc_2):
earth_radius = 3958.75
lat_dif = math.radians(loc_1[0] - loc_2[0])
long_dif = math.radians(loc_1[1] - loc_2[1])
sin_d_lat = math.sin(lat_dif / 2)
sin_d_long = math.sin(long_dif / 2)
step_1 = (sin_d_lat ** 2) + (sin_d_long ** 2) * math.cos(math.radians(loc_1[0])) * math.cos(math.radians(loc_2[0]))
step_2 = 2 * math.atan2(math.sqrt(step_1), math.sqrt(1-step_1))
dist = step_2 * earth_radius
return dist
我的预期输出是:
>>> locations_1 = np.array([[34, -81], [32, -87], [35, -83]])
>>> locations_2 = np.array([[33, -84], [39, -81], [40, -88], [30, -80]])
>>> get_distances(locations_1, locations_2)
array([[ 186.13522573, 345.46610882, 566.23466349, 282.51056676],
[ 187.96657622, 589.43369894, 555.55312473, 436.88855214],
[ 149.5853537 , 297.56950329, 440.81203371, 387.12153747]])
性能对我来说很重要,我可以做的一件事是使用Cython
来加速循环,但如果我不必去那里,那就太好了
有没有一个模块可以做这样的事情?或任何其他解决方案?使用meshgrid替换双for循环更有效:
import numpy as np
earth_radius = 3958.75
def get_distances(locs_1, locs_2):
lats1, lats2 = np.meshgrid(locs_1[:,0], locs_2[:,0])
lons1, lons2 = np.meshgrid(locs_1[:,1], locs_2[:,1])
lat_dif = np.radians(lats1 - lats2)
long_dif = np.radians(lons1 - lons2)
sin_d_lat = np.sin(lat_dif / 2.)
sin_d_long = np.sin(long_dif / 2.)
step_1 = (sin_d_lat ** 2) + (sin_d_long ** 2) * np.cos(np.radians(lats1[0])) * np.cos(np.radians(lats2[0]))
step_2 = 2 * np.arctan2(np.sqrt(step_1), np.sqrt(1-step_1))
dist = step_2 * earth_radius
return dist
这只是对代码进行矢量化:
def new_get_distances(loc1, loc2):
earth_radius = 3958.75
locs_1 = np.deg2rad(loc1)
locs_2 = np.deg2rad(loc2)
lat_dif = (locs_1[:,0][:,None]/2 - locs_2[:,0]/2)
lon_dif = (locs_1[:,1][:,None]/2 - locs_2[:,1]/2)
np.sin(lat_dif, out=lat_dif)
np.sin(lon_dif, out=lon_dif)
np.power(lat_dif, 2, out=lat_dif)
np.power(lon_dif, 2, out=lon_dif)
lon_dif *= ( np.cos(locs_1[:,0])[:,None] * np.cos(locs_2[:,0]) )
lon_dif += lat_dif
np.arctan2(np.power(lon_dif,.5), np.power(1-lon_dif,.5), out = lon_dif)
lon_dif *= ( 2 * earth_radius )
return lon_dif
locations_1 = np.array([[34, -81], [32, -87], [35, -83]])
locations_2 = np.array([[33, -84], [39, -81], [40, -88], [30, -80]])
old = get_distances(locations_1, locations_2)
new = new_get_distances(locations_1,locations_2)
np.allclose(old,new)
True
如果我们看一下时间安排:
%timeit new_get_distances(locations_1,locations_2)
10000 loops, best of 3: 80.6 µs per loop
%timeit get_distances(locations_1,locations_2)
10000 loops, best of 3: 74.9 µs per loop
对于一个小例子来说,它实际上比较慢;但是,让我们看一个更大的例子:
locations_1 = np.random.rand(1000,2)
locations_2 = np.random.rand(1000,2)
%timeit get_distances(locations_1,locations_2)
1 loops, best of 3: 5.84 s per loop
%timeit new_get_distances(locations_1,locations_2)
10 loops, best of 3: 149 ms per loop
我们现在有40倍的加速。可能会在一些地方挤出更多的速度
编辑:进行了一些更新,以删除多余的位置,并明确说明我们没有改变原始位置阵列。您使用的哈弗森方程中有很多次优的东西。您可以修剪其中的一些,并最小化需要计算的正弦、余弦和平方根的数量。以下是我所能想到的最好的方法,在我的系统上,在1000和2000个元素的两个随机数组上运行的速度比Ophion的代码(在矢量化方面基本相同)快5倍左右:
def spherical_dist(pos1, pos2, r=3958.75):
pos1 = pos1 * np.pi / 180
pos2 = pos2 * np.pi / 180
cos_lat1 = np.cos(pos1[..., 0])
cos_lat2 = np.cos(pos2[..., 0])
cos_lat_d = np.cos(pos1[..., 0] - pos2[..., 0])
cos_lon_d = np.cos(pos1[..., 1] - pos2[..., 1])
return r * np.arccos(cos_lat_d - cos_lat1 * cos_lat2 * (1 - cos_lon_d))
如果您将两个阵列“按原样”提供给它,它会抱怨,但这不是一个bug,而是一个特性。基本上,此函数计算球体上最后一个维度上的距离,并在其余维度上广播。因此,您可以通过以下方式获得您想要的:
>>> spherical_dist(locations_1[:, None], locations_2)
array([[ 186.13522573, 345.46610882, 566.23466349, 282.51056676],
[ 187.96657622, 589.43369894, 555.55312473, 436.88855214],
[ 149.5853537 , 297.56950329, 440.81203371, 387.12153747]])
但它也可用于计算两个点列表之间的距离,即:
>>> spherical_dist(locations_1, locations_2[:-1])
array([ 186.13522573, 589.43369894, 440.81203371])
或在两个单点之间:
>>> spherical_dist(locations_1[0], locations_2[0])
186.1352257300577
这是受gufuncs工作原理的启发,一旦你习惯了它,我发现它是一种很棒的“瑞士军刀”编码风格,可以让你在许多不同的设置中重复使用单个函数。哈弗森公式是否为你的使用提供了足够的准确度?它可以关闭相当多。我认为,如果您使用python绑定,您将能够获得准确性和速度。请注意,pyproj可以直接在numpy坐标数组上工作。对于初学者来说,有许多事情需要多次计算——比如从度到弧度的转换,等等。通常,
*0.5
比/2
快,但我不知道这有多重要。但我想真正的问题是——循环是花费时间的事情,还是花在函数中的时间?你有没有尝试过对这一点进行基准测试?标杆管理始终是第一步……您是否偶然拥有了numba?实际系统有多大?一般来说,使用pos1=np.deg2rad(pos1)
或pos1=pos1*np.pi/180
?np.deg2rad
更快,但如果预先计算转换因子,也可以使两种方法运行得同样快,即k=np.pi/180;pos1=pos1*k
。在任何情况下,在那里花费的时间与函数调用中其他地方发生的事情无关。我以前没有见过这种技巧pos1[…,0]
,这种索引称为什么?。
是省略号
对象,它的用法在中进行了解释。基本上,它相当于为数组的每个维度建立索引所需的任意多个:
,以及为最后维度建立索引的最简单方法。哈弗森公式不就是球面三角吗?错误的来源是什么?地球缺乏球形?你知道pyproj
使用了什么修正吗?@Jaime:事实上,缺少球形,以及与完美椭球形状偏差的修正。这可能看起来没什么大不了的,如果你只是按距离排序,可能也没什么大不了的,但如果你在导航,这是一个大不了的。@Jamie-只是为了比较,在这个特殊情况下,球形结果和使用WGS84数据的结果之间的差异约为0.5英里(因此小于1%)。这对某些事情来说无关紧要,但对另一些事情却很重要。一个典型的例子是人们认为lat,long唯一地指定了一个位置。如果不知道lat、long所参考的基准面和椭球面,则只能到达~1km范围内。无论如何,我只是漫谈,但是proj.4实际上是这类事情的标准库,pyproj
是一个特别好的python绑定。这是一个有趣且有用的信息(+1),但就我而言,Haversine公式应该是不错的。