3. GeoDataFrame与地质矢量数据模型

在GeoPandas中,GeoDataFrame是承载地质矢量数据的核心容器。它继承自pandas的DataFrame,因此拥有完整的属性表能力;同时额外增加了一列特殊的几何列(默认名为geometry),用于存储每个要素的空间形状。理解“属性表 + 几何列 + 坐标参考系”这三件套,是后续批量处理断层、地层、钻孔、地质灾害点等地质数据的基础。

一、GeoDataFrame的三层结构

一个GeoDataFrame可以拆成三个层次来看:索引(index)、属性列(columns)和几何列(geometry)。属性列与普通DataFrame完全一致,可以存字符串、数值、日期、类别;几何列则是一个GeoSeries,每个元素是Shapely几何对象(Point、LineString、Polygon等),并统一挂载一个坐标参考系(CRS)。

创建方式通常有两种。第一种是从带几何信息的字典直接构造:

import geopandas as gpd
from shapely.geometry import Point, LineString, Polygon

faults = gpd.GeoDataFrame(
    {
        "fault_id": ["F01", "F02"],
        "fault_name": ["青龙断裂", "白水断裂"],
        "fault_type": ["正断层", "逆断层"],
        "length_km": [12.4, 8.7],
    },
    geometry=[
        LineString([(102.1, 30.2), (102.5, 30.6)]),
        LineString([(102.6, 30.1), (102.9, 30.4)]),
    ],
    crs="EPSG:4326",
)
print(faults.crs)
print(faults.geom_type)

第二种是从Shapefile、GeoJSON、GPKG等文件读取:gpd.read_file("faults.shp")。读取后应立刻检查gdf.crs、gdf.geom_type.value_counts()和gdf.total_bounds,确认坐标系、几何类型和范围是否符合预期,这是地质数据入库前的第一道体检。

二、几何类型与地质要素的对应关系

地质矢量数据并非随意选择几何类型,而是由地质对象的空间形态决定。常见对应关系如下表(示例为教学假设数据):

几何类型典型地质要素关键属性示例
Point / MultiPoint钻孔、泉点、滑坡监测点、采样点孔深、水位、岩性代号
LineString / MultiLineString断层、褶皱轴线、地质剖面线、河流断层性质、走向、倾角
Polygon / MultiPolygon地层单元、岩体、地质灾害易发区地层代号、岩性、面积

需要特别注意:断层在中小比例尺下常被抽象为线,但在大比例尺构造建模中可能表示为带宽度和倾向的面;地层单元则几乎总是面。几何类型一旦确定,后续的空间查询、叠加分析和制图符号化都会依赖它,因此入库前应统一规范,避免同一图层混用点、线、面。

GeoDataFrame 结构 index 0 1 2 3 属性列(DataFrame) fault_id / name fault_type length_km … geometry(GeoSeries) LineString Point Polygon + CRS 统一坐标参考系 CRS(如 EPSG:4326 / EPSG:4547) 几何类型 → 地质要素 Point / MultiPoint 钻孔、泉点、滑坡监测点、采样点 LineString / MultiLineString 断层、褶皱轴、剖面线、河流 Polygon / MultiPolygon 地层单元、岩体、灾害易发区 同一图层应统一几何类型,避免点线面混存

三、属性表操作:筛选、派生与连接

属性表操作是批处理中最频繁的环节。以断层图层为例,常见操作包括按属性筛选、派生新字段、按空间位置连接属性。下面示例均为教学假设数据:

# 1) 按属性筛选:只保留正断层且长度大于10km
sel = faults[(faults["fault_type"] == "正断层") & (faults["length_km"] > 10)]

# 2) 派生字段:按长度分级
faults["length_class"] = faults["length_km"].apply(
    lambda v: "长" if v >= 10 else ("中" if v >= 5 else "短")
)

# 3) 空间连接:把断层与地层面叠加,取断层穿过的地层代号
strata = gpd.read_file("strata.gpkg")
joined = gpd.sjoin(faults, strata[["stratum_code", "geometry"]],
                   how="left", predicate="intersects")
print(joined[["fault_id", "stratum_code"]])

这里的关键判断点有三个:筛选条件必须用&、|并加括号,避免运算符优先级错误;派生字段用apply或map时要注意空值和类型;sjoin的predicate参数决定空间关系(intersects、within、contains),选错会直接导致属性挂接错误。执行后应检查行数变化和stratum_code的空值比例,判断连接是否合理。

四、空间参考初识:CRS不是可选项

坐标参考系(CRS)定义了几何坐标如何对应地球表面。地质数据常见两类:地理坐标系(如EPSG:4326,单位度)和投影坐标系(如EPSG:4547,CGCS2000 3度带,单位米)。长度、面积、缓冲区等计算必须在投影坐标系下进行,否则以度为单位的“长度”没有地质意义。

# 查看与转换 CRS
print(faults.crs)                      # EPSG:4326
faults_m = faults.to_crs("EPSG:4547")  # 转为米制投影
faults_m["length_m"] = faults_m.length
print(faults_m[["fault_id", "length_m"]].head())

实操中要养成三个习惯:读入后先查crs是否为None;跨图层叠加前确认两者CRS一致,不一致先用to_crs统一;需要距离或面积时先投影再计算。若crs为None,说明数据缺少空间参考信息,应结合数据来源补设,而不是直接假设为WGS84。

建议在批处理脚本开头固定一段“数据体检”代码:输出crs、geom_type.value_counts()、total_bounds和空几何数量,能提前发现大多数入库问题。

把GeoDataFrame的结构、几何类型与地质要素的对应、属性表操作和CRS这四件事串起来,就形成了地质矢量数据批处理的基本数据模型:属性描述“是什么”,几何描述“在哪里、什么形态”,CRS保证“位置和量算可信”。后续的批量裁剪、叠加、制图与入库,都建立在这一模型之上。