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

推荐订阅源

S
Securelist
L
LangChain Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
腾讯CDC
月光博客
月光博客
S
Schneier on Security
Simon Willison's Weblog
Simon Willison's Weblog
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
美团技术团队
S
Security @ Cisco Blogs
人人都是产品经理
人人都是产品经理
L
LINUX DO - 热门话题
S
SegmentFault 最新的问题
D
DataBreaches.Net
C
CXSECURITY Database RSS Feed - CXSecurity.com
Microsoft Security Blog
Microsoft Security Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
N
News | PayPal Newsroom
W
WeLiveSecurity
F
Fortinet All Blogs
The Hacker News
The Hacker News
The Register - Security
The Register - Security
P
Palo Alto Networks Blog
Engineering at Meta
Engineering at Meta
T
The Exploit Database - CXSecurity.com
Vercel News
Vercel News
G
GRAHAM CLULEY
博客园 - 聂微东
P
Privacy International News Feed
P
Privacy & Cybersecurity Law Blog
Hacker News: Ask HN
Hacker News: Ask HN
Webroot Blog
Webroot Blog
G
Google Developers Blog
T
Tailwind CSS Blog
V
Vulnerabilities – Threatpost
L
Lohrmann on Cybersecurity
T
Tenable Blog
Cisco Talos Blog
Cisco Talos Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
Security Affairs
云风的 BLOG
云风的 BLOG
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
B
Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hugging Face - Blog
Hugging Face - Blog
T
Threatpost

博客园 - 我才是银古

第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
第18章:与第三方库集成
我才是银古 · 2026-06-24 · via 博客园 - 我才是银古

第18章:与第三方库集成

本章讲解 Shapely 与 Python GIS 生态中其他重要库的集成方式,包括 GeoPandas、Fiona、matplotlib、NumPy、pyproj、rasterio 等。Shapely 作为几何计算的核心引擎,与这些库的良好配合是构建完整 GIS 工作流的关键。


18.1 与 GeoPandas 集成

18.1.1 GeoPandas 简介

GeoPandas 是基于 Pandas 的地理空间数据处理库,其核心 GeoDataFramegeometry 列直接使用 Shapely 几何对象。

18.1.2 从 Shapely 到 GeoDataFrame

import geopandas as gpd
from shapely import Point, Polygon, LineString
import pandas as pd

# 从 Shapely 几何体创建 GeoDataFrame
data = {
    'name': ['北京', '上海', '广州'],
    'population': [2171, 2487, 1868],
    'geometry': [
        Point(116.46, 39.92),
        Point(121.47, 31.23),
        Point(113.26, 23.13),
    ]
}

gdf = gpd.GeoDataFrame(data, crs="EPSG:4326")
print(gdf)
print(f"\n几何列类型: {type(gdf.geometry.iloc[0])}")

18.1.3 GeoDataFrame 中的 Shapely 操作

import geopandas as gpd
from shapely import Point

# 创建城市点数据
cities = gpd.GeoDataFrame({
    'name': ['A', 'B', 'C'],
    'geometry': [Point(0, 0), Point(3, 4), Point(6, 0)]
}, crs="EPSG:4326")

# 使用 Shapely 的 buffer 操作
cities['buffer_500m'] = cities.geometry.buffer(0.005)  # 约 500m

# 使用 Shapely 的 centroid
print("质心:", cities.geometry.values[0])

# 计算两两距离
for i, city1 in cities.iterrows():
    for j, city2 in cities.iterrows():
        if i < j:
            dist = city1.geometry.distance(city2.geometry)
            print(f"{city1['name']} → {city2['name']}: {dist:.4f}")

18.1.4 空间连接

import geopandas as gpd
from shapely import Point, Polygon

# 点数据
points = gpd.GeoDataFrame({
    'id': [1, 2, 3, 4],
    'geometry': [Point(1, 1), Point(3, 3), Point(5, 5), Point(7, 2)]
})

