惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
aimingoo的专栏
aimingoo的专栏
IT之家
IT之家
G
Google Developers Blog
爱范儿
爱范儿
博客园 - 司徒正美
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
J
Java Code Geeks
The Cloudflare Blog
M
MIT News - Artificial intelligence
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
Vercel News
Vercel News
宝玉的分享
宝玉的分享
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog
小众软件
小众软件
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
T
Troy Hunt's Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Hacker News: Front Page
H
Help Net Security
S
Security @ Cisco Blogs
V
V2EX
Security Archives - TechRepublic
Security Archives - TechRepublic
Stack Overflow Blog
Stack Overflow Blog
O
OpenAI News
L
LINUX DO - 最新话题
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
Secure Thoughts
Help Net Security
Help Net Security
F
Full Disclosure
博客园 - 叶小钗
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Jina AI
Jina AI
K
Kaspersky official blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
Scott Helme
Scott Helme

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第8章:数据读写——Parquet 与 Arrow 格式
我才是银古 · 2026-06-22 · via 博客园 - 我才是银古

第8章:数据读写——Parquet 与 Arrow 格式

随着地理空间数据量的快速增长,传统的 Shapefile 和 GeoJSON 格式在性能上越来越难以满足需求。GeoParquet 和 GeoArrow 作为新一代地理空间数据格式,提供了卓越的读写性能和存储效率。本章将深入介绍这些现代格式在 GeoPandas 中的使用方法。


8.1 什么是 GeoParquet

8.1.1 Apache Parquet 简介

Apache Parquet 是一种开源的列式存储文件格式,最初由 Twitter 和 Cloudera 为 Hadoop 生态系统设计。它具有以下核心特性:

特性 说明
列式存储 数据按列而非按行存储,适合分析查询
高效压缩 同列数据类型相同,压缩率极高
编码优化 支持字典编码、RLE、Delta 等多种编码方式
Schema 嵌入 文件自带数据结构描述
分块读取 支持行组(Row Group)级别的随机访问
跨平台 Java、Python、C++、Rust 等多语言支持

8.1.2 GeoParquet 的诞生

GeoParquet 是 Apache Parquet 的地理空间扩展,它在标准 Parquet 文件的元数据中添加了地理空间相关的信息,使 Parquet 能够存储和管理地理空间数据。

┌─────────────────────────────────────────┐
│           GeoParquet 文件结构            │
├─────────────────────────────────────────┤
│  Parquet 文件头                         │
├─────────────────────────────────────────┤
│  Row Group 1                            │
│    ├── Column: name  (字符串)           │
│    ├── Column: population (整数)        │
│    └── Column: geometry (WKB/GeoArrow)  │
├─────────────────────────────────────────┤
│  Row Group 2                            │
│    ├── ...                              │
├─────────────────────────────────────────┤
│  Parquet 文件尾(含元数据)             │
│    └── geo 元数据(CRS、几何类型等)    │
└─────────────────────────────────────────┘

8.1.3 GeoParquet 的核心优势

与传统格式相比,GeoParquet 具有显著优势:

优势 详细说明
读写速度快 列式存储 + 高效编码,比 Shapefile 快 5-10 倍
文件体积小 内置压缩,通常比 GeoJSON 小 10-50 倍
列选择读取 只读取需要的列,减少 IO 和内存
类型丰富 支持各种数据类型,无 Shapefile 的限制
云原生 支持远程读取,适合云存储
生态丰富 DuckDB、BigQuery、Spark 等均支持
import geopandas as gpd

# GeoParquet 的读写非常简洁
# 写入
gdf.to_parquet("data.parquet")

# 读取
gdf = gpd.read_parquet("data.parquet")

8.2 GeoParquet 规范

8.2.1 元数据规范

GeoParquet 通过在 Parquet 文件的元数据中添加一个名为 geo 的 JSON 键来存储地理空间信息:

{
  "version": "1.1.0",
  "primary_column": "geometry",
  "columns": {
    "geometry": {
      "encoding": "WKB",
      "geometry_types": ["Polygon", "MultiPolygon"],
      "crs": {
        "type": "GeographicCRS",
        "name": "WGS 84",
        "id": {"authority": "EPSG", "code": 4326}
      },
      "bbox": [73.5, 18.2, 135.1, 53.6],
      "covering": {
        "bbox": {
          "xmin": ["bbox", "xmin"],
          "ymin": ["bbox", "ymin"],
          "xmax": ["bbox", "xmax"],
          "ymax": ["bbox", "ymax"]
        }
      }
    }
  }
}

