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

如何在Python中基于Shapely实现更快的点面相交检测?

优化Shapely点与多边形匹配的速度问题

我正在用Python复现GIS软件的功能——将51个质心点与9万+地块多边形匹配,检测点是否位于多边形内部。目前基于Shapely编写的脚本可以正常运行,但耗时约5分钟。考虑到后续需要处理更多的质心点和地块数据,希望找到优化速度的方法。

原代码

import json, csv, time
from shapely.geometry import Point, Polygon

start = time.time()

with open('M035TaxPar_CY20_FY20.csv') as csv_file:
    csv_reader = csv.reader(csv_file, delimiter=',')
    line_count = 0
    for row in csv_reader:
        if line_count == 0:
            #print(f'Column names are {", ".join(row)}')
            line_count += 1
        else:
            my_coord = tuple([float(row[10]), float(row[11])])
            my_coord = Point(my_coord)
            line_count += 1
            
            with open('boston_parcels_all.json', 'r') as f:
                array = json.load(f)
                
            dict_size = len(array.get("features"))
            for i in range(0, dict_size -1):
                sub_dict = array.get("features")[i] 
                geometry_dict = sub_dict.get("geometry")
                current_Pol = geometry_dict.get("coordinates")
                
                attribute_data = sub_dict.get("properties")
                parcel_id = attribute_data.get("MAP_PAR_ID")
                
                try:
                    final_Pol = Polygon(current_Pol[0])
                except AttributeError:
                    continue
                except ValueError:
                    continue
                if not my_coord.within(final_Pol):
                    continue
                else:
                    if parcel_id == None:
                        continue
                    else:
                        print(f"My pol: {parcel_id}, my point: {row[0]}")
                
    print(f'Processed {line_count} lines.')
end = time.time()
total_time = end - start
print("\n"+ str(total_time))

数据示例

CSV文件(M035TaxPar_CY20_FY20.csv)

MAP_PAR_ID,LOC_ID,POLY_TYPE,MAP_NO,SOURCE,PLAN_ID,LAST_EDIT,BND_CHK,NO_MATCH,TOWN_ID,X_coord,Y_coord
0301290001,F_772282_2959076,FEE,3,ASSESS,,20191107,,N,35,-71.07030535235137,42.36703589444949
0301286000,F_772818_2959719,FEE,3,ASSESS,,20191107,,N,35,-71.0682938996932,42.36876187375208

JSON文件(boston_parcels_all.json)

{
    "type" : "FeatureCollection",
    "name" : "M035TaxPar_CY20_FY20",
    "features" : [
        {
            "type" : "Feature",
            "geometry" : {
                "type" : "Polygon",
                "coordinates" : [
                    [
                        [ -71.074767681, 42.3489358757 ],
                        [ -71.0745071763, 42.3490067112 ],
                        [ -71.074852421, 42.3497214512 ],
                        [ -71.0757149372, 42.3494869825 ],
                        [ -71.0753495281, 42.3487373608 ],
                        [ -71.074767681, 42.3489358757 ]
                    ]
                ]
            },
            "properties" : {
                "SHAPE_Leng" : 324.191190544,
                "SHAPE_Area" : 6483.24124923,
                "MAP_PAR_ID" : "0401134000",
                "LOC_ID" : "F_771022_2952578",
                "POLY_TYPE" : "FEE",
                "MAP_NO" : "4",
                "SOURCE" : "ASSESS",
                "LAST_EDIT" : 20191107,
                "NO_MATCH" : "N",
                "TOWN_ID" : 35
            }
        },
        {
            "type" : "Feature",
            "geometry" : {
                "type" : "Polygon",
                "coordinates" : [
                    [
                        [ -71.0554563931, 42.3547109221 ],
                        [ -71.0550339868, 42.3548837812 ],
                        [ -71.0557334699, 42.3555313301 ],
                        [ -71.0564003565, 42.3554356917 ],
                        [ -71.0562325294, 42.3545663782 ],
                        [ -71.0554563931, 42.3547109221 ]
                    ]
                ]
            },
            "properties" : {
                "SHAPE_Leng" : 351.344209198,
                "SHAPE_Area" : 7627.60108948,
                "MAP_PAR_ID" : "0304410000",
                "LOC_ID" : "F_776224_2954721",
                "POLY_TYPE" : "FEE",
                "MAP_NO" : "3",
                "SOURCE" : "ASSESS",
                "LAST_EDIT" : 20191107,
                "NO_MATCH" : "N",
                "TOWN_ID" : 35
            }
        }
    ]
}