# 面数据
polygons = gpd.GeoDataFrame({
    'zone': ['A', 'B'],
    'geometry': [
        Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]),
        Polygon([(4, 0), (8, 0), (8, 4), (4, 4)]),
    ]
})

# 空间连接(点在哪个区域内)
joined = gpd.sjoin(points, polygons, how='left', predicate='within')
print(joined[['id', 'zone']])

18.1.5 批量几何操作

import geopandas as gpd
from shapely import Polygon
from shapely.ops import unary_union

# 创建多个地块
parcels = gpd.GeoDataFrame({
    'type': ['住宅', '商业', '住宅', '工业', '商业'],
    'geometry': [
        Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
        Polygon([(2, 0), (4, 0), (4, 2), (2, 2)]),
        Polygon([(0, 2), (2, 2), (2, 4), (0, 4)]),
        Polygon([(2, 2), (4, 2), (4, 4), (2, 4)]),
        Polygon([(4, 0), (6, 0), (6, 2), (4, 2)]),
    ]
})

# 按类型分组合并
for ptype, group in parcels.groupby('type'):
    merged = unary_union(group.geometry.values)
    print(f"{ptype}: 面积={merged.area:.2f}")

# 计算总面积
print(f"总面积: {parcels.geometry.area.sum():.2f}")

18.2 与 Fiona 集成

18.2.1 使用 shape() 和 mapping()

Shapely 提供 shape()mapping() 函数与 Fiona(以及任何 GeoJSON 兼容接口)交互:

from shapely.geometry import shape, mapping
from shapely import Polygon

