GeoPandas如何筛选点?空间查询实战(附:源码)
《GeoPandas如何筛选点?空间查询实战(附:源码)》这篇教程专门解决一个常见问题:手里有一批点数据,例如门店、采样点、POI 或 GPS 点,怎样用 GeoPandas 按行政区、缓冲区或多边形范围把目标点筛选出来,并导出结果。
本文以 Python GIS 工作流为主,围绕 GeoPandas筛选点、GeoPandas空间查询、GeoPandas点在面内 这几个实际场景展开。你可以直接复制文中的源码,替换为自己的 Shapefile、GeoPackage 或 GeoJSON 数据路径。
引言:GeoPandas筛选点适合解决什么问题
在 GIS 项目中,“筛选点”通常不是简单按字段过滤,而是要回答空间关系问题。例如:
- 哪些学校点落在某个区县范围内?
- 哪些采样点位于河流 500 米缓冲区内?
- 哪些门店点不在服务边界内,需要检查坐标或地址?
- 哪些 GPS 点落入多个网格、街道或管控区中?
如果数据量不大,可以在 QGIS 或 ArcGIS Pro 中用“按位置选择”完成。但当你需要批量处理、多次重复、接入自动化脚本时,使用 GeoPandas 进行空间查询会更高效,也更容易复现。

