GeoPandas行多轮应用函数报错:列长度与键不一致的解决
问题:GeoDataFrame分割相交线时出现"Columns must be same length as key"错误
背景与需求
我有一个包含以下列的GeoDataFrame,其中node_locations列是存储OSM节点ID及其对应坐标的字典:
{ "geometry": LineString(LINESTRING (8.6320625 49.3500941, 8.632062 49.3501782)), "node_locations": {75539413: {"lat": 52.5749342, "lon": 13.3008981}, 75539407: {"lat": 52.5746156, "lon": 13.3029441}, 75539412: {"lat": 52.5751579, "lon": 13.3012622} ... }
目标是分割所有相交的线,但仅当交点存在于node_locations列中时才执行分割——比如仅分割交点为绿色点(存在于节点集合)的线,红点对应的线不处理。
为提升性能,我尝试用apply()实现,但迭代几次后出现错误:Columns must be same length as key。
原代码
def split_intersecting_geometries(intersecting, row): if intersecting.name != row.name and intersecting.geometry.type != 'GeometryCollection': intersection = row.geometry.intersection(intersecting.geometry) if intersection.type == 'Point': lon, lat = intersection.coords.xy for key, value in row.node_locations.items(): if lat[0] == value["lat"] and lon[0] == value["lon"]: return split(row.geometry, intersecting.geometry) # 创建包含分割后线段的GeometryCollection return row.geometry def split_intersecting_ways(row, data): intersecting_rows = data[data.geometry.intersects(row.geometry)] data['geometry'] = intersecting_rows.apply(split_intersecting_geometries, args=(row,), axis=1) return data['geometry'] edges['geometry'] = edges.apply(split_intersecting_ways, args=(edges,), axis=1)
错误原因
- 返回值长度不匹配:
split_intersecting_ways函数返回的是整个data['geometry']列(长度等于原GeoDataFrame行数),但apply()逐行处理时,每行应该返回单个几何对象,而非整列,导致赋值时长度不匹配,触发报错。 - 修改原始数据引发状态混乱:函数内部直接修改传入的
data的geometry列,后续迭代会使用被修改后的数据,导致交叉污染,进一步加剧长度或几何类型的问题。
解决方案
1. 预处理节点点集合,提升查询效率
先提取所有node_locations中的坐标,构建哈希集合,避免每次分割时遍历字典,提升性能:
import geopandas as gpd import pandas as pd from shapely.geometry import Point, LineString from shapely.ops import split # 提取所有节点坐标,转为元组存入集合(哈希查询比遍历字典快) all_node_coords = set() for nodes in edges['node_locations']: for coord in nodes.values(): all_node_coords.add((coord['lat'], coord['lon']))
2. 重新设计分割函数,避免修改原始数据
修改逻辑为逐行处理当前线的几何,仅返回当前行的修改结果,不触碰整个DataFrame:
def split_line_valid_intersection(row, gdf): current_geom = row.geometry # 找到所有与当前线相交且不是自身的行 intersecting_rows = gdf[(gdf.geometry.intersects(current_geom)) & (gdf.index != row.index)] for _, intersect_row in intersecting_rows.iterrows(): intersect_geom = intersect_row.geometry intersection = current_geom.intersection(intersect_geom) # 仅处理点类型的交点,且交点在节点集合中 if intersection.type == 'Point': lon, lat = intersection.coords.xy point_tuple = (lat[0], lon[0]) if point_tuple in all_node_coords: # 分割当前线 current_geom = split(current_geom, intersection) return current_geom
3. 应用函数并处理分割后的GeometryCollection
分割后可能产生GeometryCollection,需要将其展开为单独的行,避免后续操作报错:
# 应用分割函数,传入原始数据的副本,避免修改原始数据 edges['geometry'] = edges.apply(split_line_valid_intersection, args=(edges.copy(),), axis=1) # 展开GeometryCollection为单独的行 def explode_geometry(row): geom = row.geometry if geom.type == 'GeometryCollection': # 为每个分割后的线段生成新行,保留node_locations列 return [gpd.GeoSeries({'geometry': part, 'node_locations': row['node_locations']}) for part in geom.geoms] else: return [gpd.GeoSeries(row)] # 执行展开,生成新的GeoDataFrame exploded_edges = pd.concat(edges.apply(explode_geometry, axis=1).explode().tolist(), ignore_index=True)
关键修复点
- 避免在
apply()中返回整列,改为返回单个几何对象,解决长度不匹配问题。 - 使用数据副本传入函数,防止修改原始数据导致的迭代状态污染。
- 提前构建节点坐标集合,大幅提升交点校验的效率。
- 展开
GeometryCollection为单独行,保证后续操作的兼容性。
内容的提问来源于stack exchange,提问作者Kewitschka
相关产品推荐
相关产品推荐

