第五章:变量系统与步骤引用

GeoPipeAgent 流水线中有两种核心数据传递机制:变量替换(${var})和步骤引用($step-id)。本章深入解析这两种机制的工作原理和使用技巧。


5.1 变量替换:${var_name}

5.1.1 基本用法

在 pipeline.variables 中定义变量,在步骤 params 中用 ${变量名} 引用:

pipeline:
  variables:
    input_path: "data/roads.shp"
    buffer_dist: 500
    output_crs: "EPSG:4326"

  steps:
    - id: load
      use: io.read_vector
      params:
        path: "${input_path}"          # → "data/roads.shp"

    - id: buffer
      use: vector.buffer
      params:
        input: "$load"
        distance: "${buffer_dist}"     # → 500 (数值类型)

    - id: reproject
      use: vector.reproject
      params:
        input: "$buffer"
        target_crs: "${output_crs}"    # → "EPSG:4326"

5.1.2 类型保留

当整个参数值是单一 ${var} 引用时,变量的原始 Python 类型被保留:

variables:
  buffer_dist: 500          # int
  tolerance: 0.001          # float
  auto_fix: true            # bool
  fields: ["name", "type"]  # list

# 传入步骤时类型不变
params:
  distance: "${buffer_dist}"    # → 500 (int)
  tolerance: "${tolerance}"     # → 0.001 (float)
  auto_fix: "${auto_fix}"       # → True (bool)
  required_fields: "${fields}"  # → ["name", "type"] (list)

5.1.3 字符串插值

当 ${var} 嵌入字符串中时,变量被转为字符串拼接:

variables:
  project: "highway-2024"
  suffix: ".geojson"

params:
  path: "output/${project}${suffix}"   # → "output/highway-2024.geojson"
  layer: "buffer_${project}"           # → "buffer_highway-2024"

5.1.4 通过 --var 在命令行覆盖

--var 参数以 key=value 格式传入,值始终是字符串。框架会尝试进行类型转换(字符串→数字/布尔):

# 整数
geopipe-agent run pipeline.yaml --var buffer_dist=1000

# 浮点数
geopipe-agent run pipeline.yaml --var tolerance=0.005

# 布尔值("true"/"1"/"yes" 转为 True)
geopipe-agent run pipeline.yaml --var auto_fix=true

# 字符串路径
geopipe-agent run pipeline.yaml --var input_path=data/new_roads.shp

# 多个变量
geopipe-agent run pipeline.yaml \
    --var input_path=data/new.shp \
    --var buffer_dist=200 \
    --var output_path=output/new_buffer.geojson

注意:通过 --var 传入的值优先于 YAML 文件中 variables 的定义,且会修改 PipelineDefinition.variables 字典,影响所有引用该变量的步骤。


5.2 步骤引用:$step-id 和 $step-id.attr

5.2.1 步骤引用基础

步骤引用用于将一个步骤的输出传递给后续步骤的参数。格式:

语法 等价形式 含义
$step-id $step-id.output 步骤的主输出(StepResult.output)
$step-id.output 显式写法 同上
$step-id.stats — 步骤的 stats 字典整体
$step-id.feature_count — stats 中的 feature_count 值
$step-id.issues_count — stats 中的 issues_count 值(QC 步骤)
$step-id.issues_gdf — QC 步骤的问题要素 GeoDataFrame
steps:
  - id: load-data
    use: io.read_vector
    params:
      path: "data/roads.shp"

  - id: buffer
    use: vector.buffer
    params:
      input: "$load-data"           # 等价于 $load-data.output
      distance: 500

  - id: save
    use: io.write_vector
    params:
      input: "$buffer"              # 等价于 $buffer.output
      path: "output/result.geojson"

5.2.2 访问统计属性

每个步骤执行后,StepResult.stats 字典的键可直接作为引用属性访问:

steps:
  - id: load-data
    use: io.read_vector
    params:
      path: "data/roads.shp"

  - id: check-geometry
    use: qc.geometry_validity
    params:
      input: "$load-data"
      auto_fix: false

  - id: auto-fix
    use: qc.geometry_validity
    params:
      input: "$load-data"
      auto_fix: true
    # 只有当几何问题数 > 0 时才执行
    when: "$check-geometry.issues_count > 0"

outputs:
  # 在 outputs 中引用统计数据
  total_features: "$load-data.feature_count"   # stats.feature_count
  bad_geometry:   "$check-geometry.issues_count"

5.2.3 StepResult 结构

理解步骤引用需要了解 StepResult 的数据结构:

@dataclass
class StepResult:
    output: Any = None          # 主输出(GeoDataFrame、文件路径等)
    stats: dict = {}            # 统计信息(feature_count、crs 等)
    metadata: dict = {}         # 元数据(driver、bands 等)
    issues: list[QcIssue] = []  # QC 问题列表(仅 QC 步骤填充)

属性访问规则:

  • $step.output → result.output
  • $step.stats → result.stats(返回整个 stats 字典)
  • $step.feature_count → result.stats["feature_count"](先查 stats)
  • $step.crs → result.stats["crs"](然后查 metadata)
  • 若以上都找不到,抛出 VariableResolutionError

5.2.4 各类步骤的输出结构

IO 步骤(io.read_vector):

StepResult(
    output = GeoDataFrame,      # 读取的地理数据
    stats = {
        "feature_count": int,
        "crs": str,
        "geometry_types": list,
        "columns": list,
    }
)

引用示例:

  • $load.output → GeoDataFrame(传给下一步的 input)
  • $load.feature_count → 要素数量
  • $load.columns → 属性字段列表

