feat(preview): add continuous vehicle turn routes
This commit is contained in:
@@ -306,4 +306,5 @@ python3 -m http.server 8765
|
||||
|
||||
- [CLI 与阶段](../pipeline/cli-and-stages.md):`cesium` / `preview` 阶段如何生成这些文件
|
||||
- [资产生成](../blender/asset-generation.md):GLB 里的材质为什么要单独调色
|
||||
- [车辆连续路线](vehicle-routes.md):路线 JSON、转向选择与预览标签契约
|
||||
- README「实验:车辆巡航」节:面向使用者的说明
|
||||
|
||||
91
.trellis/spec/preview/vehicle-routes.md
Normal file
91
.trellis/spec/preview/vehicle-routes.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 车辆连续路线
|
||||
|
||||
## 1. Scope / Trigger
|
||||
|
||||
适用于 `scripts/lib/vehicle-route.js` 生成的路线 JSON,以及
|
||||
`scripts/lib/cesium-preview.js` 对车辆巡航路线的读取与展示。
|
||||
|
||||
触发:修改路线生成、OSM 转向标签解析、车辆选择菜单或路线 JSON 字段时。
|
||||
路线仅用于 Cesium 验证预览,不构成交通仿真或法规级导航。
|
||||
|
||||
## 2. Signatures
|
||||
|
||||
```js
|
||||
buildVehicleRoute(osmPath) => {
|
||||
source, bounds, generatedAt, speedMetersPerSecond, loop,
|
||||
routes, segments
|
||||
}
|
||||
|
||||
allowedTurns(tags, direction) => Set<"left" | "through" | "right">
|
||||
classifyConnection(incomingEdge, outgoingEdge) =>
|
||||
"left" | "through" | "right" | "u_turn"
|
||||
```
|
||||
|
||||
浏览器运行时调用 `addVehicleCruises(viewer, routeData, vehicleModelName)`;它首先读取
|
||||
`routeData.routes`,仅在其不存在时回退到 `routeData.segments`。
|
||||
|
||||
## 3. Contracts
|
||||
|
||||
- `routes` 是当前主字段;`segments` 必须是同一数组的兼容别名,供旧预览使用。
|
||||
- 每个路线至少包含 `id`、`coordinates`、`centerlineCoordinates`、`lengthMeters`、
|
||||
`maneuvers` 与 `edgeIds`。`coordinates` 是右侧车道偏移后的闭合巡航轨迹。
|
||||
- 有 `oneway=yes`(及等价真值)的 way 只能按 OSM 原始方向生成 edge,绝不能生成反向
|
||||
`:backward` edge;`oneway=-1` 仅允许反向 edge。
|
||||
- 去程在路口按入边方向读取 `turn:lanes:forward` 或 `turn:lanes:backward`,只有标签中的
|
||||
`left`、`through`、`right` 才是候选出口;无标签时允许这三类非 U-turn 动作。
|
||||
- 返程是展示路线的原路回返,不以反向 `turn:lanes` 再次否决,但依旧不可逆行单行道。
|
||||
- 路网没有闭环时,在去程和返程端点插入平滑调头曲线;不得在 way 端点或路口瞬移。
|
||||
- 选择菜单使用 `#编号 · 长度 m · 左 N / 右 N / 直 N`,因为一条路线可跨越多个道路名称。
|
||||
|
||||
## 4. Validation & Error Matrix
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 缺少或无法读取 route JSON | 预览继续加载,只取消巡航控件 |
|
||||
| `routes` 存在但为空 | 不回退到旧 `segments`;没有可播放车辆 |
|
||||
| 可行驶 way 少于两个节点或不在区域范围 | 不生成 edge |
|
||||
| 只存在反向单行可达路径 | 不生成违反单行限制的路线 |
|
||||
| 路口夹角接近掉头 | 分类为 `u_turn`,不作为去程出口 |
|
||||
| 候选路线不足五条 | 输出实际可用数量,预览按已有路线加载 |
|
||||
|
||||
## 5. Good/Base/Bad Cases
|
||||
|
||||
- 正常:树状道路网产生多条跨 way 往返路线,车辆经过左、右、直三种连接并在端点平滑掉头。
|
||||
- 基础:旧 JSON 只有 `segments` 时,预览仍能创建车辆与 Follow 控制。
|
||||
- 错误:对返程再次套用反向 `turn:lanes`,使原路返回在树状网络中被错误过滤。
|
||||
|
||||
## 6. Tests Required
|
||||
|
||||
- `node scripts/test-preview-assets.js`:断言路线闭合、端点调头、`turn:lanes` 拆分、左/右/直
|
||||
分类、单行道不逆行,以及 `segments === routes`。
|
||||
- `node --check scripts/lib/vehicle-route.js` 与
|
||||
`node --check scripts/lib/cesium-preview.js`:保证 Node 与浏览器直载脚本语法可用。
|
||||
- 对目标区域运行 `npm run build:area -- --config config/areas/<area>.json --stages preview`,确认
|
||||
`routes` 中存在左、右、直动作,且 Cesium 下拉标签显示编号、长度与动作统计。
|
||||
|
||||
## 7. Wrong vs Correct
|
||||
|
||||
错误:优先使用旧字段,导致新路线元数据无法被消费。
|
||||
|
||||
```js
|
||||
const segments = routeData.segments || routeData.routes || [];
|
||||
```
|
||||
|
||||
正确:新字段优先,旧字段仅作兼容回退。
|
||||
|
||||
```js
|
||||
const routes = routeData.routes || routeData.segments || [];
|
||||
```
|
||||
|
||||
错误:为使路线闭合而生成单行道路的反向 edge。
|
||||
|
||||
```js
|
||||
edges.push(makeEdge(way, refs.reverse(), coords.reverse(), "backward"));
|
||||
```
|
||||
|
||||
正确:单行仅保留其允许的方向,树状网络用端点调头闭合预览路线。
|
||||
|
||||
```js
|
||||
if (oneway !== "-1") edges.push(makeEdge(way, refs, coords, "forward"));
|
||||
if (!isOneWay(oneway)) edges.push(makeEdge(way, [...refs].reverse(), [...coords].reverse(), "backward"));
|
||||
```
|
||||
1
.trellis/tasks/08-05-vehicle-turn-routes/check.jsonl
Normal file
1
.trellis/tasks/08-05-vehicle-turn-routes/check.jsonl
Normal file
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
31
.trellis/tasks/08-05-vehicle-turn-routes/design.md
Normal file
31
.trellis/tasks/08-05-vehicle-turn-routes/design.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 设计:车辆连续巡航与转弯
|
||||
|
||||
## 数据与图模型
|
||||
|
||||
`vehicle-route.js` 继续只读取 OSM XML,但解析改为保留 node ID、坐标、way 标签和 node
|
||||
引用。每条可行驶 way 产生一个或两个有向 edge:`oneway=yes` 只保留原始方向,其他道路增加
|
||||
反向 edge。edge 的终点 node 连接其他以该 node 为起点的 edge。
|
||||
|
||||
入边在路口前的最后一个线段给出入射方位,出边第一个线段给出离去方位。二者的有符号夹角
|
||||
分类为 `left`、`through`、`right`;接近 180 度的 U-turn 一律排除。读取去程入边方向对应的
|
||||
`turn:lanes:forward/backward`,拆分 `|` 与 `;` 后得到允许动作集合。无 `turn:lanes` 的
|
||||
道路保守允许三种非 U-turn 动作,避免未标注区域没有路线。返程沿反向 edge 回走,不用反向
|
||||
`turn:lanes` 过滤;这是一项预览展示边界,不是交通规则模拟。
|
||||
|
||||
## 路线选择与几何
|
||||
|
||||
南台子湖主道路图是树状网络,不能生成真实 cycle。改为枚举连接两个端点、长度足够的有向
|
||||
道路路径,并按稳定的 road ID / maneuver 序列排序。选择彼此不完全相同、且合计覆盖
|
||||
left / through / right 的前 5 条。路径在两个端点各接一段平滑调头曲线,再沿反向道路返回;
|
||||
这使得路线可循环播放而不在端点瞬移。
|
||||
|
||||
每个路口连接把入边末段和出边首段裁去固定距离,用三次 Bezier 采样 6 个点衔接。偏移在
|
||||
整条连续路线完成后计算,避免每个 way 单独偏移在路口产生断裂。
|
||||
|
||||
route JSON 升级为 `routes`,每项有 `id`、`maneuvers`、`coordinates`、`lengthMeters`;同时
|
||||
继续写 `segments` 作为旧预览的兼容别名。Cesium 运行时优先读取 `routes`,退回 `segments`。
|
||||
|
||||
## 边界
|
||||
|
||||
没有 `restriction` relation 时不能声称交通法规完全正确。它只影响未来候选出口过滤,不改变
|
||||
路线格式或 Cesium 播放逻辑。路口曲线是视觉轨迹,车道级精确曲率与道路 polygon 不在首版。
|
||||
1
.trellis/tasks/08-05-vehicle-turn-routes/implement.jsonl
Normal file
1
.trellis/tasks/08-05-vehicle-turn-routes/implement.jsonl
Normal file
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
10
.trellis/tasks/08-05-vehicle-turn-routes/implement.md
Normal file
10
.trellis/tasks/08-05-vehicle-turn-routes/implement.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# 实施计划:车辆连续巡航与转弯
|
||||
|
||||
1. 重构 `vehicle-route.js` 的 OSM 读取和纯几何 helper,构造有向 road graph、动作分类与
|
||||
`turn:lanes` 过滤。
|
||||
2. 枚举稳定的端点间道路路径、选择不重复且覆盖三种动作的至多 5 条路线;在端点添加
|
||||
平滑调头并生成路口连接。
|
||||
3. 扩展 route JSON,保留 `segments` 兼容字段;预览优先消费新路线数组,UI 标识路线及动作。
|
||||
4. 在 `test-preview-assets.js` 增加单行、标签过滤、左直右分类、闭环和曲线连续性夹具。
|
||||
5. 运行 Node 语法检查、预览测试、现有箭头测试和目标区域 `preview` 重建;在浏览器观察
|
||||
普通与压缩预览的多车转弯。
|
||||
49
.trellis/tasks/08-05-vehicle-turn-routes/prd.md
Normal file
49
.trellis/tasks/08-05-vehicle-turn-routes/prd.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 车辆连续巡航与转弯
|
||||
|
||||
## Goal
|
||||
|
||||
让 Cesium 预览中的车辆沿连续道路路线穿越路口,并以可见的平滑曲线完成左转、右转和直行;
|
||||
同屏展示多条确定性路线以核对效果。
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- 当前 `scripts/lib/vehicle-route.js` 将每条可行驶 OSM way 独立导为折返巡航段;车辆不会跨
|
||||
way 行驶。
|
||||
- Cesium 预览最多加载 5 辆车辆,已有位置插值、朝向计算、路线可见性和 Follow 控制。
|
||||
- 南台子湖 OSM 使用单引号 XML 属性,含 15 处 `turn:lanes:forward`、15 处
|
||||
`turn:lanes:backward` 和 4 条 `oneway=yes`。
|
||||
- 没有 `restriction` relation;首版不能承诺处理禁止左转等限制关系。
|
||||
- 路口 `1140799725` 与 `1140799919` 各连接四条道路,另有多个三岔连接,足以形成多条
|
||||
左转、右转、直行的连续候选路线。
|
||||
|
||||
## Requirements
|
||||
|
||||
1. 从 OSM 可行驶 way 构建有向道路图:单行道只能按 tag 方向通行,双向道路提供两个方向。
|
||||
2. 在共享端点连接道路,依据入射和出射方向将候选动作分类为 left、through、right;去程
|
||||
只有动作出现在该方向 `turn:lanes:*` 的任一车道时才可通行。返程原路返回,不用反向
|
||||
`turn:lanes` 二次否决路线。
|
||||
3. 确定性地产生至多 5 条连续往返路线,覆盖至少一条左转、一条右转和一条直行路线。
|
||||
4. 路口与路线端点均用平滑连接补充路线点;车辆位置、朝向和可见路线必须连续,不能在
|
||||
way 端点跳回起点。
|
||||
5. 保持现有路线 JSON 是预览的可选输入,旧 route JSON 仍可由预览加载。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 不做实时交通仿真、避碰、红绿灯、变道或速度控制。
|
||||
- 不使用 QGIS 图层或改动 Blender/GLB。
|
||||
- 不支持 OSM `restriction` relation;后续数据具备时再接入。
|
||||
- 不承诺为每条车道生成独立精确轨迹;`turn:lanes` 首版用于许可转向过滤。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 输出最多 5 条连续往返路线,且每条均有多个跨 way 的路口连接和端点平滑调头。
|
||||
- [ ] 南台子湖预览同屏可见左转、右转、直行三类路口动作,车辆不在路口或 way 端点瞬移。
|
||||
- [ ] `oneway=yes` 的路段不会逆向进入;去程没有匹配 `turn:lanes` 动作的出口不会被选入路线。
|
||||
- [ ] 路线生成对同一 OSM 输入稳定,自动化测试覆盖单行、动作过滤、转向分类、曲线连接和闭环。
|
||||
- [ ] 旧 route JSON 的预览兼容性不回归,现有 Pause、Follow、路线开关和车辆选择仍可用。
|
||||
|
||||
## Open Questions
|
||||
|
||||
已决:南台子湖可行驶道路主连通分量是树状网络(23 个端点节点、19 条道路连接),没有
|
||||
真实闭环;首版以端点平滑调头的往返路线替代闭环。为展示多条路线,返程不以反向
|
||||
`turn:lanes` 过滤,但仍不允许逆行单行道。
|
||||
26
.trellis/tasks/08-05-vehicle-turn-routes/task.json
Normal file
26
.trellis/tasks/08-05-vehicle-turn-routes/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "vehicle-turn-routes",
|
||||
"name": "vehicle-turn-routes",
|
||||
"title": "车辆连续巡航与转弯",
|
||||
"description": "",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-05",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in New Issue
Block a user