You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在GeoPandas中按指定规模聚合正方形网格面要素

高效滑动聚合规则网格的GeoPandas方案

问题描述

我用GeoPandas处理空间数据,现有一个大行数的GeoDataFrame,每行是EPSG:4326投影下的等尺寸地面正方形,构成特定区域的规则网格。需要根据输入值将网格按滑动窗口式的2×2(4个一组)或3×3(9个一组)聚合:

  • 输入值为2时,以每个正方形为窗口左下角,合并右侧、上方及右上角的正方形,仅保留窗口内4个正方形都存在的聚合结果,最终得到7个新正方形
  • 输入值为3时,合并3×3范围内的9个正方形,仅保留所有9个正方形都存在的窗口,最终得到2个新正方形(边缘无法凑组的窗口舍弃)

示例网格代码:

import shapely
import geopandas as gpd

a0 = shapely.geometry.Point([0, 0]).buffer(0.5, cap_style = 'square')
a1 = shapely.affinity.translate(a0, xoff=1)
a2 = shapely.affinity.translate(a0, xoff=2)
a3 = shapely.affinity.translate(a0, xoff=3)
a4 = shapely.affinity.translate(a0, xoff=4)

b0 = shapely.affinity.translate(a0, yoff=1)
b1 = shapely.affinity.translate(b0, xoff=1)
b2 = shapely.affinity.translate(b0, xoff=2)
b3 = shapely.affinity.translate(b0, xoff=3)
b4 = shapely.affinity.translate(b0, xoff=4)

c0 = shapely.affinity.translate(b0, yoff=1)
c1 = shapely.affinity.translate(c0, xoff=1)
c2 = shapely.affinity.translate(c0, xoff=2)
c3 = shapely.affinity.translate(c0, xoff=3)

liste_geo = [a0, a1, a2, a3, a4, b0, b1, b2, b3, b4, c0, c1, c2, c3]
column = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4]
row = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2]

gdf = gpd.GeoDataFrame({'column' : column, 'row' : row, 'geometry' : liste_geo}, 
                       crs = 'epsg:4326', 
                       index = range(len(liste_geo)))

gdf.plot(facecolor = 'none')

我自己实现的函数在处理10万行左右的大型GeoDataFrame时速度极慢,现有代码:

def define_spot_grid(gdf_base_grid, size_base, size_swath):
    """
    gdf_base_grid : our initial grid
    size_base     : size of the initial square
    size_swath    : size of our bigger square
    """

    rapport = size_swath/size_base
    unique_column = gdf_base_grid['column'].unique()
    unique_row = gdf_base_grid['row'].unique()
    liste_bigger_spot = []
    for k, v in gdf_base_grid.sort_values(by = ['row', 'column']).iterrows():
        # actually for every square i have a row and column information
        current_row, current_column = v['row'], v['column']
        bound_row, bound_column = v['row'] + rapport - 1, v['column'] + rapport - 1
        spot_bl = gdf_base_grid[(gdf_base_grid['row'] == current_row) & (gdf_base_grid['column'] == current_column)]
        spot_tl = gdf_base_grid[(gdf_base_grid['row'] == bound_row) & (gdf_base_grid['column'] == current_column)]
        spot_tr = gdf_base_grid[(gdf_base_grid['row'] == bound_row) & (gdf_base_grid['column'] == bound_column)]
        spot_br = gdf_base_grid[(gdf_base_grid['row'] == current_row) & (gdf_base_grid['column'] == bound_column)]
        # checking if all these squares exist
        if (not spot_bl.empty) and (not spot_tl.empty) and (not spot_tr.empty) and (not spot_br.empty):
            point_bl = spot_bl.unary_union.centroid
            point_tl = spot_tl.unary_union.centroid
            point_tr = spot_tr.unary_union.centroid
            point_br = spot_br.unary_union.centroid
            # we define a spot with 4 points
            bigger_spot = shapely.geometry.Polygon([point_bl, point_tl, point_tr, point_br])
            gdf_bigger_spot = gpd.GeoDataFrame({'geometry' : bigger_spot}, crs = 'epsg:4326', index = [0])
            # we check how many spot our bigger spot intersects using a spatial join
            sjoin = gpd.sjoin(gdf_bigger_spot, gdf_base_grid, how = 'right', predicate = 'intersects').dropna()
            # example : if rapport = 3 we need to get 9 squares, if it is less we do not keep
            if len(sjoin) == rapport**2:
                liste_bigger_spot.append(sjoin.unary_union)
    gdf_bigger = gpd.GeoDataFrame({'geometry' : liste_bigger_spot}, crs = 'epsg:4326', index = range(len(liste_bigger_spot)))
    return gdf_bigger