优化方案及代码

核心优化点

  • 避免重复IO操作:原代码每个点都重新加载JSON文件,改为仅加载一次。
  • 预处理多边形数据:提前创建所有有效Polygon对象并关联对应ID,避免循环中重复创建。
  • 空间索引过滤:用R-tree索引快速筛选候选多边形,仅对可能包含点的多边形做精确判断,大幅降低计算量。

优化后代码(Shapely+Rtree)

先安装依赖:pip install rtree shapely

import json, csv, time
from shapely.geometry import Point, Polygon
from rtree import index

start = time.time()

# 1. 预处理多边形与空间索引
polygons = []
idx = index.Index()

with open('boston_parcels_all.json', 'r') as f:
    feature_collection = json.load(f)
    for i, feature in enumerate(feature_collection.get("features")):
        geometry = feature.get("geometry")
        if geometry.get("type") != "Polygon":
            continue
        coords = geometry.get("coordinates")
        try:
            poly = Polygon(coords[0])
        except (AttributeError, ValueError):
            continue
        parcel_id = feature.get("properties").get("MAP_PAR_ID")
        if not parcel_id:
            continue
        polygons.append((poly, parcel_id))
        idx.insert(i, poly.bounds)

# 2. 处理CSV点数据
with open('M035TaxPar_CY20_FY20.csv') as csv_file:
    csv_reader = csv.reader(csv_file, delimiter=',')
    next(csv_reader)  # 跳过表头
    line_count = 0
    for row in csv_reader:
        line_count += 1
        x = float(row[10])
        y = float(row[11])
        point = Point(x, y)
        
        # 用索引筛选候选多边形
        candidate_indices = list(idx.intersection(point.bounds))
        for i in candidate_indices:
            poly, parcel_id = polygons[i]
            if point.within(poly):
                print(f"My pol: {parcel_id}, my point: {row[0]}")
                break  # 单匹配场景下找到即停止,多匹配则移除break

print(f'Processed {line_count} lines.')
end = time.time()
print(f"\nTotal time: {total_time:.2f} seconds")

更简洁高效方案:GeoPandas

GeoPandas内置空间索引,适合大规模空间数据处理,代码更简洁。
安装依赖:pip install geopandas

import geopandas as gpd
import time

start = time.time()

# 加载多边形与点数据
gdf_parcels = gpd.read_file('boston_parcels_all.json')
gdf_points = gpd.read_file(
    'M035TaxPar_CY20_FY20.csv',
    GEOM_POSSIBLE_NAMES=['X_coord', 'Y_coord'],
    crs=gdf_parcels.crs
)
gdf_points['geometry'] = gpd.points_from_xy(gdf_points['X_coord'], gdf_points['Y_coord'])

# 空间连接匹配点与多边形
result = gpd.sjoin(gdf_points, gdf_parcels, how='left', predicate='within')

# 输出匹配结果
for _, row in result.dropna(subset=['MAP_PAR_ID_right']).iterrows():
    print(f"My pol: {row['MAP_PAR_ID_right']}, my point: {row['MAP_PAR_ID_left']}")

print(f'Processed {len(gdf_points)} lines.')
end = time.time()
print(f"\nTotal time: {end - start:.2f} seconds")

优化效果说明

原代码的核心性能瓶颈是重复加载JSON和无索引遍历所有多边形,优化后:

  • 空间索引将单一点的匹配复杂度从O(N)降至O(logN),9万+多边形场景下速度提升几十倍甚至上百倍。
  • GeoPandas方案封装了底层优化,代码更简洁,后续扩展大规模数据更方便。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 23:15:52