8.2.2 元数据字段说明

字段 说明
version GeoParquet 规范版本
primary_column 主几何列的名称
encoding 几何编码方式(WKB 或 GeoArrow)
geometry_types 几何类型列表
crs 坐标参考系统(PROJJSON 格式)
bbox 整体边界框 [xmin, ymin, xmax, ymax]
covering 覆盖列(用于空间过滤优化)

8.2.3 版本演进

版本 发布时间 主要变化
0.4.0 2022 初始规范草案
1.0.0 2023 首个稳定版本,WKB 编码标准化
1.1.0 2024 支持 GeoArrow 原生编码、covering 列
import geopandas as gpd
import pyarrow.parquet as pq

# 查看 GeoParquet 的元数据
parquet_file = pq.ParquetFile("data.parquet")
metadata = parquet_file.schema_arrow.metadata

# 解析 geo 元数据
import json
geo_metadata = json.loads(metadata[b"geo"])
print(json.dumps(geo_metadata, indent=2))

8.3 read_parquet() 详解

8.3.1 基本语法

geopandas.read_parquet(
    path,               # 文件路径(本地或远程)
    columns=None,       # 要读取的列
    storage_options=None,  # 存储选项(用于远程文件)
    bbox=None,          # 边界框过滤
    **kwargs            # 传递给底层引擎的参数
)

8.3.2 基本读取

import geopandas as gpd

# 读取本地文件
gdf = gpd.read_parquet("data.parquet")
print(f"行数: {len(gdf)}")
print(f"列数: {len(gdf.columns)}")
print(f"CRS: {gdf.crs}")
print(f"几何类型: {gdf.geom_type.unique()}")

8.3.3 列选择 — columns 参数

列选择是 Parquet 格式的核心优势之一。由于列式存储,只读取需要的列可以显著减少 IO 和内存使用:

# 只读取特定列
gdf = gpd.read_parquet(
    "large_dataset.parquet",
    columns=["name", "population", "geometry"]
)

# 对比全量读取
# 如果文件有 50 列,只读 3 列可以减少约 94% 的读取量

# 查看文件中的所有列(不读取数据)
import pyarrow.parquet as pq
schema = pq.read_schema("large_dataset.parquet")
print("可用列:")
for field in schema:
    print(f"  {field.name}: {field.type}")

8.3.4 空间过滤 — bbox 参数

# 使用边界框进行空间过滤
gdf = gpd.read_parquet(
    "china_pois.parquet",
    bbox=(115, 39, 117, 41)  # 北京周边
)
print(f"过滤后行数: {len(gdf)}")

# bbox 过滤利用 Parquet 的行组统计信息
# 可以跳过不在范围内的行组,大大提高性能

8.3.5 组合使用

# 同时使用列选择和空间过滤
gdf = gpd.read_parquet(
    "nationwide_buildings.parquet",
    columns=["building_type", "area_sqm", "geometry"],
    bbox=(120, 30, 122, 32)  # 上海区域
)

# 这种组合使用可以极大地减少数据读取量
# 对于 TB 级别的数据集尤其有效

8.3.6 读取分区数据集

# Parquet 数据集可以按目录分区
# 例如:
# data/
#   province=北京/
#     part-0.parquet
#   province=上海/
#     part-0.parquet
#   province=广东/
#     part-0.parquet

# 读取整个分区数据集
gdf = gpd.read_parquet("data/")

# 使用 PyArrow 的过滤功能
import pyarrow.parquet as pq
import pyarrow.dataset as ds

dataset = ds.dataset("data/", format="parquet")
table = dataset.to_table(
    filter=ds.field("province") == "北京"
)
gdf = gpd.GeoDataFrame.from_arrow(table)

8.4 to_parquet() 详解

8.4.1 基本语法

