【问题标题】:Spatial index to find points within polygon, if points and polygon have same minimum bounding box如果点和多边形具有相同的最小边界框,则在多边形内查找点的空间索引
【发布时间】:2017-01-30 12:39:23
【问题描述】:

我有一个形状优美的多边形,它代表了洛杉矶市的边界。我还在 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% 的计算时间,所以这仍然是一个非常缓慢的过程。

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

【问题讨论】:

  • 我认为值得尝试构造(甚至手动)近似“廉价”(相对较少的顶点)多边形pP,这样你的多边形Q 包含@987654325 @ 并且包含在 P 中。然后对于每个点,只有当特定点既不在 p 内部也不在 P 外部时,人们才能再次测试 Q...
  • 有趣的建议。通过对Q 执行convex_hull 操作,我可以轻松地创建多边形P,使P 包含多边形Q。但是有没有一种算法可以构造一个廉价的多边形p,使得Q 包含p,因为Q 不一定是凸的?您建议手动进行,这仅适用于洛杉矶,但我最终需要将其自动化,以便它可以在任何城市的多边形边界上工作。所以,我正在寻找一种算法自动化的解决方案。
  • 也许只需将Q 的边界框划分为MxN 方格,然后将p 构造为完全包含在Q 中的方格的并集就足够了- 这种构造需要一些计算工作,但如果M*N 仍然明显低于总点数,那么它可能是值得的......
  • 边界多边形中有多少点?
  • 说大约 1,000,000 中的 400,000

标签: python gis geospatial shapely geopandas


【解决方案1】:

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

解决方案:从此geopandas r-tree spatial index tutorial,使用样方例程将多边形划分为子多边形。然后,对于每个子多边形,首先将其与点的 r-tree 索引相交以获得一小组可能的匹配,然后将这些可能的匹配与子多边形相交以获得精确匹配的集合。这提供了大约 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-nearest neighbors classifier 的尝试:

    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 regression

    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)
    

    【讨论】:

    • 有趣。使用您的 KNN 方法是否存在误报或误报?是的,对我来说问题几乎完全是多边形的“复杂性”。例如,洛杉矶和休斯顿有相当分形的边界,导致交叉路口运行速度非常慢。使用几何简化的问题是我需要找到城市边界内的每个点,而不会出现任何误报/误报。
    • 使用回归技术和大量邻居可能会很幸运。如果你有,比如说多边形内有 10 个邻居,那么你就在里面,而外面的 10 个邻居就出去了。在您之间手动检查的任何内容。这至少会降低你的问题的复杂性。您也可以在稍大的多边形(poly.buffer() 或其他东西)上执行 KNN,然后根据原始点验证那些少得多的点。这是一个棘手的问题,但我认为巧妙地使用 KNN 可以让您的生活更轻松。
    猜你喜欢
    • 2017-10-01
    • 2015-12-13
    • 2014-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-15
    • 2013-04-30
    • 1970-01-01
    相关资源
    最近更新 更多