我有一个包含两列(纬度,经度)的csv文件,其中包含超过500万行的地理位置数据。
我需要确定与列表中任何其他点不在5英里范围内的点,并将所有内容输出回另一个具有额外列(CloseToAnotherPoint
)的CSV,如果有另一列True
积分在5英里以内,False
如果没有。
以下是我使用geopy
的当前解决方案(不进行任何网络调用,仅使用函数计算距离):
from geopy.point import Point
from geopy.distance import vincenty
import csv
class CustomGeoPoint(object):
def __init__(self, latitude, longitude):
self.location = Point(latitude, longitude)
self.close_to_another_point = False
try:
output = open('output.csv','w')
writer = csv.writer(output, delimiter = ',', quoting=csv.QUOTE_ALL)
writer.writerow(['Latitude', 'Longitude', 'CloseToAnotherPoint'])
# 5 miles
close_limit = 5
geo_points = []
with open('geo_input.csv', newline='') as geo_csv:
reader = csv.reader(geo_csv)
next(reader, None) # skip the headers
for row in reader:
geo_points.append(CustomGeoPoint(row[0], row[1]))
# for every point, look at every point until one is found within 5 miles
for geo_point in geo_points:
for geo_point2 in geo_points:
dist = vincenty(geo_point.location, geo_point2.location).miles
if 0 < dist <= close_limit: # (0,close_limit]
geo_point.close_to_another_point = True
break
writer.writerow([geo_point.location.latitude, geo_point.location.longitude,
geo_point.close_to_another_point])
finally:
output.close()
正如您可能从中看到的那样,这种解决方案非常缓慢。事实上,我让它运行 3天这么慢,但它仍然没有完成!
我已经考虑过将数据拆分成块(多个CSV文件或其他东西),这样内部循环就不必查看其他每一点,但是我必须要考虑如何确保每个部分的边界检查其相邻部分的边界,这看起来过于复杂,我担心这将是一件令人头疼的事情,而不是它的价值。
那么关于如何加快这一点的任何指示?
答案 0 :(得分:3)
正如Python calculate lots of distances quickly的答案所指出的,这是k-D树的经典用例。
另一种方法是使用扫描线算法,如How do I match similar coordinates using Python?
的答案所示这里是适合您的问题的扫描线算法。在我的笔记本电脑上,它需要&lt; 5分钟可以通过5M随机点。
import itertools as it
import operator as op
import sortedcontainers # handy library on Pypi
import time
from collections import namedtuple
from math import cos, degrees, pi, radians, sqrt
from random import sample, uniform
Point = namedtuple("Point", "lat long has_close_neighbor")
miles_per_degree = 69
number_of_points = 5000000
data = [Point(uniform( -88.0, 88.0), # lat
uniform(-180.0, 180.0), # long
True
)
for _ in range(number_of_points)
]
start = time.time()
# Note: lat is first in Point, so data is sorted by .lat then .long.
data.sort()
print(time.time() - start)
# Parameter that determines the size of a sliding lattitude window
# and therefore how close two points need to be to be to get flagged.
threshold = 5.0 # miles
lat_span = threshold / miles_per_degree
coarse_threshold = (.98 * threshold)**2
# Sliding lattitude window. Within the window, observations are
# ordered by longitude.
window = sortedcontainers.SortedListWithKey(key=op.attrgetter('long'))
# lag_pt is the 'southernmost' point within the sliding window.
point = iter(data)
lag_pt = next(point)
milepost = len(data)//10
# lead_pt is the 'northernmost' point in the sliding window.
for i, lead_pt in enumerate(data):
if i == milepost:
print('.', end=' ')
milepost += len(data)//10
# Dec of lead_obs represents the leading edge of window.
window.add(lead_pt)
# Remove observations further than the trailing edge of window.
while lead_pt.lat - lag_pt.lat > lat_span:
window.discard(lag_pt)
lag_pt = next(point)
# Calculate 'east-west' width of window_size at dec of lead_obs
long_span = lat_span / cos(radians(lead_pt.lat))
east_long = lead_pt.long + long_span
west_long = lead_pt.long - long_span
# Check all observations in the sliding window within
# long_span of lead_pt.
for other_pt in window.irange_key(west_long, east_long):
if other_pt != lead_pt:
# lead_pt is at the top center of a box 2 * long_span wide by
# 1 * long_span tall. other_pt is is in that box. If desired,
# put additional fine-grained 'closeness' tests here.
# coarse check if any pts within 80% of threshold distance
# then don't need to check distance to any more neighbors
average_lat = (other_pt.lat + lead_pt.lat) / 2
delta_lat = other_pt.lat - lead_pt.lat
delta_long = (other_pt.long - lead_pt.long)/cos(radians(average_lat))
if delta_lat**2 + delta_long**2 <= coarse_threshold:
break
# put vincenty test here
#if 0 < vincenty(lead_pt, other_pt).miles <= close_limit:
# break
else:
data[i] = data[i]._replace(has_close_neighbor=False)
print()
print(time.time() - start)
答案 1 :(得分:2)
如果按纬度(n log(n))对列表进行排序,并且点大致均匀分布,则每个点5英里内将其降低到大约1000点(餐巾纸数学,不精确)。通过仅查看纬度附近的点,运行时从n ^ 2变为n * log(n)+。0004n ^ 2。希望这能加快它的速度。
答案 2 :(得分:1)
我会分三步重做算法:
使用大圆距离,假设误差为1%,因此限制等于1.01 *限制。
将大圆距离编码为内联函数,此测试应该快速
你会得到一些误报,你可以用vincenty进一步测试
答案 3 :(得分:1)
我会试试pandas。 Pandas用于高效处理大量数据。无论如何,这可能有助于提高csv部分的效率。但是从它的声音来看,你已经找到了一个固有的低效问题需要解决。你拿第1点并将它与4,999,999个其他点进行比较。然后你拿第2点并将它与4,999,998个其他点进行比较,依此类推。算一算。这是您正在进行的12.5万亿比较。如果你每秒可以进行1,000,000次比较,那就是144天的计算。如果你每秒可以做10,000,000次比较,那就是14天。对于直接python中的添加,10,000,000次操作可能需要1.1秒,但我怀疑你的比较与添加操作一样快。所以至少要两周或两周。
或者,您可以提出一种替代算法,但我没有考虑任何特定的算法。
答案 4 :(得分:1)
这只是第一次通过,但到目前为止我使用great_circle()
代替vincinty()
加快了一半,并清理了其他一些事情。区别在于here,准确度的损失约为0.17%
:
from geopy.point import Point
from geopy.distance import great_circle
import csv
class CustomGeoPoint(Point):
def __init__(self, latitude, longitude):
super(CustomGeoPoint, self).__init__(latitude, longitude)
self.close_to_another_point = False
def isCloseToAnother(pointA, points):
for pointB in points:
dist = great_circle(pointA, pointB).miles
if 0 < dist <= CLOSE_LIMIT: # (0, close_limit]
return True
return False
with open('geo_input.csv', 'r') as geo_csv:
reader = csv.reader(geo_csv)
next(reader, None) # skip the headers
geo_points = sorted(map(lambda x: CustomGeoPoint(x[0], x[1]), reader))
with open('output.csv', 'w') as output:
writer = csv.writer(output, delimiter=',', quoting=csv.QUOTE_ALL)
writer.writerow(['Latitude', 'Longitude', 'CloseToAnotherPoint'])
# for every point, look at every point until one is found within a mile
for point in geo_points:
point.close_to_another_point = isCloseToAnother(point, geo_points)
writer.writerow([point.latitude, point.longitude,
point.close_to_another_point])
我将进一步改进这一点。
在:
$ time python geo.py
real 0m5.765s
user 0m5.675s
sys 0m0.048s
后:
$ time python geo.py
real 0m2.816s
user 0m2.716s
sys 0m0.041s
答案 5 :(得分:1)
奥斯卡史密斯提出的更好的解决方案。你有一个csv文件,只是在excel中排序它是非常有效的)。然后在您的程序中使用二进制搜索来查找5英里范围内的城市(您可以对二进制搜索方法进行小的更改,以便在找到满足您条件的一个城市时会中断)。 另一个改进是当你发现一个城市在另一个城市之内时,设置地图以记住这对城市。例如,当您发现城市A在距离城市B 5英里范围内时,请使用地图存储该对(B是键,A是值)。因此,下次遇到B时,首先在Map中搜索它,如果它有相应的值,则不需要再次检查。但它可能会使用更多的内存,所以关心它。希望它可以帮到你。
答案 6 :(得分:1)
使用VP tree可以解决此问题。这些允许查询数据 距离是服从三角不等式的度量。
VP树相对于k-D树的一大优势是它们可以盲目地使用 适用于世界任何地方的地理数据,无需担心 将其投射到合适的2D空间。另外还有一个真正的测地线 距离可以使用(无需担心之间的差异 投影中的测地距离和距离)。
这是我的测试:随机和均匀地产生500万点 世界。将它们放入VP树中。
循环遍历所有点,查询VP树以查找任何邻居a 距离(0km,10km)距离(0km不包括在此设置中以避免 找到查询点。)计算没有这样的点数 邻居(在我的情况下是229573)。
设置VP树的成本= 5000000 * 20距离计算。
查询成本= 5000000 * 23距离计算。
设置和查询的时间是5分7秒。
我正在使用带有GeographicLib的C ++来计算距离,但是 该算法当然可以用任何语言实现,这里是 python version of GeographicLib。
ADDENDUM :实现此方法的C ++代码为here。