# OSM Asset Pipeline 把单个园区/片区 OSM XML 转为可消费的 Blender 场景和 Cesium GLB。osm2streets GeoJSON、GeoPackage、QGIS 工程和预览图都是中间资产,用来提供道路几何、调试标线效果,以及给 Blender/Cesium 生成提供输入。 ## 目标产物 每个区域默认输出到 `outputs//`: - `.blend`:Blender 场景,包含道路、建筑、水体、植被等 - `.png`:Blender 预览渲染 - `.glb`:Cesium 可加载的完整 3D 模型(基线产物) - `-roads.glb`、`-buildings.glb`、`-vegetation.glb`、`-water.glb`:Cesium 分类检查用的辅助模型 - `.json`:Cesium 放置元数据和示例代码 - `-cesium-preview.html`:Cesium 本地预览页 - `-compressed-webp768.glb/json/html`:显式 `compress` 阶段生成的可选压缩预览产物 - `osm2streets_web_out/`:osm2streets GeoJSON 中间层 - `.gpkg` / `.qgz` / `-preview.png`:QGIS 调试资产 ## 环境 需要: - QGIS 与 GDAL 工具链 - Blender - Node.js / npm 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 ``` 指定区域配置: ```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 ``` `intermediates` 会生成 osm2streets GeoJSON、GeoPackage、QGIS 工程和 QGIS 预览图。`blender` 使用 OSM 和 osm2streets GeoJSON 生成 `.blend`/`.png`。`cesium` 从 `.blend` 导出 `.glb`/`.json`,并生成 Cesium 预览 HTML。`preview` 只在已有 `.glb/.json` 时补生成 HTML。`compress` 从已有 `.glb/.json/html` 生成并列压缩产物,不覆盖默认 GLB。`reimport` 把手工编辑过的 GeoPackage 回导为 GeoJSON,不含在 `all` 里,详见 [QGIS 手工修正工作流](#qgis-手工修正工作流)。 Cesium 预览默认显示完整场景。点击 `Inspect` 后会按道路、建筑、绿化与设施、水体加载辅助 GLB;该模式用于单独检查生成结果,主 GLB 仍是完整场景和下游使用的基线。 压缩预览同样保留这些分类检查资产;重跑 `compress` 后应打开新生成的压缩预览 HTML。 `compress` 不含在 `all` 里,也不能从配置文件默认开启。需要重导出 Cesium 后立刻生成压缩产物时,显式跑: ```bash npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium,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 文件摘要和检查结果。 诊断会输出 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 / GeoJSON feature counts,Blender 记录 `.blend` / render,Cesium/压缩记录 GLB digest, preview 记录 GLB、metadata、车辆路线和 runtime 文件。`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": true, "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": "" }, "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", "compressedGlb": "/absolute/path/to/custom-compressed.glb" } } ``` 预览 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 bounds 内的可行驶 `highway` way 提取道路中心线,并向右偏移约 1.3 米作为车辆行驶线,避免车辆压道路中心线。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` 阶段。它不会覆盖默认 `.glb`,只生成并列的压缩 GLB、 metadata 和预览页: ```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 outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley-compressed-webp768.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 -- --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`,正好会抹掉后者要读回的手工修改 - `blender,cesium` 阶段读取的是 `osm2streets_web_out/*.geojson`,不是直接读取 `gpkg` - 如果 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) - 变更记录