一个复制问题的小例子
import pandas as pd
import shapely
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Polygon
from shapely.geometry import Point
import seaborn as sns
import numpy as np
# some lon/lat points in a DataFrame
n = 1000000
data = {'lat':np.random.uniform(low=0.0, high=3.0, size=(n,)), 'lon':np.random.uniform(low=0.0, high=3.0, size=(n,))}
df = pd.DataFrame(data)
# the 'bounding' polygon
poly1 = shapely.geometry.Polygon([(1,1), (1.5,1.2), (2,.7), (2.1,1.2), (1.8,2.3), (1.6,1.8), (1.2,3)])
# poly2 = shapely.geometry.Polygon([(1,1), (1.3,1.6), (1.4,1.55), (1.5,1.2), (2,.7), (2.1,1.2), (1.8,2.3), (1.6,1.8), (1.2,3), (.8,1.5),(.91,1.3)])
# poly3 = shapely.geometry.Polygon([(1,1), (1.3,1.6), (1.4,1.55), (1.5,1.2), (2,.7), (2.1,1.2), (1.8,2.3), (1.6,1.8), (1.5,2), (1.4,2.5),(1.3,2.4), (1.2,3), (.8,2.8),(1,2.8),(1.3,2.2),(.7,1.5),(.66,1.4)])
# limit DataFrame to interior points
mask = [poly1.intersects(shapely.geometry.Point(lat,lon)) for lat,lon in zip(df.lat,df.lon)]
df = df[mask]
# plot bounding polygon
fig1, ax1 = sns.plt.subplots(1, figsize=(4,4))
patches = PatchCollection([Polygon(poly1.exterior)], facecolor='red', linewidth=.5, alpha=.5)
ax1.add_collection(patches, autolim=True)
# plot the lat/lon points
df.plot(x='lat',y='lon', kind='scatter',ax=ax1)
plt.show()
在一个简单的多边形上用一百万个点调用 intersects() 不会花费太多时间。使用 poly1,我得到以下图像。找到多边形内的纬度/经度点不到 10 秒。仅在边界多边形顶部绘制内部点如下所示:
In [45]: %timeit mask = [Point(lat,lon).intersects(poly1) for lat,lon in zip(df.lat,df.lon)]
1 loops, best of 3: 9.23 s per loop
Poly3 更大更有趣。新图像看起来像这样,大约需要一分钟才能通过瓶颈 intersects() 线。
In [2]: %timeit mask = [poly3.intersects(shapely.geometry.Point(lat,lon)) for lat,lon in zip(df.lat,df.lon)]
1 loops, best of 3: 51.4 s per loop
所以罪犯不一定是纬度/经度点的数量。同样糟糕的是边界多边形的复杂性。首先,我会推荐poly.simplify()
,或者您可以做的任何事情来减少边界多边形中的点数(显然不会大幅改变它)。
接下来,我建议考虑一些概率方法。如果一个点p
被所有都在边界多边形内的点包围,那么很有可能p
也在边界多边形内。通常,在速度和准确性之间需要权衡取舍,但也许它可以减少您需要检查的点数。这是我使用k-最近邻分类器的尝试:
from sklearn.neighbors import KNeighborsClassifier
# make a knn object, feed it some training data
neigh = KNeighborsClassifier(n_neighbors=4)
df_short = df.sample(n=40000)
df_short['labels'] = np.array([poly3.intersects(shapely.geometry.Point(lat,lon)) for lat,lon in zip(df_short.lat,df_short.lon)])*1
neigh.fit(df_short[['lat','lon']], df_short['labels'])
# now use the training data to guess whether a point is in polygon or not
df['predict'] = neigh.predict(df[['lat','lon']])
给我这张图。不完美,但这个块的 %timeit 只需要 3.62 秒(n=50000 为 4.39 秒),而检查每个点大约需要 50 秒。
如果相反,我只想丢弃那些有 30% 的机会在多边形中的点(只是扔掉明显的违规者并手动检查其余部分)。我可以使用knn 回归:
from sklearn.neighbors import KNeighborsRegressor
neigh = KNeighborsRegressor(n_neighbors=3, weights='distance')
#everything else using 'neigh' is the same as before
# only keep points with more than 30\% chance of being inside
df = df[df.predict>.30]
现在我只有大约 138000 个点要检查,如果我想使用intersects()
.
当然,如果我增加邻居的数量,或者训练集的大小,我仍然可以获得更清晰的图像。这种概率方法的一些好处是(1)它是算法性的,所以你可以把它扔到任何时髦的边界多边形上,(2)你可以轻松地上下调整它的精度,(3)它速度更快,而且扩展性很好(至少最好用蛮力)。
就像机器学习中的许多事情一样,可以有 100 种方法来做到这一点。希望这可以帮助您找出可行的方法。这是具有以下设置的另一张图片(使用分类器,而不是回归)。你可以看到它正在变得更好。
neigh = KNeighborsClassifier(n_neighbors=3, weights='distance')
df_short = df.sample(n=80000)