背景:为什么点筛选不能只看经纬度字段
很多初学者会先想到用经纬度字段做条件筛选,例如经度大于某值、纬度小于某值。这种方式只能筛选矩形范围,无法准确处理行政区边界、缓冲区、规划红线、生态保护区等复杂多边形。
真实 GIS 项目中的边界通常是不规则面,点是否落在边界内,需要判断点几何对象与面几何对象之间的空间关系。这就是 GeoPandas空间查询 的核心用途。
GeoPandas 常见的点筛选方式包括:
- 按属性筛选点:根据字段值过滤,例如类型、名称、等级。
- 按范围筛选点:用 bounding box 粗筛,例如地图视窗范围。
- 按面筛选点:判断点是否在行政区、多边形范围内。
- 按缓冲区筛选点:判断点是否在道路、河流、设施点一定距离内。
- 按空间连接筛选点:把点和面关联起来,同时保留面属性。
原理:GeoPandas点在面内查询的关键逻辑
GeoPandas 基于 Shapely 处理几何关系。对于 GeoPandas点在面内 查询,常用空间谓词包括 within、contains、intersects 和 covers。
| 空间谓词 | 含义 | 适用场景 |
|---|---|---|
within |
点完全位于面内部 | 筛选落在行政区内的点 |
contains |
面包含点 | 从面图层角度判断包含哪些点 |
intersects |
几何对象有任意相交 | 点、线、面混合查询,边界点也常被纳入 |
covers |
面覆盖点,通常包括边界 | 希望把落在边界线上的点也算入范围 |
实际写代码时,推荐优先使用 geopandas.sjoin 做空间连接。它不仅能筛选点,还能把面图层的行政区名称、编码等字段带到点结果中。
注意:空间查询前必须检查坐标系。点图层和面图层的 CRS 不一致,是 GeoPandas筛选点结果为空或结果错误的最常见原因。
步骤:使用GeoPandas筛选点的完整源码
1. 安装和导入依赖
建议使用 conda 环境安装 GeoPandas,能减少 GDAL、Fiona、PyProj 等底层依赖冲突。
conda create -n gis python=3.11
conda activate gis
conda install -c conda-forge geopandas pyogrio rtree shapely
如果你使用 pip,也可以尝试:
pip install geopandas pyogrio rtree shapely
导入 Python 包:
import geopandas as gpd
from pathlib import Path
2. 读取点图层和面图层
下面假设点数据是 POI 点,面数据是区县边界。你可以替换为自己的文件路径。
point_path = Path("data/poi_points.shp")
polygon_path = Path("data/district_boundary.shp")
points = gpd.read_file(point_path)
polygons = gpd.read_file(polygon_path)
print(points.head())
print(polygons.head())
print(points.crs)
print(polygons.crs)
如果你的数据是 GeoJSON 或 GeoPackage,也可以直接读取:
points = gpd.read_file("data/poi_points.geojson")
polygons = gpd.read_file("data/district_boundary.gpkg", layer="district")
3. 检查几何类型和坐标系
GeoPandas筛选点之前,先确认点图层真的是点,面图层真的是 Polygon 或 MultiPolygon。
print(points.geom_type.value_counts())
print(polygons.geom_type.value_counts())
print("点图层CRS:", points.crs)
print("面图层CRS:", polygons.crs)
如果两个图层 CRS 不一致,需要统一坐标系。通常以面图层为目标坐标系:
if points.crs != polygons.crs:
points = points.to_crs(polygons.crs)
如果某个图层没有 CRS,需要先确认数据真实坐标系,再用 set_crs 指定。不要盲目使用 to_crs。
# 示例:如果点数据本身就是WGS84经纬度,但文件缺少CRS定义
points = points.set_crs("EPSG:4326", allow_override=True)
4. 方法一:使用sjoin筛选落在面内的点
这是最推荐的 GeoPandas空间查询 方法。它可以筛选出落在面内的点,并保留对应面要素的属性。
selected_points = gpd.sjoin(
points,
polygons,
how="inner",
predicate="within"
)
print(selected_points.head())
print("筛选前点数量:", len(points))
print("筛选后点数量:", len(selected_points))
参数说明:
points:左表,通常是要筛选的点图层。polygons:右表,作为筛选范围的面图层。how="inner":只保留匹配成功的点。predicate="within":判断点是否位于面内部。
如果你希望边界上的点也被选中,可以把 within 改为 intersects:
selected_points = gpd.sjoin(
points,
polygons,
how="inner",
predicate="intersects"
)
5. 方法二:筛选某一个区县内的点
如果面图层中有多个行政区,只想筛选某一个区县,可以先按属性过滤面,再执行空间查询。
target_name = "天河区"
target_polygon = polygons[polygons["NAME"] == target_name]
selected_points = gpd.sjoin(
points,
target_polygon,
how="inner",
predicate="within"
)
print(f"{target_name}内点数量:", len(selected_points))
字段名不一定叫 NAME,请根据自己的面图层属性表修改。例如可能是 县名称、district、XZQMC 或 adname。
6. 方法三:筛选缓冲区内的点
如果你想筛选道路 500 米范围内的点,或者医院 1 公里服务半径内的点,可以先构建缓冲区再做空间连接。
缓冲区距离必须在投影坐标系下计算。如果数据是 EPSG:4326 经纬度坐标,单位是度,不适合直接做米级缓冲。
# 假设points和lines已经读取
# 先投影到适合本地的米制坐标系,这里仅示例使用EPSG:3857
points_m = points.to_crs("EPSG:3857")
lines_m = gpd.read_file("data/roads.shp").to_crs("EPSG:3857")
buffer_500m = lines_m.copy()
buffer_500m["geometry"] = buffer_500m.geometry.buffer(500)
points_near_road = gpd.sjoin(
points_m,
buffer_500m,
how="inner",
predicate="within"
)
print("道路500米范围内点数量:", len(points_near_road))
如果是正式生产项目,建议根据所在地区选择合适的本地投影坐标系,而不是长期依赖 EPSG:3857。
7. 导出筛选结果
筛选完成后,可以导出为 Shapefile、GeoPackage 或 GeoJSON。中文字段较多时,更推荐 GeoPackage。
output_path = "output/selected_points.gpkg"
selected_points.to_file(output_path, layer="selected_points", driver="GPKG")
如果需要导出为 GeoJSON:
selected_points.to_file("output/selected_points.geojson", driver="GeoJSON")
如果要导出为 Shapefile,需要注意字段名长度限制和中文编码问题:
selected_points.to_file("output/selected_points.shp", encoding="utf-8")
常见坑:GeoPandas筛选点结果为空怎么办
1. CRS 不一致或 CRS 缺失
最常见情况是点图层显示为 EPSG:4326,面图层是 CGCS2000、高斯投影或 Web Mercator,但没有正确转换。
print(points.crs)
print(polygons.crs)
如果 CRS 不一致,用 to_crs 转换;如果 CRS 缺失,先确认真实坐标系,再用 set_crs 指定。
2. 把set_crs当成to_crs使用
set_crs 是“声明当前坐标系”,不会改变坐标值;to_crs 是“转换到另一个坐标系”,会重新计算坐标值。两者不能混用。
# 正确:已知原始点数据就是EPSG:4326,但文件缺失CRS
points = points.set_crs("EPSG:4326")
# 正确:把点数据从EPSG:4326转换到面图层CRS
points = points.to_crs(polygons.crs)
3. 边界点没有被选中
within 通常要求点在面内部。如果点刚好落在边界线上,可能不会被选中。此时可以尝试 intersects,或从面图层角度使用覆盖关系。
selected_points = gpd.sjoin(
points,
polygons,
how="inner",
predicate="intersects"
)
4. 面数据几何无效
如果面图层存在自相交、空几何、破碎面,空间查询可能异常或结果不稳定。可以先检查几何有效性。
print(polygons.is_valid.value_counts())
print(polygons.geometry.is_empty.value_counts())
polygons = polygons[~polygons.geometry.is_empty]
polygons["geometry"] = polygons.geometry.make_valid()
5. 字段重名导致结果列名混乱
sjoin 后,如果点图层和面图层有相同字段名,GeoPandas 会自动添加后缀,例如 NAME_left 和 NAME_right。导出前建议整理字段。
keep_cols = ["id", "name", "geometry", "district_name"]
selected_points = selected_points[[col for col in keep_cols if col in selected_points.columns]]
方法比较:GeoPandas筛选点的几种写法怎么选
| 方法 | 适合场景 | 优点 | 注意点 |
|---|---|---|---|
gpd.sjoin |
点在面内、点与缓冲区匹配 | 写法清晰,可保留面属性 | 需要 CRS 一致,注意谓词选择 |
within |
单个面筛选点 | 直观,代码少 | 多个面时不如 sjoin 方便 |
cx |
矩形范围快速粗筛 | 速度快,适合地图视窗过滤 | 不能处理复杂边界 |
overlay |
面与面叠加分析 | 适合裁剪和叠置 | 筛选点通常不优先使用 |
如果你的目标是“找出哪些点落在某个面范围内”,优先选择 gpd.sjoin。如果只是对一个单独多边形筛选点,可以使用 within 写得更短。
# 单个面筛选点的简洁写法
one_polygon = polygons.loc[0, "geometry"]
selected_points = points[points.within(one_polygon)]
如果只是筛选一个矩形范围,可以用 cx:
# 按经纬度矩形范围粗筛,前提是数据坐标系为经纬度
bbox_points = points.cx[113.2:113.5, 23.0:23.3]
检查清单:运行GeoPandas空间查询前先确认这些项
- 点图层是否有有效的
geometry列? - 点图层几何类型是否为
Point或MultiPoint? - 面图层几何类型是否为
Polygon或MultiPolygon? - 点图层和面图层 CRS 是否一致?
- 是否把缺失 CRS 和坐标转换混为一谈?
- 缓冲区分析是否使用了米制投影坐标系?
- 是否需要包含边界点?如果需要,是否应使用
intersects? - 面图层是否存在空几何或无效几何?
- 空间连接后是否检查了结果数量和字段后缀?
- 导出格式是否适合中文字段和长字段名?
FAQ:GeoPandas筛选点常见问题
GeoPandas如何筛选点在某个行政区内?
读取点图层和行政区面图层后,先统一 CRS,再使用 gpd.sjoin(points, polygons, how="inner", predicate="within")。如果只筛选某一个行政区,先按行政区名称过滤面图层,再执行空间连接。
GeoPandas空间查询结果为什么是空的?
优先检查 CRS 是否一致,其次检查几何类型是否正确、面几何是否有效、点是否真的落在范围内。如果点刚好在边界上,within 可能不返回结果,可以尝试 intersects。
GeoPandas点在面内应该用within还是intersects?
如果你只想要严格位于面内部的点,用 within。如果希望把落在边界上的点也纳入结果,可以用 intersects。行政区统计、网格归属等场景中,边界点规则需要提前约定。
GeoPandas筛选缓冲区内点为什么距离不对?
通常是因为数据仍在 EPSG:4326 经纬度坐标系下,缓冲距离单位是“度”,不是“米”。应先转换到合适的投影坐标系,再执行 buffer。
GeoPandas处理大量点数据会不会很慢?
GeoPandas 会利用空间索引提升空间查询效率,但超大规模数据仍可能较慢。可以先用边界框粗筛、分块处理,或考虑 PostGIS、DuckDB Spatial、Dask-GeoPandas 等方案。
筛选结果应该导出为Shapefile还是GeoPackage?
如果只是兼容旧软件,可以导出 Shapefile。但 Shapefile 有字段名长度、编码和多文件管理问题。实际项目中更推荐 GeoPackage,它更适合保存中文字段、长字段名和多个图层。
结论:GeoPandas筛选点的稳定流程
GeoPandas如何筛选点,关键不是记住某一行代码,而是建立稳定的空间查询流程:读取数据、检查几何类型、统一坐标系、选择合适空间谓词、验证数量和位置、最后导出结果。
对于大多数 GeoPandas筛选点 任务,推荐使用 gpd.sjoin。它既能完成 GeoPandas点在面内 判断,也能保留面图层属性,适合行政区归属、服务区分析、缓冲区筛选等常见 GIS 场景。
如果结果为空或明显不对,先不要急着改代码,按本文的检查清单依次排查 CRS、谓词、边界点、几何有效性和导出字段,通常就能定位问题。