# GeoJSON 字典 → Shapely 几何
geojson_dict = {
    "type": "Polygon",
    "coordinates": [[(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]]
}

geom = shape(geojson_dict)
print(f"类型: {geom.geom_type}")
print(f"面积: {geom.area}")

# Shapely 几何 → GeoJSON 字典
poly = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
geojson = mapping(poly)
print(f"GeoJSON: {geojson}")

18.2.2 读取 Shapefile

import fiona
from shapely.geometry import shape

# 读取 Shapefile
# with fiona.open("buildings.shp") as src:
#     for feature in src:
#         geom = shape(feature['geometry'])
#         props = feature['properties']
#         print(f"名称: {props.get('name')}")
#         print(f"面积: {geom.area:.2f}")
#         print(f"类型: {geom.geom_type}")

# 示例:模拟 Fiona 输出
features = [
    {
        'geometry': {'type': 'Polygon', 'coordinates': [[(0,0),(1,0),(1,1),(0,1),(0,0)]]},
        'properties': {'name': '建筑A', 'floors': 5}
    },
    {
        'geometry': {'type': 'Polygon', 'coordinates': [[(2,0),(3,0),(3,1),(2,1),(2,0)]]},
        'properties': {'name': '建筑B', 'floors': 3}
    }
]

for feature in features:
    geom = shape(feature['geometry'])
    print(f"{feature['properties']['name']}: 面积={geom.area:.2f}")

18.2.3 写入 GeoJSON 文件

from shapely.geometry import mapping
from shapely import Point, Polygon
import json

# 创建 GeoJSON FeatureCollection
features = []

geometries = [
    ("公园A", Point(116.4, 39.9).buffer(0.01)),
    ("公园B", Point(116.5, 39.8).buffer(0.015)),
]

for name, geom in geometries:
    feature = {
        "type": "Feature",
        "geometry": mapping(geom),
        "properties": {"name": name, "area": geom.area}
    }
    features.append(feature)

geojson = {
    "type": "FeatureCollection",
    "features": features
}

# 输出 GeoJSON
print(json.dumps(geojson, indent=2, ensure_ascii=False)[:500])

18.3 与 matplotlib 可视化

18.3.1 基本绘图

import matplotlib.pyplot as plt
from shapely import Point, Polygon, LineString
from shapely.plotting import plot_polygon, plot_line, plot_points

# 创建几何体
polygon = Polygon([(0, 0), (4, 0), (4, 3), (0, 3)])
line = LineString([(0, 0), (2, 3), (4, 0)])
point = Point(2, 1.5)

fig, axes = plt.subplots(1, 3, figsize=(12, 4))

# 绘制多边形
plot_polygon(polygon, ax=axes[0], add_points=False, color='lightblue', alpha=0.5)
axes[0].set_title('多边形')

# 绘制线
plot_line(line, ax=axes[1], add_points=True, color='red')
axes[1].set_title('线')

# 绘制点
axes[2].plot(point.x, point.y, 'ko', markersize=10)
axes[2].set_title('点')

for ax in axes:
    ax.set_aspect('equal')

plt.tight_layout()
plt.savefig('/tmp/shapely_plot.png', dpi=100)
plt.close()

18.3.2 使用 Matplotlib Patches

import matplotlib.pyplot as plt
from matplotlib.patches import Polygon as MplPolygon
from matplotlib.collections import PatchCollection
from shapely import Polygon, MultiPolygon
import numpy as np

def shapely_to_mpl_patch(polygon, **kwargs):
    """将 Shapely Polygon 转换为 Matplotlib Patch"""
    exterior = np.array(polygon.exterior.coords)
    patch = MplPolygon(exterior, **kwargs)
    return patch

# 创建多个多边形
polys = [
    Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
    Polygon([(3, 0), (5, 0), (5, 2), (3, 2)]),
    Polygon([(1, 3), (4, 3), (4, 5), (1, 5)]),
]

fig, ax = plt.subplots(figsize=(8, 6))

colors = ['#ff6b6b', '#4ecdc4', '#45b7d1']
for poly, color in zip(polys, colors):
    patch = shapely_to_mpl_patch(poly, facecolor=color, edgecolor='black', alpha=0.7)
    ax.add_patch(patch)

ax.set_xlim(-1, 6)
ax.set_ylim(-1, 6)
ax.set_aspect('equal')
ax.set_title('Shapely + Matplotlib')
plt.savefig('/tmp/shapely_patches.png', dpi=100)
plt.close()

18.3.3 缓冲区可视化

import matplotlib.pyplot as plt
from shapely import Point, LineString
from shapely.plotting import plot_polygon, plot_line

fig, ax = plt.subplots(figsize=(8, 8))

# 点缓冲区
center = Point(5, 5)
for dist in [1, 2, 3]:
    buf = center.buffer(dist)
    plot_polygon(buf, ax=ax, add_points=False, 
                 color=f'C{dist}', alpha=0.2)
ax.plot(center.x, center.y, 'ko', markersize=5)

# 线缓冲区
line = LineString([(0, 0), (3, 2), (6, 1)])
buf = line.buffer(0.5)
plot_polygon(buf, ax=ax, add_points=False, color='yellow', alpha=0.3)
plot_line(line, ax=ax, add_points=False, color='red')

ax.set_aspect('equal')
ax.set_title('缓冲区可视化')
plt.savefig('/tmp/shapely_buffer_viz.png', dpi=100)
plt.close()

18.4 与 NumPy 集成

18.4.1 坐标数组操作

import numpy as np
from shapely import get_coordinates, set_coordinates, points
from shapely import Point, LineString, Polygon

# 从几何体提取坐标到 NumPy 数组
polygon = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
coords = get_coordinates(polygon)
print(f"坐标数组形状: {coords.shape}")
print(coords)

# 修改坐标生成新几何体
new_coords = coords * 2  # 所有坐标乘以 2
new_polygon = set_coordinates(polygon, new_coords)
print(f"原面积: {polygon.area}, 新面积: {new_polygon.area}")

18.4.2 向量化几何创建

import numpy as np
from shapely import points, linestrings, polygons, box

# 批量创建点
coords = np.random.uniform(0, 100, (1000, 2))
pts = points(coords)
print(f"创建 {len(pts)} 个点")

# 批量创建矩形
boxes = [box(i, 0, i+1, 1) for i in range(10)]
print(f"创建 {len(boxes)} 个矩形")

# 向量化属性计算
import shapely
areas = shapely.area(boxes)
print(f"面积: {areas}")

18.4.3 NumPy 数组中的几何体

import numpy as np
import shapely
from shapely import Point

# Shapely 2.0+ 支持几何体的 NumPy 数组
geom_array = np.array([
    Point(0, 0), Point(1, 1), Point(2, 2), Point(3, 3)
])

# 向量化操作
buffers = shapely.buffer(geom_array, 0.5)
print(f"缓冲区面积: {shapely.area(buffers)}")

# 向量化谓词
target = Point(1.5, 1.5).buffer(2)
results = shapely.contains(target, geom_array)
print(f"包含结果: {results}")

18.5 与 pyproj 坐标转换

18.5.1 基本坐标转换

from shapely import Point, Polygon
from shapely.ops import transform
from pyproj import Transformer

# 创建坐标转换器(WGS84 → Web Mercator)
transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)