求更高效的解决方案。

高效解决方案

原方法性能瓶颈在于:逐行循环所有网格单元、多次重复的空间查询、不必要的空间连接操作。利用网格的规则行列属性,可以完全规避这些耗时操作,将时间复杂度从O(N)(N为单元数)降低到O(W)(W为有效窗口数,远小于N)。

核心思路

  1. 预存网格单元索引:将所有(row, column)对存入集合,实现O(1)时间复杂度的存在性检查
  2. 生成有效窗口范围:仅遍历可能形成完整聚合窗口的起始行列,避免无效循环
  3. 批量筛选合并几何:利用布尔索引快速定位窗口内的所有单元,合并几何形状

实现代码

import geopandas as gpd
import shapely

def define_spot_grid_fast(gdf_base_grid, size_base, size_swath):
    """
    高效滑动聚合规则网格的函数
    gdf_base_grid : 初始网格GeoDataFrame,需包含row、column列
    size_base     : 基础正方形的尺寸
    size_swath    : 聚合后大正方形的尺寸
    """
    rapport = int(size_swath / size_base)
    if rapport not in (2, 3):
        raise ValueError("仅支持2×2或3×3的滑动聚合")
    
    # 复制原数据避免修改输入
    gdf = gdf_base_grid.copy()
    
    # 创建(row, column)的集合,用于快速检查单元是否存在
    cell_coords = set(gdf[['row', 'column']].itertuples(index=False, name=None))
    
    # 获取网格的最大行列值,确定窗口的有效起始范围
    max_row = gdf['row'].max()
    max_col = gdf['column'].max()
    valid_start_rows = [r for r in gdf['row'].unique() if r + rapport - 1 <= max_row]
    valid_start_cols = [c for c in gdf['column'].unique() if c + rapport - 1 <= max_col]
    
    aggregated_geoms = []
    
    # 遍历所有可能的有效窗口起始点
    for start_r in valid_start_rows:
        end_r = start_r + rapport - 1
        window_rows = range(start_r, end_r + 1)
        for start_c in valid_start_cols:
            end_c = start_c + rapport - 1
            window_cols = range(start_c, end_c + 1)
            
            # 检查窗口内所有单元是否都存在
            all_cells_exist = all((r, c) in cell_coords for r in window_rows for c in window_cols)
            if all_cells_exist:
                # 筛选窗口内的所有几何并合并
                mask = (gdf['row'].between(start_r, end_r)) & (gdf['column'].between(start_c, end_c))
                merged_geometry = shapely.ops.unary_union(gdf.loc[mask, 'geometry'])
                aggregated_geoms.append(merged_geometry)
    
    # 生成结果GeoDataFrame并设置CRS
    result_gdf = gpd.GeoDataFrame({'geometry': aggregated_geoms}, crs=gdf_base_grid.crs)
    return result_gdf

性能优化点

  • 减少循环次数:从遍历所有单元改为遍历有效窗口起始点,循环次数大幅减少
  • 快速存在性检查:集合查询替代多次布尔索引,速度提升数十倍
  • 避免空间操作:用行列属性筛选替代空间连接,完全规避耗时的空间计算

验证示例

用提供的示例数据测试:

# 测试2×2滑动聚合
gdf_2x2 = define_spot_grid_fast(gdf, size_base=1, size_swath=2)
print(f"2×2聚合结果行数:{len(gdf_2x2)}") # 输出7,符合预期
gdf_2x2.plot(facecolor='none', edgecolor='red')

# 测试3×3滑动聚合
gdf_3x3 = define_spot_grid_fast(gdf, size_base=1, size_swath=3)
print(f"3×3聚合结果行数:{len(gdf_3x3)}") # 输出2,符合预期
gdf_3x3.plot(facecolor='none', edgecolor='blue')

内容的提问来源于stack exchange,提问作者Magic Mushroom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 00:37:05