Files
road-compiler/.trellis/tasks/08-26-direct-manipulation-road-editor/research/data-model-options.md
que01 93f09e399e chore(road-editor): plan direct-edit task tree and add client test infra
Planning: parent design.md becomes the single authoritative contract
(constraint model with 6 kinds, handle manifest, coordinate/unit
layering, preview sequencing, storage layout and lazy migration, area
config snapshot, API contract). Work is split into four independently
verifiable child tasks with per-step gates and rollback points.

Test infra: pin vitest 4.1.11, add test:client:unit for client pure
logic, extend prettier globs to root *.ts so vitest.config.ts is checked.

Add .gitignore: the repo had none, so inputs/, outputs/, workbench-data/
and the client build output were untracked rather than ignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 17:33:42 +08:00

5.0 KiB
Raw Blame History

可编辑数据模型的比较

本文是方案比较的探索记录。约束模型、kind 枚举、锚点类型与文档 schema 的权威定义在 design.md;如有冲突以 design.md 为准。

共同不变量

  • OSM 原文与其解析出的道路模型是基线,不由拖拽直接覆盖。
  • 所有持久化修改都必须含版本、稳定锚点、创建时基线指纹、参数、状态和解释信息。
  • 每个输出 feature 都必须能给出生成它的输入语义 ID 与适用约束,供 UI 选择和诊断使用。
  • 拖拽过程可使用临时求解结果;只有显式保存才写入可重放命令。

方案比较

方案 持久化内容 优点 根本限制
参数反推 widthMeters、lane count、步行带宽度等 可最大复用 v1重编译最稳 不能表达沿程局部收放、独立边缘或路口角部
语义几何约束 对道路横断面、边缘、过渡、路口的约束 兼顾直接操纵与 OSM 重放;适合本项目 需要 solver、优先级和冲突诊断
局部 patch 几何 渲染 polygon 的顶点/边偏移 表达最自由 极难在 OSM 或生成算法变化后重定位,拓扑易破坏

建议方向:版本化的约束文档

不是将所有编辑统一存为一个巨型 mesh而是将它们存为带锚点的声明式约束并保留“由哪个交互生成”的命令记录。

interface RoadEditDocument {
  schema: 'native-road-edits/v2'
  base: {
    osmSha256: string
    compilerGeometryVersion: string
    createdAt: string
  }
  constraints: RoadConstraint[]
  operations: RoadEditOperation[]
}

interface RoadConstraint {
  id: string
  kind: RoadConstraintKind // 6 个取值见 design.md「约束模型」
  anchor: SemanticAnchor
  anchorSnapshot: AnchorSnapshot
  value: unknown
  enabled: boolean
  status: ConstraintStatus // exact | recheck | pending | conflicted | stale
  provenance: { operationId: string; createdAt: string; author?: string }
}

type SemanticAnchor =
  | { type: 'road-station'; roadId: string; station: number; side?: 'left' | 'right' }
  | { type: 'road-interval'; roadId: string; startStation: number; endStation: number; side?: 'left' | 'right' }
  | { type: 'junction-approach'; nodeId: string; segmentId: string; side?: 'left' | 'right' }
  | { type: 'junction-corner'; nodeId: string; incomingRoadId: string; outgoingRoadId: string }

station 是相对道路中心线归一化弧长 0..1,而不是绝对经纬度或数组下标。它对顶点加减和轻微 OSM 几何调整更稳定;保存时还应记录 anchorSnapshot(当时位置、切线、道路长度、相邻 OSM node ID作为重定位/冲突检测证据。

道路横断面拖拽默认写入 road-interval 约束:拖拽位置为 interval 中心,系统根据道路长度与邻近路口预留距离推导初始 startStation / endStation,并在两端使用明确的 transition: 'smoothstep' 回归基线。用户调整范围手柄时只更新该 interval不存储鼠标轨迹。

分层求解规则

  1. 基线层OSM → 当前道路模型和默认横断面。
  2. 现有参数层v1 road/connection/style overrides可由简单拖拽写入继续兼容。
  3. 几何约束层:按语义锚点计算横断面、边界和路口局部形状;冲突时按明确优先级或提示用户处理。
  4. 派生层:道路面、步行带、车道中心线、标线、停止线和 connector 必须一起重新计算,不能只移动视觉面。

操作、撤销和重导入

  • UI 维护本地命令栈pointer down 生成草稿pointer move 更新临时 constraintpointer up 合并为一个 operationundo/redo 仅移动指针,不写文件。
  • 保存后写入完整约束文档和不可变 operations 记录;已保存编辑的撤销创建反向操作或禁用约束,不修改历史。
  • 重导入时先按 roadId/nodeId 精确匹配;失败时使用 anchorSnapshot 的 OSM node、距离和切线作候选匹配。匹配不唯一或偏差超阈值即标记 conflicted,不自动应用。
  • v1 覆盖项保留并逐步迁移:全路宽度仍是 road override只有无法表达的局部调整进入 v2 constraints。

需要在产品边界确认后细化

  • 路口角部是否由独立约束处理,还是将路口升级为可编辑的模板/参数对象。
  • 是否需要多人协作若需要operation log 必须具备 actor、revision 和并发合并策略。

已确认边界

OSM 中心线与拓扑保持只读。直接操纵不会新增 centerline-control-point、分段、合并或连接/断开拓扑操作;这类修改通过修正 OSM 后重新导入完成。v2 约束因此只作用在由中心线派生的横断面、边缘、步行带与路口细节。

首期范围包含道路外缘、步行带、车道分隔以及路口进口、cutback 与转弯角部。路口必须是独立的 junction-* anchor不应伪装成某一条道路末端的 edge offset它会同时影响道路截断、路口面、connector、停止线、斑马线和标线。