que01 bc845444bb fix: restore live V2X signal and vehicle fidelity in Cesium preview
The ported overlay used the correct REST paths but lost the data handling
from the source dashboard's live-intersection view (HologramCross), so
signals rendered permanently red and vehicles often never appeared.

- Lamp status codes now follow the dashboard dictionary (11/21/22/23/31).
  The previous 2/3 reading made every real push fall through to red. The
  dictionary lives only in the overlay; the preview consumes normalized
  {nodeKeys, color, countDown} entries so the two copies cannot drift.
- Bind V2X phases to native signal heads geometrically. The runtime
  document has no phaseNo, so the old lookup fell back to signal.id and
  never matched, leaving the dynamic assembly dark. Travel heading is
  recovered as faceHeadingDegrees + 180, per the generator's
  mast = travel - 90 / face = travel + 180. Verified 7/7 exact matches
  against the fengshu-er-road runtime document.
- A phase now lights every approach it drives; the phase -> single entity
  map silently overwrote all but the last.
- Drive the countdown assets from the push's countDown field.
- All three sockets heartbeat every 30s and reconnect with backoff,
  replaying their subscription frame. Without this the service dropped
  the connection and the scene emptied after about a minute.
- The OBU socket sends its bounds frame on connect and on camera move;
  it previously sent nothing at all.
- Vehicles are swept when a push goes stale and their slots reused, so
  they no longer accumulate as ghosts. Models follow the dashboard's
  car_obu.glb / ${type}${subType}.glb naming.
- Parse vehicle pushes leniently, since the dashboard uses saferEval and
  the payload is not guaranteed to be strict JSON. Failures are counted
  and surfaced rather than dropped; no eval is introduced.
- Drop FlowTravelRatio/queryListWeek, which is not part of this view.

Also corrects a stale spec rule that required vehicleModelNames to be
empty. Live V2X vehicles need packaged models; the real invariant is no
generated routes or traffic simulation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 09:20:50 +08:00

OSM Asset Pipeline

原始 QGIS/osm2streets 基线保留在 Git tag qgis-original-baseline-20260818,用于回撤和对照;当前分支的默认生产链路是 native-only。

把单个园区/片区 OSM XML 转为可消费的 Blender 场景和 Cesium GLB。Native road compiler 是默认生产道路来源osm2streets GeoJSON、GeoPackage、QGIS 工程和预览图仅作为显式 legacy/debug 资产或 Workbench 参考层。

目标产物

每个区域默认输出到 outputs/<area-id>/。其中只有 package/ 是可复制给下游的发布边界:

  • package/manifest.json:版本化 osm-asset-package/v1 manifest声明 WGS84 anchor、ENUX east/Y north/Z up、heading correction 和包内模型 URL
  • package/models/<area-id>.glb:完整静态场景;roads.glbbuildings.glbvegetation.glbwater.glb 是可选重叠分层,默认不要与完整场景同时加载

其余内容仍是本项目工作目录:

  • <area-id>.blendBlender 场景,包含道路、建筑、水体、植被等
  • <area-id>.pngBlender 预览渲染
  • <area-id>.glbCesium 可加载的完整 3D 模型(默认压缩交付产物)
  • <area-id>-roads.glb-buildings.glb-vegetation.glb-water.glbCesium 分类检查用的辅助模型
  • <area-id>.jsonCesium 放置元数据和示例代码
  • <area-id>-cesium-preview.htmlCesium 本地预览页
  • native-road/native road compiler 的道路、路口、标线和信号灯运行时产物
  • osm2streets_web_out/:可选的 osm2streets/QGIS 参考中间层
  • <area-id>.gpkg / <area-id>.qgz / <area-id>-preview.pngQGIS 调试资产

环境

默认 native-only 构建需要:

  • Blender
  • Node.js / npm

QGIS/GDAL 仅在显式运行 legacy intermediates/reimport 或 Workbench 参考链路时需要。

