5.2 KiB
车辆连续路线
1. Scope / Trigger
适用于 scripts/lib/vehicle-route.js 生成的路线 JSON,以及
scripts/lib/cesium-preview.js 对车辆巡航路线的读取与展示。
触发:修改路线生成、OSM 转向标签解析、车辆选择菜单或路线 JSON 字段时。 路线仅用于 Cesium 验证预览,不构成交通仿真或法规级导航。
2. Signatures
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,绝不能生成反向:backwardedge;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
错误:优先使用旧字段,导致新路线元数据无法被消费。
const segments = routeData.segments || routeData.routes || [];
正确:新字段优先,旧字段仅作兼容回退。
const routes = routeData.routes || routeData.segments || [];
错误:为使路线闭合而生成单行道路的反向 edge。
edges.push(makeEdge(way, refs.reverse(), coords.reverse(), "backward"));
正确:单行仅保留其允许的方向,树状网络用端点调头闭合预览路线。
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 坐标轴。