znlgis 博客

GIS开发与技术分享

第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 格式的读写方法。