GeoDataFrame.to_parquet(
    path,                   # 输出文件路径
    index=None,             # 是否包含索引
    compression="snappy",   # 压缩算法
    schema_version=None,    # GeoParquet 规范版本
    write_covering_bbox=None,  # 是否写入覆盖边界框
    geometry_encoding=None, # 几何编码方式
    **kwargs                # 传递给底层引擎的参数
)

8.4.2 基本写入

import geopandas as gpd
from shapely.geometry import Point

# 创建示例数据
gdf = gpd.GeoDataFrame(
    {
        "name": ["北京", "上海", "广州"],
        "population": [2171, 2487, 1868],
        "province": ["北京市", "上海市", "广东省"]
    },
    geometry=[
        Point(116.407, 39.904),
        Point(121.474, 31.230),
        Point(113.264, 23.129)
    ],
    crs="EPSG:4326"
)

# 基本写入
gdf.to_parquet("cities.parquet")

8.4.3 压缩选项

Parquet 支持多种压缩算法,可以根据需求选择:

压缩算法 压缩率 速度 说明
snappy ⭐⭐⭐ ⭐⭐⭐⭐⭐ 默认值,速度快,适合一般场景
gzip ⭐⭐⭐⭐ ⭐⭐ 压缩率高,但速度较慢
zstd ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 推荐,压缩率和速度的最佳平衡
lz4 ⭐⭐ ⭐⭐⭐⭐⭐ 极快,适合需要极速读写的场景
brotli ⭐⭐⭐⭐⭐ 最高压缩率,但速度最慢
None - ⭐⭐⭐⭐⭐ 不压缩
# 使用不同的压缩算法
gdf.to_parquet("data_snappy.parquet", compression="snappy")  # 默认
gdf.to_parquet("data_zstd.parquet", compression="zstd")      # 推荐
gdf.to_parquet("data_gzip.parquet", compression="gzip")      # 高压缩
gdf.to_parquet("data_none.parquet", compression=None)        # 无压缩

# 比较文件大小
import os
for comp in ["snappy", "zstd", "gzip", None]:
    filename = f"data_{comp}.parquet"
    gdf.to_parquet(filename, compression=comp)
    size = os.path.getsize(filename)
    print(f"{str(comp):10s} → {size:,} bytes")

8.4.4 索引处理

# 不写入索引(推荐,减少文件大小)
gdf.to_parquet("data.parquet", index=False)

# 写入索引
gdf.to_parquet("data.parquet", index=True)

# 自动判断(默认行为)
gdf.to_parquet("data.parquet")  # 如果索引有意义则写入

8.4.5 几何编码选择

# WKB 编码(默认,兼容性最好)
gdf.to_parquet("data_wkb.parquet", geometry_encoding="WKB")

# GeoArrow 编码(更高效,需要 GeoParquet 1.1+)
gdf.to_parquet("data_geoarrow.parquet", geometry_encoding="geoarrow")

8.4.6 覆盖边界框

# 写入覆盖边界框列(用于空间过滤优化)
gdf.to_parquet("data.parquet", write_covering_bbox=True)

# 这会在 Parquet 文件中额外添加 bbox 列
# 使得空间过滤查询可以利用 Parquet 的行组统计信息
# 极大地提升大数据集的空间查询性能

8.5 Apache Arrow Feather 格式

8.5.1 Feather 格式简介

Apache Arrow Feather(也称为 Arrow IPC 格式)是 Arrow 的原生序列化格式,特别适合在内存中进行数据交换。

特性 Parquet Feather
设计目标 持久存储 内存映射 / 快速序列化
压缩 内置多种压缩 可选压缩
列选择读取 支持 支持
行组过滤 支持 不支持
读取速度 极快(接近内存速度)
文件大小 较小 较大(无压缩时)
适用场景 数据仓库、持久存储 进程间通信、临时缓存

8.5.2 read_feather() 读取

import geopandas as gpd

# 基本读取
gdf = gpd.read_feather("data.feather")

# 列选择
gdf = gpd.read_feather(
    "data.feather",
    columns=["name", "geometry"]
)

8.5.3 to_feather() 写入

# 基本写入
gdf.to_feather("data.feather")

# 带压缩
gdf.to_feather("data.feather", compression="zstd")
gdf.to_feather("data.feather", compression="lz4")
gdf.to_feather("data.feather", compression="uncompressed")

8.5.4 使用场景

