2

我有一个形状匀称的多边形,代表洛杉矶市的边界。我还在geopandas GeoDataFrame 中有一组约 100 万个经纬度点,所有这些点都在该多边形的最小边界框中。其中一些点位于多边形本身内,而其他点则不在。我只想保留洛杉矶边界内的那些点,并且由于洛杉矶的不规则形状,其最小边界框中只有大约 1/3 的点在多边形本身内。

鉴于点和多边形具有相同的最小边界框,使用 Python 识别这些点中哪些点位于多边形内的最快方法是什么?

我尝试使用 geopandas 及其 r-tree 空间索引:

sindex = gdf['geometry'].sindex
possible_matches_index = list(sindex.intersection(polygon.bounds))
possible_matches = gdf.iloc[possible_matches_index]
points_in_polygon = possible_matches[possible_matches.intersects(polygon)]

这使用 GeoDataFrame 的 r-tree 空间索引来快速找到可能的匹配项,然后找到多边形和那些可能的匹配项的确切交集。但是,由于多边形的最小边界框与点集的最小边界框相同,因此 r-tree 认为每个点都是可能的匹配项。因此,使用 r-tree 空间索引使交叉点的运行速度不会比没有空间索引的情况快。这种方法很慢:大约需要 30 分钟才能完成。

我还尝试将我的多边形划分为小的子多边形,然后使用空间索引来查找哪些点可能与这些子多边形中的每一个相交。该方法成功地找到了更少的可能匹配项,因为每个子多边形的最小边界框都远小于点的最小边界框集。然而,将这组可能的匹配与我的多边形相交仍然只减少了大约 25% 的计算时间,所以它仍然是一个非常缓慢的过程。

我应该使用更好的空间索引方法吗?如果点和多边形具有相同的最小边界框,那么找到多边形内哪些点的最快方法是什么?

4

2 回答 2

3

总结这个问题:当多边形的边界框与点集相同时,r-tree 将每个点识别为可能的匹配,因此不会提供任何加速。当与大量点和具有大量顶点的多边形相结合时,相交过程非常缓慢。

解决方案:从这个geopandas r-tree spatial index tutorial,使用样方例程将多边形划分为子多边形。然后,对于每个子多边形,首先将其与点的 r-tree 索引相交以获得一小组可能的匹配,然后将这些可能的匹配与子多边形相交以获得精确匹配的集合。这提供了大约 100 倍的加速。

于 2016-10-27T00:12:49.100 回答
2

一个复制问题的小例子

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)

在此处输入图像描述

于 2016-09-23T03:05:31.440 回答