# 可编辑数据模型的比较 > 本文是方案比较的探索记录。约束模型、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、停止线、斑马线和标线。