gwa*_*dze 5 python django algorithm distance
我的数据库中有55249个城市.每一个都有纬度经度值.对于每个城市,我想计算到每个其他城市的距离,并存储不超过30公里的城市.这是我的算法:
# distance function
from math import sin, cos, sqrt, atan2, radians
def distance(obj1, obj2):
lat1 = radians(obj1.latitude)
lon1 = radians(obj1.longitude)
lat2 = radians(obj2.latitude)
lon2 = radians(obj2.longitude)
dlon = lon2 - lon1
dlat = lat2 - lat1
a = (sin(dlat/2))**2 + cos(lat1) * cos(lat2) * (sin(dlon/2))**2
c = 2 * atan2(sqrt(a), sqrt(1-a))
return round(6373.0 * c, 2)
def distances():
cities = City.objects.all() # I am using Django ORM
for city in cities:
closest = list()
for tested_city in cities:
distance = distance(city, tested_city)
if distance <= 30. and distance != 0.:
closest.append(tested_city)
city.closest_cities.add(*closest) # again, Django thing
city.save() # Django
Run Code Online (Sandbox Code Playgroud)
这可行,但需要花费大量时间.要花上好几周才能完成.我可以用任何方式加快速度吗?
你无法计算每对城市之间的距离.相反,您需要将城市放在空间分区数据结构中,以便进行快速最近邻查询. SciPy带有kd -tree实现,scipy.spatial.KDTree适用于此应用程序.
这里有两个困难.首先,scipy.spatial.KDTree使用点之间的欧几里德距离,但是你想要沿着地球表面使用大圆距离.其次,经度环绕,因此最近的邻居可能有相差360°的经度.如果采用以下方法,这两个问题都可以解决:
将这些ECEF坐标放入scipy.spatial.KDTree.
将您的大圆距离(例如,30公里)转换为欧几里德距离.
打电话scipy.spatial.KDTree.query_ball_point让城市在范围内.
下面是一些示例代码来说明这种方法.该功能geodetic2ecef来自David Parunakian的PySatel,并根据GPL许可.
from math import radians, cos, sin, sqrt
# Constants defined by the World Geodetic System 1984 (WGS84)
A = 6378.137
B = 6356.7523142
ESQ = 6.69437999014 * 0.001
def geodetic2ecef(lat, lon, alt=0):
"""Convert geodetic coordinates to ECEF."""
lat, lon = radians(lat), radians(lon)
xi = sqrt(1 - ESQ * sin(lat))
x = (A / xi + alt) * cos(lat) * cos(lon)
y = (A / xi + alt) * cos(lat) * sin(lon)
z = (A / xi * (1 - ESQ) + alt) * sin(lat)
return x, y, z
def euclidean_distance(distance):
"""Return the approximate Euclidean distance corresponding to the
given great circle distance (in km).
"""
return 2 * A * sin(distance / (2 * B))
Run Code Online (Sandbox Code Playgroud)
让我们组成五万个随机城市位置并将它们转换为ECEF坐标:
>>> from random import uniform
>>> cities = [(uniform(-90, 90), uniform(0, 360)) for _ in range(50000)]
>>> ecef_cities = [geodetic2ecef(lat, lon) for lat, lon in cities]
Run Code Online (Sandbox Code Playgroud)
把它们放入scipy.spatial.KDTree:
>>> import numpy
>>> from scipy.spatial import KDTree
>>> tree = KDTree(numpy.array(ecef_cities))
Run Code Online (Sandbox Code Playgroud)
查找伦敦约100公里范围内的所有城市:
>>> london = geodetic2ecef(51, 0)
>>> tree.query_ball_point([london], r=euclidean_distance(100))
array([[37810, 15755, 16276]], dtype=object)
Run Code Online (Sandbox Code Playgroud)
对于您查询的每个点,此数组包含距离内的城市数组r.每个邻居都作为其传递给您的原始数组中的索引KDTree.因此,在伦敦约100公里范围内有三个城市,即原始列表中索引为37810,15755和16276的城市:
>>> from pprint import pprint
>>> pprint([cities[i] for i in [37810, 15755, 16276]])
[(51.7186871990946, 359.8043453670437),
(50.82734317063884, 1.1422052710187103),
(50.95466110717763, 0.8956257749604779)]
Run Code Online (Sandbox Code Playgroud)
笔记:
您可以从示例输出中看到,正确发现了经度相差大约360°的邻居.
这种方法似乎足够快.在这里,我们发现前1000个城市30公里范围内的邻居,大约需要5秒钟:
>>> from timeit import timeit
>>> timeit(lambda:tree.query_ball_point(ecef_cities[:1000], r=euclidean_distance(30)), number=1)
5.013611573027447
Run Code Online (Sandbox Code Playgroud)
根据推断,我们期望在大约四分钟内找到所有50,000个城市30公里范围内的邻居.
我的euclidean_distance函数高估了与给定的大圆距离相对应的欧几里德距离(以免错过任何城市).对于某些应用程序来说这可能已经足够了 - 毕竟,城市不是点对象 - 但如果你需要更高的精度,那么你可以使用比如geopy中的一个大圆距离函数来过滤结果点.
| 归档时间: |
|
| 查看次数: |
2552 次 |
| 最近记录: |