# 转换点
point_wgs84 = Point(116.46, 39.92)  # 北京(经纬度)

# 使用 transform 函数
point_mercator = transform(transformer.transform, point_wgs84)
print(f"WGS84: {point_wgs84}")
print(f"Web Mercator: {point_mercator}")

18.5.2 多边形坐标转换

from shapely import Polygon
from shapely.ops import transform
from pyproj import Transformer

transformer = Transformer.from_crs("EPSG:4326", "EPSG:32650", always_xy=True)

# WGS84 多边形
polygon_wgs84 = Polygon([
    (116.0, 39.5), (117.0, 39.5),
    (117.0, 40.5), (116.0, 40.5)
])

# 转换到 UTM Zone 50N
polygon_utm = transform(transformer.transform, polygon_wgs84)
print(f"WGS84 面积(度²): {polygon_wgs84.area:.6f}")
print(f"UTM 面积(m²): {polygon_utm.area:.0f}")
print(f"UTM 面积(km²): {polygon_utm.area / 1e6:.2f}")

18.5.3 使用 transform_coordseq(Shapely 2.1+)

from shapely import transform as shapely_transform
from shapely import Point, LineString
from pyproj import Transformer
import numpy as np

transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)

def proj_transform(coords):
    """坐标转换函数,接收 (N, 2) 数组"""
    x, y = transformer.transform(coords[:, 0], coords[:, 1])
    return np.column_stack([x, y])

line_wgs84 = LineString([(116, 39), (117, 40), (118, 39)])

# 注意:Shapely 2.1+ 的 shapely.transform 函数
# line_mercator = shapely_transform(line_wgs84, proj_transform)

18.6 与 rasterio 集成

18.6.1 几何体作为掩膜

# 使用 Shapely 几何体裁剪栅格数据
from shapely import Polygon, box

# 创建掩膜几何体
mask_polygon = Polygon([
    (116.0, 39.5), (117.0, 39.5),
    (117.0, 40.5), (116.0, 40.5)
])

# 与 rasterio 配合使用
# import rasterio
# from rasterio.mask import mask as rio_mask
#
# with rasterio.open("dem.tif") as src:
#     out_image, out_transform = rio_mask(
#         src,
#         [mapping(mask_polygon)],
#         crop=True
#     )
#     print(f"裁剪后形状: {out_image.shape}")

# 创建边界框用于窗口读取
bbox = box(116.0, 39.5, 117.0, 40.5)
print(f"边界框: {bbox.bounds}")

18.7 与 scipy 空间分析

18.7.1 KDTree vs STRtree

import numpy as np
from shapely import STRtree, points

# 使用 Shapely STRtree
np.random.seed(42)
coords = np.random.uniform(0, 100, (1000, 2))
geom_points = points(coords)

tree = STRtree(geom_points)

# 查询最近邻
from shapely import Point
query_point = Point(50, 50)
nearest_idx = tree.nearest(query_point)
print(f"STRtree 最近邻索引: {nearest_idx}")

