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>
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/v1manifest,声明 WGS84 anchor、ENU(X east/Y north/Z up,米)、heading correction 和包内模型 URLpackage/models/<area-id>.glb:完整静态场景;roads.glb、buildings.glb、vegetation.glb、water.glb是可选重叠分层,默认不要与完整场景同时加载
其余内容仍是本项目工作目录:
<area-id>.blend:Blender 场景,包含道路、建筑、水体、植被等<area-id>.png:Blender 预览渲染<area-id>.glb:Cesium 可加载的完整 3D 模型(默认压缩交付产物)<area-id>-roads.glb、-buildings.glb、-vegetation.glb、-water.glb:Cesium 分类检查用的辅助模型<area-id>.json:Cesium 放置元数据和示例代码<area-id>-cesium-preview.html:Cesium 本地预览页native-road/:native road compiler 的道路、路口、标线和信号灯运行时产物osm2streets_web_out/:可选的 osm2streets/QGIS 参考中间层<area-id>.gpkg/<area-id>.qgz/<area-id>-preview.png:QGIS 调试资产
环境
默认 native-only 构建需要:
- Blender
- Node.js / npm
QGIS/GDAL 仅在显式运行 legacy intermediates/reimport 或 Workbench 参考链路时需要。
macOS 默认使用 QGIS /Applications/QGIS.app 与 Blender
/Applications/Blender.app。Linux 配置中,qgisApp 是安装前缀(通常为
/usr),blenderApp 是可执行文件路径(通常为 /usr/bin/blender)。
首次使用:
cd /Users/que01/osm2streets-qgis-workflow
npm install
主流程
交互式选择区域和构建阶段:
npm run build
使用方向键移动、空格多选阶段、回车开始构建;intermediates 与 reimport 不能同时选择。
指定区域配置:
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
intermediates 和 reimport 是同一批 GeoJSON 的两种所有权路径:前者从 OSM 重建,
后者从手工编辑的 GeoPackage 回导。成功运行其中一个会清掉另一个的 manifest,避免旧路径
造成假 stale。
manifest 记录阶段输入/输出文件的 bytes、mtime、sha256、耗时和结构摘要:前段记录 OSM /
native-road 或 legacy GeoJSON feature counts,Blender 记录 .blend / render,Cesium/压缩记录 GLB digest,
preview 记录 GLB、metadata、provider 对应的道路输入、可选车辆路线和 runtime 文件。legacy
巡航道路区间按 osm2streets internal road 匹配真实 Driving lane 中轴,native preview 可省略巡航,
路口 connector 必须通过 intersection surface 越界检查。diagnose:area 会读取这些
manifest;缺失或当前输入/输出 sha/bytes 不一致会在 Stage manifests 和 Warnings
里标出来。
区域质量门
提交或交付某个区域前,跑只读质量门:
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 default0.05removes the two malformed tail vertices without changing the arrow head; the remaining tail edge is aligned perpendicular to the shaft.intersectionCornerSourceMaxDimensionMeters: keeps only small osm2streetssidewalk cornerpolygons. 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。
实验:车辆巡航
preview 和 cesium 阶段会额外生成 <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.jsonconfig/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 可选 natural、procedural、shapespark。shapespark 使用
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"
压缩 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,否则会把手工修改覆盖掉。推荐流程是:
- 在 QGIS 中打开
outputs/<area-id>/<area-id>.qgz - 直接编辑项目内关联的
gpkg图层并保存 - 跑
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 字节文件,直接覆盖会静默损坏数据) intermediates与reimport互斥,同时指定会直接报错:前者用 OSM 重建gpkg,正好会抹掉后者要读回的手工修改- 在显式 legacy provider 下,
blender,cesium阶段读取的是osm2streets_web_out/*.geojson,不是直接读取gpkg;native provider 不读取这些文件 - 如果 Blender 当前环境不稳定,先确认
geojson已完成回导,再单独排查 Blender 本身 - 增删图层或调整
z_index只需改scripts/lib/scene-layers.js,构建、场景合并、场景样式、QGIS 工程会一并同步
文档
- docs/input-cases.md - 已验证的 OSM 输入案例
- docs/changelog.md - 变更记录