矢量步骤(vector.buffer):

StepResult(
    output = GeoDataFrame,      # 缓冲区结果 GeoDataFrame
    stats = {
        "feature_count": int,   # 结果要素数量
        "total_area": float,    # 缓冲区总面积
    }
)

IO 写入步骤(io.write_vector):

StepResult(
    output = str,               # 输出文件路径(字符串)
    stats = {
        "feature_count": int,
        "output_path": str,
        "format": str,
    }
)

QC 步骤(qc.geometry_validity):

StepResult(
    output = GeoDataFrame,      # 原始输入数据(透传)
    stats = {
        "total_features": int,
        "issues_count": int,
        "valid_count": int,     # 部分 QC 步骤提供
    },
    issues = [QcIssue, ...]    # 问题列表
)

QC 步骤专有属性:

  • $check.issues_count → 问题数量
  • $check.issues_gdf → 包含问题要素的 GeoDataFrame
  • $check.issues → QcIssue 对象列表(不可直接序列化,一般不在 YAML 中引用)

5.3 引用解析顺序与规则

引用解析由 engine/references.py 中的 ReferenceResolver 完成(context.py 的 PipelineContext.resolve() 委托给它)。正则模式 \$\{(\w+)\}|\$(\w[\w-]*(?:\.\w+)?) 要求整个字符串恰好是一个引用才会解析:

参数值处理顺序:
1. 非字符串值 → 直接返回(保持原类型)
2. 整串恰好匹配 "$step-id" 或 "$step-id.attr" → 步骤引用(attr 缺省为 output)
3. 包含 "${" → 变量替换
   3a. 整个值是 "${var}" → 返回原始类型
   3b. "${var}" 嵌入字符串 → 字符串插值
4. 其他 → 原样返回

注意事项:

  • 步骤引用只能引用已执行步骤的输出,不能向前引用
  • 步骤 ID 中的连字符 - 在引用中需要保留($load-roads,不能写成 $load_roads)
  • 步骤引用只支持一层属性($step-id.attr,attr 为单个单词)。多级写法如 $step-id.stats.feature_count 不匹配引用模式,会被当作普通字符串原样传入步骤参数;正确写法是直接访问 stats 键:$step-id.feature_count

5.4 在 when 条件中使用引用

when 表达式也支持步骤引用和变量替换:

steps:
  - id: check-issues
    use: qc.geometry_validity
    params:
      input: "$load-data"
      auto_fix: false

  # 条件1:基于步骤 stats 属性
  - id: fix-issues
    use: qc.geometry_validity
    params:
      input: "$load-data"
      auto_fix: true
    when: "$check-issues.issues_count > 0"

  # 条件2:基于变量
  - id: simplify
    use: vector.simplify
    params:
      input: "$fix-issues"
      tolerance: 10
    when: "${enable_simplify} == true"

  # 条件3:复合条件
  - id: strict-fix
    use: qc.geometry_validity
    params:
      input: "$load-data"
      auto_fix: true
      severity: "error"
    when: "$check-issues.issues_count > 10 and ${strict_mode} == true"

5.5 在 outputs 中使用引用

outputs 声明支持完整的引用语法:

outputs:
  final_result: "$save-result"            # 输出文件路径
  feature_count: "$buffer.feature_count"  # 缓冲区要素数
  total_area: "$buffer.total_area"        # 缓冲区总面积
  crs: "$reproject.crs"                   # 最终坐标系
  qc_issues: "$check-geo.issues_count"    # 质检问题数量

这些值出现在 JSON 报告的 outputs 节中,可供 AI 或下游系统提取。


5.6 嵌套参数中的引用

对于接受字典或列表类型参数的步骤,引用也可以出现在嵌套结构中:

# 假设某步骤接受字典参数
params:
  config:
    input: "$load-data"          # 嵌套字典中的引用
    crs: "${output_crs}"
  field_list:
    - "$load-data.columns"       # 列表中的引用(按实际步骤支持情况)

5.7 常见引用错误

错误 1:引用未执行的步骤

steps:
  - id: buffer
    use: vector.buffer
    params:
      input: "$load-data"   # ❌ load-data 在 buffer 之后,尚未执行

  - id: load-data
    use: io.read_vector
    params: { path: "data.shp" }

错误 2:引用不存在的属性

- id: process
  use: vector.buffer
  params:
    distance: "$load-data.record_count"  # ❌ io.read_vector 没有 record_count 属性
                                          # ✅ 应改为 feature_count

错误 3:ID 拼写不一致

steps:
  - id: load-roads       # ID 使用连字符

  - id: buffer
    params:
      input: "$load_roads"  # ❌ 使用了下划线,与 ID 不符
      input: "$load-roads"  # ✅ 正确

5.8 本章小结

本章深入讲解了 GeoPipeAgent 的两种核心数据传递机制:

  1. 变量替换 ${var}:引用 variables 中的值,整个值时保留类型,嵌入字符串时做字符串插值
  2. 步骤引用 $step-id:$step-id 是 $step-id.output 的简写,.attr 可访问 stats/metadata 键
  3. 类型保留:单一 ${var} 引用保留原始 Python 类型,确保步骤参数类型正确
  4. when 条件:支持完整的引用语法,通过 AST 白名单安全求值
  5. StepResult 结构:理解 output/stats/metadata/issues 各字段是正确使用引用的基础

下一章将讲解条件执行、自动重试和错误处理策略的详细用法。


导航:← 第四章:YAML 流水线格式 | 第六章:条件执行与错误策略 →