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>
82 lines
5.0 KiB
Markdown
82 lines
5.0 KiB
Markdown
# 可编辑数据模型的比较
|
||
|
||
> 本文是方案比较的探索记录。约束模型、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 更新临时 constraint,pointer 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、停止线、斑马线和标线。
|