第7章:数据读写——矢量文件格式
数据读写是任何空间数据处理工作的起点和终点。GeoPandas 提供了功能强大且接口统一的 IO 模块,支持读写 50 多种矢量数据格式。本章将详细介绍 GeoPandas 中各种矢量文件格式的读写方法,包括 Shapefile、GeoJSON、GeoPackage 等常用格式,以及性能优化技巧。
7.1 GeoPandas IO 概述
7.1.1 IO 后端引擎
GeoPandas 的 IO 操作底层依赖于两种引擎:
| 引擎 | 说明 | 状态 |
|---|---|---|
| PyOGRIO | 基于 GDAL/OGR 的现代化 Python 绑定 | ✅ 默认引擎(≥1.0) |
| Fiona | 传统的 OGR Python 封装 | 可选替代 |
从 GeoPandas 1.0 开始,PyOGRIO 成为默认的 IO 引擎,它直接通过 GDAL/OGR C 库的 Python 绑定来读写数据,性能更优。
import geopandas as gpd
# 查看当前使用的 IO 引擎
print(gpd.options.io_engine)
# pyogrio(默认)
# 切换引擎(如果需要)
gpd.options.io_engine = "pyogrio"
# 或在读取时指定
gdf = gpd.read_file("data.shp", engine="pyogrio")
7.1.2 支持的格式
PyOGRIO/GDAL 支持超过 50 种矢量格式,常用的包括:
| 格式 | 扩展名 | 驱动名称 | 特点 |
|---|---|---|---|
| Shapefile | .shp | ESRI Shapefile | 最广泛,但有诸多限制 |
| GeoJSON | .geojson/.json | GeoJSON | 文本格式,易于交互 |
| GeoPackage | .gpkg | GPKG | 现代化单文件格式 |
| FileGDB | .gdb | OpenFileGDB | ESRI 地理数据库 |
| KML | .kml | KML | Google Earth 格式 |
| GML | .gml | GML | OGC 标准 XML 格式 |
| MapInfo | .tab/.mif | MapInfo File | MapInfo 格式 |
| FlatGeobuf | .fgb | FlatGeobuf | 云优化的矢量格式 |
| CSV | .csv | CSV | 文本表格(需指定几何列) |
# 查看所有支持的驱动
import pyogrio
drivers = pyogrio.list_drivers()
print(f"支持 {len(drivers)} 种格式")
for name, mode in sorted(drivers.items()):
print(f" {name}: {mode}")
7.1.3 核心读写函数
# 读取
gdf = gpd.read_file(path) # 通用读取
layers = gpd.list_layers(path) # 列出图层
# 写入
gdf.to_file(path) # 通用写入
7.2 read_file() 详解
7.2.1 基本语法
geopandas.read_file(
filename, # 文件路径或 URL
bbox=None, # 边界框过滤
mask=None, # 几何掩码过滤
rows=None, # 读取行数限制
columns=None, # 读取列选择
engine=None, # IO 引擎选择
layer=None, # 图层名称或索引
where=None, # SQL WHERE 过滤条件
**kwargs # 传递给底层引擎的额外参数
)
7.2.2 参数全解析
filename — 文件路径
# 本地文件
gdf = gpd.read_file("data/provinces.shp")
gdf = gpd.read_file("/absolute/path/to/data.geojson")
# URL(远程文件)
gdf = gpd.read_file("https://example.com/data.geojson")
# ZIP 压缩包中的文件
gdf = gpd.read_file("zip://data.zip!provinces.shp")
# 也支持 pathlib.Path
from pathlib import Path
gdf = gpd.read_file(Path("data") / "provinces.shp")
layer — 图层选择
# 对于多图层文件(如 GeoPackage、FileGDB)
# 按名称选择
gdf = gpd.read_file("data.gpkg", layer="provinces")
# 按索引选择(从 0 开始)
gdf = gpd.read_file("data.gpkg", layer=0)
# 列出所有图层
layers = gpd.list_layers("data.gpkg")
print(layers)
# 返回 DataFrame,包含 name 和 geometry_type 列
bbox — 边界框过滤
# 只读取指定范围内的要素
# bbox 格式:(min_x, min_y, max_x, max_y)
gdf = gpd.read_file(
"china.shp",
bbox=(115, 39, 117, 41) # 北京周边区域
)
# 只返回与该边界框相交的要素
mask — 几何掩码过滤
from shapely.geometry import box
# 使用任意几何对象作为空间过滤器
mask_geom = box(115, 39, 117, 41)
gdf = gpd.read_file("china.shp", mask=mask_geom)
# 也可以使用 GeoDataFrame 作为掩码
mask_gdf = gpd.read_file("beijing_boundary.shp")
gdf = gpd.read_file("pois.shp", mask=mask_gdf)
rows — 行数限制
# 只读取前 N 行(适合快速预览)
gdf = gpd.read_file("large_file.shp", rows=100)
# 使用 slice 对象
gdf = gpd.read_file("data.shp", rows=slice(0, 50))
gdf = gpd.read_file("data.shp", rows=slice(10, 20)) # 第 10-19 行
columns — 列选择
# 只读取特定列(减少内存使用)
gdf = gpd.read_file(
"provinces.shp",
columns=["NAME", "POPULATION", "geometry"]
)
# 注意:geometry 列总是会被包含
where — SQL WHERE 过滤
# 使用 SQL WHERE 子句过滤
gdf = gpd.read_file(
"cities.shp",
where="POPULATION > 1000000"
)
# 字符串条件
gdf = gpd.read_file(
"provinces.shp",
where="NAME = '北京市'"
)
# 组合条件
gdf = gpd.read_file(
"cities.shp",
where="POPULATION > 500000 AND PROVINCE = '广东省'"
)
engine — 引擎选择
# 指定使用 pyogrio 引擎
gdf = gpd.read_file("data.shp", engine="pyogrio")
# 指定使用 fiona 引擎(需要安装 fiona)
gdf = gpd.read_file("data.shp", engine="fiona")
7.2.3 综合使用示例
# 高效读取:只读取北京周边、人口大于100万的城市的名称和人口
gdf = gpd.read_file(
"china_cities.gpkg",
layer="cities",
bbox=(115, 39, 117, 41),
columns=["name", "population"],
where="population > 1000000"
)
print(gdf)
7.3 读取 Shapefile
7.3.1 Shapefile 的组成
Shapefile 是一种多文件格式,一个完整的 Shapefile 至少包含以下文件:
| 文件 | 后缀 | 说明 | 必需 |
|---|---|---|---|
| 主文件 | .shp | 存储几何形状 | ✅ |
| 索引文件 | .shx | 几何形状的空间索引 | ✅ |
| 属性文件 | .dbf | 存储属性数据(dBASE格式) | ✅ |
| 投影文件 | .prj | 坐标参考系统定义 | ⚠️ 强烈建议 |
| 编码文件 | .cpg | 指定 .dbf 文件的字符编码 | 可选 |
| 空间索引 | .sbn/.sbx | 空间索引文件 | 可选 |
7.3.2 基本读取
import geopandas as gpd
# 指定 .shp 文件路径即可,其他文件会自动关联
gdf = gpd.read_file("data/provinces.shp")
print(f"要素数量: {len(gdf)}")
print(f"列名: {list(gdf.columns)}")
print(f"CRS: {gdf.crs}")
print(f"几何类型: {gdf.geom_type.unique()}")
7.3.3 编码问题
Shapefile 的属性数据(.dbf 文件)使用的是 dBASE 格式,编码处理是一个常见痛点:
# 如果中文出现乱码,尝试指定编码
gdf = gpd.read_file("data.shp", encoding="utf-8")
gdf = gpd.read_file("data.shp", encoding="gbk")
gdf = gpd.read_file("data.shp", encoding="gb2312")
gdf = gpd.read_file("data.shp", encoding="gb18030")
# 常见的中文编码
# UTF-8: 现代标准编码
# GBK/GB2312/GB18030: 中文 Windows 系统常用
# CP936: Windows 中文代码页(等同于 GBK)
自动检测编码的辅助函数:
def read_shapefile_auto_encoding(filepath):
"""尝试多种编码读取 Shapefile"""
encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'cp936', 'latin-1']
for enc in encodings:
try:
gdf = gpd.read_file(filepath, encoding=enc)
# 简单验证:检查是否有乱码(粗略判断)
sample = str(gdf.iloc[0])
if '?' * 3 not in sample: # 简单判断
print(f"成功使用编码: {enc}")
return gdf
except (UnicodeDecodeError, Exception):
continue
raise ValueError(f"无法自动检测编码: {filepath}")
7.3.4 Shapefile 的局限性
| 限制 | 说明 |
|---|---|
| 文件大小 | 单个 .shp/.dbf 文件不超过 2 GB |
| 字段名长度 | 最多 10 个字符 |
| 字段类型 | 不支持 datetime、boolean 等类型 |
| NULL 值 | 不支持真正的 NULL,用特定值替代 |
| 几何类型 | 每个文件只能存储一种几何类型 |
| 文件数量 | 至少 3-4 个文件,不便于传输 |
| 字符串长度 | 最多 254 个字符 |
| 坐标精度 | 双精度浮点数 |
建议: 对于新项目,推荐使用 GeoPackage 替代 Shapefile,它克服了上述大部分限制。
7.4 读取 GeoJSON
7.4.1 GeoJSON 格式简介
GeoJSON 是基于 JSON 的地理空间数据交换格式,遵循 RFC 7946 规范。
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [116.407, 39.904]
},
"properties": {
"name": "北京",
"population": 21710000
}
}
]
}
7.4.2 读取 GeoJSON 文件
# 从文件读取
gdf = gpd.read_file("data.geojson")
# 从 URL 读取
gdf = gpd.read_file("https://example.com/data.geojson")
# GeoJSON 默认使用 WGS84 (EPSG:4326)
print(gdf.crs)
# EPSG:4326
7.4.3 从 GeoJSON 字符串读取
import json
# 从 JSON 字符串创建
geojson_str = '''
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [116.4, 39.9]},
"properties": {"name": "北京"}
},
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [121.5, 31.2]},
"properties": {"name": "上海"}
}
]
}
'''
# 方法1:写入临时文件后读取(不推荐)
# 方法2:使用 from_features()
geojson = json.loads(geojson_str)
gdf = gpd.GeoDataFrame.from_features(geojson["features"], crs="EPSG:4326")
print(gdf)
7.4.4 GeoJSON 的特点
| 优点 | 缺点 |
|---|---|
| 纯文本,人类可读 | 文件体积大(无压缩) |
| 标准化(RFC 7946) | 读写性能较差 |
| Web 友好,JavaScript 原生支持 | 精度受文本表示限制 |
| 单文件,便于传输 | 无空间索引 |
| 支持嵌套属性 | 大文件内存占用大 |
7.5 读取 GeoPackage
7.5.1 GeoPackage 格式简介
GeoPackage(.gpkg)是 OGC 标准的开放格式,基于 SQLite 数据库。它是 Shapefile 的现代替代品。
GeoPackage 的优势:
- 单文件,便于管理和传输
- 支持多图层
- 支持多种几何类型混合存储
- 无字段名长度限制
- 支持完整的数据类型
- 支持空间索引
- 无文件大小限制
- 原生 UTF-8 编码
7.5.2 列出图层
# 查看 GeoPackage 中的所有图层
layers = gpd.list_layers("data.gpkg")
print(layers)
# name geometry_type
# 0 provinces MultiPolygon
# 1 cities Point
# 2 roads LineString
7.5.3 读取特定图层
# 按图层名读取
gdf_provinces = gpd.read_file("data.gpkg", layer="provinces")
gdf_cities = gpd.read_file("data.gpkg", layer="cities")
# 按图层索引读取(从 0 开始)
gdf_first = gpd.read_file("data.gpkg", layer=0)
# 不指定图层时,读取第一个图层
gdf = gpd.read_file("data.gpkg")
7.5.4 空间过滤读取
# 使用边界框过滤
gdf = gpd.read_file(
"data.gpkg",
layer="cities",
bbox=(115, 39, 117, 41)
)
# 使用 SQL WHERE 过滤
gdf = gpd.read_file(
"data.gpkg",
layer="cities",
where="population > 1000000"
)
# 组合过滤
gdf = gpd.read_file(
"data.gpkg",
layer="cities",
bbox=(100, 20, 125, 45),
where="province = '广东省'",
columns=["name", "population"]
)
7.6 读取 Geodatabase
7.6.1 ESRI File Geodatabase
File Geodatabase(.gdb)是 ESRI ArcGIS 的原生数据格式。GeoPandas 通过 GDAL 的 OpenFileGDB 驱动读取。
# 读取 File Geodatabase
# .gdb 实际上是一个文件夹
gdf = gpd.read_file("data.gdb", layer="provinces")
# 列出所有图层
layers = gpd.list_layers("data.gdb")
print(layers)
# 注意:写入 File Geodatabase 可能需要 GDAL 的 FileGDB 驱动
# OpenFileGDB 驱动在较新版本的 GDAL 中也支持写入
7.6.2 读取注意事项
# 1. 确保 GDAL 版本支持
import osgeo.gdal as gdal
print(gdal.__version__)
# 2. 某些高级功能可能不受支持(如注记、拓扑、域等)
# 3. 字段类型映射
# FileGDB 的一些字段类型在转换时可能需要注意:
# - GlobalID → 字符串
# - Date → datetime
# - Blob → 可能不支持
7.7 其他格式
7.7.1 KML(Keyhole Markup Language)
KML 是 Google Earth 使用的 XML 格式。
# 读取 KML
gdf = gpd.read_file("data.kml")
# 读取 KMZ(压缩的 KML)
gdf = gpd.read_file("data.kmz")
# KML 使用 WGS84 坐标系
print(gdf.crs)
# EPSG:4326
# 注意:KML 可能包含复杂的样式和文件夹结构
# GeoPandas 只读取几何和属性数据
7.7.2 GML(Geography Markup Language)
GML 是 OGC 标准的 XML 地理数据格式。
# 读取 GML
gdf = gpd.read_file("data.gml")
# GML 常用于以下场景:
# - OGC Web 服务(WFS)
# - 国家级空间数据基础设施(SDI)
# - CityGML(三维城市模型)
7.7.3 MapInfo 格式
# MapInfo TAB 格式(二进制)
gdf = gpd.read_file("data.tab")
# MapInfo MIF/MID 格式(文本)
gdf = gpd.read_file("data.mif")
7.7.4 VRT(Virtual Format)
VRT 是 GDAL 的虚拟格式,用于定义数据源的虚拟视图。
# VRT 文件内容示例:
# <OGRVRTDataSource>
# <OGRVRTLayer name="cities">
# <SrcDataSource>cities.csv</SrcDataSource>
# <GeometryType>wkbPoint</GeometryType>
# <GeometryField encoding="PointFromColumns" x="longitude" y="latitude"/>
# </OGRVRTLayer>
# </OGRVRTDataSource>
gdf = gpd.read_file("cities.vrt")
7.7.5 FlatGeobuf
FlatGeobuf 是一种高性能的云优化矢量格式。
# 读取 FlatGeobuf
gdf = gpd.read_file("data.fgb")
# FlatGeobuf 的优势:
# - 流式读取,支持 HTTP 范围请求
# - 内置空间索引
# - 快速的随机访问
# - 适合云存储和 Web 应用
7.8 to_file() 输出详解
7.8.1 基本语法
GeoDataFrame.to_file(
filename, # 输出文件路径
driver=None, # 输出格式驱动名称
schema=None, # 自定义 schema
index=None, # 是否包含索引
engine=None, # IO 引擎
layer=None, # 图层名称
encoding=None, # 字符编码
mode="w", # 写入模式(w: 覆盖, a: 追加)
**kwargs # 传递给底层引擎的额外参数
)
7.8.2 driver 参数
GeoPandas 通常根据文件扩展名自动选择驱动,但也可以显式指定:
# 自动识别驱动(推荐)
gdf.to_file("output.shp") # → ESRI Shapefile
gdf.to_file("output.geojson") # → GeoJSON
gdf.to_file("output.gpkg") # → GPKG
# 显式指定驱动
gdf.to_file("output.json", driver="GeoJSON")
gdf.to_file("output.gpkg", driver="GPKG", layer="provinces")
7.8.3 schema 参数
schema 定义了输出文件的结构(字段名称和类型):
# 自定义 schema
schema = {
"geometry": "Point",
"properties": {
"name": "str",
"population": "int",
"area_km2": "float",
"is_capital": "bool"
}
}
gdf.to_file("output.shp", schema=schema)
7.8.4 mode 参数 — 追加写入
# 覆盖写入(默认)
gdf.to_file("output.gpkg", layer="cities", mode="w")
# 追加写入
gdf_new.to_file("output.gpkg", layer="cities", mode="a")
7.8.5 encoding 参数
# 指定输出编码(主要用于 Shapefile)
gdf.to_file("output.shp", encoding="utf-8")
gdf.to_file("output.shp", encoding="gbk")
7.9 输出为 Shapefile
import geopandas as gpd
# 基本输出
gdf.to_file("output.shp")
# 指定编码(重要:中文数据务必指定编码)
gdf.to_file("output.shp", encoding="utf-8")
# 注意 Shapefile 的限制
# 1. 字段名会被截断为 10 个字符
# 2. 日期时间字段可能丢失时间部分
# 3. 布尔值会转换为整数
# 检查字段名截断
for col in gdf.columns:
if col != "geometry" and len(col) > 10:
print(f"⚠️ 字段名 '{col}' 将被截断为 '{col[:10]}'")
输出为 Shapefile 的注意事项
# 1. 处理长字段名
# 在输出前重命名过长的列
gdf = gdf.rename(columns={
"population_total": "pop_total",
"administrative_level": "admin_lvl"
})
# 2. 处理不支持的数据类型
import pandas as pd
# 将 datetime 转为字符串
if 'date_column' in gdf.columns:
gdf['date_column'] = gdf['date_column'].astype(str)
# 将布尔值转为整数
if 'bool_column' in gdf.columns:
gdf['bool_column'] = gdf['bool_column'].astype(int)
# 3. 大文件拆分
if gdf.memory_usage(deep=True).sum() > 1.5e9: # 接近 2GB 限制
print("⚠️ 数据量较大,建议使用 GeoPackage 格式")
7.10 输出为 GeoJSON
# 基本输出
gdf.to_file("output.geojson", driver="GeoJSON")
# GeoJSON 规范要求使用 WGS84 坐标系
# 如果数据不是 WGS84,建议先转换
if gdf.crs and gdf.crs.to_epsg() != 4326:
gdf_out = gdf.to_crs("EPSG:4326")
else:
gdf_out = gdf
gdf_out.to_file("output.geojson", driver="GeoJSON")
# 控制坐标精度(减小文件大小)
gdf.to_file(
"output.geojson",
driver="GeoJSON",
COORDINATE_PRECISION=6 # 保留 6 位小数
)
GeoJSON 输出选项
# 排序键(便于版本控制)
gdf.to_file("output.geojson", driver="GeoJSON", WRITE_BBOX="YES")
# 不写入 CRS(GeoJSON RFC 7946 规定始终为 WGS84)
# GeoJSON 规范中没有 CRS 属性,始终假定为 EPSG:4326
7.11 输出为 GeoPackage
# 基本输出
gdf.to_file("output.gpkg", driver="GPKG")
# 指定图层名称
gdf.to_file("output.gpkg", layer="provinces")
# 在同一个 GeoPackage 中写入多个图层
gdf_provinces.to_file("china.gpkg", layer="provinces")
gdf_cities.to_file("china.gpkg", layer="cities", mode="a")
gdf_roads.to_file("china.gpkg", layer="roads", mode="a")
# 验证
layers = gpd.list_layers("china.gpkg")
print(layers)
# name geometry_type
# 0 provinces MultiPolygon
# 1 cities Point
# 2 roads LineString
GeoPackage 的高级选项
# 创建空间索引(默认会创建)
gdf.to_file("output.gpkg", layer="data", SPATIAL_INDEX="YES")
# 覆盖已有图层
gdf.to_file("output.gpkg", layer="data", mode="w")
# 追加到已有图层
gdf_new.to_file("output.gpkg", layer="data", mode="a")
7.12 读写性能优化
7.12.1 过滤读取 — 只读取需要的数据
import geopandas as gpd
from shapely.geometry import box
# ❌ 低效:读取全部数据后再过滤
gdf_all = gpd.read_file("huge_file.shp")
gdf_filtered = gdf_all[gdf_all["province"] == "北京市"]
# ✅ 高效:在读取时就过滤
gdf_filtered = gpd.read_file(
"huge_file.shp",
where="province = '北京市'"
)
# ✅ 高效:空间过滤(利用空间索引)
gdf_spatial = gpd.read_file(
"huge_file.shp",
bbox=(115, 39, 117, 41)
)
# ✅ 高效:只读取需要的列
gdf_cols = gpd.read_file(
"huge_file.shp",
columns=["name", "population"]
)
7.12.2 分块读取
对于超大文件,可以分块读取以控制内存使用:
import geopandas as gpd
def read_in_chunks(filepath, chunk_size=10000):
"""分块读取大文件"""
offset = 0
chunks = []
while True:
chunk = gpd.read_file(
filepath,
rows=slice(offset, offset + chunk_size)
)
if len(chunk) == 0:
break
chunks.append(chunk)
offset += chunk_size
print(f"已读取 {offset} 行...")
return gpd.pd.concat(chunks, ignore_index=True)
# 使用分块读取
gdf = read_in_chunks("very_large_file.shp", chunk_size=50000)
7.12.3 格式选择对性能的影响
| 格式 | 读取速度 | 写入速度 | 文件大小 | 推荐场景 |
|---|---|---|---|---|
| GeoParquet | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 分析与存储 |
| FlatGeobuf | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 云存储与流式读取 |
| GeoPackage | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | 通用交换 |
| Shapefile | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | 兼容性 |
| GeoJSON | ⭐⭐ | ⭐⭐ | ⭐ | Web 应用 |
7.12.4 使用 PyOGRIO 直接读取
对于需要更细粒度控制的场景,可以直接使用 PyOGRIO:
import pyogrio
# 只读取元数据(不读取数据)
info = pyogrio.read_info("data.shp")
print(f"要素数量: {info['features']}")
print(f"字段: {info['fields']}")
print(f"几何类型: {info['geometry_type']}")
print(f"CRS: {info['crs']}")
# 直接读取为 DataFrame + 几何数组(更底层,更快)
result = pyogrio.read_dataframe("data.shp")
7.13 GeoJSON 字符串操作
7.13.1 to_json() — 转换为 GeoJSON 字符串
import geopandas as gpd
from shapely.geometry import Point
gdf = gpd.GeoDataFrame(
{"name": ["北京", "上海"], "pop": [2171, 2487]},
geometry=[Point(116.4, 39.9), Point(121.5, 31.2)],
crs="EPSG:4326"
)
# 转换为 GeoJSON 字符串
geojson_str = gdf.to_json()
print(geojson_str)
输出:
{
"type": "FeatureCollection",
"features": [
{
"id": "0",
"type": "Feature",
"properties": {"name": "北京", "pop": 2171},
"geometry": {"type": "Point", "coordinates": [116.4, 39.9]}
},
{
"id": "1",
"type": "Feature",
"properties": {"name": "上海", "pop": 2487},
"geometry": {"type": "Point", "coordinates": [121.5, 31.2]}
}
]
}
7.13.2 to_json() 参数
# 控制缩进
geojson_str = gdf.to_json(indent=2)
# 不使用 NaN(替换为 null)
geojson_str = gdf.to_json(na="null")
# 控制小数精度(通过 drop_id 等参数)
geojson_str = gdf.to_json(drop_id=True)
# 只导出部分列
geojson_str = gdf[["name", "geometry"]].to_json()
7.13.3 geo_interface 属性
__geo_interface__ 是 Python 地理空间生态系统的通用协议,返回 GeoJSON 兼容的 Python 字典:
# GeoDataFrame 级别
geo_dict = gdf.__geo_interface__
print(type(geo_dict)) # dict
print(geo_dict["type"]) # FeatureCollection
# GeoSeries 级别(单个几何)
geo_geom = gdf.geometry.iloc[0].__geo_interface__
print(geo_geom)
# {'type': 'Point', 'coordinates': (116.4, 39.9)}
# 可用于与其他库交互
# 例如:Folium、Plotly、Bokeh 等都支持 __geo_interface__
7.14 from_features() 从 GeoJSON Feature 创建
7.14.1 基本用法
import geopandas as gpd
# 从 GeoJSON Feature 列表创建 GeoDataFrame
features = [
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [116.4, 39.9]},
"properties": {"name": "北京", "population": 21710000}
},
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [121.5, 31.2]},
"properties": {"name": "上海", "population": 24870000}
},
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [113.3, 23.1]},
"properties": {"name": "广州", "population": 18680000}
}
]
gdf = gpd.GeoDataFrame.from_features(features, crs="EPSG:4326")
print(gdf)
输出:
name population geometry
0 北京 21710000 POINT (116.40000 39.90000)
1 上海 24870000 POINT (121.50000 31.20000)
2 广州 18680000 POINT (113.30000 23.10000)
7.14.2 从 FeatureCollection 创建
import json
# 从 GeoJSON FeatureCollection 创建
feature_collection = {
"type": "FeatureCollection",
"features": features # 使用上面定义的 features
}
# 注意:from_features() 接受 Feature 列表,不是 FeatureCollection
gdf = gpd.GeoDataFrame.from_features(
feature_collection["features"],
crs="EPSG:4326"
)
# 或者从 JSON 字符串解析
json_str = json.dumps(feature_collection, ensure_ascii=False)
fc = json.loads(json_str)
gdf = gpd.GeoDataFrame.from_features(fc["features"], crs="EPSG:4326")
7.14.3 与 API 响应集成
import requests
import geopandas as gpd
# 从 Web API 获取 GeoJSON 数据
# response = requests.get("https://api.example.com/geojson/cities")
# geojson_data = response.json()
# 假设获取到以下数据
geojson_data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [116.4, 39.9]},
"properties": {"name": "北京", "level": "直辖市"}
}
]
}
# 创建 GeoDataFrame
gdf = gpd.GeoDataFrame.from_features(
geojson_data["features"],
crs="EPSG:4326"
)
# 也可以直接使用 from_dict() 方法
# 或者将 GeoJSON 字符串保存后用 read_file() 读取
7.14.4 从 dict 列表手动构建
import geopandas as gpd
from shapely.geometry import shape
# 如果有自定义格式的数据
raw_data = [
{"name": "北京", "lon": 116.4, "lat": 39.9, "pop": 2171},
{"name": "上海", "lon": 121.5, "lat": 31.2, "pop": 2487},
]
# 构建为 GeoJSON Features
features = []
for item in raw_data:
feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [item["lon"], item["lat"]]
},
"properties": {
"name": item["name"],
"population": item["pop"]
}
}
features.append(feature)
gdf = gpd.GeoDataFrame.from_features(features, crs="EPSG:4326")
print(gdf)
7.15 本章小结
本章详细介绍了 GeoPandas 的矢量数据读写功能,涵盖了以下核心内容:
| 主题 | 关键要点 |
|---|---|
| IO 引擎 | PyOGRIO 是默认引擎,基于 GDAL/OGR,支持 50+ 格式 |
| read_file() | 统一读取接口,支持 bbox/mask/where/columns 等多种过滤方式 |
| Shapefile | 最广泛的格式,但有诸多限制(字段名 10 字符、2GB 等) |
| GeoJSON | 文本格式,Web 友好,规范要求 WGS84 坐标系 |
| GeoPackage | 现代化单文件格式,推荐替代 Shapefile |
| to_file() | 统一写入接口,支持 driver/schema/encoding 等参数 |
| 性能优化 | 优先使用过滤读取(bbox/where/columns),避免读取全部数据 |
| GeoJSON 操作 | to_json()、from_features()、geo_interface |
格式选择建议:
| 场景 | 推荐格式 |
|---|---|
| 通用数据交换 | GeoPackage |
| Web 前端展示 | GeoJSON |
| 高性能分析 | GeoParquet(下一章详述) |
| 兼容旧系统 | Shapefile |
| 云存储与流式 | FlatGeobuf |
| 与 ArcGIS 交互 | File Geodatabase |
核心要诀:
- 新项目优先使用 GeoPackage,避免 Shapefile 的各种限制
- 读取大文件时务必使用过滤参数(bbox/where/columns)
- 中文数据注意编码问题(UTF-8 为首选)
- GeoJSON 适合 Web 应用,但不适合大数据量场景
下一章我们将学习更高效的 Parquet 与 Arrow 格式的读写方法。