# 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//`。其中只有 `package/` 是可复制给下游的发布边界: - `package/manifest.json`:版本化 `osm-asset-package/v1` manifest,声明 WGS84 anchor、ENU(X east/Y north/Z up,米)、heading correction 和包内模型 URL - `package/models/.glb`:完整静态场景;`roads.glb`、`buildings.glb`、`vegetation.glb`、`water.glb` 是可选重叠分层,默认不要与完整场景同时加载 其余内容仍是本项目工作目录: - `.blend`:Blender 场景,包含道路、建筑、水体、植被等 - `.png`:Blender 预览渲染 - `.glb`:Cesium 可加载的完整 3D 模型(默认压缩交付产物) - `-roads.glb`、`-buildings.glb`、`-vegetation.glb`、`-water.glb`:Cesium 分类检查用的辅助模型 - `.json`:Cesium 放置元数据和示例代码 - `-cesium-preview.html`:Cesium 本地预览页 - `native-road/`:native road compiler 的道路、路口、标线和信号灯运行时产物 - `osm2streets_web_out/`:可选的 osm2streets/QGIS 参考中间层 - `.gpkg` / `.qgz` / `-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`)。 首次使用: ```bash cd /Users/que01/osm2streets-qgis-workflow npm install ``` ## 主流程 交互式选择区域和构建阶段: ```bash npm run build ``` 使用方向键移动、空格多选阶段、回车开始构建;`intermediates` 与 `reimport` 不能同时选择。 指定区域配置: ```bash npm run build:area -- --config config/areas/hanyang-block.json ``` 只跑部分阶段: ```bash 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 手工修正工作流](#qgis-手工修正工作流)。 Cesium 预览默认显示完整场景。点击 `Inspect` 后会按道路、建筑、绿化与设施、水体加载辅助 GLB;该模式用于单独检查生成结果,主 GLB 是压缩后的下游交付资产。 `compress` 包含在完整构建的默认阶段和 `all` 中。需要对已有标准产物重新压缩时,可以显式跑: ```bash npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages compress ``` ## 区域诊断 手工编辑 OSM 或怀疑产物变大时,先跑快速诊断。它只读取区域配置、OSM XML 和已有输出 文件,不会启动 QGIS、Blender 或 Cesium 构建: ```bash 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` 标签: ```bash 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//native-road/`,不会覆盖 `osm2streets_web_out/` 或影响现有 Blender/Cesium 构建: ```bash 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 会额外生成: ```text outputs//_preview/-traffic-simulation.json ``` 该文件使用 `native-preview-traffic-simulation/v1`,记录 native 路线、connector、停止点、 信号 `signal_uid`、源文件 SHA-256、坐标系和仿真参数。它与 `package/manifest.json` 一起可 迁移到其他平台;消费方不需要运行 Node 或 QGIS。预览中的车辆会根据现有信号相位在停止线 前停车,并按最小车距跟车排队。该功能是确定性的验证预览,不是法规级导航或完整交通仿真。 迁移契约、JSON 示例、更新循环和回撤方式见 [native preview traffic simulation](docs/native-preview-traffic-simulation.md)。 启动本地浏览器工作台: ```bash npm run road:workbench -- --config config/areas/nantaizi-lake-innovation-valley.json ``` 工作台默认先编译,并在 `http://127.0.0.1:8787/` 启动基于 OpenLayers 的 GIS 工作台,展示 OSM 道路、原生结果、车道、行驶动作、诊断和每个值的来源。可编辑道路宽度、车道数、两侧 人行道,以及候选路口端点连接。保存写入 `outputs//native-road-overrides.json`;此文件是版本化的可审查输入,下一次编译和 启动工作台时会自动加载。编辑不会直接修改最终 polygon。 道路按行驶方向显示:点击道路面、中心线、车道或转向路径后,蓝色三角形标记当前方向在 OSM 原始中心线上的方向;右侧“路口连接”只列出该方向到达终点路口后可驶入的目标道路,并标记为 左转、直行、右转或掉头。车道按当前方向从左向右编号。内部断头诊断中的近邻候选不会自动连接, 必须由用户显式暂存、保存并重新编译。 诊断会输出 OSM bounds、building way / multipolygon relation、显式高度、植被数量、 现有产物状态,以及 GLB 的 size / nodes / meshes / materials / images / extensions。 缺少已期望的基线产物、异常 building relation、GLB 超过保守预算等会进入 `Warnings`。 预检和每个成功的区域构建阶段都会写机器可读的 manifest: ```text outputs//_pipeline/stages/preflight.manifest.json outputs//_pipeline/stages/intermediates.manifest.json outputs//_pipeline/stages/reimport.manifest.json outputs//_pipeline/stages/blender.manifest.json outputs//_pipeline/stages/cesium.manifest.json outputs//_pipeline/stages/preview.manifest.json outputs//_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` 里标出来。 ## 区域质量门 提交或交付某个区域前,跑只读质量门: ```bash 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 和最大贴图。区域可在配置中收紧预算;要放宽 默认值,必须写明原因: ```json "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,不阻断。 ## 区域配置 新区域从模板复制: ```bash cp config/examples/template.json config/areas/my-area.json ``` 核心配置: ```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` 覆盖: ```json { "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 服务,避免浏览器拦截本地文件请求: ```bash cd outputs/my-area python3 -m http.server 8765 ``` 然后打开 `http://localhost:8765/my-area-cesium-preview.html`。 ## 实验:车辆巡航 `preview` 和 `cesium` 阶段会额外生成 `-vehicle-route.json` 和 `-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 阶段,可以直接运行: ```bash npm run build:qgis -- --config config/hanyang-block.json ``` Blender 低层命令: ```bash /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 低层导出: ```bash /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](blender/README.md)。 ## 压缩 GLB 完整构建默认运行 `compress`。它将未压缩 Cesium 导出复制到 `_pipeline` 下的临时目录, 压缩成功后才替换标准 `.glb`、`.json` 和预览页;最终输出目录不保留并列压缩文件: ```bash npm run build:area -- \ --config config/areas/nantaizi-lake-innovation-valley.json \ --stages compress ``` 底层脚本也可以单独调用: ```bash 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//.qgz` 2. 直接编辑项目内关联的 `gpkg` 图层并保存 3. 跑 `reimport` 回导 GeoJSON 并重建场景,再接 `blender,cesium` 南台子湖创新谷当前可直接使用下面这条命令: ```bash npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages reimport,blender,cesium ``` `reimport` 阶段(`scripts/reimport-gpkg.js`)做两件事: - 把 `.gpkg` 里的 9 个图层逐个导出到 `osm2streets_web_out/.geojson` - 按 `scripts/lib/scene-layers.js` 的图层表重建 `osm2streets_scene.geojson`(写入 `render_layer` / `z_index`)和 `osm2streets_scene_style.json` 说明: - 这套流程假设你的手工修改已经保存在 `outputs//.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](docs/input-cases.md) - 已验证的 OSM 输入案例 - [docs/changelog.md](docs/changelog.md) - 变更记录