# 场景1:中间结果缓存
# 在复杂的分析流程中,将中间结果保存为 Feather 以加速后续步骤
gdf_processed = complex_spatial_analysis(gdf_raw)
gdf_processed.to_feather("cache/step1_result.feather")

# 后续步骤直接读取(极快)
gdf_cached = gpd.read_feather("cache/step1_result.feather")

# 场景2:Python 进程间数据传递
# 进程 A 写入
gdf.to_feather("shared/data.feather")
# 进程 B 读取
gdf = gpd.read_feather("shared/data.feather")

8.6 GeoArrow 编码

8.6.1 什么是 GeoArrow

GeoArrow 是一种在 Apache Arrow 列式内存格式中表示地理空间数据的规范。与传统的 WKB(Well-Known Binary)编码不同,GeoArrow 使用原生的 Arrow 数组结构来存储几何坐标。

8.6.2 WKB vs GeoArrow

WKB 编码:每个几何体是一个独立的二进制 blob
┌──────────────────────────────────────┐
│ Binary Column                        │
│  [WKB_blob_1, WKB_blob_2, ...]     │
│  每个 blob 需要单独解析              │
└──────────────────────────────────────┘

GeoArrow 编码:坐标直接存储在 Arrow 数组中
┌──────────────────────────────────────┐
│ Struct Column                        │
│  x: [116.4, 121.5, 113.3, ...]     │
│  y: [39.9, 31.2, 23.1, ...]        │
│  坐标可以直接访问,无需解析          │
└──────────────────────────────────────┘

8.6.3 GeoArrow 编码类型

不同的几何类型使用不同的 Arrow 数据结构:

几何类型 Arrow 结构 说明
Point Struct<x: double, y: double> 固定结构体
LineString List<Struct<x: double, y: double>> 点的列表
Polygon List<List<Struct<x: double, y: double>>> 环的列表(环是点的列表)
MultiPoint List<Struct<x: double, y: double>> 点的列表
MultiLineString List<List<Struct<x: double, y: double>>> 线的列表
MultiPolygon List<List<List<Struct<x: double, y: double>>>> 多边形的列表
import geopandas as gpd

# 使用 GeoArrow 编码写入 Parquet
gdf.to_parquet("data_geoarrow.parquet", geometry_encoding="geoarrow")

# 读取时自动识别编码方式
gdf = gpd.read_parquet("data_geoarrow.parquet")

8.6.4 GeoArrow 的性能优势

import geopandas as gpd
import time

# 创建大数据集进行性能测试
import numpy as np
from shapely.geometry import Point

n = 1_000_000
gdf_large = gpd.GeoDataFrame(
    {"value": np.random.randn(n)},
    geometry=[Point(x, y) for x, y in
              zip(np.random.uniform(73, 135, n),
                  np.random.uniform(18, 54, n))],
    crs="EPSG:4326"
)

# WKB 编码写入
start = time.time()
gdf_large.to_parquet("test_wkb.parquet", geometry_encoding="WKB")
print(f"WKB 写入: {time.time() - start:.2f}s")

# GeoArrow 编码写入
start = time.time()
gdf_large.to_parquet("test_geoarrow.parquet", geometry_encoding="geoarrow")
print(f"GeoArrow 写入: {time.time() - start:.2f}s")

# 对比读取速度
start = time.time()
_ = gpd.read_parquet("test_wkb.parquet")
print(f"WKB 读取: {time.time() - start:.2f}s")

start = time.time()
_ = gpd.read_parquet("test_geoarrow.parquet")
print(f"GeoArrow 读取: {time.time() - start:.2f}s")

典型结果(具体数值因硬件而异):

WKB 写入: 3.45s
GeoArrow 写入: 1.82s
WKB 读取: 2.13s
GeoArrow 读取: 0.95s

8.7 WKB 编码回退机制

8.7.1 什么时候使用 WKB

在某些情况下,GeoArrow 编码无法使用,GeoPandas 会自动回退到 WKB 编码:

情况 说明
混合几何类型 同一列中包含 Point 和 Polygon 等不同类型
旧版软件 目标软件不支持 GeoArrow
空几何 某些空几何的处理方式不同
三维几何 Z 和 M 坐标的支持取决于实现

