如何加快搜索最近的地理点?
Posted
技术标签:
【中文标题】如何加快搜索最近的地理点?【英文标题】:How to speed up the search of the closest geopoint? 【发布时间】:2021-11-19 22:03:44 【问题描述】:目前,我使用以下代码来查找最近的给定地理点 -
def get_closest_stops(lat, lng, data=db.STOPS):
def distance(lat1, lon1, lat2, lon2):
p = 0.017453292519943295
a = 0.5 - cos((lat2-lat1)*p)/2 + cos(lat1*p)*cos(lat2*p) * (1-cos((lon2-lon1)*p)) / 2
return 12742 * asin(sqrt(a))
v = 'lat': lat, 'lng': lng
return sorted(data.values(), key=lambda p: distance(v['lat'],v['lng'],p['lat'],p['lng']), reverse=False)
这里是db.STOPS
:
STOPS =
'1282': 'lat': 59.773368, 'lng': 30.125112, 'image_osm': '1652229/464dae0763a8b1d5495e', 'name': 'name 1', 'descr': '',
'1285': 'lat': 59.941117, 'lng': 30.271756, 'image_osm': '965417/63af068831d93dac9830', 'name': 'name 2', 'descr': '',
...
dict 包含大约 7000 条记录,搜索速度很慢。 有什么方法可以加快搜索速度? 我只需要 5 个结果。我可以重新排序字典。如果需要,我什至可以创建字典的副本。
【问题讨论】:
R-树。您的数据库可能已经支持它 对不起,@Marat。这不是数据库,只是纯 python dict。 然后按照增加曼哈顿距离的顺序进行迭代(计算起来超级便宜),保持 5 个最近点的简短列表。一旦曼哈顿距离高于短名单中的最远点,就停止。您介意为此使用 Pandas 吗? 第二个想法,仅仅将 Pandas 带到桌面上可能会因为矢量化而实现 10 倍的加速 我在 Google App Engine 上运行代码,不确定那里是否支持 Pandas cloud.google.com/appengine/docs/standard/python/tools/…。 【参考方案1】:TLDR:numpy 的速度提高了 10 倍,分区距离又提高了 3 倍,总共约 30..50 倍。使用曼哈顿距离作为一种更便宜的近似值来消除不可行的候选者具有大致相同的效果,但不太直观。
这是一个colab,其中包含我的实验的完整代码,结果如下:
# Setup:
import numpy as np
DTYPE = np.float64
STOPS_NP = np.array([(stop['lat'], stop['lng']) for stop in STOPS.values()], dtype=[('lat', DTYPE), ('lng', DTYPE)])
简单实现:每个循环 8 毫秒
def distance(lat1, lon1, lat2, lon2):
p = 0.017453292519943295
a = 0.5 - cos((lat2-lat1)*p)/2 + cos(lat1*p)*cos(lat2*p) * (1-cos((lon2-lon1)*p)) / 2
return 12742 * asin(sqrt(a))
def get_closest_stops(lat, lng, data=STOPS):
v = 'lat': lat, 'lng': lng
return sorted(data.values(), key=lambda p: distance(v['lat'],v['lng'],p['lat'],p['lng']), reverse=False)
移植到 Numpy 的相同算法:使用 float64 的每个循环 1.3ms,使用 float32 的 500us(13 倍改进!):
def distance_np(lat1, lon1, lat2, lon2):
# numpy version of distance
p = 0.017453292519943295
a = 0.5 - np.cos((lat2-lat1)*p)/2 + np.cos(lat1*p)*np.cos(lat2*p) * (1-np.cos((lon2-lon1)*p)) / 2
# multiplication by constant does not affect sorting
return 12742 * np.arcsin(np.sqrt(a))
def get_closest_stops_np(lat, lng, data=STOPS_NP):
distances = distance_np(lat, lng, data['lat'], data['lng'])
indexes = distances.argsort()
return data[indexes]
使用部分数组排序来获得前 5 个候选 - 500us float64、160us float32(50 倍改进!):
def get_closest_stops_np_argpartition(lat, lng, data=STOPS_NP, n=5):
distances = distance_np(lat, lng, data['lat'], data['lng'])
indexes = np.argpartition(distances, n)[:n]
return data[indexes]
使用更便宜的距离近似值来避免计算更昂贵的精确距离 - 我最初在 cmets 中提出的建议:600us
def get_closest_stops_np_early_stop(lat, lng, data=STOPS_NP, n=5):
# shortcut: use cheaper approximate distance as an upper bound,
# create a much smaller shortlist, then sort by real (expensive) distance
distances = manhattan_distance_np(lat, lng, data['lat'], data['lng'])
indexes = distances.argsort()
nth_large_manhattan_distance = distances[indexes[n-1]]
# manhattan distance is at most 1.5x higher than real distance
# on a plane; perhaps should be adjusted for the globe
max_manhattan_for_equivalent_distance = nth_large_manhattan_distance * 1.5
n_feasible_candidates = np.searchsorted(distances, max_manhattan_for_equivalent_distance, sorter=indexes)
data_shortlist = data[indexes[:n_feasible_candidates]]
# the rest is the same as get_closest_stops_np
distances = distance_np(lat, lng, data_shortlist['lat'], data_shortlist['lng'])
indexes = distances.argsort()
return data_shortlist[indexes[:n]]
所有四种算法在模拟数据上产生相同的结果。 请注意,地球上的经度通常小于纬度,因此此模拟图上的水平比例与垂直比例不匹配:
【讨论】:
【参考方案2】:使用多线程,并打破可用线程数的记录
希望这个帮助Tutorial point page about multithreading
【讨论】:
希望有帮助。 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center。以上是关于如何加快搜索最近的地理点?的主要内容,如果未能解决你的问题,请参考以下文章