121 lines
6.6 KiB
Markdown
121 lines
6.6 KiB
Markdown
# 车辆连续路线
|
||
|
||
## 1. Scope / Trigger
|
||
|
||
适用于 `scripts/lib/vehicle-route.js` 生成的路线 JSON,以及
|
||
`scripts/lib/cesium-preview.js` 对车辆巡航路线的读取与展示。
|
||
|
||
触发:修改路线生成、OSM 转向标签解析、车辆选择菜单或路线 JSON 字段时。
|
||
路线仅用于 Cesium 验证预览,不构成交通仿真或法规级导航。
|
||
|
||
## 2. Signatures
|
||
|
||
```js
|
||
buildVehicleRoute(osmPath, lanePolygonsPath, networkPath, intersectionSurfacePath) => {
|
||
source, laneSource, networkSource, intersectionSource, bounds, generatedAt, speedMetersPerSecond, loop,
|
||
routes, segments, diagnostics
|
||
}
|
||
|
||
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`、`laneSegments` 与 `connectors`。道路区间来自匹配的 Driving lane polygon 中轴。
|
||
- 路线拓扑以 `network.json` 的 internal road 和 intersection 为准;禁止把整个 OSM way 直接当作一条不可分割 edge。
|
||
- connector 必须绑定同一个 internal intersection,并位于对应 `intersection_surface.geojson` 内或允许的边界容差内;越界时拒绝候选路线。
|
||
- legacy osm2streets preview 必须将 `lane_polygons.geojson`、`network.json` 和 `intersection_surface.geojson` 作为强制输入;native preview 不读取这些文件,路线缺失时保留可用预览并省略车辆巡航。
|
||
- route 经纬度由 Cesium 按 WGS84 直接放置;最终道路 GLB 必须由 WGS84 ECEF→ENU
|
||
`Projector` 生成。禁止以固定米/度近似投影道路,否则即使 route 与 lane polygon
|
||
完全一致,最终画面仍会随离锚点距离产生横向偏移。
|
||
- 单条路线无法可靠匹配时跳过并写结构化 `diagnostics`,不得回退固定或默认车道宽度。
|
||
- 有 `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`,因为一条路线可跨越多个道路名称。
|
||
- 预览只显示当前下拉框选中车辆的 route polyline,避免多条闭环轨迹在路口重叠造成错误的偏移判断。
|
||
|
||
## 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 下拉标签显示编号、长度与动作统计。
|
||
- 修改地理投影时必须运行 `blender,cesium,preview`,不能只重跑 preview;最终检查青色路线
|
||
到黄色中心线及道路边缘的横截面距离,确认两侧路线分别位于各自车道中心。
|
||
|
||
## 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"));
|
||
```
|
||
|
||
## 信号动态 GLB 契约
|
||
|
||
`traffic_signals.json` 的 `pose.*` 是 Blender 静态设施、动态灯珠和倒计时共享的锚点。Blender
|
||
把发光灯珠导出为独立的 `*-traffic-signals-dynamic.glb`,preview 必须使用与主 GLB 相同的
|
||
`scenePlacement(metadata).modelMatrix` 加载它;Cesium 仅按命名灯珠节点切换 `show`。倒计时
|
||
例外:它由 Cesium Entity 从 `pose.countdown` 的 ENU 坐标与面向直接绘制,避免 glTF 轴变换
|
||
反转七段字形。
|
||
|
||
动态表面不能与静态镜片或倒计时外壳共面:镜片和数码管必须沿本地 `face` 轴前移
|
||
`(static_depth + dynamic_depth) / 2 + epsilon`。这是模型局部几何关系,不是经纬度修正;
|
||
否则静态网格会通过深度测试遮住发光状态,表现为灯不切换或数字不可见。
|
||
|
||
错误:在 Cesium 用 `fromDegrees`/Entity 重新计算动态设施,或将动态网格中心与静态表面中心
|
||
重合。
|
||
|
||
正确:Blender 生成命名节点 `TrafficSignalDynamic_<signal-id>_<state>`;浏览器在同一 model
|
||
matrix 下加载该 GLB,并只切换这些灯珠节点。倒计时 Entity 使用 `pose.countdown` 的经纬度、
|
||
高度、`faceHeadingDegrees` 生成与牌面相同的 ENU 坐标轴。
|