8.7.2 WKB 编码的工作方式

# WKB 编码将每个几何体序列化为二进制格式
from shapely import wkb
from shapely.geometry import Point

point = Point(116.4, 39.9)
wkb_bytes = wkb.dumps(point)
print(f"WKB 字节数: {len(wkb_bytes)}")
print(f"WKB 内容: {wkb_bytes.hex()}")

# 在 Parquet 中,WKB 存储为 Binary 类型的列
# 每个几何体是一个独立的二进制 blob

8.7.3 显式控制编码

# 显式使用 WKB 编码(确保最大兼容性)
gdf.to_parquet("compatible.parquet", geometry_encoding="WKB")

# 显式使用 GeoArrow 编码(更高性能)
# 如果几何类型不一致,会抛出错误
try:
    gdf_mixed.to_parquet("data.parquet", geometry_encoding="geoarrow")
except ValueError as e:
    print(f"GeoArrow 编码失败: {e}")
    # 回退到 WKB
    gdf_mixed.to_parquet("data.parquet", geometry_encoding="WKB")

8.8 Arrow Table 互操作

8.8.1 to_arrow() — 转换为 Arrow Table

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"
)

# 转换为 Arrow Table
table = gdf.to_arrow()
print(type(table))
# <class 'pyarrow.lib.Table'>

print(table.schema)
# name: string
# pop: int64
# geometry: binary  (WKB 编码)
# -- schema metadata --
# geo: '{"version":"1.1.0",...}'

# 使用 GeoArrow 编码
table_geoarrow = gdf.to_arrow(geometry_encoding="geoarrow")
print(table_geoarrow.schema)
# geometry 列会使用 struct 或 list 类型

8.8.2 from_arrow() — 从 Arrow Table 创建

import geopandas as gpd
import pyarrow as pa

# 从 Arrow Table 创建 GeoDataFrame
# (Arrow Table 必须包含 GeoParquet 兼容的 geo 元数据)
gdf = gpd.GeoDataFrame.from_arrow(table)

print(type(gdf))
# <class 'geopandas.geodataframe.GeoDataFrame'>
print(gdf.crs)
# EPSG:4326

8.8.3 与 DuckDB 集成

import duckdb
import geopandas as gpd

# DuckDB 原生支持 GeoParquet
con = duckdb.connect()

# 安装并加载空间扩展
con.install_extension("spatial")
con.load_extension("spatial")

# 直接在 GeoParquet 上执行 SQL 查询
result = con.execute("""
    SELECT name, population, ST_AsText(geometry) as geom_wkt
    FROM read_parquet('cities.parquet')
    WHERE population > 10000000
""").fetchdf()

# 从 DuckDB 结果创建 GeoDataFrame
gdf = con.execute("""
    SELECT *
    FROM read_parquet('cities.parquet')
    WHERE ST_Within(
        geometry,
        ST_GeomFromText('POLYGON((115 39, 117 39, 117 41, 115 41, 115 39))')
    )
""").fetch_arrow_table()

gdf = gpd.GeoDataFrame.from_arrow(gdf)

8.8.4 与 Polars 集成

import polars as pl
import geopandas as gpd

# Polars 可以高效读取 Parquet
df_polars = pl.read_parquet("data.parquet")

# 转换为 Arrow Table,再转为 GeoDataFrame
arrow_table = df_polars.to_arrow()
# 注意:需要确保 Arrow Table 包含正确的 geo 元数据

8.9 性能对比

8.9.1 读写速度对比

以下是不同格式在典型数据集上的性能对比(参考值,实际可能因数据和硬件而异):

import geopandas as gpd
import time
import os

def benchmark_format(gdf, format_name, write_func, read_func, filepath):
    """格式性能基准测试"""
    # 写入测试
    start = time.time()
    write_func(gdf, filepath)
    write_time = time.time() - start

    # 文件大小
    if os.path.isfile(filepath):
        file_size = os.path.getsize(filepath) / (1024 * 1024)  # MB
    else:
        # 目录(如 Shapefile)
        total = sum(
            os.path.getsize(os.path.join(dirpath, f))
            for dirpath, _, filenames in os.walk(filepath)
            for f in filenames
        )
        file_size = total / (1024 * 1024)

    # 读取测试
    start = time.time()
    _ = read_func(filepath)
    read_time = time.time() - start

    print(f"{format_name:15s} | "
          f"写入: {write_time:6.2f}s | "
          f"读取: {read_time:6.2f}s | "
          f"大小: {file_size:8.2f} MB")

