363 lines
16 KiB
Markdown
363 lines
16 KiB
Markdown
# OSM Asset Pipeline
|
||
|
||
把单个园区/片区 OSM XML 转为可消费的 Blender 场景和 Cesium GLB。osm2streets GeoJSON、GeoPackage、QGIS 工程和预览图都是中间资产,用来提供道路几何、调试标线效果,以及给 Blender/Cesium 生成提供输入。
|
||
|
||
## 目标产物
|
||
|
||
每个区域默认输出到 `outputs/<area-id>/`。其中只有 `package/` 是可复制给下游的发布边界:
|
||
|
||
- `package/manifest.json`:版本化 `osm-asset-package/v1` manifest,声明 WGS84 anchor、ENU(X east/Y north/Z up,米)、heading correction 和包内模型 URL
|
||
- `package/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 本地预览页
|
||
- `osm2streets_web_out/`:osm2streets GeoJSON 中间层
|
||
- `<area-id>.gpkg` / `<area-id>.qgz` / `<area-id>-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
|
||
```
|
||
|
||
使用方向键移动、空格多选阶段、回车开始构建;`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
|
||
```
|
||
|
||
`intermediates` 会生成 osm2streets GeoJSON、GeoPackage、QGIS 工程和 QGIS 预览图。`blender` 使用 OSM 和 osm2streets GeoJSON 生成 `.blend`/`.png`。`cesium` 从 `.blend` 导出包 staging,`compress` 压缩 staging 主 GLB,`package` 校验并原子发布 `package/`,`preview` 生成仅用于本地验证的 HTML 和 `_preview/` 动态描述符。`reimport` 把手工编辑过的 GeoPackage 回导为 GeoJSON,不含在 `all` 里,详见 [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/<area-id>/native-road/`,不会覆盖
|
||
`osm2streets_web_out/` 或影响现有 Blender/Cesium 构建:
|
||
|
||
```bash
|
||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||
```
|
||
|
||
启动本地浏览器工作台:
|
||
|
||
```bash
|
||
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 bounds、building way / multipolygon relation、显式高度、植被数量、
|
||
现有产物状态,以及 GLB 的 size / nodes / meshes / materials / images / extensions。
|
||
缺少已期望的基线产物、异常 building relation、GLB 超过保守预算等会进入 `Warnings`。
|
||
|
||
预检和每个成功的区域构建阶段都会写机器可读的 manifest:
|
||
|
||
```text
|
||
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 /
|
||
GeoJSON feature counts,Blender 记录 `.blend` / render,Cesium/压缩记录 GLB digest,
|
||
preview 记录 GLB、metadata、`lane_polygons.geojson`、`network.json`、`intersection_surface.geojson`、
|
||
车辆路线和 runtime 文件。巡航道路区间按 osm2streets internal road 匹配真实 Driving lane 中轴,
|
||
路口 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": 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"
|
||
}
|
||
}
|
||
```
|
||
|
||
预览 Cesium 页面时,需要在输出目录启动 HTTP 服务,避免浏览器拦截本地文件请求:
|
||
|
||
```bash
|
||
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.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` 下的临时目录,
|
||
压缩成功后才替换标准 `<area-id>.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/<area-id>/<area-id>.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`)做两件事:
|
||
|
||
- 把 `<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`,正好会抹掉后者要读回的手工修改
|
||
- `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) - 变更记录
|