第12章:数据质检(QC)步骤详解
本章全面介绍 GeoPipeAgent 的 10 个数据质检步骤。这些步骤采用独特的”Check and Passthrough”模式——输入数据原样通过,质检问题以
QcIssue对象形式收集。本章逐一剖析每个 QC 步骤的参数、检测逻辑和源码实现,并通过组合实战演示如何构建完整的质检流水线。
12.1 数据质检概述
12.1.1 QC 步骤清单
GeoPipeAgent 提供 10 个 QC 步骤,覆盖矢量和栅格两大数据类型:
矢量质检步骤(7 个):
| 步骤 ID | 功能 | 检查目标 |
|---|---|---|
qc.geometry_validity |
几何有效性检查 | 自相交、空几何、环方向 |
qc.crs_check |
坐标参考系检查 | CRS 存在性和正确性 |
qc.topology |
拓扑关系检查 | 重叠、缝隙、悬挂节点 |
qc.attribute_completeness |
属性完整性检查 | 必填字段空值/缺失 |
qc.attribute_domain |
属性值域检查 | 字段值域和正则匹配 |
qc.value_range |
数值范围检查 | 数值字段越界 |
qc.duplicate_check |
重复要素检查 | 几何/属性重复 |
栅格质检步骤(3 个):
| 步骤 ID | 功能 | 检查目标 |
|---|---|---|
qc.raster_nodata |
NoData 一致性检查 | NoData 值和比例 |
qc.raster_resolution |
分辨率一致性检查 | 像素尺寸一致性 |
qc.raster_value_range |
栅格值域检查 | 像素值范围 |
12.1.2 “Check and Passthrough” 模式
QC 步骤的核心设计模式是“Check and Passthrough”(检查并通过):
┌─────────────────┐
│ QC 步骤执行 │
│ │
输入数据 ──────────────▶│ 1. 执行检查 │──────────────▶ 输出数据
(GeoDataFrame) │ 2. 收集问题 │ (原样输出)
│ 3. 数据原样通过 │
│ │
└───────┬─────────┘
│
▼
┌─────────────────┐
│ $step.issues │
│ (QcIssue 列表) │
└─────────────────┘
关键特征:
| 特征 | 说明 |
|---|---|
| 数据透传 | 输入数据不做任何修改(除 auto_fix 为 true 时),直接作为步骤输出 |
| 问题收集 | 所有质检问题存储在 StepResult.issues 属性中 |
| 不中断流水线 | 即使发现问题,流水线也继续执行(除非配合 on_error 使用) |
| 问题可引用 | 后续步骤可通过 $steps.{step_id}.issues 引用质检结果 |
12.1.3 _helpers.py 工具函数
QC 步骤共享 _helpers.py 中的工具函数来构建统一格式的步骤结果:
# geopipeagent/steps/qc/_helpers.py
def build_qc_result(gdf, issues, stats=None):
"""构建 QC 步骤的标准返回结果
Args:
gdf: 原始输入数据(透传)
issues: QcIssue 列表
stats: 额外统计信息
Returns:
StepResult: 包含透传数据和质检问题
"""
issues_gdf = _issues_to_geodataframe(issues, crs=gdf.crs)
base_stats = {
"feature_count": len(gdf),
"issue_count": len(issues),
"error_count": sum(1 for i in issues if i.severity == "error"),
"warning_count": sum(1 for i in issues if i.severity == "warning"),
}
if stats:
base_stats.update(stats)
return StepResult(
output=gdf, # 数据原样透传
stats=base_stats,
issues=issues, # 问题列表
)
12.2 QcIssue 数据模型详解
12.2.1 数据类定义
QcIssue 是所有质检问题的统一数据模型,使用 Python dataclass 定义:
from dataclasses import dataclass, field
from typing import Any
@dataclass
class QcIssue:
rule_id: str # 规则标识
severity: str # 严重级别
feature_index: int | None = None # 要素索引
message: str = "" # 问题描述
geometry: Any = None # 问题几何
details: dict = field(default_factory=dict) # 扩展信息
12.2.2 字段详解
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
rule_id |
str |
质检规则标识,对应步骤 ID | "geometry_validity" |
severity |
str |
严重级别:error/warning/info |
"error" |
feature_index |
int\|None |
出错要素的行索引,栅格检查时为 None |
42 |
message |
str |
人类可读的问题描述 | "Self-intersection at (116.3, 39.9)" |
geometry |
Any |
问题所在位置的几何,可为点/线/面 | Point(116.3, 39.9) |
details |
dict |
扩展详情字典,存放额外的诊断信息 | {"field": "name", "value": None} |
12.2.3 severity 级别含义
严重级别层次:
┌──────────┐
│ error │ ← 数据错误,必须修复
├──────────┤ 例:自相交多边形、CRS 缺失
│ warning │ ← 数据异常,建议检查
├──────────┤ 例:属性空值、值接近边界
│ info │ ← 提示信息,仅供参考
└──────────┘ 例:重复要素(可能是合理的)
| 级别 | 含义 | 对流水线的影响 |
|---|---|---|
error |
严重数据错误 | 可配合 on_error: fail 终止流水线 |
warning |
潜在问题 | 记录但不终止 |
info |
信息提示 | 仅记录到报告 |
12.2.4 QcIssue 序列化
QcIssue 支持 to_dict() 方法,用于 JSON 报告输出:
issue = QcIssue(
rule_id="geometry_validity",
severity="error",
feature_index=7,
message="Self-intersection detected",
geometry=Point(116.39, 39.91),
details={"type": "self_intersection"},
)
issue.to_dict()
# {
# "rule_id": "geometry_validity",
# "severity": "error",
# "feature_index": 7,
# "message": "Self-intersection detected",
# "geometry": "POINT (116.39 39.91)",
# "details": {"type": "self_intersection"}
# }
12.3 qc.geometry_validity 几何有效性检查
12.3.1 功能说明
qc.geometry_validity 检查矢量数据中每个要素的几何是否有效。这是最基础的质检步骤,通常作为质检流水线的第一步。
12.3.2 参数定义
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
input |
GeoDataFrame |
✅ | — | 输入矢量数据 |
auto_fix |
bool |
❌ | False |
是否自动修复无效几何 |
severity |
str |
❌ | "error" |
问题严重级别 |
12.3.3 检测项目
geometry_validity 检查以下三类几何问题:
| 检测项 | 说明 | 检测方法 | 修复方法 |
|---|---|---|---|
| 自相交 (Self-intersection) | 多边形边界与自身相交 | geometry.is_valid |
make_valid() |
| 空几何 (Empty geometry) | 几何对象为空或 None | geometry.is_empty |
移除或标记 |
| 环方向错误 (Ring orientation) | 外环非逆时针/内环非顺时针 | shapely.is_ccw |
orient() |
几何问题示例:
自相交多边形: 空几何: 环方向错误:
╱╲ 外环应逆时针
╱ ╲ EMPTY ╭───→───╮
╱ ╳ ╲ ← 交叉点 │ │
╱ ╱ ╲ ╲ ↑ ↓ ← 正确
╱__╱ ╲__╲ │ │
╰───←───╯
12.3.4 源码分析
@step(id="qc.geometry_validity", ...)
def geometry_validity(ctx):
gdf = ctx.input("input")
auto_fix = ctx.param("auto_fix") or False
severity = ctx.param("severity") or "error"
issues = []
result_gdf = gdf.copy()
for idx, row in gdf.iterrows():
geom = row.geometry
# 检查空几何
if geom is None or geom.is_empty:
issues.append(QcIssue(
rule_id="geometry_validity",
severity=severity,
feature_index=idx,
message="Empty or null geometry",
details={"type": "empty_geometry"},
))
continue
# 检查几何有效性(含自相交)
if not geom.is_valid:
reason = shapely.validation.explain_validity(geom)
issues.append(QcIssue(
rule_id="geometry_validity",
severity=severity,
feature_index=idx,
message=f"Invalid geometry: {reason}",
geometry=geom,
details={"type": "invalid", "reason": reason},
))
# 自动修复
if auto_fix:
from shapely.validation import make_valid
result_gdf.at[idx, "geometry"] = make_valid(geom)
output = result_gdf if auto_fix else gdf
return build_qc_result(output, issues)
12.3.5 auto_fix 模式
当 auto_fix=true 时,步骤尝试自动修复无效几何:
| 问题类型 | 修复方法 | 说明 |
|---|---|---|
| 自相交 | shapely.validation.make_valid() |
分割或清理自相交区域 |
| 环方向 | shapely.geometry.polygon.orient() |
统一为 OGC 标准方向 |
| 空几何 | 不修复 | 仅记录问题 |
⚠️ 注意:
auto_fix=true是唯一会修改输出数据的 QC 模式。修复后的数据作为步骤输出,而原始问题仍记录在issues中。
12.3.6 使用示例
- id: check_geometry
step: qc.geometry_validity
params:
input: "$steps.read_parcels"
auto_fix: true
severity: "error"
12.4 qc.crs_check 坐标参考系检查
12.4.1 功能说明
qc.crs_check 验证输入数据的坐标参考系(CRS)是否存在且符合预期。CRS 错误会导致所有空间计算结果失误,因此该检查至关重要。
12.4.2 参数定义
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
input |
GeoDataFrame |
✅ | — | 输入矢量数据 |
expected_crs |
str |
❌ | None |
期望的 CRS(如 "EPSG:4326") |
12.4.3 验证逻辑
@step(id="qc.crs_check", ...)
def crs_check(ctx):
gdf = ctx.input("input")
expected_crs = ctx.param("expected_crs")
issues = []
# 检查 CRS 是否存在
if gdf.crs is None:
issues.append(QcIssue(
rule_id="crs_check",
severity="error",
message="CRS is not defined",
))
elif expected_crs:
# 检查 CRS 是否匹配
from pyproj import CRS
expected = CRS.from_user_input(expected_crs)
if gdf.crs != expected:
issues.append(QcIssue(
rule_id="crs_check",
severity="error",
message=f"CRS mismatch: expected {expected_crs}, "
f"got {gdf.crs.to_epsg() or gdf.crs}",
details={
"expected": expected_crs,
"actual": str(gdf.crs),
},
))
return build_qc_result(gdf, issues)
检查分两个阶段:
| 阶段 | 检查内容 | 问题级别 |
|---|---|---|
| 1. CRS 存在性 | gdf.crs is None → CRS 未定义 |
error |
| 2. CRS 正确性 | gdf.crs != expected_crs → CRS 不匹配 |
error |
12.4.4 使用示例
- id: check_crs
step: qc.crs_check
params:
input: "$steps.read_data"
expected_crs: "EPSG:4326"
12.5 qc.topology 拓扑关系检查
12.5.1 功能说明
qc.topology 检查矢量数据要素之间的空间拓扑关系,确保数据满足特定的拓扑规则。常用于土地利用数据、行政区划等需要严格拓扑完整性的场景。
12.5.2 参数定义
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
input |
GeoDataFrame |
✅ | — | 输入矢量数据 |
rules |
list[str] |
✅ | — | 拓扑规则列表 |
tolerance |
float |
❌ | 0.0 |
容差值 |
12.5.3 支持的拓扑规则
| 规则 | 说明 | 检测方法 |
|---|---|---|
no_overlaps |
要素之间不得重叠 | 检查任意两个多边形的交集面积 |
no_gaps |
要素之间不得有缝隙 | 检查合并后多边形的孔洞 |
no_dangles |
线要素不得有悬挂端点 | 检查线端点的连通性 |
拓扑问题示例:
no_overlaps(无重叠): no_gaps(无缝隙): no_dangles(无悬挂):
┌────┐ ┌────┐ ┌────┐ ───┬───
│ A ┌┼───┐ │ A │ │ B │ │
│ ││ B │ ← 重叠区域 │ │ │ │ ───┤
└───┼┘ │ └────┘ └────┘ │
└────┘ ↑ 缝隙 ╵ ← 悬挂端点
12.5.4 使用示例
- id: check_topology
step: qc.topology
params:
input: "$steps.read_parcels"
rules:
- no_overlaps
- no_gaps
tolerance: 0.001
12.6 qc.attribute_completeness 属性完整性检查
12.6.1 功能说明
qc.attribute_completeness 检查指定的必填字段是否存在空值(None、NaN)或空字符串。确保数据表中的关键字段均已填写。
12.6.2 参数定义
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
input |
GeoDataFrame |
✅ | — | 输入矢量数据 |
required_fields |
list[str] |
✅ | — | 必填字段名列表 |
severity |
str |
❌ | "error" |
问题严重级别 |
12.6.3 源码分析
@step(id="qc.attribute_completeness", ...)
def attribute_completeness(ctx):
gdf = ctx.input("input")
required_fields = ctx.param("required_fields")
severity = ctx.param("severity") or "error"
issues = []
for field_name in required_fields:
# 检查字段是否存在
if field_name not in gdf.columns:
issues.append(QcIssue(
rule_id="attribute_completeness",
severity=severity,
message=f"Required field '{field_name}' not found in data",
details={"field": field_name, "type": "missing_field"},
))
continue
# 检查空值
for idx, value in gdf[field_name].items():
if value is None or (isinstance(value, float) and
pd.isna(value)) or value == "":
issues.append(QcIssue(
rule_id="attribute_completeness",
severity=severity,
feature_index=idx,
message=f"Field '{field_name}' is null or empty",
geometry=gdf.geometry.iloc[idx]
if idx < len(gdf) else None,
details={
"field": field_name,
"value": str(value),
},
))
return build_qc_result(gdf, issues)
12.6.4 使用示例
- id: check_completeness
step: qc.attribute_completeness
params:
input: "$steps.read_buildings"
required_fields:
- "building_id"
- "name"
- "address"
- "category"
severity: "error"
12.7 qc.attribute_domain 属性值域检查
12.7.1 功能说明
qc.attribute_domain 验证字段值是否在允许的值域内,支持两种模式:枚举值列表和正则表达式匹配。
12.7.2 参数定义
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
input |
GeoDataFrame |
✅ | — | 输入矢量数据 |
field |
str |
✅ | — | 待检查的字段名 |
allowed_values |
list |
❌ | None |
允许的枚举值列表 |
pattern |
str |
❌ | None |
正则表达式模式 |
allowed_values和pattern二选一,至少指定一个。
12.7.3 两种检查模式
模式 1:枚举值列表
- id: check_landuse_type
step: qc.attribute_domain
params:
input: "$steps.read_parcels"
field: "land_use"
allowed_values:
- "residential"
- "commercial"
- "industrial"
- "agricultural"
- "public"
模式 2:正则表达式
- id: check_parcel_id
step: qc.attribute_domain
params:
input: "$steps.read_parcels"
field: "parcel_id"
pattern: "^[A-Z]{2}\\d{6}$" # 例如 BJ123456
12.7.4 源码分析
@step(id="qc.attribute_domain", ...)
def attribute_domain(ctx):
import re
gdf = ctx.input("input")
field_name = ctx.param("field")
allowed_values = ctx.param("allowed_values")
pattern = ctx.param("pattern")
issues = []
for idx, value in gdf[field_name].items():
if value is None or (isinstance(value, float) and pd.isna(value)):
continue # 空值由 attribute_completeness 检查
if allowed_values and value not in allowed_values:
issues.append(QcIssue(
rule_id="attribute_domain",
severity="error",
feature_index=idx,
message=f"Value '{value}' not in allowed values "
f"for field '{field_name}'",
details={
"field": field_name,
"value": value,
"allowed": allowed_values,
},
))
if pattern and not re.match(pattern, str(value)):
issues.append(QcIssue(
rule_id="attribute_domain",
severity="error",
feature_index=idx,
message=f"Value '{value}' does not match pattern "
f"'{pattern}' for field '{field_name}'",
details={
"field": field_name,
"value": value,
"pattern": pattern,
},
))
return build_qc_result(gdf, issues)
12.8 qc.value_range 数值范围检查
12.8.1 功能说明
qc.value_range 检查数值字段的值是否在指定的最小值和最大值范围内。
12.8.2 参数定义
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
input |
GeoDataFrame |
✅ | — | 输入矢量数据 |
field |
str |
✅ | — | 待检查的数值字段名 |
min |
float |
❌ | None |
最小允许值(含) |
max |
float |
❌ | None |
最大允许值(含) |
severity |
str |
❌ | "error" |
问题严重级别 |
12.8.3 检查逻辑
@step(id="qc.value_range", ...)
def value_range(ctx):
gdf = ctx.input("input")
field_name = ctx.param("field")
min_val = ctx.param("min")
max_val = ctx.param("max")
severity = ctx.param("severity") or "error"
issues = []
for idx, value in gdf[field_name].items():
if value is None or (isinstance(value, float) and pd.isna(value)):
continue
numeric_val = float(value)
if min_val is not None and numeric_val < min_val:
issues.append(QcIssue(
rule_id="value_range",
severity=severity,
feature_index=idx,
message=f"Value {numeric_val} < min {min_val} "
f"for field '{field_name}'",
details={"field": field_name, "value": numeric_val,
"min": min_val},
))
if max_val is not None and numeric_val > max_val:
issues.append(QcIssue(
rule_id="value_range",
severity=severity,
feature_index=idx,
message=f"Value {numeric_val} > max {max_val} "
f"for field '{field_name}'",
details={"field": field_name, "value": numeric_val,
"max": max_val},
))
return build_qc_result(gdf, issues)
12.8.4 使用示例
# 检查建筑层数合理性
- id: check_floors
step: qc.value_range
params:
input: "$steps.read_buildings"
field: "floor_count"
min: 1
max: 200
severity: "warning"
# 检查人口密度
- id: check_population
step: qc.value_range
params:
input: "$steps.read_districts"
field: "population_density"
min: 0
max: 100000
12.9 qc.duplicate_check 重复要素检查
12.9.1 功能说明
qc.duplicate_check 检测数据中的重复要素,支持基于几何和/或属性字段的重复判定。
12.9.2 参数定义
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
input |
GeoDataFrame |
✅ | — | 输入矢量数据 |
check_geometry |
bool |
❌ | True |
是否检查几何重复 |
check_fields |
list[str] |
❌ | None |
用于判重的属性字段列表 |
tolerance |
float |
❌ | 0.0 |
几何比较的容差值 |
12.9.3 重复判定逻辑
重复检查有三种组合模式:
check_geometry |
check_fields |
判重条件 |
|---|---|---|
True |
None |
几何完全相同(考虑容差) |
False |
["id", "name"] |
指定字段值完全相同 |
True |
["id"] |
几何相同 且 字段值相同 |
@step(id="qc.duplicate_check", ...)
def duplicate_check(ctx):
gdf = ctx.input("input")
check_geometry = ctx.param("check_geometry")
if check_geometry is None:
check_geometry = True
check_fields = ctx.param("check_fields")
tolerance = ctx.param("tolerance") or 0.0
issues = []
seen = {} # 用于跟踪已见过的要素
for idx, row in gdf.iterrows():
key_parts = []
if check_geometry and row.geometry is not None:
if tolerance > 0:
geom_key = row.geometry.simplify(tolerance).wkt
else:
geom_key = row.geometry.wkt
key_parts.append(geom_key)
if check_fields:
field_key = tuple(row[f] for f in check_fields)
key_parts.append(str(field_key))
key = "|".join(key_parts)
if key in seen:
issues.append(QcIssue(
rule_id="duplicate_check",
severity="warning",
feature_index=idx,
message=f"Duplicate of feature {seen[key]}",
geometry=row.geometry,
details={
"duplicate_of": seen[key],
"key": key[:100],
},
))
else:
seen[key] = idx
return build_qc_result(gdf, issues)
12.9.4 使用示例
# 检查几何和属性重复
- id: check_duplicates
step: qc.duplicate_check
params:
input: "$steps.read_poi"
check_geometry: true
check_fields:
- "poi_name"
- "category"
tolerance: 0.0001
12.10 qc.raster_nodata NoData 一致性检查
12.10.1 功能说明
qc.raster_nodata 检查栅格数据的 NoData 值是否与预期一致,并验证 NoData 像素占比是否超出阈值。
12.10.2 参数定义
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
input |
raster_info |
✅ | — | 输入栅格数据 |
expected_nodata |
float |
❌ | None |
期望的 NoData 值 |
max_nodata_ratio |
float |
❌ | None |
NoData 像素最大占比(0~1) |
12.10.3 检查逻辑
@step(id="qc.raster_nodata", ...)
def raster_nodata(ctx):
raster = ctx.input("input") # raster_info dict
expected_nodata = ctx.param("expected_nodata")
max_nodata_ratio = ctx.param("max_nodata_ratio")
issues = []
profile = raster["profile"]
data = raster["data"]
actual_nodata = profile.get("nodata")
# 检查 NoData 值一致性
if expected_nodata is not None and actual_nodata != expected_nodata:
issues.append(QcIssue(
rule_id="raster_nodata",
severity="error",
message=f"NoData mismatch: expected {expected_nodata}, "
f"got {actual_nodata}",
details={
"expected": expected_nodata,
"actual": actual_nodata,
},
))
# 检查 NoData 像素占比
if max_nodata_ratio is not None and actual_nodata is not None:
total_pixels = data.size
nodata_pixels = (data == actual_nodata).sum()
ratio = nodata_pixels / total_pixels
if ratio > max_nodata_ratio:
issues.append(QcIssue(
rule_id="raster_nodata",
severity="warning",
message=f"NoData ratio {ratio:.2%} exceeds "
f"threshold {max_nodata_ratio:.2%}",
details={
"nodata_ratio": float(ratio),
"threshold": max_nodata_ratio,
"nodata_pixels": int(nodata_pixels),
"total_pixels": int(total_pixels),
},
))
return build_qc_result(raster, issues)
12.10.4 使用示例
- id: check_nodata
step: qc.raster_nodata
params:
input: "$steps.read_dem"
expected_nodata: -9999
max_nodata_ratio: 0.1 # NoData 不超过 10%
12.11 qc.raster_resolution 分辨率一致性检查
12.11.1 功能说明
qc.raster_resolution 检查栅格数据的像素分辨率是否与预期一致。
12.11.2 参数定义
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
input |
raster_info |
✅ | — | 输入栅格数据 |
expected_x |
float |
✅ | — | 期望的 X 方向分辨率 |
expected_y |
float |
✅ | — | 期望的 Y 方向分辨率 |
tolerance |
float |
❌ | 0.0 |
允许的误差范围 |
12.11.3 检查逻辑
@step(id="qc.raster_resolution", ...)
def raster_resolution(ctx):
raster = ctx.input("input")
expected_x = ctx.param("expected_x")
expected_y = ctx.param("expected_y")
tolerance = ctx.param("tolerance") or 0.0
issues = []
transform = raster["transform"]
# 从仿射变换矩阵获取实际分辨率
actual_x = abs(transform.a) # X 方向像素大小
actual_y = abs(transform.e) # Y 方向像素大小
if abs(actual_x - expected_x) > tolerance:
issues.append(QcIssue(
rule_id="raster_resolution",
severity="error",
message=f"X resolution mismatch: expected {expected_x}, "
f"got {actual_x}",
details={"axis": "x", "expected": expected_x,
"actual": actual_x},
))
if abs(actual_y - expected_y) > tolerance:
issues.append(QcIssue(
rule_id="raster_resolution",
severity="error",
message=f"Y resolution mismatch: expected {expected_y}, "
f"got {actual_y}",
details={"axis": "y", "expected": expected_y,
"actual": actual_y},
))
return build_qc_result(raster, issues)
12.11.4 使用示例
- id: check_resolution
step: qc.raster_resolution
params:
input: "$steps.read_dem"
expected_x: 30.0 # 30 米分辨率
expected_y: 30.0
tolerance: 0.5 # 允许 0.5 米误差
12.12 qc.raster_value_range 栅格值域检查
12.12.1 功能说明
qc.raster_value_range 检查栅格数据的像素值是否在合理范围内。
12.12.2 参数定义
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
input |
raster_info |
✅ | — | 输入栅格数据 |
min |
float |
❌ | None |
最小允许值 |
max |
float |
❌ | None |
最大允许值 |
severity |
str |
❌ | "error" |
问题严重级别 |
band |
int |
❌ | 1 |
波段索引(从 1 开始) |
12.12.3 源码分析
@step(id="qc.raster_value_range", ...)
def raster_value_range(ctx):
raster = ctx.input("input")
min_val = ctx.param("min")
max_val = ctx.param("max")
severity = ctx.param("severity") or "error"
band = ctx.param("band") or 1
issues = []
data = raster["data"]
nodata = raster["profile"].get("nodata")
# 提取指定波段(band 索引从 1 开始)
band_data = data[band - 1]
# 排除 NoData 值
if nodata is not None:
valid_data = band_data[band_data != nodata]
else:
valid_data = band_data.ravel()
actual_min = float(valid_data.min())
actual_max = float(valid_data.max())
if min_val is not None and actual_min < min_val:
issues.append(QcIssue(
rule_id="raster_value_range",
severity=severity,
message=f"Band {band}: min value {actual_min} < {min_val}",
details={"band": band, "actual_min": actual_min,
"expected_min": min_val},
))
if max_val is not None and actual_max > max_val:
issues.append(QcIssue(
rule_id="raster_value_range",
severity=severity,
message=f"Band {band}: max value {actual_max} > {max_val}",
details={"band": band, "actual_max": actual_max,
"expected_max": max_val},
))
return build_qc_result(raster, issues, stats={
"band": band,
"actual_min": actual_min,
"actual_max": actual_max,
})
12.12.4 使用示例
# DEM 高程值域检查
- id: check_dem_values
step: qc.raster_value_range
params:
input: "$steps.read_dem"
min: -500 # 最低高程(死海 -431m)
max: 9000 # 最高高程(珠峰 8849m)
band: 1
# NDVI 值域检查
- id: check_ndvi
step: qc.raster_value_range
params:
input: "$steps.compute_ndvi"
min: -1.0
max: 1.0
severity: "error"
band: 1
12.13 QC 步骤组合实战
12.13.1 矢量数据质检流水线
以下流水线对土地利用矢量数据执行完整的质检链:
# vector-qc.yaml
name: 土地利用数据质量检查
description: 对土地利用矢量数据执行全链路质量检查
steps:
# ─── 数据读取 ───
- id: read_parcels
step: io.read_vector
params:
path: "data/land_use_parcels.gpkg"
# ─── 基础检查 ───
- id: check_crs
step: qc.crs_check
params:
input: "$steps.read_parcels"
expected_crs: "EPSG:4326"
- id: check_geometry
step: qc.geometry_validity
params:
input: "$steps.read_parcels"
auto_fix: true
severity: "error"
# ─── 拓扑检查 ───
- id: check_topo
step: qc.topology
params:
input: "$steps.check_geometry"
rules:
- no_overlaps
- no_gaps
tolerance: 0.0001
# ─── 属性检查 ───
- id: check_completeness
step: qc.attribute_completeness
params:
input: "$steps.check_geometry"
required_fields:
- "parcel_id"
- "land_use"
- "area_sqm"
- "owner"
severity: "error"
- id: check_landuse_domain
step: qc.attribute_domain
params:
input: "$steps.check_geometry"
field: "land_use"
allowed_values:
- "residential"
- "commercial"
- "industrial"
- "agricultural"
- "public"
- "green_space"
- id: check_parcel_id_format
step: qc.attribute_domain
params:
input: "$steps.check_geometry"
field: "parcel_id"
pattern: "^[A-Z]{2}\\d{8}$"
- id: check_area
step: qc.value_range
params:
input: "$steps.check_geometry"
field: "area_sqm"
min: 1
max: 10000000
severity: "warning"
# ─── 重复检查 ───
- id: check_dup
step: qc.duplicate_check
params:
input: "$steps.check_geometry"
check_geometry: true
check_fields:
- "parcel_id"
tolerance: 0.0001
12.13.2 栅格数据质检流水线
# raster-qc.yaml
name: DEM 栅格数据质量检查
description: 对 DEM 高程数据执行质量检查
steps:
# ─── 读取栅格 ───
- id: read_dem
step: io.read_raster
params:
path: "data/srtm_dem.tif"
# ─── NoData 检查 ───
- id: check_nodata
step: qc.raster_nodata
params:
input: "$steps.read_dem"
expected_nodata: -9999
max_nodata_ratio: 0.05
# ─── 分辨率检查 ───
- id: check_resolution
step: qc.raster_resolution
params:
input: "$steps.read_dem"
expected_x: 30.0
expected_y: 30.0
tolerance: 1.0
# ─── 值域检查 ───
- id: check_elevation
step: qc.raster_value_range
params:
input: "$steps.read_dem"
min: -500
max: 9000
band: 1
severity: "error"
12.13.3 质检流水线数据流
矢量质检数据流:
read_parcels
│
├──▶ check_crs ──▶ [CRS 问题]
│
├──▶ check_geometry ──(auto_fix)──▶ 修复后的数据
│ │
│ ┌───────────────────────────────────┘
│ │
│ ├──▶ check_topo ──▶ [拓扑问题]
│ ├──▶ check_completeness ──▶ [完整性问题]
│ ├──▶ check_landuse_domain ──▶ [值域问题]
│ ├──▶ check_parcel_id_format ──▶ [格式问题]
│ ├──▶ check_area ──▶ [范围问题]
│ └──▶ check_dup ──▶ [重复问题]
│
▼
所有 issues 汇总 → QC 报告
12.14 QC 报告解读
12.14.1 qc_summary 结构
执行引擎的 reporter.py 会汇总所有 QC 步骤的问题到 qc_summary 中:
{
"pipeline": "土地利用数据质量检查",
"status": "completed",
"qc_summary": {
"total_issues": 23,
"by_severity": {
"error": 5,
"warning": 15,
"info": 3
},
"by_rule": {
"geometry_validity": 2,
"attribute_completeness": 8,
"attribute_domain": 3,
"value_range": 5,
"duplicate_check": 2,
"topology": 3
},
"steps": {
"check_geometry": {
"issue_count": 2,
"errors": 2,
"warnings": 0
},
"check_completeness": {
"issue_count": 8,
"errors": 8,
"warnings": 0
}
}
},
"steps": [...]
}
12.14.2 报告字段说明
| 字段 | 说明 |
|---|---|
total_issues |
所有 QC 步骤发现的问题总数 |
by_severity |
按严重级别分组统计 |
by_rule |
按规则 ID 分组统计 |
steps |
每个 QC 步骤的独立统计 |
12.14.3 利用报告做质量决策
# 在质检之后根据问题数量决定是否继续
- id: process_data
step: vector.buffer
params:
input: "$steps.check_geometry"
distance: 100
when: "$steps.check_geometry.stats.error_count == 0"
通过 when 条件表达式结合 QC 步骤的统计信息,可以实现条件化流水线执行:仅在质检通过时才执行后续处理步骤。
12.15 本章小结
本章全面介绍了 GeoPipeAgent 的 10 个数据质检步骤:
核心概念:
- “Check and Passthrough” 模式:数据原样通过,问题以
QcIssue对象收集到$step.issues - QcIssue 数据模型:包含
rule_id、severity、feature_index、message、geometry、details六个字段 - 三级严重性:
error(错误)、warning(警告)、info(提示)
矢量质检步骤(7 个):
| 步骤 | 核心功能 |
|---|---|
qc.geometry_validity |
几何有效性检查,支持 auto_fix 自动修复 |
qc.crs_check |
CRS 存在性和正确性验证 |
qc.topology |
拓扑规则检查(no_overlaps / no_gaps / no_dangles) |
qc.attribute_completeness |
必填字段空值检查 |
qc.attribute_domain |
枚举值/正则表达式值域检查 |
qc.value_range |
数值字段范围检查 |
qc.duplicate_check |
几何和属性重复检查 |
栅格质检步骤(3 个):
| 步骤 | 核心功能 |
|---|---|
qc.raster_nodata |
NoData 值和占比检查 |
qc.raster_resolution |
像素分辨率一致性检查 |
qc.raster_value_range |
像素值域范围检查 |
通过组合这些 QC 步骤,可以构建针对矢量和栅格数据的全链路质量检查流水线,并利用 JSON 报告中的 qc_summary 进行质量评估和决策。