# 运行基准测试
# benchmark_format(gdf, "Shapefile",
#     lambda g, p: g.to_file(p),
#     lambda p: gpd.read_file(p),
#     "benchmark.shp")
# benchmark_format(gdf, "GeoJSON",
#     lambda g, p: g.to_file(p, driver="GeoJSON"),
#     lambda p: gpd.read_file(p),
#     "benchmark.geojson")
# ... 等等

8.9.2 典型性能数据

以 100 万个点要素(含 10 个属性列)为例的参考数据:

格式 写入时间 读取时间 文件大小
GeoParquet (Snappy) 1.5 s 0.8 s 45 MB
GeoParquet (Zstd) 2.0 s 0.9 s 35 MB
Feather 1.0 s 0.5 s 70 MB
Shapefile 8.0 s 5.0 s 120 MB
GeoPackage 12.0 s 6.0 s 90 MB
GeoJSON 25.0 s 15.0 s 350 MB

注意: 以上数据为近似参考值,实际性能受数据特征、硬件配置、操作系统等因素影响。

8.9.3 什么时候选择什么格式

# 决策辅助函数
def suggest_format(use_case):
    """根据使用场景推荐文件格式"""
    suggestions = {
        "长期存储与分析": "GeoParquet (Zstd 压缩)",
        "Web 前端展示": "GeoJSON 或 FlatGeobuf",
        "进程间数据传递": "Feather",
        "与 ArcGIS 交互": "File Geodatabase 或 Shapefile",
        "云存储与远程访问": "GeoParquet 或 FlatGeobuf",
        "与旧系统兼容": "Shapefile",
        "多图层数据": "GeoPackage",
        "大数据处理": "GeoParquet (分区)",
    }
    return suggestions.get(use_case, "GeoParquet(通用推荐)")

8.10 云存储集成

8.10.1 远程文件读取

GeoParquet 的一个重要优势是支持从云存储直接读取:

import geopandas as gpd

# 从 AWS S3 读取
gdf = gpd.read_parquet(
    "s3://my-bucket/data/cities.parquet",
    storage_options={
        "key": "your-access-key",
        "secret": "your-secret-key",
        "region": "us-east-1"
    }
)

# 从 Google Cloud Storage 读取
gdf = gpd.read_parquet(
    "gs://my-bucket/data/cities.parquet",
    storage_options={
        "token": "path/to/credentials.json"
    }
)

# 从 Azure Blob Storage 读取
gdf = gpd.read_parquet(
    "abfs://container@account.dfs.core.windows.net/data/cities.parquet",
    storage_options={
        "account_name": "your-account",
        "account_key": "your-key"
    }
)

8.10.2 远程文件写入

# 写入到 S3
gdf.to_parquet(
    "s3://my-bucket/output/result.parquet",
    storage_options={
        "key": "your-access-key",
        "secret": "your-secret-key"
    }
)

8.10.3 HTTP/HTTPS 直接读取

# 从公开的 HTTP URL 读取
gdf = gpd.read_parquet(
    "https://data.example.com/open-data/buildings.parquet"
)

# 许多开放数据平台提供 GeoParquet 格式
# 例如:Overture Maps、Microsoft Building Footprints 等

8.10.4 使用 fsspec 配置

import fsspec

# 使用 fsspec 的文件系统抽象
fs = fsspec.filesystem("s3", anon=True)  # 匿名访问公开数据

# 列出远程文件
files = fs.ls("s3://open-data-bucket/geo/")
print(files)

# 读取
with fs.open("s3://open-data-bucket/geo/data.parquet", "rb") as f:
    gdf = gpd.read_parquet(f)

8.11 最佳实践

8.11.1 写入最佳实践

import geopandas as gpd

# 1. 选择合适的压缩算法
# 推荐使用 Zstd — 在压缩率和速度之间取得最佳平衡
gdf.to_parquet("data.parquet", compression="zstd")

# 2. 启用覆盖边界框(用于大数据集的空间查询优化)
gdf.to_parquet("data.parquet", write_covering_bbox=True)