macOS 默认使用 QGIS /Applications/QGIS.app 与 Blender /Applications/Blender.app。Linux 配置中,qgisApp 是安装前缀(通常为 /usrblenderApp 是可执行文件路径(通常为 /usr/bin/blender)。

首次使用:

cd /Users/que01/osm2streets-qgis-workflow
npm install

主流程

交互式选择区域和构建阶段:

npm run build

使用方向键移动、空格多选阶段、回车开始构建;intermediatesreimport 不能同时选择。

指定区域配置:

npm run build:area -- --config config/areas/hanyang-block.json

只跑部分阶段:

npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages intermediates
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages preview
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages compress
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages reimport

默认构建依次由 native road compiler、Blender、Cesium、压缩、package 和 preview 组成,不读取 osm2streets_web_out/。native preview 在没有 legacy 路线文件时仍可用,只是不提供可选车辆巡航。intermediates/reimport 仍可显式运行 legacy QGIS 链路,详见 QGIS 手工修正工作流

Cesium 预览默认显示完整场景。点击 Inspect 后会按道路、建筑、绿化与设施、水体加载辅助 GLB该模式用于单独检查生成结果主 GLB 是压缩后的下游交付资产。

compress 包含在完整构建的默认阶段和 all 中。需要对已有标准产物重新压缩时,可以显式跑:

npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages compress

区域诊断

手工编辑 OSM 或怀疑产物变大时先跑快速诊断。它只读取区域配置、OSM XML 和已有输出 文件,不会启动 QGIS、Blender 或 Cesium 构建:

npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json

每次手工编辑 OSM 后、进入重型构建前,先跑预检。它只读取区域配置和 OSM XML检查 bounds、缺失 node 引用、building way 闭合性、building multipolygon member/ring、以及 显式 height / building:levels 标签:

npm run preflight:area -- --config config/areas/nantaizi-lake-innovation-valley.json

预检有 error 时退出非零且不会更新记录;通过后写 preflight.manifest.json,保留本次 验证的 config / OSM 文件摘要和检查结果。

Native Road Workbench

road:compile 是独立于 osm2streets 的实验性道路编译器入口。它从 OSM 生成可追溯的 道路模型、基础道路面、诊断与对比摘要,写入 outputs/<area-id>/native-road/,不会覆盖 osm2streets_web_out/ 或影响现有 Blender/Cesium 构建:

npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json

road:check 验证已发布 connector 与语义行驶动作的一致性,并在诊断包含 error、图层缺失或 动作/几何对应关系不一致时以非零退出。warning 保留给工作台审查,不会阻止产物打开。

Native Preview Traffic Simulation

native preview 会额外生成:

outputs/<area-id>/_preview/<area-id>-traffic-simulation.json

该文件使用 native-preview-traffic-simulation/v1,记录 native 路线、connector、停止点、 信号 signal_uid、源文件 SHA-256、坐标系和仿真参数。它与 package/manifest.json 一起可 迁移到其他平台;消费方不需要运行 Node 或 QGIS。预览中的车辆会根据现有信号相位在停止线 前停车,并按最小车距跟车排队。该功能是确定性的验证预览,不是法规级导航或完整交通仿真。

迁移契约、JSON 示例、更新循环和回撤方式见 native preview traffic simulation

启动本地浏览器工作台:

npm run road:workbench -- --config config/areas/nantaizi-lake-innovation-valley.json

工作台默认先编译,并在 http://127.0.0.1:8787/ 启动基于 OpenLayers 的 GIS 工作台,展示 OSM 道路、原生结果、车道、行驶动作、诊断和每个值的来源。可编辑道路宽度、车道数、两侧 人行道,以及候选路口端点连接。保存写入 outputs/<area-id>/native-road-overrides.json;此文件是版本化的可审查输入,下一次编译和 启动工作台时会自动加载。编辑不会直接修改最终 polygon。

道路按行驶方向显示:点击道路面、中心线、车道或转向路径后,蓝色三角形标记当前方向在 OSM 原始中心线上的方向;右侧“路口连接”只列出该方向到达终点路口后可驶入的目标道路,并标记为 左转、直行、右转或掉头。车道按当前方向从左向右编号。内部断头诊断中的近邻候选不会自动连接, 必须由用户显式暂存、保存并重新编译。

诊断会输出 OSM bounds、building way / multipolygon relation、显式高度、植被数量、 现有产物状态,以及 GLB 的 size / nodes / meshes / materials / images / extensions。 缺少已期望的基线产物、异常 building relation、GLB 超过保守预算等会进入 Warnings

预检和每个成功的区域构建阶段都会写机器可读的 manifest

outputs/<area-id>/_pipeline/stages/preflight.manifest.json
outputs/<area-id>/_pipeline/stages/intermediates.manifest.json
outputs/<area-id>/_pipeline/stages/reimport.manifest.json
outputs/<area-id>/_pipeline/stages/blender.manifest.json
outputs/<area-id>/_pipeline/stages/cesium.manifest.json
outputs/<area-id>/_pipeline/stages/preview.manifest.json
outputs/<area-id>/_pipeline/stages/compress.manifest.json

intermediatesreimport 是同一批 GeoJSON 的两种所有权路径:前者从 OSM 重建, 后者从手工编辑的 GeoPackage 回导。成功运行其中一个会清掉另一个的 manifest避免旧路径 造成假 stale。

manifest 记录阶段输入/输出文件的 bytes、mtime、sha256、耗时和结构摘要前段记录 OSM / native-road 或 legacy GeoJSON feature countsBlender 记录 .blend / renderCesium/压缩记录 GLB digest preview 记录 GLB、metadata、provider 对应的道路输入、可选车辆路线和 runtime 文件。legacy 巡航道路区间按 osm2streets internal road 匹配真实 Driving lane 中轴native preview 可省略巡航, 路口 connector 必须通过 intersection surface 越界检查。diagnose:area 会读取这些 manifest缺失或当前输入/输出 sha/bytes 不一致会在 Stage manifestsWarnings 里标出来。

区域质量门

提交或交付某个区域前,跑只读质量门:

npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json

check:area 复用 diagnose:area 的 OSM、产物、metadata、GLB digest 和 stage manifest 检查,但输出更短的 PASS/FAIL 报告。它不会启动 QGIS、Blender、Cesium、压缩或任何重建阶段。

GLB 预算默认阻断 size、nodes、images、实例化后的 render triangles 和嵌入贴图字节。 diagnose:area 会显示有效预算、top source 和最大贴图。区域可在配置中收紧预算;要放宽 默认值,必须写明原因:

"budget": {
  "nodes": 1400,
  "triangles": 350000,
  "reason": "Dense campus vegetation approved for this area"
}

第一版会在这些条件下退出非零OSM bounds 缺失/无效、building multipolygon relation 异常、建筑 height 无法按正数米解析、Cesium GLB / metadata / preview 缺失或类型错误、 metadata JSON 损坏、GLB size / nodes / images / render triangles / embedded image bytes 超过保守预算、期望存在的 stage manifest 缺失/损坏/stale。缺 QGIS preview 目前只作为 warning不阻断。

区域配置

新区域从模板复制:

cp config/examples/template.json config/areas/my-area.json

核心配置:

{
  "id": "my-area",
  "input": "/absolute/path/to/input.osm",
  "outputRoot": "/absolute/path/to/outputs",
  "qgisApp": "/Applications/QGIS.app",
  "blenderApp": "/Applications/Blender.app",
  "stages": {
    "intermediates": false,
    "blender": true,
    "cesium": true
  },
  "qgis": {
    "arrowScale": 0.8,
    "arrowMergeTriangles": true,
    "arrowOutlineSimplifyMeters": 0.05,
    "intersectionCornerSourceMaxDimensionMeters": 2.6,
    "clipPad": 0.002,
    "canvasPad": 0.001,
    "previewPad": 0.0007,
    "canvasExtent": null,
    "previewExtent": null,
    "layerPrefix": "osm2streets"
  },
  "blender": {
    "treeStyle": "natural",
    "officeOverrides": "",
    "roadProvider": "native"
  },
  "compress": {
    "textureSize": 768,
    "quality": 82,
    "effort": 80,
    "meshopt": false
  }
}

QGIS road-layer knobs:

  • arrowScale: scales osm2streets lane-arrow polygons before export.
  • arrowMergeTriangles: merges each osm2streets lane-arrow triangle mesh into one valid polygon. This preserves the original arrow shape and turn direction while removing renderer gaps along shared triangle edges.
  • arrowOutlineSimplifyMeters: removes sub-decimeter kinks from the merged arrow exterior. The default 0.05 removes the two malformed tail vertices without changing the arrow head; the remaining tail edge is aligned perpendicular to the shaft.
  • intersectionCornerSourceMaxDimensionMeters: keeps only small osm2streets sidewalk corner polygons. Large intersection-marking polygons are not treated as sidewalk because they can cover the drivable junction.

scripts/build-area.js 会按 id 自动推导默认输出路径。确实需要定制时,可以增加 outputs 覆盖:

{
  "outputs": {
    "areaDir": "/absolute/path/to/custom-area",
    "blend": "/absolute/path/to/custom.blend",
    "glb": "/absolute/path/to/custom.glb",
    "cesiumPreview": "/absolute/path/to/custom-preview.html"
  }
}

预览 Cesium 页面时,需要在输出目录启动 HTTP 服务,避免浏览器拦截本地文件请求:

cd outputs/my-area
python3 -m http.server 8765

然后打开 http://localhost:8765/my-area-cesium-preview.html

实验:车辆巡航

previewcesium 阶段会额外生成 <area-id>-vehicle-route.json<area-id>-vehicle-car.gltf。OSM 提供可行驶拓扑与转向语义,实际巡航坐标来自 osm2streets 的 Driving lane polygon 中轴;同向多车道按下一次 maneuver 选择兼容车道。路线中的 laneSegments 记录每个 polygon fragment 的 OSM way、方向、lane index、width、center offset 和来源。车道数据缺失、歧义、断裂或没有兼容转向车道时,候选路线会被跳过并写入顶层 diagnostics不会回退到固定偏移或默认宽度。Cesium 预览页会加载多条道路段并显示多辆实验车辆循环巡航;Vehicle 下拉框决定 Follow 跟随哪一辆车。

这是用于验证高精度巡航可用性的预览层功能,不会改变 Blender/GLB 主资产本身。车辆模型是无 logo 的轻量预览模型,生成在输出目录中。

已沉淀区域

  • config/areas/nantaizi-lake-innovation-valley.json
  • config/areas/hanyang-block.json

低层命令

通常优先使用 npm run build:area。如果只想调试旧 QGIS/osm2streets 阶段,可以直接运行:

npm run build:qgis -- --config config/hanyang-block.json

Blender 低层命令:

/Applications/Blender.app/Contents/MacOS/Blender \
  --background --factory-startup \
  --python blender/generate_scene.py -- \
  --osm "/path/to/input.osm" \
  --geojson "/path/to/osm2streets_web_out" \
  --output "/path/to/scene.blend" \
  --render "/path/to/preview.png" \
  --tree-style natural

--tree-style 可选 naturalproceduralshapesparkshapespark 使用 assets/models/custom/shapespark_plants/ 里的拆分低模植物资产;模型缺失时会回落到 natural

Cesium GLB 低层导出:

/Applications/Blender.app/Contents/MacOS/Blender \
  --background \
  --python blender/export_cesium.py -- \
  --blend "/path/to/scene.blend" \
  --glb "/path/to/scene.glb" \
  --metadata "/path/to/scene.json"

详见 blender/README.md

压缩 GLB

完整构建默认运行 compress。它将未压缩 Cesium 导出复制到 _pipeline 下的临时目录, 压缩成功后才替换标准 <area-id>.glb.json 和预览页;最终输出目录不保留并列压缩文件:

npm run build:area -- \
  --config config/areas/nantaizi-lake-innovation-valley.json \
  --stages compress

底层脚本也可以单独调用:

npm run compress:glb -- \
  --input outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley.glb \
  --output /tmp/nantaizi-lake-innovation-valley-compressed.glb \
  --texture-size 768 \
  --metadata outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley.json \
  --preview outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley-cesium-preview.html

该脚本通过 npx @gltf-transform/cli@4.1.4 执行 resize -> webp。如需进一步压几何, 可追加 --meshopt,但会引入 EXT_meshopt_compression / KHR_mesh_quantization 应先确认目标 Cesium 版本加载正常。

QGIS 手工修正工作流

如果已经在现成的 .qgz 项目里直接编辑了 gpkg 图层,不要再重跑 intermediates,否则会把手工修改覆盖掉。推荐流程是:

  1. 在 QGIS 中打开 outputs/<area-id>/<area-id>.qgz
  2. 直接编辑项目内关联的 gpkg 图层并保存
  3. reimport 回导 GeoJSON 并重建场景,再接 blender,cesium

南台子湖创新谷当前可直接使用下面这条命令:

npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages reimport,blender,cesium

reimport 阶段(scripts/reimport-gpkg.js)做两件事:

  • <area-id>.gpkg 里的 9 个图层逐个导出到 osm2streets_web_out/<layer>.geojson
  • scripts/lib/scene-layers.js 的图层表重建 osm2streets_scene.geojson(写入 render_layer / z_index)和 osm2streets_scene_style.json

说明:

  • 这套流程假设你的手工修改已经保存在 outputs/<area-id>/<area-id>.gpkg
  • 所有图层先导出到临时目录并校验通过后才写回 osm2streets_web_out/;任一图层缺失或导出结果不是合法 FeatureCollection整批都不落盘ogr2ogr 遇到不存在的图层会留下 0 字节文件,直接覆盖会静默损坏数据)
  • intermediatesreimport 互斥,同时指定会直接报错:前者用 OSM 重建 gpkg,正好会抹掉后者要读回的手工修改
  • 在显式 legacy provider 下,blender,cesium 阶段读取的是 osm2streets_web_out/*.geojson,不是直接读取 gpkgnative provider 不读取这些文件
  • 如果 Blender 当前环境不稳定,先确认 geojson 已完成回导,再单独排查 Blender 本身
  • 增删图层或调整 z_index 只需改 scripts/lib/scene-layers.js构建、场景合并、场景样式、QGIS 工程会一并同步

文档

Description
No description provided
Readme 82 MiB
Languages
Python 60.9%
JavaScript 32%
HTML 6.7%
CSS 0.4%