小能豆

如果一个多边形包含第二个多边形的点,则有效合并 GeoDataFrames

py

我有两个 GeoDataFrames gdf_point:

       Unnamed: 0   latitude  longitude                  geometry
0               0  50.410203   7.236583  POINT (7.23658 50.41020)
1               1  51.303545   7.263082  POINT (7.26308 51.30354)
2               2  50.114965   8.672785  POINT (8.67278 50.11496)

和gdf_poly:

       Unnamed: 0  Id                                       geometry
0               0  301286  POLYGON ((9.67079 49.86762, 9.67079 49.86987, ...
1               1  302258  POLYGON ((9.67137 54.75650, 9.67137 54.75874, ...
2               2  302548  POLYGON ((9.66808 48.21535, 9.66808 48.21760, ...

我想匹配 gdf_point 中的点是否包含在 gdf_poly 的任何多边形中,如果是,我希望将该多边形的 Id 添加到 gdf_point 的相应行。

这是我当前的代码:

COUNTER = 0

def f(x, gdf_poly, df_new_point):
    global COUNTER

    for row in gdf_poly.itertuples():
        geom = getattr(row, 'geometry')
        id = getattr(row, 'Id')
        if geom.contains(x):
            print('True')
            df_new_point.loc[COUNTER, 'Id'] = id

    COUNTER = COUNTER + 1

df_new_point = gdf_point
gdf_point['geometry'].apply(lambda x: f(x, gdf_poly, df_new_point))

它能正常工作,并能完成我想要的功能。但问题是它的速度太慢了,处理 10k 行数据需要大约 50 分钟(多线程是未来的选项),我希望它能够处理数百万行数据。一定有更好更快的方法来做到这一点。谢谢你的帮助。


阅读 26

收藏
2024-12-03

共1个答案

小能豆

你现在的代码使用了 applyfor 循环,这在处理大量数据时效率较低。可以通过以下几种方法提升性能,尤其是在处理几百万行数据时。

1. 使用 geopandas 的空间查询 (sjoin)

geopandas 提供了一个空间连接(spatial join)的方法,叫做 sjoin,可以直接用于找出点是否位于多边形内。通过空间连接,你可以避免循环,提高效率。

2. 使用 sjoin 执行空间查询

你可以使用 geopandas.sjoin 来匹配点是否在多边形内,并将多边形的 Id 添加到点的 GeoDataFrame 中。

改进后的代码:

import geopandas as gpd

# 假设你已经有了 gdf_point 和 gdf_poly 数据

# 确保投影坐标一致
gdf_point = gdf_point.set_crs("EPSG:4326", allow_override=True)  # 或者根据需要更改 EPSG 代码
gdf_poly = gdf_poly.set_crs("EPSG:4326", allow_override=True)

# 使用空间连接(sjoin)进行查询
gdf_point_with_id = gpd.sjoin(gdf_point, gdf_poly, how="left", op='within')

# 结果会在 gdf_point_with_id 中包含每个点所属的多边形的 'Id'(如果点在多边形内)
print(gdf_point_with_id[['latitude', 'longitude', 'Id']])

解释:

  • sjoin 会将 gdf_pointgdf_poly 按空间关系连接。这里的 how='left' 表示保留 gdf_point 中所有的点, op='within' 表示检查点是否在多边形内。
  • 最后,返回的 gdf_point_with_id 将包含每个点对应的多边形 Id(如果点在多边形内)。

优点:

  • 更高效sjoin 会利用 geopandas 内部优化,避免了逐行循环,能显著提高计算速度。
  • 简洁代码:相比手动实现空间查询,sjoin 提供了一个简洁、直接的解决方案。

3. 对大数据集进一步优化:

如果数据集非常大(例如数百万行),可能需要进一步优化性能。以下是一些可能的优化方法:

  • 转换为 R-tree 索引:使用 rtree 或其他空间索引可以加速空间查询。geopandas 也支持 R-tree 索引,但你需要确保已安装 rtree
pip install rtree
  • 分批处理:如果数据集非常大,可以将数据分成较小的批次,逐批处理每个部分,以避免内存不足。
# 例如,分批处理
batch_size = 100000
for start in range(0, len(gdf_point), batch_size):
    batch = gdf_point.iloc[start:start+batch_size]
    batch_with_id = gpd.sjoin(batch, gdf_poly, how="left", op='within')
    # 处理每批数据
    print(batch_with_id[['latitude', 'longitude', 'Id']])

4. 使用 Dask for Parallel Computing(适用于超大数据)

如果你还需要并行化,Dask 是一个可以帮助加速大规模数据处理的库。你可以将数据分块处理,并在多个核心上并行执行。

pip install dask

然后,你可以使用 Dask 来处理大型 GeoDataFrame,并通过多线程/多进程并行化计算。

总结:

  • 使用 geopandas.sjoin 会显著提高处理速度,并且简化代码。
  • 如果数据集较大,考虑使用 R-tree 索引或分批处理方法。
  • 对于更大规模的任务,可以尝试使用 Dask 来并行化计算。

使用这些方法,你将能够以更高效的方式完成大规模空间查询,处理百万行数据时显著提升性能。

2024-12-03