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

82 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 可编辑数据模型的比较
> 本文是方案比较的探索记录。约束模型、kind 枚举、锚点类型与文档 schema 的权威定义在 `design.md`;如有冲突以 `design.md` 为准。
## 共同不变量
- OSM 原文与其解析出的道路模型是基线,不由拖拽直接覆盖。
- 所有持久化修改都必须含版本、稳定锚点、创建时基线指纹、参数、状态和解释信息。
- 每个输出 feature 都必须能给出生成它的输入语义 ID 与适用约束,供 UI 选择和诊断使用。
- 拖拽过程可使用临时求解结果;只有显式保存才写入可重放命令。
## 方案比较
| 方案 | 持久化内容 | 优点 | 根本限制 |
| --- | --- | --- | --- |
| 参数反推 | `widthMeters`、lane count、步行带宽度等 | 可最大复用 v1重编译最稳 | 不能表达沿程局部收放、独立边缘或路口角部 |
| 语义几何约束 | 对道路横断面、边缘、过渡、路口的约束 | 兼顾直接操纵与 OSM 重放;适合本项目 | 需要 solver、优先级和冲突诊断 |
| 局部 patch 几何 | 渲染 polygon 的顶点/边偏移 | 表达最自由 | 极难在 OSM 或生成算法变化后重定位,拓扑易破坏 |
## 建议方向:版本化的约束文档
不是将所有编辑统一存为一个巨型 mesh而是将它们存为带锚点的声明式约束并保留“由哪个交互生成”的命令记录。
```ts
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 合并为一个 `operation`undo/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、停止线、斑马线和标线。