# scipy KDTree 对比
# from scipy.spatial import KDTree
# kdtree = KDTree(coords)
# dist, idx = kdtree.query([50, 50])
# print(f"KDTree 最近邻: 索引={idx}, 距离={dist:.4f}")

18.8 GeoJSON 与 Web 集成

18.8.1 生成 Leaflet/Folium 兼容数据

from shapely import Point, Polygon
from shapely.geometry import mapping
import json

# 创建几何体
features = []

# 添加点要素
for name, lon, lat in [("北京", 116.46, 39.92), ("上海", 121.47, 31.23)]:
    features.append({
        "type": "Feature",
        "geometry": mapping(Point(lon, lat)),
        "properties": {"name": name}
    })

# 添加面要素
area = Polygon([(116, 39), (117, 39), (117, 40), (116, 40)])
features.append({
    "type": "Feature",
    "geometry": mapping(area),
    "properties": {"name": "区域A"}
})

geojson = {"type": "FeatureCollection", "features": features}
print(json.dumps(geojson, indent=2, ensure_ascii=False)[:500])

# 可直接用于 Folium
# import folium
# m = folium.Map(location=[35, 115], zoom_start=5)
# folium.GeoJson(geojson).add_to(m)
# m.save("map.html")

18.9 与 PostGIS 数据库交互

18.9.1 通过 psycopg2 读写几何体

from shapely import Point, Polygon
from shapely import wkb, wkt

# 写入 PostGIS
polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
wkb_hex = polygon.wkb_hex

# SQL 示例
sql_insert = f"""
INSERT INTO parcels (name, geom)
VALUES ('地块A', ST_GeomFromWKB(decode('{wkb_hex}', 'hex'), 4326))
"""
print("插入 SQL:")
print(sql_insert[:100] + "...")

# 从 PostGIS 读取
# cursor.execute("SELECT ST_AsBinary(geom) FROM parcels")
# for row in cursor:
#     geom = wkb.loads(row[0])
#     print(f"类型: {geom.geom_type}, 面积: {geom.area}")

18.9.2 通过 SQLAlchemy + GeoAlchemy2

# SQLAlchemy + GeoAlchemy2 集成示例
# from sqlalchemy import create_engine
# from geoalchemy2 import Geometry
# from shapely import Point
#
# engine = create_engine("postgresql://user:pass@localhost/gisdb")
#
# # 使用 WKT 格式
# point = Point(116.46, 39.92)
# sql = f"INSERT INTO cities (name, geom) VALUES ('北京', ST_GeomFromText('{point.wkt}', 4326))"

18.10 集成最佳实践

18.10.1 推荐的库组合

任务 推荐组合
矢量数据分析 Shapely + GeoPandas + Fiona
坐标转换 Shapely + pyproj
可视化 Shapely + matplotlib / Folium
栅格分析 Shapely + rasterio
数据库操作 Shapely + psycopg2 / GeoAlchemy2
高性能计算 Shapely + NumPy
Web GIS Shapely + Flask/FastAPI + GeoJSON

18.10.2 数据流转模式

数据读取                    核心处理                    输出
┌──────────┐           ┌────────────┐           ┌──────────┐
│ Fiona    │──shape()─→│  Shapely   │──mapping()─→│ GeoJSON  │
│ GeoPandas│           │  几何操作   │            │ Fiona    │
│ PostGIS  │──wkb──→   │  空间分析   │──wkb──→    │ PostGIS  │
│ WKT/WKB  │           │  缓冲/裁剪  │            │ matplotlib│
└──────────┘           └────────────┘           └──────────┘

18.10.3 注意事项

  1. CRS 一致性:Shapely 不处理坐标参考系统,确保在操作前统一 CRS
  2. 单位一致性:面积、距离计算结果取决于坐标单位(度 vs 米)
  3. 几何有效性:在库间传递几何体时验证有效性
  4. 版本兼容:确保 Shapely 2.x 与其他库的版本兼容
  5. 内存管理:大数据集使用流式处理,避免一次性加载所有几何体