# 3. 优先使用 GeoArrow 编码(如果几何类型一致)
if len(gdf.geom_type.unique()) == 1:
    gdf.to_parquet("data.parquet", geometry_encoding="geoarrow")
else:
    gdf.to_parquet("data.parquet", geometry_encoding="WKB")

# 4. 不要将不必要的索引写入文件
gdf.to_parquet("data.parquet", index=False)

8.11.2 读取最佳实践

# 1. 只读取需要的列
gdf = gpd.read_parquet("data.parquet", columns=["name", "geometry"])

# 2. 使用空间过滤
gdf = gpd.read_parquet("data.parquet", bbox=(115, 39, 117, 41))

# 3. 对于大数据集,先查看元数据
import pyarrow.parquet as pq
pf = pq.ParquetFile("data.parquet")
print(f"行数: {pf.metadata.num_rows}")
print(f"行组数: {pf.metadata.num_row_groups}")
print(f"列数: {pf.metadata.num_columns}")

8.11.3 数据管道中的最佳实践

# 推荐的数据处理管道

# 1. 原始数据:可能是 Shapefile、GeoJSON 等
raw_gdf = gpd.read_file("raw_data.shp")

# 2. 数据清洗和处理
cleaned_gdf = clean_and_process(raw_gdf)

# 3. 持久化为 GeoParquet(高效存储)
cleaned_gdf.to_parquet(
    "processed/data.parquet",
    compression="zstd",
    write_covering_bbox=True
)

# 4. 后续分析直接读取 GeoParquet(极快)
gdf = gpd.read_parquet("processed/data.parquet")

# 5. 最终结果按需导出为其他格式
gdf.to_file("output/result.geojson", driver="GeoJSON")  # Web 展示
gdf.to_file("output/result.gpkg")                        # GIS 软件

8.11.4 格式转换工具函数

def convert_to_parquet(input_path, output_path, **kwargs):
    """将任意格式转换为 GeoParquet"""
    gdf = gpd.read_file(input_path)

    # 默认选项
    defaults = {
        "compression": "zstd",
        "write_covering_bbox": True,
        "index": False
    }
    defaults.update(kwargs)

    gdf.to_parquet(output_path, **defaults)

    # 打印统计信息
    import os
    input_size = os.path.getsize(input_path) / (1024 * 1024)
    output_size = os.path.getsize(output_path) / (1024 * 1024)
    ratio = input_size / output_size if output_size > 0 else 0

    print(f"转换完成:")
    print(f"  输入: {input_path} ({input_size:.2f} MB)")
    print(f"  输出: {output_path} ({output_size:.2f} MB)")
    print(f"  压缩比: {ratio:.1f}x")
    print(f"  行数: {len(gdf)}")

# 使用示例
convert_to_parquet("old_data.shp", "new_data.parquet")

8.12 本章小结

本章详细介绍了 GeoParquet 和 GeoArrow 格式在 GeoPandas 中的应用,涵盖了以下核心内容:

主题 关键要点
GeoParquet Apache Parquet 的地理空间扩展,列式存储,高效压缩
规范版本 1.1.0 支持 GeoArrow 原生编码和覆盖边界框
read_parquet() 支持列选择、空间过滤,云存储直接读取
to_parquet() 支持多种压缩算法,推荐 Zstd
Feather Arrow IPC 格式,极快的序列化/反序列化速度
GeoArrow 编码 原生 Arrow 数组结构存储几何,性能优于 WKB
Arrow 互操作 to_arrow() / from_arrow() 实现 Arrow 生态集成
性能 比 Shapefile 快 5-10 倍,文件小 2-3 倍
云存储 原生支持 S3、GCS、Azure Blob 等远程路径

核心建议:

  • 新项目优先使用 GeoParquet 作为数据存储格式
  • 压缩算法推荐 Zstd(最佳平衡)
  • 大数据集启用 write_covering_bbox=True
  • 几何类型一致时使用 GeoArrow 编码
  • 读取时充分利用列选择和空间过滤
  • 中间缓存数据可使用 Feather 格式

下一章我们将学习 GeoPandas 与数据库的交互,包括 PostGIS 和 Spatialite 的读写操作。