34 Commits

Author SHA1 Message Date
c5956365fa chore: record journal 2026-08-05 16:03:01 +08:00
c850a0da49 chore(task): archive 08-05-lowpoly-cars-assets 2026-08-05 16:02:30 +08:00
2489b8a10b fix(assets): make vehicle kit verification standalone 2026-08-05 16:02:11 +08:00
3108336bf4 feat(preview): add low poly vehicle library 2026-08-05 16:00:04 +08:00
436eb482fd chore: record journal 2026-08-05 15:00:16 +08:00
eb4906b296 chore(task): archive 08-05-vehicle-turn-routes 2026-08-05 14:59:48 +08:00
30846b6df9 feat(preview): add continuous vehicle turn routes 2026-08-05 14:58:24 +08:00
eb9e510e13 chore: record journal 2026-08-05 13:29:32 +08:00
289c5a29e7 chore(task): archive 08-05-cesium-preview-controls 2026-08-05 13:25:50 +08:00
607d8fc0b1 feat: add Cesium semantic inspection mode 2026-08-05 13:24:39 +08:00
400525dd07 fix: match Cesium intersection asphalt 2026-08-05 11:54:26 +08:00
15a8429af9 chore(task): archive OSM turn lane arrows 2026-08-05 10:23:58 +08:00
9a23f74f0f feat: add OSM turn lane arrows 2026-08-05 10:17:34 +08:00
9970e3eeef chore: record journal 2026-08-04 12:56:19 +08:00
1f9d99d9b5 chore(task): archive 08-04-split-area-build-orchestration 2026-08-04 12:55:34 +08:00
396df5127c Refactor area preview orchestration 2026-08-04 12:55:20 +08:00
0ce7d1ea5e chore: record journal 2026-08-04 12:29:15 +08:00
abfd9e401e chore(task): archive 08-04-add-area-asset-budgets 2026-08-04 12:29:03 +08:00
463cb06be4 Add configurable area asset budgets 2026-08-04 12:28:43 +08:00
f78080bbfa chore: record journal 2026-08-04 11:58:41 +08:00
d263c02ea7 chore(task): archive 08-04-add-osm-area-preflight 2026-08-04 11:58:32 +08:00
e1f5207e97 Add OSM area preflight 2026-08-04 11:58:12 +08:00
34153a31ab chore: record journal 2026-08-04 11:38:13 +08:00
d36948cd8b chore(task): archive 08-04-add-full-stage-manifests 2026-08-04 11:38:04 +08:00
5874cf9379 Add full stage manifests 2026-08-04 11:37:20 +08:00
0b7e69770c chore: record journal 2026-08-04 10:42:59 +08:00
429e61e51d chore(task): archive 08-04-add-area-quality-gate 2026-08-04 10:42:39 +08:00
7fcc4ee8cc Add area quality gate 2026-08-04 10:42:10 +08:00
b791c4350e chore: record journal 2026-08-04 09:46:49 +08:00
b26fb730e7 chore(task): archive 08-04-add-stage-manifests 2026-08-04 09:46:31 +08:00
2832383f6e Add GLB stage manifests 2026-08-04 09:45:51 +08:00
b7b5530430 chore: record journal 2026-08-04 09:16:50 +08:00
c4d4bf9db2 chore(task): archive 08-04-add-area-diagnostics 2026-08-04 09:16:38 +08:00
d4c3baf608 Add area diagnostics command 2026-08-04 09:16:14 +08:00
160 changed files with 22301 additions and 727 deletions

View File

@@ -259,6 +259,17 @@ tilt_y = TILT_JITTER * math.cos(index * 0.927295)
| 靠调 `FOLIAGE_EMISSION` 提亮植被 | 用错了旋钮,该调 albedo gain | | 靠调 `FOLIAGE_EMISSION` 提亮植被 | 用错了旋钮,该调 albedo gain |
| 在 `MATERIALS` 中间插入条目 | GLB 材质索引整体平移 | | 在 `MATERIALS` 中间插入条目 | GLB 材质索引整体平移 |
## 第三方资产导入的源文件边界
第三方 Blend 只属于一次性入库工具的输入,不能成为 `build:area` 或 Cesium 预览的运行时依赖。
完成拆分后,仓库必须包含可直接消费的 glTF/bin、共享贴图、manifest 和人工预览;原始下载文件可
以删除。导入工具应将源文件路径作为显式 `--source` 参数manifest 最多保留源文件名作溯源,
不能写死用户 Downloads 目录。
删除原始文件前必须确认入库资产已通过结构验证和人工预览;删除后若需重新导入,必须重新取得
同一 Blend 与其外部贴图。`blender/tools/split_lowpoly_cars.py` 是这一约定的实例,不参与区域
构建阶段。
--- ---
## 相关 ## 相关

View File

@@ -12,7 +12,8 @@
``` ```
config/areas/<id>.json ← 你写的 config/areas/<id>.json ← 你写的
build-area.js: normalizeAreaConfig() 补默认值 + 推导输出路径 scripts/lib/area-config.js: normalizeAreaConfig()
│ 补默认值 + 推导输出路径
<areaDir>/_pipeline/osm2streets-qgis.config.json ← 生成的,不要手改 <areaDir>/_pipeline/osm2streets-qgis.config.json ← 生成的,不要手改
@@ -50,9 +51,11 @@ cp config/examples/template.json config/areas/my-area.json
| `blenderApp` | | `/Applications/Blender.app` | | | `blenderApp` | | `/Applications/Blender.app` | |
| `stages` | | 见下 | 各阶段默认开关 | | `stages` | | 见下 | 各阶段默认开关 |
| `qgis` | | 见下 | QGIS/osm2streets 旋钮 | | `qgis` | | 见下 | QGIS/osm2streets 旋钮 |
| `turnLaneArrows` | | 见下 | 从 OSM `turn:lanes:*` 生成自定义车道箭头的发布开关 |
| `osm2streets` | | 见下 | 透传给 osm2streets 的选项 | | `osm2streets` | | 见下 | 透传给 osm2streets 的选项 |
| `blender` | | 见下 | Blender 侧选项 | | `blender` | | 见下 | Blender 侧选项 |
| `compress` | | 见下 | 显式 `compress` 阶段的 GLB 压缩选项 | | `compress` | | 见下 | 显式 `compress` 阶段的 GLB 压缩选项 |
| `budget` | | 见下 | 区域 GLB 性能与体量预算 |
| `outputs` | | 从 `id` 推导 | 输出路径覆盖,逃生舱 | | `outputs` | | 从 `id` 推导 | 输出路径覆盖,逃生舱 |
**路径一律绝对**`normalizeAreaConfig` 对每一项都做 `path.resolve`,相对路径会 **路径一律绝对**`normalizeAreaConfig` 对每一项都做 `path.resolve`,相对路径会
@@ -95,6 +98,15 @@ cp config/examples/template.json config/areas/my-area.json
> 这四个 arrow/corner 旋钮的默认值都是调出来的,**改之前先看 README 里记的理由**。 > 这四个 arrow/corner 旋钮的默认值都是调出来的,**改之前先看 README 里记的理由**。
> 尤其 `arrowOutlineSimplifyMeters`——调大会开始削箭头头部。 > 尤其 `arrowOutlineSimplifyMeters`——调大会开始削箭头头部。
### `turnLaneArrows`
| 字段 | 默认 | 说明 |
|---|---|---|
| `enabled` | `false` | 仅在样张经用户确认后启用。启用时从 `turn:lanes:forward` / `turn:lanes:backward` 追加经过测试的自定义箭头;未测试素材永不参与映射。 |
该开关经 `normalizeAreaConfig()``writeDerivedConfig()` 传入 intermediates 阶段。必须用
`??` 保留 `false`;不要将它改为按隐式标签或环境变量自动启用。
### `osm2streets` ### `osm2streets`
原样透传给 `JsStreetNetwork` 构造函数(`build-osm2streets-qgis.js:77`)。默认: 原样透传给 `JsStreetNetwork` 构造函数(`build-osm2streets-qgis.js:77`)。默认:
@@ -109,7 +121,7 @@ cp config/examples/template.json config/areas/my-area.json
} }
``` ```
⚠️ **给了就整体替换,不做逐字段合并**`build-area.js:132``raw.osm2streets || {...}`)。 ⚠️ **给了就整体替换,不做逐字段合并**`raw.osm2streets || {...}`)。
只想改一个开关也必须把五个字段全写上,否则其余四个会退到 osm2streets 自己的默认值。 只想改一个开关也必须把五个字段全写上,否则其余四个会退到 osm2streets 自己的默认值。
### `blender` ### `blender`
@@ -131,6 +143,23 @@ cp config/examples/template.json config/areas/my-area.json
| `effort` | `80` | WebP 编码 effort范围 `0..100` | | `effort` | `80` | WebP 编码 effort范围 `0..100` |
| `meshopt` | `false` | 是否追加 `EXT_meshopt_compression`。开启前要单独验证 Cesium 兼容性 | | `meshopt` | `false` | 是否追加 `EXT_meshopt_compression`。开启前要单独验证 Cesium 兼容性 |
### `budget`
`budget``diagnose:area``check:area` 和 Cesium / compress manifest 共用的 GLB
限制。未配置时采用全局默认;用户字段统一用 MB 或整数,归一化后内部使用 bytes / counts
| 字段 | 默认 | 说明 |
|---|---:|---|
| `glbSizeMb` | `25` | GLB 文件总大小MB |
| `nodes` | `1000` | GLB node 数量 |
| `images` | `24` | GLB image 数量 |
| `triangles` | `250000` | node 实例化后的 render triangles不是唯一 mesh 的静态 triangles |
| `embeddedImageBytesMb` | `20` | GLB 内嵌图片字节MB |
| `reason` | `""` | 任一值高于默认时必填,记录区域例外原因 |
所有数值必须为正数,`nodes` / `images` / `triangles` 必须为正整数。收紧任何默认值不需要
`reason`;放宽任一默认值而没有非空 `reason` 会在配置归一化时失败。
### `outputs`(逃生舱) ### `outputs`(逃生舱)
默认全部从 `id` 推导为 `<outputRoot>/<id>/<fileStem>.<ext>`。需要定制时逐项覆盖: 默认全部从 `id` 推导为 `<outputRoot>/<id>/<fileStem>.<ext>`。需要定制时逐项覆盖:
@@ -146,10 +175,10 @@ cp config/examples/template.json config/areas/my-area.json
} }
``` ```
可覆盖的键(`build-area.js:87-102``areaDir``fileStem``geojsonDir``gpkg` 可覆盖的键(`scripts/lib/area-config.js``areaDir``fileStem``geojsonDir``gpkg`
`qgisProject``qgisPreview``blend``render``glb``metadata``cesiumPreview` `qgisProject``qgisPreview``blend``render``glb``metadata``cesiumPreview`
`compressedFileStem``compressedGlb``compressedMetadata``compressedCesiumPreview` `compressedFileStem``compressedGlb``compressedMetadata``compressedCesiumPreview`
`vehicleRoute``vehicleModel``pipelineDir` `vehicleRoute``vehicleModel``pipelineDir``stageManifestDir`
**优先改 `fileStem` 或 `areaDir`**——它们能一次性影响全部派生路径。逐个覆盖容易漏。 **优先改 `fileStem` 或 `areaDir`**——它们能一次性影响全部派生路径。逐个覆盖容易漏。
@@ -157,14 +186,16 @@ cp config/examples/template.json config/areas/my-area.json
## 加一个配置字段 ## 加一个配置字段
1. `normalizeAreaConfig``build-area.js:74`)里加进对应的分组,**用 `??` 不用 `||`** 1. `normalizeAreaConfig``scripts/lib/area-config.js`)里加进对应的分组,**用 `??` 不用 `||`**
`false` / `0` 可能是合法值) `false` / `0` 可能是合法值)
2. 只写两级 fallback`raw.<group>?.<key> ?? 默认值` 2. 只写两级 fallback`raw.<group>?.<key> ?? 默认值`
**不要**制造新的顶层平铺别名——那三级写法是历史兼容,不是模式 **不要**制造新的顶层平铺别名——那三级写法是历史兼容,不是模式
3. 若要传给低层脚本,加进 `writeDerivedConfig``:189``derivedConfig` 对象 3. 若要传给低层脚本,加进 `writeDerivedConfig` `derivedConfig` 对象
4. 若是数值,在消费侧加 `Number.isFinite` + 范围校验,**在任何副作用之前** 4. 若是数值,在消费侧加 `Number.isFinite` + 范围校验,**在任何副作用之前**
5. 更新 `config/examples/template.json` 5. 更新 `config/examples/template.json`
6. 更新本文档的字段表 6. 若字段影响区域质量门,确认 `diagnose:area``check:area` 和 stage manifest 共用同一
个评估 helper不能在入口脚本各自比较阈值
7. 更新本文档的字段表
若新字段产出新文件,同时在 `outputs` 里加一行路径推导。 若新字段产出新文件,同时在 `outputs` 里加一行路径推导。

View File

@@ -42,13 +42,14 @@ Blender 高度与线性颜色。两侧靠 `catalog.check_layers()` 对账集合
### 区域输出路径 ### 区域输出路径
输出路径只在 `scripts/build-area.js:74``normalizeAreaConfig()` 推导。 输出路径只在 `scripts/lib/area-config.js``normalizeAreaConfig()` 推导。
低层脚本读取 `_pipeline/osm2streets-qgis.config.json`,不要重新读取 `scripts/build-area.js``scripts/diagnose-area.js` 都必须通过 `readAreaConfig()` 读取
区域配置。低层脚本读取 `_pipeline/osm2streets-qgis.config.json`,不要重新读取
`config/areas/*.json` 或在阶段函数里现场拼路径。 `config/areas/*.json` 或在阶段函数里现场拼路径。
新增产物时,在 `normalizeAreaConfig``outputs` 里加一项,再按需写入 新增产物时,在 `area-config.js``outputs` 里加一项,再按需写入
`writeDerivedConfig()``build-area.js:189`。这样 `intermediates``reimport` `writeDerivedConfig()`。这样 `intermediates``reimport``blender``cesium`
`blender``cesium``preview` 仍然只通过磁盘产物耦合。 `preview` 和读-only 诊断仍然只通过磁盘产物耦合。
### 材质声明 ### 材质声明
@@ -65,18 +66,20 @@ Cesium 导出调色也属于同一个材质声明:新场景把 `catalog.MATERI
## 可接受的重复 ## 可接受的重复
### 份 `parseArgs` ### 份 `parseArgs`
`parseArgs` 现在重复在个独立入口: `parseArgs` 现在重复在个独立入口:
- `scripts/build-area.js:50` - `scripts/build-area.js:54`
- `scripts/build-osm2streets-qgis.js:153` - `scripts/build-osm2streets-qgis.js:153`
- `scripts/reimport-gpkg.js:93` - `scripts/reimport-gpkg.js:93`
- `scripts/compress-glb.js:16`
- `scripts/diagnose-area.js:17`
语义一致:`--kebab-case value``kebabCase: "value"`,无值 flag 变字符串 `"true"` 语义一致:`--kebab-case value``kebabCase: "value"`,无值 flag 变字符串 `"true"`
这份重复目前是可接受技术债,因为三个脚本都能独立运行。改其中一处解析语义时,不要顺手 这份重复目前是可接受技术债,因为这些脚本都能独立运行。改其中一处解析语义时,不要顺手
只改一份;要么保持三份一致,要么把"抽公共模块"作为独立重构并跑 parity 只改一份;要么保持全部入口一致,要么把"抽公共模块"作为独立重构并跑对应入口检查
### JS 与 Python 的图层颜色 ### JS 与 Python 的图层颜色
@@ -115,7 +118,7 @@ Cesium 导出调色也属于同一个材质声明:新场景把 `catalog.MATERI
- OSM XML → `osmassets/osm.py:parse_osm()` - OSM XML → `osmassets/osm.py:parse_osm()`
- 米制几何 → `osmassets/geom.py` - 米制几何 → `osmassets/geom.py`
- GeoJSON 场景合并 → `scene-layers.js:mergeScene(getCollection)` - GeoJSON 场景合并 → `scene-layers.js:mergeScene(getCollection)`
- 区域配置 → `build-area.js:normalizeAreaConfig()` - 区域配置 → `scripts/lib/area-config.js:normalizeAreaConfig()`
如果确实需要新解析器,把输入格式、容错语义和调用者写清楚,并给纯 Python 逻辑补测试。 如果确实需要新解析器,把输入格式、容错语义和调用者写清楚,并给纯 Python 逻辑补测试。
@@ -132,7 +135,7 @@ Cesium 导出调色也属于同一个材质声明:新场景把 `catalog.MATERI
不要因为代码相似就抽象: 不要因为代码相似就抽象:
- `parseArgs` 当前保持独立入口价值 - `parseArgs` 当前保持独立入口价值
- `ROAD_LAYERS``SCENE_LAYERS` 跨语言且承载不同字段 - `ROAD_LAYERS``SCENE_LAYERS` 跨语言且承载不同字段
- 每个要素模块各自调用 `clip_polygon` 是模块边界,不是可消除重复 - 每个要素模块各自调用 `clip_polygon` 是模块边界,不是可消除重复
@@ -156,6 +159,6 @@ Cesium 导出调色也属于同一个材质声明:新场景把 `catalog.MATERI
| 新增一份图层名列表 | 回到旧的四份同步,漏改静默错栈 | | 新增一份图层名列表 | 回到旧的四份同步,漏改静默错栈 |
| 把两套颜色表统一 | 破坏 QGIS 与 Blender 各自调过的视觉结果 | | 把两套颜色表统一 | 破坏 QGIS 与 Blender 各自调过的视觉结果 |
| 低层脚本直接读 `config/areas/*.json` | 两层配置边界失效 | | 低层脚本直接读 `config/areas/*.json` | 两层配置边界失效 |
| 只改一份 `parseArgs` 的语义 | 三个入口行为分裂 | | 只改一份 `parseArgs` 的语义 | 独立入口行为分裂 |
| 把要素模块裁剪逻辑挪到调用方 | 不同要素的越界处理开始漂移 | | 把要素模块裁剪逻辑挪到调用方 | 不同要素的越界处理开始漂移 |
| 只改 `export_cesium.py` 的旧回退表,不写 `MATERIALS[*]["cesium"]` | 新 `.blend` 不会携带 Cesium 导出契约 | | 只改 `export_cesium.py` 的旧回退表,不写 `MATERIALS[*]["cesium"]` | 新 `.blend` 不会携带 Cesium 导出契约 |

View File

@@ -11,6 +11,8 @@
``` ```
config/areas/*.json JSON 数据 config/areas/*.json JSON 数据
↓ ① ↓ ①
lib/area-config.js 区域配置归一化
build-area.js Node宿主机 build-area.js Node宿主机
↓ ② 派生配置 JSON ↓ ② 派生配置 JSON
build-osm2streets-qgis.js Node + osm2streets WASM build-osm2streets-qgis.js Node + osm2streets WASM
@@ -26,7 +28,7 @@ cesium-preview.js 浏览器
| # | 边界 | 常见问题 | | # | 边界 | 常见问题 |
|---|---|---| |---|---|---|
| ① | 用户配置 → 归一化 | `??` vs `\|\|`、相对路径、字段整体替换 | | ① | 用户配置 → 归一化 | `??` vs `\|\|`、相对路径、字段整体替换、绕开共享归一化 |
| ② | 两层配置 | 低层脚本读错配置源 | | ② | 两层配置 | 低层脚本读错配置源 |
| ③ | Node → 外部进程 | 环境变量缺失、退出码与信号、0 字节产物 | | ③ | Node → 外部进程 | 环境变量缺失、退出码与信号、0 字节产物 |
| ④ | 文件交换 | 图层集合/顺序漂移、精度丢失 | | ④ | 文件交换 | 图层集合/顺序漂移、精度丢失 |
@@ -81,6 +83,18 @@ cesium-preview.js 浏览器
## 本项目真实踩过的坑 ## 本项目真实踩过的坑
### 坑 0用原始 OSM 节点度数代替归一化路网拓扑
OSM way 的端点不一定在原始 XML 中有三个以上相连 wayosm2streets 可能把相邻 way
合并、切分或通过 `network.intersections[*].osm_ids` 表达路口。任何需要判断道路是否
进入路口的中间层逻辑,都必须优先使用已经生成的 normalized `network.json` 事实源,
原始节点度数只能作为没有 normalized network 的纯单元测试回退。
### 坑 0.1:普通 JSON 误走 FeatureCollection 写入器
`writeJson()` 的隐式契约是传入带 `features` 数组的图层集合;诊断 manifest、计数摘要等
普通对象必须用显式 `JSON.stringify` 写入,不能为了复用日志代码把它们塞进图层写入器。
### 坑 1同一份事实存了四份 ### 坑 1同一份事实存了四份
九个图层的顺序曾同时存在于 z_index 表、样式 JSON、QGIS 工程、README。 九个图层的顺序曾同时存在于 z_index 表、样式 JSON、QGIS 工程、README。

View File

@@ -23,15 +23,16 @@
- [ ]`blender/osmassets/catalog.py:28``ROAD_LAYERS``catalog.py:56``MATERIALS` - [ ]`blender/osmassets/catalog.py:28``ROAD_LAYERS``catalog.py:56``MATERIALS`
- [ ]`catalog.MATERIALS[*]["cesium"]``material["cesium_export"]` - [ ]`catalog.MATERIALS[*]["cesium"]``material["cesium_export"]`
`export_cesium.py` 的旧材质名回退表 `export_cesium.py` 的旧材质名回退表
- [ ]`build-area.js:74``normalizeAreaConfig()``config/examples/template.json` - [ ]`scripts/lib/area-config.js``normalizeAreaConfig()``config/examples/template.json`
- [ ] 改任何 `execFileSync` / `spawnSync` 调起的脚本或参数 - [ ] 改任何 `execFileSync` / `spawnSync` 调起的脚本或参数
- [ ]`SCENE_DONE` / `CESIUM_EXPORT_DONE` 的 stdout 标记 - [ ]`SCENE_DONE` / `CESIUM_EXPORT_DONE` 的 stdout 标记
### 读代码复用思考指南 ### 读代码复用思考指南
- [ ] 准备新增第二份或第三份图层、材质、配置字段枚举 - [ ] 准备新增第二份或第三份图层、材质、配置字段枚举
- [ ] 修改份重复的 `parseArgs` 之一: - [ ] 修改份重复的 `parseArgs` 之一:
`build-area.js:50``build-osm2streets-qgis.js:153``reimport-gpkg.js:93` `build-area.js:54``build-osm2streets-qgis.js:153``reimport-gpkg.js:93`
`compress-glb.js:16``diagnose-area.js:17`
- [ ] 多个要素模块都要做同一件几何预处理,比如 - [ ] 多个要素模块都要做同一件几何预处理,比如
`water.py:9``grass.py:9``scrub.py:8` 都先 `clip_polygon` `water.py:9``grass.py:9``scrub.py:8` 都先 `clip_polygon`
- [ ] 低层脚本想直接读取 `config/areas/*.json`,绕开派生配置 - [ ] 低层脚本想直接读取 `config/areas/*.json`,绕开派生配置
@@ -66,7 +67,7 @@ grep -rn "要改的值" scripts blender config
- 先看它有没有读到对应包的 index 和本目录指南 - 先看它有没有读到对应包的 index 和本目录指南
- 对任何"行为没变"的结论,要求说明是否需要 parity需要却没跑就是风险 - 对任何"行为没变"的结论,要求说明是否需要 parity需要却没跑就是风险
- 对任何"可以合并重复"的建议,先判断重复是不是刻意边界: - 对任何"可以合并重复"的建议,先判断重复是不是刻意边界:
`parseArgs` 目前是可接受技术债JS/Python 图层颜色则是刻意不同步 `parseArgs` 目前是可接受技术债JS/Python 图层颜色则是刻意不同步
- 对任何"加精度、加默认值、直接覆盖文件"的建议,回到真实代码注释验证; - 对任何"加精度、加默认值、直接覆盖文件"的建议,回到真实代码注释验证;
`reimport-gpkg.js:152-156``reimport-gpkg.js:11-13` 都是反直觉约束 `reimport-gpkg.js:152-156``reimport-gpkg.js:11-13` 都是反直觉约束

View File

@@ -9,7 +9,8 @@
本仓库不是前端应用,而是 **OSM → QGIS/Blender/Cesium 的资产生成管线** 本仓库不是前端应用,而是 **OSM → QGIS/Blender/Cesium 的资产生成管线**
- `scripts/build-area.js:74``normalizeAreaConfig()` 归一化区域配置并调度阶段 - `scripts/lib/area-config.js``normalizeAreaConfig()` 归一化区域配置和输出路径
- `scripts/build-area.js` 调度阶段并写入 GLB 相关 stage manifest
- `scripts/lib/scene-layers.js:15``SCENE_LAYERS` 是 osm2streets 九个 2D 图层的 JS 侧事实源 - `scripts/lib/scene-layers.js:15``SCENE_LAYERS` 是 osm2streets 九个 2D 图层的 JS 侧事实源
- `blender/osmassets/catalog.py:28``ROAD_LAYERS` 是 Blender 侧道路图层与材质顺序事实源 - `blender/osmassets/catalog.py:28``ROAD_LAYERS` 是 Blender 侧道路图层与材质顺序事实源
- `scripts/lib/cesium-preview.js:1` 是无构建步骤的浏览器预览 IIFE - `scripts/lib/cesium-preview.js:1` 是无构建步骤的浏览器预览 IIFE

View File

@@ -4,13 +4,16 @@
--- ---
## 三个入口脚本 ## 命令入口脚本
| 脚本 | 角色 | 入口方式 | | 脚本 | 角色 | 入口方式 |
|---|---|---| |---|---|---|
| `scripts/build-area.js` | **主入口**。读区域配置,按阶段调度 | `npm run build` / `build:area` | | `scripts/build-area.js` | **主入口**。读区域配置,按阶段调度 | `npm run build` / `build:area` |
| `scripts/build-osm2streets-qgis.js` | intermediates 阶段的实现 | 由 build-area 调起;`npm run build:qgis` 可单跑 | | `scripts/build-osm2streets-qgis.js` | intermediates 阶段的实现 | 由 build-area 调起;`npm run build:qgis` 可单跑 |
| `scripts/reimport-gpkg.js` | reimport 阶段的实现 | 由 build-area 调起 | | `scripts/reimport-gpkg.js` | reimport 阶段的实现 | 由 build-area 调起 |
| `scripts/diagnose-area.js` | 快速区域诊断只读配置、OSM 和已有产物 | `npm run diagnose:area` |
| `scripts/preflight-area.js` | OSM 构建前预检;只读配置和 OSM并写成功预检记录 | `npm run preflight:area` |
| `scripts/check-area.js` | 区域质量门;复用诊断事实源并给出 PASS/FAIL 退出码 | `npm run check:area` |
`scripts/parity.js``scripts/glb-digest.js` 是校验工具,不属于构建链,见 `scripts/parity.js``scripts/glb-digest.js` 是校验工具,不属于构建链,见
[产物一致性指南](../guides/artifact-parity-guide.md)。 [产物一致性指南](../guides/artifact-parity-guide.md)。
@@ -67,7 +70,8 @@ npm run compress:glb -- --input in.glb --output out.glb [options]
- `<fileStem>-compressed-webp768.glb` - `<fileStem>-compressed-webp768.glb`
- `<fileStem>-compressed-webp768.json` - `<fileStem>-compressed-webp768.json`
- `<fileStem>-compressed-webp768-cesium-preview.html` - `<fileStem>-compressed-webp768-cesium-preview.html`
- 伴生 metadata 的 `asset` `assets[0].url` 改为压缩 GLB 文件名 - 伴生 metadata 的 `asset` `id="main"` 资产 URL 改为压缩 GLB 文件名;其余资产(包括
`category="semantic"` 的 Cesium 分类检查 GLB必须原样保留。
- preview HTML 只替换 `window.OSM_ASSET_PREVIEW_CONFIG``glbName` / - preview HTML 只替换 `window.OSM_ASSET_PREVIEW_CONFIG``glbName` /
`metadataName` 和 loading 文案,不改 preview runtime。 `metadataName` 和 loading 文案,不改 preview runtime。
- 成功时 stdout 打印 `GLB_COMPRESS_DONE <json>`包含压缩前后大小、image / - 成功时 stdout 打印 `GLB_COMPRESS_DONE <json>`包含压缩前后大小、image /
@@ -97,6 +101,7 @@ npm run compress:glb -- --input in.glb --output out.glb [options]
### 6. Tests Required ### 6. Tests Required
- `node --check scripts/compress-glb.js` - `node --check scripts/compress-glb.js`
- `npm run test:compress-glb`:断言压缩 metadata 只替换主资产,不丢失语义资产。
- `node --check scripts/build-area.js` - `node --check scripts/build-area.js`
- 对目标区域跑一次 `npm run compress:glb -- ... --metadata --preview` - 对目标区域跑一次 `npm run compress:glb -- ... --metadata --preview`
- 对目标区域跑一次 `npm run build:area -- --stages compress` - 对目标区域跑一次 `npm run build:area -- --stages compress`
@@ -119,10 +124,647 @@ npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a-compressed-
--- ---
## OSM 预检命令
### 1. Scope / Trigger
`preflight:area` 用于手工编辑 OSM 后、运行 QGIS / Blender / Cesium 前的快速结构门槛。
它不是构建阶段,不进入 `build-area --stages`,不会启动外部工具或写场景产物。
### 2. Signatures
```bash
npm run preflight:area
npm run preflight:area -- --config config/areas/<area>.json
```
### 3. Contracts
- 通过 `readAreaConfig()` 读取区域配置,只读取配置文件和 OSM XML。
- OSM 解析和错误分类来自 `scripts/lib/area-diagnostics.js`,不得另建 XML 解析器。
- `<node>``<way>``<relation>``action="delete"` 时属于历史删除对象,解析统计和
几何检查必须忽略;否则旧 OSM 导出会把已删除的建筑误报为损坏面。
- 无效 bounds、任意 way 缺失 node、破损 building way、破损 building multipolygon、
不可解析为正米数的显式 building `height` 都是 error并以非零退出。
- 不填 `height` / `building:levels` 合法;无效 `building:levels` 是 warning。
- 仅在 error 为零时原子写 `<areaDir>/_pipeline/stages/preflight.manifest.json`。失败不覆盖
上次成功 manifest。manifest 记录 config / OSM 文件摘要、预检 summary、errors、warnings
和耗时。
- `diagnose:area` 显示该 manifest`check:area` 仅在 manifest 存在时检查其有效性和
freshness故采用前不会使旧区域质量门失败。
### 4. Validation & Error Matrix
| 条件 | 结果 |
|---|---|
| 无效 bounds、缺失 node、破损建筑几何、坏显式 height | 输出 error非零不覆盖旧 manifest |
| 无效 `building:levels` | 输出 warning仍可成功写 manifest |
| `action="delete"` 的节点 / way / relation | 忽略,不计入当前 OSM 或报错 |
| OSM / config 不存在 | `readAreaConfig()` 或文件读取抛错,非零 |
### 5. Good/Base/Bad Cases
- Good: 手工编辑后预检 PASS再运行 `build-area` 的任意重型阶段。
- Base: 未运行过预检的旧区域,`check:area` 仍可按既有规则通过。
- Bad: 预检失败后仍更新 manifest或把预检加入 `all` 导致每次构建修改验证记录。
### 6. Tests Required
- `npm run test:preflight`
- `npm run preflight:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- `npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- `npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
### 7. Wrong vs Correct
Wrong:
```js
const xml = fs.readFileSync(area.input, "utf8");
const missingNodes = xml.match(/<nd[^>]*ref=/g).length;
```
Correct:
```js
const osm = parseOsm(fs.readFileSync(area.input, "utf8"));
const preflight = analyzeOsmPreflight(osm);
```
## OSM 转向车道箭头
### 1. 范围与触发条件
区域配置设为 `turnLaneArrows.enabled: true` 时,`intermediates` 阶段会将已支持的
OSM `turn:lanes:forward` / `turn:lanes:backward` 标线追加到既有的
`lane_arrows_webscale` 图层。这只是既有图层的新增数据来源,不能新增 QGIS、Blender
或 Cesium 图层。
### 2. 调用形式
```json
{
"turnLaneArrows": { "enabled": true }
}
```
该字段由 `normalizeAreaConfig()` 归一化、`writeDerivedConfig()` 写入派生配置,
并由 `build-osm2streets-qgis.js` 消费。
### 3. 契约
- 已支持且已测试的转向为:`through``left``right``through;left`
`through;right``through;left;right`
- `assets/lane-icons/manifest.json` 是上游 CC0 来源、原始 SVG、镜像规则、箭杆轴线、
支持状态和测试状态的唯一事实源。未同时标记为 `supported``tested` 的素材不得写入
生产 GeoJSON。
- 自定义要素必须带有 `source="osm_turn_lanes"``osm_way_id``direction`
`lane_index``maneuver``source_asset``arrow_part` 与确定性的
`custom_arrow_id`
- `turn_lane_arrow_diagnostics.json` 记录 `generated` 和结构化跳过原因;它是普通 JSON
不是 FeatureCollection。
- 归一化后的 `network.intersections[*].osm_ids` 用来判断有效路口端点。只有单元测试中
没有 network 时,才可退回原始 OSM 节点度数。
- 可直接关联时,`Driving` 面通过 `osm_way_ids` 匹配,中心线取其相对两边的中点;闭合
四边形车道面也必须有效。JOSM 拆分产生的临时 ID 未被渲染面保留时,只能匹配距离近且
行驶方向一致的车道中心线,并按实际横向位置排序,不得单独依赖渲染器的 `index`
### 4. 校验与错误矩阵
| 条件 | 结果 |
|---|---|
| 功能关闭 | 不生成自定义要素;诊断原因为 `disabled` |
| 缺少车道数或无法判定路口端点 | 带原因跳过,不能猜测位置 |
| 转向未支持或未测试 | 以 `unsupported_or_untested_maneuver` 跳过 |
| SVG 缺少受支持路径命令或 manifest 锚点 | 写入 GeoJSON 前抛错 |
| 合法且已支持的 OSM 车道 | 每个 SVG 填充或扩展描边部件追加一个 Polygon |
### 5. 正常、基础与错误示例
- 正常:`through;right` 生成多个合法 Polygon 部件,但共享同一 OSM 溯源元组,直行杆
轴线与车道中心对齐。
- 基础:`enabled: false` 保持 osm2streets 原有箭头输出不变。
- 错误:将组合箭头写为一个 `MultiPolygon`。既有 QGIS 归一化器只接受逐个 Polygon。
### 6. 必需测试
- `npm run test:turn-lane-arrows`:覆盖转向归一化、正反向放置、确定性 ID、箭杆锚点、
关闭行为及未测试素材排除。
- 目标区域 `intermediates` 构建:确认 `lane_arrows_webscale.geojson` 含自定义 `source`
要素,且诊断可读。
- 用户确认样张后,运行完整目标区域构建和
`npm run check:area -- --config <area>`
### 7. 错误与正确写法
错误:
```js
if ((roadCounts.get(endpoint.id) || 0) < 3) return null;
writeJson(diagnosticsPath, diagnostics);
```
正确:
```js
const networkSaysIntersection = networkIntersectionNodes.has(endpoint.id);
if (!networkSaysIntersection && (roadCounts.get(endpoint.id) || 0) < 3) return null;
fs.writeFileSync(diagnosticsPath, `${JSON.stringify(diagnostics, null, 2)}\n`);
```
## 斑马线与停止线来源
### 1. 范围与触发条件
`intermediates` 阶段从带标记的 OSM `highway=crossing` 节点生成路口标线,并独占
`crosswalks``vehicle_stop_lines` 两个场景图层的数据来源。
### 2. 调用形式
```js
const crosswalkData = buildCrosswalks(osm, lanePolygons.features);
// split.vehicleStopLines === crosswalkData.stopLines
// split.crosswalks === crosswalkData.stripes
```
### 3. 契约
- `buildCrosswalks()` 是输出 `vehicle_stop_lines` 的唯一生产者。不得将 osm2streets
`lane_markings` 中类型为 `vehicle stop line` 的要素追加回来,否则会产生位置不同的
重复标线。
- 每个输出的过街进口固定生成 6 条 `crosswalk stripe`,以及恰好 1 条带有
`source="crosswalk"``crossing_node_id``vehicle stop line`
- 斑马线参考点先沿进口方向放在过街节点簇中心外 7 米处;再以相邻归一化 `Driving`
修正横向中心和方向:取局部车道中心锚点的平均值,并使用方向一致的车道切线。只有没有
匹配的渲染车道时,才回退到原始 OSM way 几何。
- 停止线从同一校正后的斑马线坐标系,以外侧固定 1.2 米偏移推导。osm2streets 的道路
横断面与原始 OSM 几何不同,斑马线与停止线也必须同步移动。
### 4. 校验与错误矩阵
| 条件 | 结果 |
|---|---|
| 有匹配渲染车道的标记过街 | 中心与方向跟随实际渲染道路横断面 |
| 没有匹配渲染车道的标记过街 | 安全回退到原始 OSM 方向向量 |
| 多个源节点投影到同一进口中心 | 写入斑马线和停止线前去重 |
| 存在 osm2streets 原生停止线 | 忽略,不写入输出图层 |
### 5. 正常、基础与错误示例
- 正常:两个四向路口产生 48 条斑马线条带和 8 条停止线,全部为
`source="crosswalk"`
- 基础:归一化 `Driving` 面之外的过街仍可按原始 way 方向渲染。
- 错误:因为原生 `vehicle stop line` 与人工线相距数米就保留它;这会重新引入第二个
生产者和视觉重复。
### 6. 必需测试
- 运行目标区域 `intermediates` 构建。
- 断言 `vehicle_stop_lines.geojson` 每个要素的 `crossing_node_id` 均不重复,且所有要素
都有 `source="crosswalk"`
- 检查四向路口的 QGIS 预览:条带必须横向居中于渲染道路宽度,每条停止线必须位于其
对应斑马线之后。
### 7. 错误与正确写法
错误:
```js
if (feature.properties.type === "vehicle stop line") {
out.vehicleStopLines.features.push(feature);
}
```
正确:
```js
const crosswalkData = buildCrosswalks(osm, lanePolygons.features);
out.vehicleStopLines = crosswalkData.stopLines;
// 原生 lane_markings 停止线不得复制到输出。
```
## 区域诊断命令
### 1. Scope / Trigger
`diagnose:area` 是手工编辑 OSM、排查 building relation、高度语义、植被数量和现有
产物体量时的快速读-only 检查。它不属于构建阶段,不进入 `--stages`,也不调用 QGIS、
Blender、Cesium 或 `gltf-transform`
### 2. Signatures
```bash
npm run diagnose:area
npm run diagnose:area -- --config config/areas/<area>.json
```
底层入口:
```bash
node scripts/diagnose-area.js [--config config/areas/<area>.json]
```
### 3. Contracts
- 不传 `--config` 时默认读取 `config/areas/nantaizi-lake-innovation-valley.json`
- 区域配置必须通过 `scripts/lib/area-config.js``readAreaConfig()` 归一化,和
`build-area.js` 共用同一套输出路径、压缩文件名、默认阶段和配置字段语义。
- 命令只读取:
- 区域配置
- 配置里的 OSM XML
- 已存在的 `area.outputs.*` 产物
- 已存在的默认 GLB通过 `scripts/glb-digest.js` 导出的 `digest()`
- 已存在的 stage manifests通过 `scripts/lib/stage-manifest.js`
- 输出为 text report包含 OSM bounds、节点/way/relation 数量、building way、
building multipolygon relation、显式 `height` / `building:levels`、植被数量、
产物存在性、stage manifest 状态、metadata 摘要、GLB size/counts/extensions 和 warnings。
- warnings 不改变退出码;配置缺失、输入 OSM 缺失、GLB 文件损坏这类无法继续读取的错误才
非零退出。
### 4. Validation & Error Matrix
| 条件 | 结果 |
|---|---|
| 配置文件不存在 | `Config file not found: <path>`,非零 |
| `id` / `input` 缺失 | `Missing config key: <key>`,非零 |
| OSM XML 不存在 | `Input OSM XML not found: <path>`,非零 |
| OSM 缺 `<bounds>` | warning不中断 |
| building multipolygon 缺 outer / unresolved way / open ring | warning不中断 |
| baseline 产物缺失 | warning不中断 |
| expected stage manifest 缺失 | warning不中断 |
| stage manifest inputs / outputs 与当前文件 sha/bytes 不一致 | stale warning不中断 |
| metadata JSON 损坏 | warning不中断 |
| GLB 存在但不是合法 GLB | 抛出 `glb-digest` 错误,非零 |
| GLB 超过保守预算 | warning不中断 |
### 5. Good/Base/Bad Cases
- Good: 手工改完 OSM 后先跑 `npm run diagnose:area -- --config ...`,确认 building
relation healthy再跑 `--stages blender,cesium`
- Base: 只生成过部分阶段时运行诊断,缺失产物以 warning 暴露,用来判断下一步该补哪个阶段。
- Bad: 把诊断做成 `build-area` 的新 stage它是读-only 工具,不应参与构建调度或产物生成。
### 6. Tests Required
- `node --check scripts/diagnose-area.js`
- `node --check scripts/lib/area-config.js`
- `node --check scripts/build-area.js`
- `node --check scripts/glb-digest.js`
- `npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- 如果抽取了配置归一化,至少跑一个轻量 `build-area` 阶段确认主入口仍能调度:
`npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages preview`
### 7. Wrong vs Correct
Wrong:
```js
const config = JSON.parse(fs.readFileSync("config/areas/a.json", "utf8"));
const glb = path.join(config.outputRoot, config.id, `${config.id}.glb`);
```
Correct:
```js
const { readAreaConfig } = require("./lib/area-config");
const area = readAreaConfig(configPath, { repoRoot });
const glb = area.outputs.glb;
```
---
## 区域质量门命令
### 1. Scope / Trigger
`check:area` 是提交或交付某个区域前的只读质量门。它复用 `scripts/lib/area-diagnostics.js`
里的 OSM、产物、metadata、stage manifest 和 GLB digest 检查,只负责把诊断结果分类为
failure / warning 并设置退出码。
它不属于构建阶段,不进入 `--stages`,也不调用 QGIS、Blender、Cesium、压缩或重建。
### 2. Signatures
```bash
npm run check:area
npm run check:area -- --config config/areas/<area>.json
```
底层入口:
```bash
node scripts/check-area.js [--config config/areas/<area>.json]
```
### 3. Contracts
- 不传 `--config` 时默认读取 `config/areas/nantaizi-lake-innovation-valley.json`
- 区域配置必须通过 `scripts/lib/area-config.js``readAreaConfig()` 归一化。
- OSM / artifacts / metadata / GLB / manifest 的解析和检查必须来自
`scripts/lib/area-diagnostics.js`,不要在 `check-area.js` 里复制解析逻辑。
- 输出为短 text report包含 area、config、output、GLB 摘要、PASS/FAIL、failure
计数和 warning 计数。
- 有 failure 时 `process.exitCode = 1`;只有 warning 或全绿时 `process.exitCode = 0`
### 4. Validation & Error Matrix
| 条件 | 结果 |
|---|---|
| 配置文件不存在 / `id` 缺失 / OSM 文件不存在 | 共享诊断抛错,非零 |
| OSM `<bounds>` 缺失或无效 | failure |
| building multipolygon 缺 outer / unresolved way / open ring | failure |
| building `height` 不能解析为正数米 | failure |
| Cesium GLB / metadata / preview 缺失或类型错误 | failure |
| metadata JSON 损坏 | failure |
| GLB size / nodes / images 超保守预算 | failure |
| GLB render triangles / embedded image bytes 超保守预算 | failure |
| expected stage manifest 缺失、损坏或 stale | failure |
| stage manifest warning 内容包含 budget exceeded | failure |
| QGIS preview 缺失 | warning不阻断 |
| GeoJSON / GeoPackage / QGIS project / Blend / render 缺失 | warning不阻断 |
| OSM way 引用缺失 node | warning |
| metadata 存在但没有 `assets[]` | warning |
### 5. Good/Base/Bad Cases
- Good: `diagnose:area` 用于调查完整细节,`check:area` 用于提交前给 CI/人一个明确退出码。
- Base: 旧区域只缺 QGIS preview 时,`check:area` 仍 PASS但报告 warning。
- Bad: `check:area` 内部重新拼输出路径或重新解析 GLB budget这会和诊断事实源漂移。
- Bad: 把 `check:area` 做成 `build-area --stages check`;质量门是只读命令,不是构建阶段。
### 6. Tests Required
- `node --check scripts/lib/area-diagnostics.js`
- `node --check scripts/diagnose-area.js`
- `node --check scripts/check-area.js`
- `npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- `npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- 用临时配置指向不存在的输出目录,确认 `node scripts/check-area.js --config <tmp>` 非零退出。
### 7. Wrong vs Correct
Wrong:
```js
const metadata = JSON.parse(fs.readFileSync("outputs/a/a.json", "utf8"));
const glb = glbDigest("outputs/a/a.glb");
```
Correct:
```js
const result = analyzeArea(configPath, { repoRoot });
const gate = classifyAreaQuality(result);
```
---
## Stage Manifest 契约
### 1. Scope / Trigger
Stage manifest 是区域构建阶段或独立验证通过后的机器可读产物契约。它覆盖预检记录与完整区域链:
`preflight``intermediates``reimport``blender``cesium``preview``compress`
它用于诊断产物是否存在、是否 stale、体量是否超预算以及后续 `check:area` /
增量构建判断。
### 2. Signatures
Manifest 路径固定:
```text
<areaDir>/_pipeline/stages/<stage>.manifest.json
```
当前会写:
```text
<areaDir>/_pipeline/stages/preflight.manifest.json
<areaDir>/_pipeline/stages/intermediates.manifest.json
<areaDir>/_pipeline/stages/reimport.manifest.json
<areaDir>/_pipeline/stages/blender.manifest.json
<areaDir>/_pipeline/stages/cesium.manifest.json
<areaDir>/_pipeline/stages/preview.manifest.json
<areaDir>/_pipeline/stages/compress.manifest.json
```
代码入口:
```js
const { writeStageManifest, readStageManifest } = require("./lib/stage-manifest");
```
### 3. Contracts
通用字段:
```json
{
"manifestVersion": 1,
"area": "nantaizi-lake-innovation-valley",
"stage": "cesium",
"status": "ok",
"config": "/absolute/config.json",
"startedAt": "2026-08-04T01:33:40.241Z",
"finishedAt": "2026-08-04T01:34:31.065Z",
"durationMs": 50824,
"inputs": {},
"outputs": {},
"summary": {},
"warnings": []
}
```
File records use this shape:
```json
{
"path": "/absolute/file.glb",
"bytes": 22381996,
"modifiedAt": "2026-08-04T01:34:30.646Z",
"sha256": "..."
}
```
`preflight` manifest:
- `inputs.config`
- `inputs.osm`
- `outputs` 固定为空对象(预检不拥有场景产物)
- `summary`bounds、node / way / relation、building way / multipolygon 与问题计数
- `errors` 固定为空数组(只有通过的预检会写 manifest`warnings`
- manifest 不存在时不算质量门失败;存在但 input 摘要漂移或 JSON 损坏时,`check:area` 失败
`intermediates` manifest:
- `inputs.config`
- `inputs.osm`
- `outputs.derivedConfig`
- `outputs.geojsonDir`
- `outputs.<nine scene layers>``outputs.osm2streets_scene.geojson`
`outputs.osm2streets_scene_style.json`
- `outputs.gpkg``outputs.qgisProject`、可选 `outputs.qgisPreview`
- `summary.geojson`:各 layer / merged scene 的 feature count
`reimport` manifest:
- `inputs.config`
- `inputs.derivedConfig`
- `inputs.gpkg`
- `outputs.geojsonDir`
- `outputs.<nine scene layers>`、merged scene/style
- `summary.geojson`
`intermediates``reimport` 是同一批 GeoJSON 的互斥 ownership
成功运行 `intermediates` 删除旧 `reimport.manifest.json`;成功运行 `reimport` 删除旧
`intermediates.manifest.json`。诊断只要求当前 ownership 路径的 manifest不要求两者同时存在。
`blender` manifest:
- `inputs.config`
- `inputs.osm`
- `inputs.geojsonDir``inputs.<nine scene layers>`、merged scene/style
- `outputs.blend`
- `outputs.render`
- `summary.geojson``summary.blendBytes``summary.renderBytes`
`cesium` manifest:
- `inputs.blend`
- `outputs.glb`
- `outputs.metadata`
- `summary.glb.fileBytes`
- `summary.glb.counts`
- `summary.glb.extensionsUsed`
- `summary.budget`effective limits、usage 和 violationswarnings 来自同一个预算评估
`cesium` 会调用 preview 生成函数,但 preview HTML / route / vehicle model 的 freshness
所有权属于独立 `preview` manifest。否则单跑 `--stages preview` 会把 Cesium manifest
错误判 stale。
### Preview Assembly Boundary
`build-area.js` 是 stage orchestrator它检查 GLB / metadata、按既有顺序写 route、vehicle、
runtime 与 HTML并写 preview manifest。预览内容实现不得回流到这里
- `lib/vehicle-route.js`OSM XML -> route JSON object保持坐标、排序、lane offset 和
`generatedAt` 语义。
- `lib/vehicle-model.js`-> preview vehicle glTF object保持 mesh/material 名称和内嵌
buffer layout。
- `lib/area-preview.js`:复制 browser runtime、生成 HTML、HTML/script JSON 转义和
preview summary。
这是保持 standalone `--stages preview` 与 preview manifest ownership 不变的边界;这些模块
不能读取 area config、写 stage manifest 或启动外部进程。
`preview` manifest:
- `inputs.config`
- `inputs.osm`
- `inputs.glb`
- `inputs.metadata`
- `inputs.previewCss`
- `inputs.previewJs`
- `outputs.cesiumPreview`
- `outputs.vehicleRoute`
- `outputs.vehicleModel`
- `summary.glbName``summary.metadataName``summary.routeSegments`
`compress` manifest:
- `inputs.glb`
- `inputs.metadata`
- `inputs.cesiumPreview`
- `outputs.compressedGlb`
- `outputs.compressedMetadata`
- `outputs.compressedCesiumPreview`
- `summary.sourceGlb`
- `summary.compressedGlb`
- `summary.options`
- `summary.compressionRatio`
- `summary.savedBytes`
- `summary.budget`:压缩 GLB 的 effective limits、usage 和 violations
Manifest files are written atomically via `*.tmp` then `renameSync`.
### 4. Validation & Error Matrix
| 条件 | 结果 |
|---|---|
| Stage command fails | 不写成功 manifest原 stage 错误继续抛出 |
| GLB digest 失败 | stage 失败manifest 不写 |
| Manifest output 文件缺失 | 写入时抛错;诊断时报告 stale/missing |
| Manifest JSON 无法解析 | 诊断报告 invalid warning |
| Manifest 记录的 input / output sha/bytes 与当前文件不同 | 诊断报告 stale warning |
| GLB size / nodes / images / render triangles / embedded image bytes 超保守预算 | manifest `warnings[]` 记录,诊断继续独立报警 |
| `intermediates` 后留有旧 reimport manifest | 成功后删除旧 reimport manifest |
| `reimport` 后留有旧 intermediates manifest | 成功后删除旧 intermediates manifest |
### 5. Good/Base/Bad Cases
- Good: `--stages intermediates` 成功后写 `intermediates.manifest.json`,诊断显示
`ok intermediates`
- Good: `--stages blender` 成功后写 `blender.manifest.json`,诊断显示 `ok blender`
- Good: `--stages cesium` 成功后写 `cesium.manifest.json``preview.manifest.json`
- Good: `--stages preview` 只更新 preview manifest不让 cesium manifest stale。
- Good: `--stages compress` 成功后写 `compress.manifest.json`summary 记录压缩比和节省字节。
- Base: 旧产物没有当前 ownership 路径的 manifest诊断显示 expected manifest missing
提示重跑对应阶段。
- Bad: 手工编辑 GLB 后不重跑阶段,诊断显示 manifest stale。
### 6. Tests Required
- `node --check scripts/lib/stage-manifest.js`
- `node --check scripts/build-area.js`
- `node --check scripts/diagnose-area.js`
- `node --check scripts/check-area.js`
- `node --check scripts/lib/vehicle-route.js`
- `node --check scripts/lib/vehicle-model.js`
- `node --check scripts/lib/area-preview.js`
- `npm run test:preview-assets`
- `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 diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- `npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- JSON parse generated manifests and assert `manifestVersion`, `stage`, `inputs`,
`outputs`, `summary`, and `warnings` fields exist.
### 7. Wrong vs Correct
Wrong:
```js
fs.writeFileSync(path.join(area.outputs.areaDir, "cesium.json"), JSON.stringify(data));
```
Correct:
```js
writeStageManifest(area, {
stage: "cesium",
status: "ok",
inputs,
outputs,
summary,
warnings,
});
```
---
## CLI 参数解析 ## CLI 参数解析
三个脚本各有一份**完全相同**`parseArgs` 独立入口脚本各有一份同语义`parseArgs`
`build-area.js:50``build-osm2streets-qgis.js:153``reimport-gpkg.js:93`
- `build-area.js:54`
- `build-osm2streets-qgis.js:153`
- `reimport-gpkg.js:93`
- `compress-glb.js:16`
- `diagnose-area.js:13`
- `check-area.js:13`
```js ```js
--kebab-case value { kebabCase: "value" } --kebab-case value { kebabCase: "value" }
@@ -136,9 +778,9 @@ npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a-compressed-
- **不做校验**。未知参数被静默收集,缺失参数由下游的 `requireText` / `Number.isFinite` - **不做校验**。未知参数被静默收集,缺失参数由下游的 `requireText` / `Number.isFinite`
报错 报错
> 这份重复是已知的、**当前被接受的**技术债:三个脚本要能各自独立运行,抽公共模块的 > 这份重复是已知的、**当前被接受的**技术债:这些脚本要能各自独立运行,抽公共模块的
> 收益还不抵引入一层依赖。改其中一份时**不要**顺手把另外两份重构掉——那是独立的决定 > 收益还不抵引入一层依赖。改其中一份解析语义时**不要**只改一份;要么保持全部一致
> 且会扩大 diff。真要抽取三处一起改并跑 parity > 要么把抽公共解析器作为独立重构并跑对应入口检查
--- ---
@@ -146,7 +788,7 @@ npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a-compressed-
``` ```
config/areas/<id>.json 用户写的区域配置(面向人) config/areas/<id>.json 用户写的区域配置(面向人)
build-area.js: normalizeAreaConfig() —— 补默认值、推导全部输出路径 lib/area-config.js: normalizeAreaConfig() —— 补默认值、推导全部输出路径
area内存中的归一化对象 area内存中的归一化对象
│ writeDerivedConfig() │ writeDerivedConfig()
@@ -158,14 +800,15 @@ build-osm2streets-qgis.js / reimport-gpkg.js
``` ```
**低层脚本从不读区域配置**,只读派生配置。这条边界让低层脚本能被独立调试,也让 **低层脚本从不读区域配置**,只读派生配置。这条边界让低层脚本能被独立调试,也让
"输出路径怎么算出来的"只有一处答案(`normalizeAreaConfig``build-area.js:74`)。 "输出路径怎么算出来的"只有一处答案(`scripts/lib/area-config.js`
`normalizeAreaConfig`)。
派生配置**落在 `_pipeline/` 目录里而不是临时目录**——构建失败时它还在,可以直接拿去 派生配置**落在 `_pipeline/` 目录里而不是临时目录**——构建失败时它还在,可以直接拿去
复现`writeDerivedConfig``build-area.js:189` 复现。
### 输出路径全部从 `id` 推导 ### 输出路径全部从 `id` 推导
`normalizeAreaConfig` 一次性算出 14 个输出路径(`build-area.js:87-102`,规则统一是 `normalizeAreaConfig` 一次性算出输出路径,规则统一是
`<outputRoot>/<id>/<fileStem>.<ext>``fileStem` 默认等于 `id` `<outputRoot>/<id>/<fileStem>.<ext>``fileStem` 默认等于 `id`
每一项都可以被 `outputs.*` 单独覆盖,写法固定: 每一项都可以被 `outputs.*` 单独覆盖,写法固定:
@@ -279,7 +922,7 @@ parity 校验依赖 stage 的 stdout 标记来判断阶段是否跑到(如 `SC
| 让 `reimport` / `preview` 能从配置文件默认开启 | 恢复动作变成常规行为 | | 让 `reimport` / `preview` 能从配置文件默认开启 | 恢复动作变成常规行为 |
| 新阶段忘了 `ensureFile` 前置校验 | 单跑时报底层堆栈而非人话 | | 新阶段忘了 `ensureFile` 前置校验 | 单跑时报底层堆栈而非人话 |
| 改 stage 的 stdout 标记 | 静默破坏 parity 契约 | | 改 stage 的 stdout 标记 | 静默破坏 parity 契约 |
| 顺手把`parseArgs` 合并 | 扩大 diff三个脚本的独立性是刻意的 | | 顺手把`parseArgs` 合并 | 扩大 diff独立入口的独立性是刻意的 |
--- ---

View File

@@ -13,6 +13,8 @@
| 改九个 osm2streets 图层(增/删/改顺序/改色) | [图层表](./layer-registry.md) ← **最容易出静默错误** | | 改九个 osm2streets 图层(增/删/改顺序/改色) | [图层表](./layer-registry.md) ← **最容易出静默错误** |
| 调 QGIS / GDAL / Blender 子进程 | [外部工具调用](./external-tools.md) | | 调 QGIS / GDAL / Blender 子进程 | [外部工具调用](./external-tools.md) |
| 加阶段、加 CLI 参数、改配置字段 | [CLI 与阶段](./cli-and-stages.md) | | 加阶段、加 CLI 参数、改配置字段 | [CLI 与阶段](./cli-and-stages.md) |
| 改区域诊断/质量门命令或共享区域配置归一化 | [CLI 与阶段](./cli-and-stages.md#区域诊断命令) 和 [质量门](./cli-and-stages.md#区域质量门命令) |
| 改 stage manifest 写入、读取或 stale 判断 | [CLI 与阶段](./cli-and-stages.md#stage-manifest-契约) |
| 改预览页生成 | [../preview/](../preview/index.md) | | 改预览页生成 | [../preview/](../preview/index.md) |
| 声称"纯重构,产物不变" | [产物一致性指南](../guides/artifact-parity-guide.md) | | 声称"纯重构,产物不变" | [产物一致性指南](../guides/artifact-parity-guide.md) |
@@ -23,7 +25,9 @@
``` ```
config/areas/<id>.json config/areas/<id>.json
build-area.js — normalizeAreaConfig() 推导全部输出路径 lib/area-config.js — normalizeAreaConfig() 推导全部输出路径
▼ build-area.js — 阶段调度
_pipeline/osm2streets-qgis.config.json (派生配置) _pipeline/osm2streets-qgis.config.json (派生配置)
├─[intermediates]─▶ build-osm2streets-qgis.js ├─[intermediates]─▶ build-osm2streets-qgis.js
@@ -34,22 +38,30 @@ config/areas/<id>.json
│ → osm2streets_scene.geojson + _scene_style.json │ → osm2streets_scene.geojson + _scene_style.json
│ → ogr2ogr 导入 <id>.gpkg │ → ogr2ogr 导入 <id>.gpkg
│ → QGIS 生成 .qgz + -preview.png │ → QGIS 生成 .qgz + -preview.png
│ → _pipeline/stages/intermediates.manifest.json
├─[reimport]──────▶ reimport-gpkg.js (反向,与 intermediates 互斥) ├─[reimport]──────▶ reimport-gpkg.js (反向,与 intermediates 互斥)
│ ogr2ogr 从 .gpkg 导出 → 校验 → 覆写 *.geojson │ ogr2ogr 从 .gpkg 导出 → 校验 → 覆写 *.geojson
│ → 重建 scene.geojson + scene_style.json │ → 重建 scene.geojson + scene_style.json
│ → _pipeline/stages/reimport.manifest.json
├─[blender]───────▶ Blender + blender/generate_scene.py ├─[blender]───────▶ Blender + blender/generate_scene.py
│ 读 .osm + osm2streets_web_out/ │ 读 .osm + osm2streets_web_out/
│ → <id>.blend + <id>.png │ → <id>.blend + <id>.png
│ → _pipeline/stages/blender.manifest.json
├─[cesium]────────▶ Blender + blender/export_cesium.py ├─[cesium]────────▶ Blender + blender/export_cesium.py
│ 读 .blend → <id>.glb + <id>.json │ 读 .blend → <id>.glb + <id>.json
│ → 并自动执行 preview │ → 并自动执行 preview
│ → _pipeline/stages/cesium.manifest.json
─[preview]───────▶ 生成 <id>-cesium-preview.html ─[preview]───────▶ 生成 <id>-cesium-preview.html
+ 拷贝 lib/cesium-preview.{js,css} + 拷贝 lib/cesium-preview.{js,css}
+ 车辆巡航路线与模型 + 车辆巡航路线与模型
│ → _pipeline/stages/preview.manifest.json
└─[compress]──────▶ 生成并列压缩 GLB / metadata / preview
→ _pipeline/stages/compress.manifest.json
``` ```
**阶段之间只通过磁盘产物耦合**不传内存状态。这是单跑任意阶段能work 的前提。 **阶段之间只通过磁盘产物耦合**不传内存状态。这是单跑任意阶段能work 的前提。
@@ -75,14 +87,22 @@ config/areas/<id>.json
| 文件 | 行数 | 职责 | | 文件 | 行数 | 职责 |
|---|---|---| |---|---|---|
| `build-area.js` | 774 | 主入口:配置归一化、阶段调度、Cesium 预览页与车辆巡航生成 | | `build-area.js` | 约 530 | 主入口:区域配置读取、阶段调度、preview 文件写入和 stage manifest ownership |
| `diagnose-area.js` | 36 | 快速诊断入口:调用共享 area diagnostics 并打印完整报告 |
| `check-area.js` | 74 | 区域质量门入口:调用共享 area diagnostics输出 PASS/FAIL 并设置退出码 |
| `lib/area-diagnostics.js` | 776 | 共享区域诊断事实源OSM、产物、metadata、stage manifest、GLB digest 和质量门分类 |
| `lib/area-config.js` | 135 | 区域配置归一化与输出路径推导,供 build / diagnose 复用 |
| `lib/stage-manifest.js` | 100 | stage manifest 路径、文件记录、GLB budget warning 和原子 JSON 写入 |
| `build-osm2streets-qgis.js` | 1468 | intermediatesosm2streets 解析、图层拆分、人行道转角合成、GeoPackage 与 QGIS 工程生成 | | `build-osm2streets-qgis.js` | 1468 | intermediatesosm2streets 解析、图层拆分、人行道转角合成、GeoPackage 与 QGIS 工程生成 |
| `reimport-gpkg.js` | 179 | reimportGeoPackage → GeoJSON 反向导出 | | `reimport-gpkg.js` | 179 | reimportGeoPackage → GeoJSON 反向导出 |
| `lib/scene-layers.js` | 164 | 九个图层的单一事实源 + 四个派生函数 | | `lib/scene-layers.js` | 164 | 九个图层的单一事实源 + 四个派生函数 |
| `lib/vehicle-route.js` | 约 180 | 从 OSM 提取确定性预览巡航路线 |
| `lib/vehicle-model.js` | 约 150 | 生成内嵌 buffer 的预览车辆 glTF |
| `lib/area-preview.js` | 约 110 | 复制 preview runtime、生成 HTML 与转义配置注入 |
| `lib/cesium-preview.js` / `.css` | 672 / 230 | 预览页运行时,见 [../preview/](../preview/index.md) | | `lib/cesium-preview.js` / `.css` | 672 / 230 | 预览页运行时,见 [../preview/](../preview/index.md) |
| `normalize-lane-arrows.py` | 182 | 合并 osm2streets 的三角网箭头(跑在 QGIS Python 里) | | `normalize-lane-arrows.py` | 182 | 合并 osm2streets 的三角网箭头(跑在 QGIS Python 里) |
| `parity.js` | 270 | 产物一致性校验驱动 | | `parity.js` | 270 | 产物一致性校验驱动 |
| `glb-digest.js` | 121 | GLB 结构摘要 | | `glb-digest.js` | 132 | GLB 结构摘要CLI、build manifest 和诊断脚本共用 |
--- ---

View File

@@ -1,6 +1,7 @@
# PreviewCesium 预览层 # PreviewCesium 预览层
> 覆盖 `scripts/lib/cesium-preview.js`672 行)与 `cesium-preview.css`230 行)。 > 覆盖浏览器运行时 `scripts/lib/cesium-preview.js`672 行)与
> `cesium-preview.css`230 行),以及 Node 侧的 `scripts/lib/area-preview.js`。
> 运行时:浏览器。全仓唯一的 DOM 环境。 > 运行时:浏览器。全仓唯一的 DOM 环境。
--- ---
@@ -19,9 +20,9 @@
``` ```
scripts/lib/cesium-preview.js ─── 原样 copyFileSync ──▶ outputs/<area>/cesium-preview.js scripts/lib/cesium-preview.js ─── 原样 copyFileSync ──▶ outputs/<area>/cesium-preview.js
scripts/lib/cesium-preview.css ─── 原样 copyFileSync ──▶ outputs/<area>/cesium-preview.css scripts/lib/cesium-preview.css ─── 原样 copyFileSync ──▶ outputs/<area>/cesium-preview.css
build-area.js:328-335 area-preview.js:writeCesiumPreviewSupportFiles
<area>-cesium-preview.html ─── 模板字符串生成 ────▶ 同目录 <area>-cesium-preview.html ─── 模板字符串生成 ────▶ 同目录
build-area.js:697 area-preview.js:cesiumPreviewHtml
``` ```
所以:**没有打包、没有转译、没有 npm 依赖、没有模块系统**。浏览器直接吃。 所以:**没有打包、没有转译、没有 npm 依赖、没有模块系统**。浏览器直接吃。
@@ -40,18 +41,22 @@ const config = window.OSM_ASSET_PREVIEW_CONFIG || {}; // :4
// config.areaId / .glbName / .metadataName / .routeName / .vehicleModelName // config.areaId / .glbName / .metadataName / .routeName / .vehicleModelName
``` ```
生成侧在 `build-area.js:697 cesiumPreviewHtml()`,注入时**必须转义** 生成侧在 `scripts/lib/area-preview.js``cesiumPreviewHtml()`,注入时**必须转义**
| 场景 | 用 | | 场景 | 用 |
|---|---| |---|---|
| HTML 文本/属性 | `escapeHtml()``build-area.js:759` | | HTML 文本/属性 | `escapeHtml()` |
| `<script>` 里的 JSON | `escapeScriptJson()``:767` | | `<script>` 里的 JSON | `escapeScriptJson()` |
`|| {}` 的兜底不能删——它让 JS 在没有配置块时也不至于在第一行就崩。 `|| {}` 的兜底不能删——它让 JS 在没有配置块时也不至于在第一行就崩。
**加一个新的可配置项**`cesiumPreviewHtml()` 里加进注入的 JSONJS 侧从 `config` 读, **加一个新的可配置项**`cesiumPreviewHtml()` 里加进注入的 JSONJS 侧从 `config` 读,
两边都要动。 两边都要动。
`build-area.js` 只保留 GLB / metadata 依赖检查、写入顺序和 preview manifest ownership
不要把 HTML 模板、runtime copy 或转义实现移回阶段调度器。路线 JSON 与车辆 glTF 分别由
`vehicle-route.js``vehicle-model.js` 生成,二者都是不启动外部工具的 Node 模块。
--- ---
## 加载流程 ## 加载流程
@@ -196,6 +201,73 @@ GLB 停留在**局部 ENU 坐标系**X 东、Y 北、Z 上),靠伴生 JSO
`scenePlacement(metadata)``:131`)负责这一步。**改动导出侧的坐标约定必须同步改这里。** `scenePlacement(metadata)``:131`)负责这一步。**改动导出侧的坐标约定必须同步改这里。**
## 语义检查资产
### 1. 范围与触发条件
`cesium` 阶段除完整主 GLB 外,会按 Blender 顶层集合导出可选检查资产:道路、建筑、
绿化与设施、水体。它们只服务于预览检查;主 GLB 仍是下游兼容基线,不能被替换。
### 2. 调用形式
不新增 CLI 参数。正常运行 Cesium 阶段即可:
```bash
npm run build:area -- --config config/areas/<area>.json --stages cesium
```
### 3. 契约
- `blender/export_cesium.py:SEMANTIC_ASSETS` 是集合名、稳定资产 ID 与展示名的唯一映射:
`03_Roads → roads``04_Buildings → buildings``02_Green + 05_Props → vegetation`
`01_Water → water`
- 每个有几何的类别额外写 `<stem>-<id>.glb`,且必须保留与主 GLB 相同的局部 ENU 坐标和
已处理的 Cesium 材质。
- metadata 的主资产保持 `id="main"``enabled=true`;辅助项设
`category="semantic"``enabled=false`,并提供 `id``label``type="model"``url`
- `build-area.js:semanticAssetRecords()` 必须验证 metadata 声明的每个语义文件存在后才写
Cesium manifest。
- 浏览器先加载非语义资产;只有点 `Inspect` 才加载辅助 GLB。检查模式必须隐藏主场景
返回 `Scene` 必须隐藏辅助模型,禁止两套几何重叠渲染。
- 没有 `category="semantic"` 的旧 metadata 仍按单资产预览打开,`Inspect` 按钮禁用。
### 4. 校验与错误矩阵
| 条件 | 结果 |
|---|---|
| 类别集合没有可导出 mesh | metadata 不声明该类别,预览不显示该开关 |
| metadata 声明语义资产但文件不存在 | Cesium 阶段失败,不能写成功 manifest |
| 辅助 GLB 浏览器加载失败 | 该开关禁用并写入诊断;主场景继续可用 |
| 旧 metadata 没有语义项 | 完整场景照常显示,`Inspect` 不可点击 |
### 5. 正常、基础与错误示例
- 正常:进入 `Inspect` 后道路、建筑、绿化与设施、水体全部显示,再单独取消任一类别。
- 基础:旧的只有 `main` 资产的 metadata 不展示分类控件,所有原有控制仍可用。
- 错误:主 GLB 和语义 GLB 同时可见,导致道路、建筑等重复渲染和闪烁。
### 6. 必需测试
- `node scripts/test-preview-assets.js`:断言生成页包含模式切换与语义开关挂载点。
- `node --check scripts/build-area.js``node --check scripts/lib/area-preview.js`
`node --check scripts/lib/cesium-preview.js`
- 目标区域运行 `--stages cesium`,确认 metadata 的语义 `assets` 与同名辅助 GLB 一一对应。
- 浏览器在桌面及窄屏分别切换 `Scene` / `Inspect`,确认不重叠且控制不溢出。
### 7. 错误与正确写法
错误:把辅助模型标为默认启用,页面加载时把它们与主 GLB 一起绘制。
```json
{ "id": "roads", "enabled": true, "category": "semantic" }
```
正确:默认关闭并在检查模式按需加载。
```json
{ "id": "roads", "enabled": false, "category": "semantic" }
```
--- ---
## 本地预览必须走 HTTP ## 本地预览必须走 HTTP
@@ -234,4 +306,5 @@ python3 -m http.server 8765
- [CLI 与阶段](../pipeline/cli-and-stages.md)`cesium` / `preview` 阶段如何生成这些文件 - [CLI 与阶段](../pipeline/cli-and-stages.md)`cesium` / `preview` 阶段如何生成这些文件
- [资产生成](../blender/asset-generation.md)GLB 里的材质为什么要单独调色 - [资产生成](../blender/asset-generation.md)GLB 里的材质为什么要单独调色
- [车辆连续路线](vehicle-routes.md):路线 JSON、转向选择与预览标签契约
- README「实验车辆巡航」节面向使用者的说明 - README「实验车辆巡航」节面向使用者的说明

View File

@@ -0,0 +1,91 @@
# 车辆连续路线
## 1. Scope / Trigger
适用于 `scripts/lib/vehicle-route.js` 生成的路线 JSON以及
`scripts/lib/cesium-preview.js` 对车辆巡航路线的读取与展示。
触发修改路线生成、OSM 转向标签解析、车辆选择菜单或路线 JSON 字段时。
路线仅用于 Cesium 验证预览,不构成交通仿真或法规级导航。
## 2. Signatures
```js
buildVehicleRoute(osmPath) => {
source, bounds, generatedAt, speedMetersPerSecond, loop,
routes, segments
}
allowedTurns(tags, direction) => Set<"left" | "through" | "right">
classifyConnection(incomingEdge, outgoingEdge) =>
"left" | "through" | "right" | "u_turn"
```
浏览器运行时调用 `addVehicleCruises(viewer, routeData, vehicleModelName)`;它首先读取
`routeData.routes`,仅在其不存在时回退到 `routeData.segments`
## 3. Contracts
- `routes` 是当前主字段;`segments` 必须是同一数组的兼容别名,供旧预览使用。
- 每个路线至少包含 `id``coordinates``centerlineCoordinates``lengthMeters`
`maneuvers``edgeIds``coordinates` 是右侧车道偏移后的闭合巡航轨迹。
-`oneway=yes`(及等价真值)的 way 只能按 OSM 原始方向生成 edge绝不能生成反向
`:backward` edge`oneway=-1` 仅允许反向 edge。
- 去程在路口按入边方向读取 `turn:lanes:forward``turn:lanes:backward`,只有标签中的
`left``through``right` 才是候选出口;无标签时允许这三类非 U-turn 动作。
- 返程是展示路线的原路回返,不以反向 `turn:lanes` 再次否决,但依旧不可逆行单行道。
- 路网没有闭环时,在去程和返程端点插入平滑调头曲线;不得在 way 端点或路口瞬移。
- 选择菜单使用 `#编号 · 长度 m · 左 N / 右 N / 直 N`,因为一条路线可跨越多个道路名称。
## 4. Validation & Error Matrix
| 条件 | 结果 |
|---|---|
| 缺少或无法读取 route JSON | 预览继续加载,只取消巡航控件 |
| `routes` 存在但为空 | 不回退到旧 `segments`;没有可播放车辆 |
| 可行驶 way 少于两个节点或不在区域范围 | 不生成 edge |
| 只存在反向单行可达路径 | 不生成违反单行限制的路线 |
| 路口夹角接近掉头 | 分类为 `u_turn`,不作为去程出口 |
| 候选路线不足五条 | 输出实际可用数量,预览按已有路线加载 |
## 5. Good/Base/Bad Cases
- 正常:树状道路网产生多条跨 way 往返路线,车辆经过左、右、直三种连接并在端点平滑掉头。
- 基础:旧 JSON 只有 `segments` 时,预览仍能创建车辆与 Follow 控制。
- 错误:对返程再次套用反向 `turn:lanes`,使原路返回在树状网络中被错误过滤。
## 6. Tests Required
- `node scripts/test-preview-assets.js`:断言路线闭合、端点调头、`turn:lanes` 拆分、左/右/直
分类、单行道不逆行,以及 `segments === routes`
- `node --check scripts/lib/vehicle-route.js`
`node --check scripts/lib/cesium-preview.js`:保证 Node 与浏览器直载脚本语法可用。
- 对目标区域运行 `npm run build:area -- --config config/areas/<area>.json --stages preview`,确认
`routes` 中存在左、右、直动作,且 Cesium 下拉标签显示编号、长度与动作统计。
## 7. Wrong vs Correct
错误:优先使用旧字段,导致新路线元数据无法被消费。
```js
const segments = routeData.segments || routeData.routes || [];
```
正确:新字段优先,旧字段仅作兼容回退。
```js
const routes = routeData.routes || routeData.segments || [];
```
错误:为使路线闭合而生成单行道路的反向 edge。
```js
edges.push(makeEdge(way, refs.reverse(), coords.reverse(), "backward"));
```
正确:单行仅保留其允许的方向,树状网络用端点调头闭合预览路线。
```js
if (oneway !== "-1") edges.push(makeEdge(way, refs, coords, "forward"));
if (!isOneWay(oneway)) edges.push(makeEdge(way, [...refs].reverse(), [...coords].reverse(), "backward"));
```

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,39 @@
# Area asset budgets design
## Boundaries
`scripts/lib/area-config.js` 归一化配置的 `budget``scripts/glb-digest.js` 承担 GLB
结构统计和 source/image 摘要;`scripts/lib/stage-manifest.js` 承担默认预算、覆盖合并、
判定和 warning 文本。`scripts/lib/area-diagnostics.js` 只消费这两个共享事实源,并向
diagnose/check 报告结果。
## Data Flow
```text
config budget + global defaults
-> normalizeAreaConfig()
GLB JSON -> digest() -> usage + source/image summaries
-> evaluateGlbBudget(digest, area.budget)
-> stage manifest summary/warnings
-> diagnose report / check failures
```
预算字段面向用户使用 `glbSizeMb`,内部归一化为 bytes其余计数为整数。有效预算对象应始终
完整,避免 consumers 自己回退默认值。
## Attribution
`digest()` 将每个 node 的 mesh primitives 转为 triangles并按有限、稳定的名称规则分组
building、foliage、roads、water、fountain、other。同一 mesh 被多个 node 引用时source
summary 统计实例化后的渲染 triangles它用于帧渲染复杂度而非 GLB 静态字节。图片条目从
`bufferViews[image.bufferView].byteLength` 读取 embedded bytes并按 bytes 排序。
## Compatibility
没有 `budget` 的区域仍使用当前全局 25 MB / 1000 nodes / 24 images并增加 triangles 和
embedded image bytes 的默认阈值。旧 stage manifest 可被读取;新字段只在后续 Cesium / compress
阶段写入。现有 `check:area` 保持超预算失败。
## Rollback
移除配置归一化、预算评估和扩展摘要即可回退到固定三项预算;旧 manifest 中的附加字段可忽略。

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,24 @@
# Area asset budgets implementation plan
1. 扩展 GLB digest 的 triangles、embedded image bytes、image/source summaries并添加纯 Node
fixture 测试。
2. 在 area config 归一化 `budget`,实现全局默认、数值范围与放宽例外原因校验;更新模板。
3. 在 stage-manifest 提供单一预算评估函数,并接入 Cesium/compress manifest 写入。
4. 让 area diagnostics 与质量门复用预算评估,改进 text report。
5. 添加临时配置或 mock digest 测试,证明超标阻断、放宽无理由拒绝和收紧允许。
6. 更新文档/spec/changelog运行只针对南台子的验证。
## Validation
```bash
node --check scripts/glb-digest.js
node --check scripts/lib/area-config.js
node --check scripts/lib/stage-manifest.js
node --check scripts/lib/area-diagnostics.js
npm run test:preflight
npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json
npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json
git diff --check
```
不跑 Blender/QGIS/Cesium除非现有 manifest 的 schema 验证无法在纯 Node 层覆盖。

View File

@@ -0,0 +1,59 @@
# Add area asset budgets
## Goal
让区域 GLB 的性能和体量约束成为可配置、可追踪、可定位的契约。手工改 OSM、资产或场景
生成逻辑后,使用者应能知道是否超出预算,以及节点、三角面和贴图主要来自哪一类资产。
## Confirmed Facts
- 原有全局保守阈值为 25 MB、1000 nodes、24 images本任务将它扩展为统一预算评估
新增 250k render triangles 与 20 MB embedded image bytes。
- `scripts/glb-digest.js` 已可无依赖读取 GLB JSON chunk且提供 node、mesh、material、
image、accessor 和 primitive vertex/index 计数。图片可通过 image 的 `bufferView` 计算嵌入
字节;三角面可由 primitive index count 计算。
- 现有南台子基线为 21.37 MB、933 nodes、117 meshes、18 images当前未超固定预算。
- GLB node/mesh 命名已经包含可用来源线索,如 `Building_*``Shapespark_*` 与道路/植被
资产名;可用于诊断“渲染实例和三角面”来源,但不能把交织二进制 buffer 的静态文件字节精确
分配给单一节点。
- `check:area` 已将超预算作为 failure新增策略必须保持这个默认阻断方向。
## Requirements
1. 区域配置新增可选 `budget` 对象,可覆盖全局默认的 GLB size、nodes、images、triangles
和 embedded image bytes 限额;未配置时保持现有默认约束,并为新增指标提供保守默认值。
2. 任何比全局默认更宽松的区域覆盖必须提供非空 `budget.reason`,使例外可审计;更严格覆盖
不需要理由。
3. 扩展共享 GLB digest提供总 triangles、embedded image bytes以及按稳定 node-name 分类的
source summary节点数、mesh instances、triangles。不虚构无法可靠归因的“每类文件字节”。
4. `diagnose:area` 显示实际值、有效预算、超标项,以及 top source / top embedded images。
5. `check:area` 复用相同预算计算,将任何超标项作为 failure不在入口脚本复制阈值或 GLB
解析逻辑。
6. Cesium/compress stage manifest 写入其对应 GLB 的 budget、usage 和 budget warnings
之后检查 stale 或回溯。
7. 更新区域配置模板、README、pipeline/config specs 和 changelog只验证南台子。
## Acceptance Criteria
- [x] `normalizeAreaConfig()` 对合法 budget 覆盖归一化,对负数、非数值或放宽默认值但缺
`reason` 的配置明确报错。
- [x] 南台子 diagnostics 显示有效预算、总 triangles、embedded image bytes、source summary
与 top images且当前默认 GLB 仍通过质量门。
- [x] 临时配置/fixture 将任一预算压低后,`check:area` 以非零退出并指出实际值和阈值。
- [x] stage manifest 包含和当前 GLB digest 对应的 budget/usage/warnings不重跑 Blender 或
Cesium 也可通过独立逻辑测试验证 payload。
- [x] 对 GLB digest 的新统计和预算分类有纯 Node 回归测试。
- [x] Node 语法检查、相关测试和 `git diff --check` 通过。
## Out Of Scope
- 不自动压缩、删减或替换资产;预算只报告和阻断。
- 不改变默认 GLB、Cesium preview 或 Blender 的视觉结果。
- 不尝试对共享/交织 GLB buffer 做不可靠的按节点文件字节归因。
- 不验证 `hanyang-block`
## Key Decisions
- 默认预算仍是阻断性质量门;区域只可通过显式配置覆盖。
- 放宽全局默认的例外必须在配置中写明原因;收紧预算不需理由。
- 归因以准确的节点实例/三角面和嵌入图片字节为准,不把静态 GLB 总字节伪分配给资产类别。

View File

@@ -0,0 +1,26 @@
{
"id": "add-area-asset-budgets",
"name": "add-area-asset-budgets",
"title": "Add area asset budgets",
"description": "Make GLB asset budgets configurable, visible in diagnostics, and enforceable by check:area.",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-04",
"completedAt": "2026-08-04",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,40 @@
# Add area diagnostics command
## Goal
Add a fast area diagnostics CLI for OSM structure and existing output artifact health.
## Requirements
- Add a fast diagnostics CLI for an area config.
- The command must not invoke QGIS, Blender, Cesium, or GLB compression.
- The report must scan the configured OSM XML for common manual-edit risks:
bounds presence, nodes, ways, relations, building ways, building multipolygon
relations, explicit heights, building levels, malformed relation members,
unresolved member ways, open rings, and basic vegetation counts.
- The report must inspect existing output artifacts when present:
GeoJSON directory, GeoPackage, QGIS project, `.blend`, render PNG, GLB,
metadata, Cesium preview, compressed GLB, compressed metadata, and compressed
preview.
- If a GLB exists, the report should reuse the existing GLB digest logic to
summarize size, nodes, meshes, materials, images, accessors, and extensions.
- The command should emit clear warnings for likely trouble, including missing
OSM bounds, malformed building multipolygons, missing expected artifacts, GLB
over a conservative size budget, high node count, and high texture count.
- Add an npm script and README usage.
## Acceptance Criteria
- [x] `npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
prints a useful text report without running heavy build stages.
- [x] The diagnostics report includes OSM structure, building relation health,
vegetation counts, output artifact status, and GLB digest summary.
- [x] Missing optional output artifacts are warnings, not hard failures.
- [x] Syntax checks pass for the changed Node scripts.
- [x] README documents the new diagnostic command.
## Notes
- Keep `prd.md` focused on requirements, constraints, and acceptance criteria.
- Lightweight tasks can remain PRD-only.
- For complex tasks, add `design.md` for technical design and `implement.md` for execution planning before `task.py start`.

View File

@@ -0,0 +1,26 @@
{
"id": "add-area-diagnostics",
"name": "add-area-diagnostics",
"title": "Add area diagnostics command",
"description": "Add a fast area diagnostics CLI for OSM structure and existing output artifact health.",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-04",
"completedAt": "2026-08-04",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,41 @@
# Add area quality gate
## Goal
Add a lightweight check:area command that turns diagnostics and manifests into pass/fail quality gates.
## Requirements
- Add a lightweight `check:area` npm command for an area quality gate.
- The command must not run QGIS, Blender, Cesium, compression, or rebuild stages.
- The command must reuse the same diagnostics source of truth used by
`diagnose:area`; avoid duplicating OSM parsing, artifact checks, GLB budget
checks, or manifest stale checks.
- The first quality gate must fail on:
- invalid or missing OSM bounds
- malformed building multipolygon relations
- missing baseline GLB / metadata / Cesium preview artifacts
- invalid metadata JSON
- GLB size / node / image budgets exceeded
- missing, invalid, or stale expected stage manifests
- Missing QGIS preview remains a warning in this first version.
- The command must print a concise pass/fail report and use exit code `0` for
pass and `1` for fail.
- README and pipeline spec must document command usage and failure policy.
## Acceptance Criteria
- [x] `npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
passes against current nantaizi outputs.
- [x] The check report includes pass/fail counts and any warning lines.
- [x] `check:area` exits non-zero for a deliberately impossible missing-output
config or equivalent controlled failure case.
- [x] `diagnose:area` still works after any shared diagnostics refactor.
- [x] Syntax checks pass for changed Node scripts.
- [x] README and Trellis pipeline spec document `check:area`.
## Notes
- Keep `prd.md` focused on requirements, constraints, and acceptance criteria.
- Lightweight tasks can remain PRD-only.
- For complex tasks, add `design.md` for technical design and `implement.md` for execution planning before `task.py start`.

View File

@@ -0,0 +1,26 @@
{
"id": "add-area-quality-gate",
"name": "add-area-quality-gate",
"title": "Add area quality gate",
"description": "Add a lightweight check:area command that turns diagnostics and manifests into pass/fail quality gates.",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-04",
"completedAt": "2026-08-04",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,77 @@
# Design
## Scope
The existing manifest infrastructure already supports file records, atomic JSON
write, digest summaries, and diagnostics-side freshness checks. This task
extends that same contract to the earlier and read-only/patched stages without
adding a new manifest format.
## Stage Boundaries
`intermediates`
- Written by `build-area.js` after `build-osm2streets-qgis.js` succeeds.
- Inputs: area config, source OSM XML.
- Outputs: derived config, GeoJSON directory, all scene-layer GeoJSON files,
merged scene/style files, GeoPackage, QGIS project, QGIS preview if present.
- Summary: GeoJSON feature counts by file.
`reimport`
- Written by `build-area.js` after `reimport-gpkg.js` succeeds.
- Inputs: area config, GeoPackage.
- Outputs: GeoJSON directory, all scene-layer GeoJSON files, merged scene/style
files.
- Summary: GeoJSON feature counts by file.
`blender`
- Written by `build-area.js` after Blender scene generation succeeds.
- Inputs: area config, source OSM XML, GeoJSON directory, all scene-layer
GeoJSON files, merged scene/style files.
- Outputs: `.blend`, render PNG.
- Summary: parsed `SCENE_DONE` payload when available, plus blend/render bytes.
`preview`
- Written by `build-area.js` after preview generation succeeds. Since `cesium`
automatically calls preview generation, this manifest should also be written
during `--stages cesium`.
- Inputs: area config, source OSM XML, GLB, metadata, preview runtime JS/CSS.
- Outputs: Cesium preview HTML, vehicle route JSON, vehicle model glTF.
- Summary: vehicle route count and asset filenames.
## Diagnostics
`scripts/lib/area-diagnostics.js` will extend `stageManifestStatus()` from two
hard-coded GLB stages to the full ordered stage list. Expected status should
derive from stage outputs:
- `intermediates`: expected when GeoJSON dir, GeoPackage, or QGIS project exists.
- `reimport`: expected when GeoJSON dir exists and `reimport.manifest.json`
already exists; it is an alternate path and should not be required for normal
intermediates builds.
- `blender`: expected when blend exists.
- `cesium`: expected when baseline GLB exists.
- `preview`: expected when Cesium preview exists.
- `compress`: expected when compressed GLB exists.
Only expected manifests are fatal in `check:area`. Optional absent manifests
remain ignored.
## Compatibility
- Existing `cesium.manifest.json` and `compress.manifest.json` remain valid.
- Existing outputs without new manifests will show missing expected manifests
until their stages are rerun.
- No stage should overwrite its primary outputs differently just to write a
manifest.
## Tradeoffs
- Manifest writing lives in `build-area.js` for this task because it already
owns normalized area paths and stage timing. Moving preview generation and
vehicle assets out of `build-area.js` is a later refactor.
- Reimport remains optional/alternate to avoid making normal `check:area` fail
for users who never use the reimport workflow.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,30 @@
# Implementation Plan
1. Read current manifest and stage code.
2. Add reusable helpers in `build-area.js` for:
- file record collection
- GeoJSON feature-count summaries
- manifest duration/timestamps
- writing `intermediates`, `reimport`, `blender`, and `preview` manifests
3. Wire manifest writes after successful stage functions only.
4. Extend `area-diagnostics.js` manifest expected/input/output definitions to
cover all stages.
5. Update README, changelog, and `.trellis/spec/pipeline/*`.
6. Validate with:
- `node --check scripts/build-area.js`
- `node --check scripts/lib/area-diagnostics.js`
- `node --check scripts/check-area.js`
- `node --check scripts/diagnose-area.js`
- `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 blender`
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages intermediates`
- `npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- `npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- controlled stale/missing manifest failure
- `git diff --check`
## Rollback
- Remove added manifest write calls and diagnostics stage definitions.
- Delete generated `_pipeline/stages/{intermediates,reimport,blender,preview}.manifest.json`
from outputs if needed; outputs are ignored and not committed.

View File

@@ -0,0 +1,50 @@
# Add full stage manifests
## Goal
Extend the stage manifest contract from GLB-only stages to the full area
pipeline so `diagnose:area` and `check:area` can reason about stage freshness
across intermediates, reimport, blender, preview, cesium, and compress.
## Requirements
- Write stage manifests for:
- `intermediates`
- `reimport`
- `blender`
- `preview`
- Preserve existing `cesium` and `compress` manifest behavior.
- Do not change default build outputs except for adding/updating manifest files
under `<areaDir>/_pipeline/stages/`.
- Stage manifests must record enough inputs and outputs to detect stale files
using the existing bytes/sha freshness logic.
- `diagnose:area` must display these additional manifests.
- `check:area` must fail when an expected full-pipeline manifest is missing,
invalid, or stale.
- `reimport` and `intermediates` remain mutually exclusive.
- Missing optional `compress` artifacts still must not make `check:area` fail.
- README, changelog, and Trellis pipeline spec must document the expanded
manifest coverage.
## Acceptance Criteria
- [x] Running `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages preview`
writes a fresh `preview.manifest.json`.
- [x] Running `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender`
writes a fresh `blender.manifest.json`.
- [x] Running `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages intermediates`
writes a fresh `intermediates.manifest.json`.
- [x] `diagnose:area` shows `intermediates`, `blender`, `cesium`, `preview`, and
optional `compress` manifest status.
- [x] `check:area` passes for current nantaizi outputs after the required
manifests are generated.
- [x] A controlled stale/missing manifest case makes `check:area` exit non-zero.
- [x] Syntax checks pass for changed Node scripts.
- [x] README, changelog, and pipeline spec describe full stage manifest coverage.
## Notes
- Validate only `nantaizi-lake-innovation-valley` unless explicitly asked.
- Ignore `hanyang-block`.
- This task is about pipeline contracts and diagnostics, not lane-arrow visual
behavior.

View File

@@ -0,0 +1,26 @@
{
"id": "add-full-stage-manifests",
"name": "add-full-stage-manifests",
"title": "Add full stage manifests",
"description": "Add read-only/verifiable stage manifests for intermediates, reimport, blender, and preview so diagnose/check can reason about full area pipeline freshness.",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-04",
"completedAt": "2026-08-04",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,30 @@
# OSM area preflight design
## Boundaries
`scripts/lib/area-diagnostics.js` 保持 OSM 解析和预检分类的唯一事实源。新增 `scripts/preflight-area.js` 只负责 CLI 参数、报告和退出码;它不读取产物,也不调起外部工具。现有 `diagnose-area.js``check-area.js` 不复制预检逻辑。
## Data Flow
```text
area config + OSM XML
-> analyzeOsmPreflight()/shared parseOsm()
-> { summary, errors, warnings }
-> preflight-area CLI report + exit code
-> successful preflight.manifest.json
-> diagnose/check manifest freshness reporting
```
预检 manifest 是一次成功验证的记录,不是输入所有权或构建依赖。它记录 config/OSM 文件摘要、错误/警告、统计和耗时;预检失败时不写入,也不删除旧 manifest以保留最后一次成功记录。
## Classification
Blocking error 仅限肯定会使场景几何或范围不可靠的 OSM 结构错误:无效 bounds、任意 way 的缺失 node 引用、破损的建筑 way / building multipolygon。没有 `height` / levels 不是错误(渲染已有默认值);填写但不可解析的显式 `height` 是错误。`building:levels` 的非正值或不可解析值将作为 warning除非现有 Blender 行为表明它会产生错误几何。
## Compatibility
现有 `diagnose:area` 保持全量只读报告;它添加 preflight manifest 一行。`check:area` 仅在 manifest 存在时才要求其有效和 fresh确保现有历史构建仍可通过。新命令的默认配置规则与其他 area CLI 一致。
## Rollback
删除 `preflight:area` CLI、预检 manifest 定义和 diagnostics 展示即可回到当前行为。预检 manifest 是可再生文件,保留或删除均不影响构建。

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,23 @@
# OSM area preflight implementation plan
1. 确认 Blender 对 `building:levels` 的解析语义;缺失 node 引用按已确认策略一律阻断。
2.`area-diagnostics.js` 抽取结构化 OSM 预检分类,并保持 diagnose/check 共用解析。
3. 新增 `preflight-area.js``package.json` 命令;成功后原子写 preflight manifest。
4. 扩展 stage manifest 状态与 `diagnose:area` / `check:area` 对 preflight 的显示、freshness 与 failure 规则。
5. 为纯 OSM 预检逻辑添加 Node fixture 或等价轻量测试,覆盖关键破损输入。
6. 更新 README、pipeline CLI spec、changelog运行质量检查。
## Validation
```bash
node --check scripts/lib/area-diagnostics.js
node --check scripts/preflight-area.js
node --check scripts/diagnose-area.js
node --check scripts/check-area.js
npm run preflight:area -- --config config/areas/nantaizi-lake-innovation-valley.json
npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json
npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json
git diff --check
```
只验证 `nantaizi-lake-innovation-valley`。破损 fixture 使用临时目录,不能改写桌面上的 OSM 或正式 outputs。

View File

@@ -0,0 +1,46 @@
# Add OSM area preflight
## Goal
在调用 QGIS、Blender 或 Cesium 前,快速验证区域 OSM 是否包含会导致建筑或场景几何错误的人工编辑问题。使用者应得到明确的通过/失败结果和可定位的问题列表,而不必先跑重型构建阶段。
## Confirmed Facts
- `scripts/lib/area-diagnostics.js` 已是区域配置和 OSM 解析的事实源;它已覆盖 bounds、building multipolygon、缺失 node 引用及高度标签,并被 `diagnose:area``check:area` 共同复用。
- `diagnose:area` 是全文诊断包含既有产物、GLB 与 manifest它不适合成为编辑 OSM 后的轻量独立门槛。
- 当前 `nantaizi-lake-innovation-valley` 是唯一验证目标。忽略 `action=delete` 的历史对象后,其 OSM 有 1 个健康的建筑 multipolygon 和 23 个有效显式高度标签。
- 全阶段 manifest 已存在;新预检不应被实现为 `build-area --stages` 的构建阶段。
## Requirements
1. 新增 `npm run preflight:area -- --config config/areas/<area>.json`,只读取区域配置和 OSM XML不得调用 QGIS、Blender、Cesium、压缩或写入场景产物。
2. 从共享 diagnostics 模块提供结构化 OSM 预检结果,避免新增第二套 XML/建筑关系解析。
3. 预检应检查:有效 bounds、way 引用的 node、building way 闭合性和最小节点数、建筑 multipolygon 的 outer/inner member、成员存在性、可拼接闭环以及 building `height` / `building:levels` 的基本可解析性。
4. 命令输出简短摘要、错误和警告;存在 blocking error 时以非零退出。
5. 成功预检写 `<areaDir>/_pipeline/stages/preflight.manifest.json`,记录配置和 OSM 输入的文件摘要、预检摘要、errors 与 warnings。失败预检不得覆盖此前成功 manifest。
6. `diagnose:area` 显示预检 manifest 的状态;`check:area` 仅在该 manifest 已存在时检查其新鲜度和有效性,避免使历史区域在首次采用前立即失败。
7. README 和 Pipeline spec 记录命令、退出码与 manifest 所有权。
## Acceptance Criteria
- [x] 当前南台子 OSM 上 `npm run preflight:area -- --config config/areas/nantaizi-lake-innovation-valley.json` 输出 PASS、写入 fresh `preflight.manifest.json`,且不触发重型工具。
- [x] 预检报告包含 buildings、multipolygon、bounds 和几何健康统计。
- [x] 通过临时 OSM fixture 验证:缺失/无效 bounds、缺失 node、未闭合 building way、破损 building multipolygon、无效显式 height 都能得到定位明确的 blocking error。
- [x] `diagnose:area` 显示 preflight manifest`check:area` 对存在但 stale/invalid 的 preflight manifest 失败。
- [x] 原有 `diagnose:area``check:area` 继续通过当前南台子区域。
- [x] 修改脚本通过 Node 语法检查和 `git diff --check`
## Out Of Scope
- 不修复 OSM 文件,不自动重组关系或补齐标签。
- 不检查道路拓扑、转向箭头渲染、osm2streets 的语义兼容性,或任意非建筑面是否合法。
- 不把预检加入 `build-area` 的默认阶段,也不要求未运行过预检的历史产物立即失败。
- 不验证 `hanyang-block`
## Key Decisions
- 缺失 node 引用一律作为 blocking error。它表示 OSM 引用完整性已损坏,即使当前出现在非建筑 way也不将问题带入后续构建。
## Notes
- 这是跨 CLI、OSM 解析、质量门和 manifest 的改动,按复杂任务处理,需要 design 和 implement 工件后再开始实现。

View File

@@ -0,0 +1,26 @@
{
"id": "add-osm-area-preflight",
"name": "add-osm-area-preflight",
"title": "Add OSM area preflight",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-04",
"completedAt": "2026-08-04",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1,4 @@
{"file":".trellis/spec/pipeline/index.md","reason":"Defines the expected intermediates artifacts and dependency boundaries."}
{"file":".trellis/spec/pipeline/layer-registry.md","reason":"Requires preserving existing lane-arrow layer collection and order across QGIS and Blender."}
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Defines quality-gate and target-area validation expectations."}
{"file":".trellis/tasks/08-04-add-osm-turn-lane-arrows/research/lane-icons.md","reason":"Defines the source provenance and assertion that untested assets cannot be mapped."}

View File

@@ -0,0 +1,74 @@
# Design: OSM turn-lane arrows
## Boundaries
- `scripts/build-osm2streets-qgis.js` remains the intermediates owner. It will
append generated custom features to the existing in-memory `laneArrows`
collection before it is written as `lane_arrows_webscale.geojson`.
- No render layer, QGIS symbol, Blender material, or Cesium export contract
changes. Existing `lane_arrows_webscale` styling and catalog ownership apply
to generated features automatically.
- Vendored upstream artwork lives under a dedicated repository-local asset
directory with its CC0 license and a machine-readable manifest. The manifest
is the source of truth for upstream path, source revision, local path,
support status, and test status.
## Asset Contract
- Import every arrow SVG from `openstreetmap/lane-icons/docs/` that represents
an arrow. Preserve the source bytes and retain `LICENSE.md` with the assets.
- Mark only `through`, `left`, `right`, `through;left`, and `through;right` as
`supported` and `tested` in the manifest. All remaining imported arrows are
`unsupported` and `untested`; no production parser may select them.
- The upstream repository has left-side examples only. Derive `right` and
`through;right` through a deterministic horizontal mirror of the matching
left-side geometry, recording `derived_from` in the manifest.
## Data Flow
1. Parse OSM ways once using the existing OSM parser.
2. Read `turn:lanes:forward` / `turn:lanes:backward`, split lane declarations
by `|`, and map only the five supported normalized maneuvers to manifest
entries.
3. For each valid lane near a determinable intersection endpoint, calculate a
stable lane center and road-local axis; transform the selected local arrow
polygon into WGS84 coordinates.
4. Add a GeoJSON feature with existing render-layer behavior and explicit
provenance: custom source, OSM way id, direction, lane index, maneuver,
source asset, and deterministic feature id/order.
5. Record a structured skipped item when tags, lane geometry, or endpoint
classification are insufficient. Do not invent a fallback location.
6. Existing normalization, scene merge, GeoPackage import, Blender assembly,
and Cesium export consume the expanded collection unchanged.
## Geometry and Compatibility
- Parse only the vendored SVG subset used by supported assets at build time:
`line` elements and `path` commands are converted into local meter-scale
polygons, with cubic curves sampled and SVG strokes expanded into outlines.
The source SVG is the sole geometry authority; no hand-drawn replacement
template is permitted. This stays dependency-free because the supported
upstream syntax is intentionally small and covered by Node tests.
- Use the upstream Standard Arrow shape as the geometry basis, but let the
existing QGIS fill and outline preserve the project's visual language.
- Preserve the existing osm2streets-generated arrow features. Custom features
are additive and have provenance that distinguishes them from upstream
osm2streets output.
- Deterministic ordering is by way id, travel direction, and lane index.
- Each asset manifest entry defines the source SVG's shaft-axis `anchor_x`.
Placement aligns that axis, rather than the SVG viewBox center, with the
lane centerline. Mirrored right-side variants mirror around the same axis.
- Upstream SVG units are converted at `0.10 m` per unit. This calibrates their
local extent against the existing approximately 1.4 m osm2streets arrows;
do not use SVG display-pixel scale as road-marking scale.
## Risks and Rollback
- OSM lane counts, one-way semantics, or endpoint topology can be incomplete.
These cases remain skipped with diagnostics rather than creating misleading
markings.
- Asset import is self-contained and CC0. Rollback removes the custom feature
generation and local asset directory; the existing arrow layer remains
unchanged.
- User approval of the two initial visual samples is required before the
feature is enabled in production output.

View File

@@ -0,0 +1,4 @@
{"file":".trellis/spec/pipeline/index.md","reason":"Defines the intermediates data flow, CommonJS conventions, and existing layer ownership."}
{"file":".trellis/spec/pipeline/layer-registry.md","reason":"The feature adds a source to an existing layer and must preserve the cross-language layer contract."}
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Defines stage ownership and target-area validation commands."}
{"file":".trellis/tasks/08-04-add-osm-turn-lane-arrows/research/lane-icons.md","reason":"Records the selected upstream SVG source, CC0 provenance, and supported-versus-untested asset policy."}

View File

@@ -0,0 +1,39 @@
# Implementation Plan: OSM turn-lane arrows
## Steps
1. Add the complete lane-icons arrow asset set, upstream revision metadata,
CC0 license text, and a manifest with the five supported/tested entries and
explicit untested status for all other entries.
2. Add a small CommonJS helper for asset-manifest loading, source-SVG path and
stroke conversion, maneuver parsing, road-local placement, and diagnostic
skip records. Keep it dependency-free and separately testable.
3. Wire the helper into the intermediates build so custom features are merged
into `laneArrows` before the existing GeoJSON write and normalization path.
4. Add Node tests for the five supported maneuvers, forward/backward endpoint
choice, lane index placement, deterministic order/id, and every required
skip reason. Assert that untested manifest entries cannot be selected.
5. Generate `right` and `through;right` inspection samples from the exact
local templates and request user confirmation. Do not enable custom output
until this gate is met.
6. Run the focused tests, syntax checks, the Nantaizi intermediates build, the
full Nantaizi chain, and `check:area`. Compare against a turn-tag-removed
control to prove an observable GeoJSON difference.
## Validation
- `node --check` for changed CommonJS scripts.
- New pure Node test script(s), invoked directly or through a package script.
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages intermediates`
- Full target-area pipeline after visual approval.
- `npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
## Review Gates
- No generated custom feature may use an asset whose manifest status is not
both supported and tested.
- Samples must show the same fill/outline treatment as the existing lane-arrow
layer.
- Keep the existing layer registry unchanged; this is a new source for an
existing layer, not a new layer.
- Do not commit without the user's explicit confirmation.

View File

@@ -0,0 +1,63 @@
# Add OSM turn lane arrows
## Goal
让 OSM `turn:lanes:forward` / `turn:lanes:backward` 在现有 QGIS -> Blender -> Cesium
道路链中生成可辨识的 lane arrow同时保持与 osm2streets 既有道路标线相同的视觉语言。
## Confirmed Facts
- 当前 `osm2streets-js-node@0.1.4` 生成 lane arrow但纯内存移除 way `98607123`
`98607128``98607147``turn:lanes:*`593 个原始箭头的 JSON 内容逐字一致;
因此该依赖不消费这些 OSM 标签。
- 当前南台子完整链已经通过:中间层落盘 580 个归一化箭头Blender 装配 397 个,
Cesium / preview / compress manifest 均 fresh`check:area` PASS。
- 现有 `lane_arrows_webscale` 已是 QGIS、Blender、Cesium 共用图层,样式为浅色填充
`#fffff6`、深色描边 `#2b2b28`,正常外轮廓为七顶点箭头。
- `turn:lanes:forward=right``turn:lanes:backward=through;right` 等标准标签已存在于
南台子 OSM。
- `openstreetmap/lane-icons` 是 OSM 官方组织发布的 CC0 SVG 图标集;其 `DESIGN.md`
以 MUTCD Standard Arrow 为基准,并规定组合转向共享箭杆、箭头数量增加时缩小箭头。
仓库只提供左侧转向示例,右转及 `through;right` 需在几何层对其镜像,不能直接作为
当前 GeoJSON polygon 图层输入。
- 用户已决定将该库中首版需要的转向图标作为本仓库维护的源素材,而不是只作为视觉参考。
## Requirements
1. 在写入 `lane_arrows_webscale.geojson` 前,从 OSM 标准 `turn:lanes:*` 生成自定义箭头
polygon并沿用既有图层、材质、QGIS/Blender/Cesium 路径。
2. 支持首版实际需要的 `through``left``right``through;left``through;right`
同一 OSM 输入必须产生确定性结果。
3. 自定义箭头必须使用现有道路标线的视觉语言,不新建一套颜色、描边或图标风格。
4. 在 OSM way 进入路口的一端、按 forward/backward 和 lane index 放置;车道配置不完整或
端点不是可判定路口时给出可诊断的跳过原因。
5. 首先完成并获得用户对 `right``through;right` 轮廓样张的确认,之后才能将其接入
GeoJSON / Blender 导出。
6. 未经用户确认,不提交 Git。
7.`openstreetmap/lane-icons` 的全部箭头 SVG 和 CC0 许可证说明纳入本仓库维护;
右转变体由左转源图镜像生成。生成的 polygon 仍由本仓库维护,并继续使用
`lane_arrows_webscale` 的既有填充与描边样式。
8. 素材清单必须逐项标记验证状态:仅 `through``left``right``through;left`
`through;right` 在首版具备 OSM 映射与自动化测试;其他已收录箭头明确标为未测试,
且不得自动参与生产 GeoJSON 输出。
## Acceptance Criteria
- [ ] 用户确认箭头样张的轮廓与视觉语言。
- [ ] 目标 OSM 标签导致 lane-arrow GeoJSON 与移除这些标签的对照产生可观察差异。
- [ ] 自定义 arrow properties 标识来源和 OSM way / direction / lane便于诊断。
- [ ] 纯 Node 测试覆盖标签解析、forward/backward、lane placement、组合转向和幂等排序。
- [ ] 本地素材清单包含所有上游箭头,逐项标记 CC0 来源、支持状态和测试状态;未测试条目
不会参与生产映射。
- [ ] 仅南台子完整链和质量门验证通过。
- [ ] 无 Git 提交,直到用户明确确认。
## Out Of Scope
- 修改 osm2streets 依赖、道路改为 Cesium 直渲染、增加新的道路渲染图层。
- 支持首版范围之外的 OSM maneuver 词汇、匝道标线、文字或自行车专用箭头。
## Delivery Gate
在自定义箭头接入生产 GeoJSON / Blender 导出前,用户必须确认 `right`
`through;right` 样张的轮廓和视觉语言。

View File

@@ -0,0 +1,40 @@
# lane-icons research
Source: <https://github.com/openstreetmap/lane-icons>, inspected 2026-08-04.
## Provenance
- Owner: `openstreetmap` GitHub organization.
- Repository tree revision: `db3cab321da93a757c2cf3cdb4f1f1db6b1a091c` (`master`).
- License: `LICENSE.md` is CC0 1.0 Universal. Keep that text adjacent to any
vendored source assets.
## Relevant Contents
- `docs/through.svg`
- `docs/left.svg`
- `docs/left-through.svg`
- `docs/slight_left.svg`
- `docs/slight_left-through.svg`
- `docs/sharp_left.svg`
- `docs/sharp_left-through.svg`
- `docs/reverse_left.svg`
- `docs/reverse_left-through.svg`
- `docs/reverse_left-left-slight_left-through.svg`
- `docs/left-slight_left-through.svg`
## Design Rules
`DESIGN.md` bases arrows on the MUTCD Standard Arrow. It specifies a shared
shaft for compound arrows, smooth curves, aligned arrow bottoms, and reduced
arrowhead scale as the number of directions grows. The upstream SVGs are
single-color and stroke-free; our existing render layer supplies the project
fill and outline treatment.
## Adoption Decision
Vendor all listed arrow SVGs with provenance and CC0 license text. The
production mapping is initially restricted to `through`, `left`, `right`,
`through;left`, and `through;right`. Right-side variants are deterministic
mirrors of upstream left-side assets. Every other vendored arrow remains
explicitly untested and unavailable to the production mapper.

View File

@@ -0,0 +1,26 @@
{
"id": "add-osm-turn-lane-arrows",
"name": "add-osm-turn-lane-arrows",
"title": "Add OSM turn lane arrows",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-04",
"completedAt": "2026-08-05",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,41 @@
# Add stage manifests
## Goal
Add first-version machine-readable stage manifests for build outputs and diagnostics.
## Requirements
- Add a first-version stage manifest contract for build pipeline outputs.
- Manifest files must live under the area `_pipeline/` directory and be JSON.
- The first implementation must cover:
- `cesium` stage: record blend input, GLB / metadata / preview outputs, GLB
digest summary, duration, and warnings.
- `compress` stage: record source GLB / metadata / preview inputs, compressed
GLB / metadata / preview outputs, compression summary, duration, and
warnings.
- Manifest writing must not replace or rename existing baseline artifacts.
- Diagnostics must report discovered stage manifest status so stale/missing
stage contracts become visible.
- Shared helpers should prevent duplicate path/stat/hash/manifest formatting
rules across scripts.
- README and pipeline spec must document the manifest location and contract.
## Acceptance Criteria
- [x] Running `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium`
writes a `cesium` manifest under `_pipeline`.
- [x] Running `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages compress`
writes a `compress` manifest under `_pipeline`.
- [x] Manifest JSON records input/output file stats and machine-readable summary
counts for GLB-producing stages.
- [x] `npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
displays stage manifest status.
- [x] Syntax checks pass for changed Node scripts.
- [x] README and Trellis pipeline spec document the new contract.
## Notes
- Keep `prd.md` focused on requirements, constraints, and acceptance criteria.
- Lightweight tasks can remain PRD-only.
- For complex tasks, add `design.md` for technical design and `implement.md` for execution planning before `task.py start`.

View File

@@ -0,0 +1,26 @@
{
"id": "add-stage-manifests",
"name": "add-stage-manifests",
"title": "Add stage manifests",
"description": "Add first-version machine-readable stage manifests for build outputs and diagnostics.",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-04",
"completedAt": "2026-08-04",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,24 @@
# Split area build orchestration design
## Module Boundaries
```text
scripts/lib/vehicle-route.js OSM XML -> preview route object
scripts/lib/vehicle-model.js -> vehicle glTF object
scripts/lib/area-preview.js HTML/template + runtime file copy + preview summary
scripts/build-area.js dependency checks, writes, stage timing, manifest ownership
```
`vehicle-route.js` and `vehicle-model.js` are pure Node modules. `area-preview.js` may use Node filesystem and path, but receives all names/paths through arguments; it must not import area config, stage manifest or spawn processes.
## Preview Flow
`build-area.writeCesiumPreview()` continues to ensure GLB/metadata, establish timing, write route/model/runtime/HTML in the existing order, and write `preview.manifest.json`. It delegates route construction, vehicle construction, support copy and HTML generation to the three modules.
## Compatibility
Public CLI and all generated filenames remain unchanged. The route's `generatedAt` naturally changes on each generation and is not a stable parity field; route segments, vehicle glTF structure and preview config are the stable contract. No Blender/GLB parity run is needed because no Blender or GLB generation code changes.
## Rollback
Restore the extracted functions to `build-area.js` and delete the three modules; no data migration or output cleanup is required.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,20 @@
# Split area build orchestration implementation plan
1. Extract vehicle route helpers into `vehicle-route.js`; add unit coverage using fixture OSM.
2. Extract vehicle glTF builder into `vehicle-model.js`; assert schema, mesh/material counts and data URI.
3. Extract preview support-file copy, HTML template, escaping and summary into `area-preview.js`.
4. Replace `build-area.js` bodies with imports/calls while preserving preview manifest ownership and ordering.
5. Run Node syntax/unit tests, then only the nantaizi `preview`, diagnose and quality gate commands.
6. Update pipeline/preview specs and changelog; inspect diff for accidental stage or output changes.
## Validation
- `node --check scripts/build-area.js`
- `node --check scripts/lib/vehicle-route.js`
- `node --check scripts/lib/vehicle-model.js`
- `node --check scripts/lib/area-preview.js`
- `npm run test:preview-assets`
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages preview`
- `npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- `npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
- `git diff --check`

View File

@@ -0,0 +1,41 @@
# Split area build orchestration
## Goal
将区域构建入口收敛为阶段调度器,把车辆路线、车辆 glTF 和 Cesium preview 组装移到职责明确的模块,降低后续修改预览功能时触碰 QGIS/Blender/Cesium 调度逻辑的风险。
## Confirmed Facts
- `scripts/build-area.js` 负责阶段调度,但其 440-928 行还包含 preview manifest、OSM 路线解析、路线几何、车辆 glTF 建模、HTML 模板和转义。
- `preview` 已有独立 manifest ownership`cesium` 调用 preview 生成,但 standalone `--stages preview` 必须继续有效。
- preview HTML 向浏览器运行时注入 `areaId`、GLB、metadata、route 和 vehicle model 文件名HTML / script JSON 使用不同转义函数。
- 车辆路线和车辆 glTF 是预览辅助产物,不改变主 GLB其内容仍属于 preview manifest 输出。
- 本任务只验证南台子,不能改动 `hanyang-block` 或主动重跑重型阶段。
## Requirements
1. 新建纯 Node 模块承载 OSM 到车辆路线的解析与几何计算,保持 route JSON 的字段、排序、lane offset、时间戳语义和 deterministic coordinates 不变。
2. 新建纯 Node 模块承载 preview 车辆 glTF 生成,保持 glTF schema、mesh/material 名称和二进制布局不变。
3. 新建 preview assembly 模块承载 runtime 文件复制、HTML 模板与安全转义;`build-area.js` 仍拥有 preview 阶段依赖检查、文件写入顺序和 manifest 写入。
4. 主入口不再定义以上预览/车辆实现细节,只通过模块 API 组装 preview 阶段。
5. 不改变 CLI、stage 名称/aliases、stdout marker、manifest 字段、输出路径、默认行为或浏览器运行时。
6. 对抽出的纯逻辑补 Node 测试,验证路线与 vehicle glTF 结构;通过南台子 preview 阶段和 diagnostics/check 确认真实产物仍有效。
## Acceptance Criteria
- [x] `build-area.js` 不再含路线解析、车辆 glTF mesh 构建、preview HTML 模板或转义实现。
- [x] 新模块在不依赖 Blender/QGIS 的 Node 测试中覆盖路线和车辆 glTF 关键结构。
- [x] 南台子 `--stages preview` 成功preview manifest 仍 freshroute / vehicle / HTML 文件可读且配置文件名不变。
- [x] `diagnose:area``check:area` 对南台子仍通过。
- [x] 所有修改脚本通过 Node 语法检查、`git diff --check`,且不运行 `hanyang-block`
## Out Of Scope
- 不改变车辆路线算法、车模型外观、预览 HTML/UI、Cesium runtime 或 GLB 导出。
- 不拆分 QGIS、Blender、Cesium 或 compression 的阶段实现。
- 不新增前端构建系统或运行时依赖。
## Key Decisions
- 这是保守纯重构;输出内容和 manifest 契约比文件行数更重要。
- preview assembly 依赖注入 filesystem/path/manifest 所需值,避免模块反向读取区域配置或启动外部进程。

View File

@@ -0,0 +1,26 @@
{
"id": "split-area-build-orchestration",
"name": "split-area-build-orchestration",
"title": "Split area build orchestration",
"description": "Extract vehicle route, vehicle model, and preview assembly from build-area.js without changing area outputs.",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-04",
"completedAt": "2026-08-04",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,28 @@
# 设计Cesium 语义资产检查
## 边界
Blender 场景已按集合组织要素。Cesium 导出器在不修改作者场景的前提下,复用现有导出材质处理,先生成完整主 GLB再按集合筛选对象生成道路、建筑、植被和水体四份辅助 GLB。主 GLB 继续是默认资产和兼容基线。
## 资产契约
metadata 保持现有 `asset` 字段指向主 GLB并将 `assets` 扩展为:
- `main`:完整场景,默认启用;
- `roads``buildings``vegetation``water`:语义检查资产,默认关闭。
每个资产提供稳定的 `id`、中文 `label``type: model`、相对 `url``enabled`。旧 metadata 缺少这些额外资产时,预览按现有单资产回退路径加载。
## 预览交互
主场景模式只显示主 GLB并保留现有 `Scene` 总开关。切入分类检查模式时隐藏主 GLB、显示四个类别复选项选择类别时加载并显隐对应资产。离开分类模式后恢复主场景避免完整模型和类别模型重叠渲染。
控制面板继续是贴边的紧凑工具面,不添加嵌套卡片。模式切换使用分段控件,类别开关按一行标签排列;窄屏时自然换行。颜色只用于状态和可访问性反馈,不做大面积装饰。
## 兼容与回滚
主 GLB、主 metadata 和既有预览文件名均不变。任意辅助 GLB 缺失时,预览记录该资产失败但完整场景仍可加载。回滚只需恢复旧 exporter/runtime主产物仍可使用。
## 风险
多次导出会增加 Cesium 阶段耗时与磁盘占用,但只影响验证辅助产物,不改变主 GLB。导出时需确保每份辅助资产保留与主 GLB 相同的局部 ENU 坐标和材质处理。

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,21 @@
# 实施计划Cesium 语义资产检查
1. 读取 Blender 场景集合,定义四个稳定的语义导出组;完整 GLB 仍走当前路径。
2. 扩展 `export_cesium.py`:导出四份辅助 GLB并把资产描述写进 metadata。
3. 扩展 `build-area.js` 的 Cesium 输出检查和 manifest使辅助资产成为该阶段的受管产物。
4. 更新 `area-preview.js``cesium-preview.js` 与 CSS增加完整/分类模式及分类显隐控件,保持旧 metadata 回退。
5. 为 HTML 注入、metadata 回退和模式状态编写 Node 侧测试;运行既有预览与纯 Python 测试。
6. 在本机重建一个区域,确认辅助 GLB 坐标、材质、模式切换及窄屏布局;此环境若 Blender 沙箱失败,不将其计为代码失败。
## 验证
```bash
node scripts/test-preview-assets.js
python3 -m unittest discover blender/tests
git diff --check
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium
```
## 回滚点
主 GLB 与原 preview 文件保持不变。若辅助导出或分类预览异常,可删除语义资产声明并恢复为单资产加载,不影响既有场景。

View File

@@ -0,0 +1,47 @@
# Cesium 预览控制
## 目标
让 Cesium 预览更便于检查已生成的园区场景,不涉及 QGIS 数据编辑或压缩产物策略。
## 已确认事实
- 预览已有 `Overview``Oblique``Detail``Route` 相机预设,以及车辆跟随;重复开发这些控件没有价值。
- 预览已有主场景、路线、车辆的显隐开关;多 GLB 时也会自动提供资产级开关。
- 当前导出物只有一个主 GLBmetadata 只记录场景统计和一个 `main` 资产,不含可用于点选的要素属性。
- 预览是验证层,改动不应改变 Blender 主资产的几何或 QGIS 工作流。
## 候选范围
- 按场景类别显隐,例如道路、建筑、植被、水体。
- 点击可识别对象后显示基础信息。
- 保持既有相机预设、车辆控制和单场景加载的兼容性。
## 已确认决策
- 场景按道路、建筑、植被、水体等语义类别额外导出 GLBCesium 预览分别加载。
- 保留现有主 GLB作为完整场景基线和兼容入口。
- 首版不提供点选要素属性;这需要逐要素 OSM 元数据映射,另行规划。
- 控件遵循现有低干扰预览风格:紧凑分组、清晰状态、克制色彩和无装饰性卡片堆叠。
## 需求
1. Cesium 导出额外生成道路、建筑、植被、水体四类语义 GLB并写入 metadata 资产清单。
2. 预览默认加载主 GLB用户可切换到语义资产检查模式在该模式独立显示或隐藏每个类别。
3. 预览保留现有相机预设、车辆巡航、主场景开关及单 GLB metadata 的兼容行为。
4. 控件在桌面和窄屏下保持可读、可操作且不遮挡关键画面。
## 不在范围内
- QGIS 图层、人工数据修补和 reimport 流程。
- 将压缩 GLB 设为默认产物。
- 地图、地形或在线底图功能。
- 点击要素属性、OSM ID 或名称映射。
## 验收标准
- [ ] 一次 Cesium 导出会保留主 GLB并产出四个可独立加载的类别 GLB。
- [ ] metadata 的 `assets` 同时声明主场景和各类别资产,旧 metadata 仍可作为单资产场景打开。
- [ ] 预览可在完整场景与分类检查模式之间切换;分类模式可独立控制道路、建筑、植被、水体。
- [ ] 现有 Overview、Oblique、Detail、Route、车辆和诊断控件仍可使用。
- [ ] 控件在常规桌面与窄屏宽度下无重叠、无溢出,视觉层级与现有预览一致。

View File

@@ -0,0 +1,26 @@
{
"id": "cesium-preview-controls",
"name": "cesium-preview-controls",
"title": "Cesium preview controls",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-05",
"completedAt": "2026-08-05",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,54 @@
# 设计LowPoly Cars 资产库
## 边界与目录
本任务独立于区域构建和 Cesium 预览。新增目录
`assets/models/custom/lowpoly_cars/`,其内部结构与 Shapespark 植物包一致:
```text
lowpoly_cars/
manifest.json
index.html
textures/
<vehicle-id>/model.gltf
<vehicle-id>/model.bin
<vehicle-id>/preview.png
```
`.blend` 保留在用户 Downloads不复制入仓库`manifest.json` 保存源文件名与根对象名,
以便可追溯而不引入二进制源文件。
## 拆分与规范化
新 Blender 工具以 Empty 根节点和其全部后代 mesh 作为一辆车。根节点名是稳定初始 ID
`car_a01_002``truck_a01_001` 等。名称重复的车辆保留全部条目,不依据几何统计猜测合并。
导出前把每辆车的根节点变换 bake 到其后代网格,再把局部原点置于车辆的水平中心和最低接地点。
规范化的预览坐标为 X 前进、Y 向上、Z 横向;若源根节点的朝向不符合它,工具按对象族显式
变换并由渲染缩略图复核。不能依赖源文件中用于陈列的全局坐标、`0.001` 缩放或 90°/180°旋转。
保留同一辆车的 mesh 分件与材质槽,避免为图库的便利合并导致窗、轮胎等材质丢失。图像依赖
集中复制到 `textures/`glTF 以相对路径引用,重复车款不能复制同一张贴图。
## 产物与预览
工具为每辆车导出 glTF/bin使用隔离的三点布光、地面阴影与固定透视相机输出
`preview.png``index.html` 只消费 manifest响应式显示缩略图、ID、类别、三角面数、尺寸和
材质;它不依赖 Cesium、Node 打包或区域输出,通过 HTTP 直接查看。
`manifest.json` 的每项至少包含 `id``name``category``sourceRoot``model``bin`
`preview`,以及 `metrics.vertices``metrics.triangles``metrics.dimensions`
`metrics.materials``category``car``truck``tractor`
## 风险与回退
Blender 的沙箱内加载会崩溃,因此导出、渲染和检查仅通过受批准的沙箱外 Blender 调用。若
某一根节点导出失败工具记录错误并继续其余资产manifest 只收录有效产物,失败根节点写入
报告。删除 `lowpoly_cars/` 不影响任何当前构建阶段或 Cesium 巡航。
## Cesium 候选集
候选集固定为 `car_a01_002``car_a02_002``car_a03_001`,以及按稳定 ID 排序的前三辆
卡车 `truck_a01_001``truck_a02_001``truck_a03_001`。预览构建把这六个 glTF/bin 与一张
共享 512px 贴图复制到区域输出目录并改写其 image URI浏览器页面加载时从文件名列表随机选
一项,所有巡航实体在本页使用相同模型。这样构建产物保持确定,随机性只属于用户可见的会话。

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,27 @@
# 实施计划LowPoly Cars 资产库
1.`blender/tools/` 增加可重复执行的拆分/导出工具,读取指定 `.blend`,枚举命名 Empty
根节点bake 变换、规范化坐标并分别导出车辆。
2. 让工具生成共享贴图目录、逐车 glTF/bin、缩略图、manifest 与静态图库 HTML输出排序必须
按稳定 ID避免重复运行产生无关差异。
3. 为导出结果提供轻量结构检查:验证 manifest 路径存在、ID 唯一、几何指标为正、模型和缩略图
可读取;对源根节点与导出条目数量做对账。
4. 在沙箱外运行导出与缩略图渲染,目检图库中的普通车、卡车和拖拉机,核对朝向、落地与比例。
5. 运行 Python/Node 语法检查和现有 preview 测试,确认不触及 `vehicle-model.js`、路线 JSON 或
Cesium 默认预览;记录验证结果。
6. 验收后提交资产库、工具、来源说明与必要的代码规范;不改当前默认巡航模型。
## 验证
```bash
blender --factory-startup --background --python blender/tools/<tool>.py -- --source <blend> --output assets/models/custom/lowpoly_cars
python3 blender/tools/<tool>.py --verify assets/models/custom/lowpoly_cars
node --check scripts/lib/vehicle-model.js
npm run test:preview-assets
git diff --check
```
## 回滚
本任务只新增独立资产库和工具。若某项导出或方向不合格,删除该新增目录和工具即可;当前区域
构建和 Cesium 预览没有依赖它们。

View File

@@ -0,0 +1,53 @@
# 拆分 LowPoly Cars 车辆资产
## Goal
将用户下载的 `011.+LowPoly_Cars_01_blend.blend` 拆分为可独立检查、可被后续
Cesium 车辆预览复用的低模车辆资产库;沿用 Shapespark 植物包的资产目录、清单与
浏览器预览体验。
## Confirmed Facts
- 源文件位于 `/Users/que01/Downloads/011.+LowPoly_Cars_01_blend.blend`,为 Blender
3.0、64 位小端格式,约 25 MB不应直接作为仓库产物引用。
- 当前 Blender 4.5.12 LTS 在沙箱中加载会崩溃;在沙箱外可稳定读取,故拆分和渲染应通过
沙箱外 Blender 调用完成。
- 源文件有 12 个可导出 Empty 根节点5 个普通车条目(其中 `car_a01``car_a02` 各有两份
同名变体、4 个卡车、3 个拖拉机;每项由 11 到 19 个 mesh 组成。
- 根节点保存了展示排布用的 `0.001` 缩放与 90°/180°旋转。拆分必须应用这些层级变换统一
比例、地面基线和车辆前进轴,不能将源文件世界坐标原样导出。
- 主体使用共享 `carmaterial`(含 base-color 图和 normal map拆分后应共用贴图不复制到
每一个车辆目录。
- 植物包位于 `assets/models/custom/shapespark_plants/`:每个资产拥有
`model.gltf``model.bin``preview.png`,共享贴图放 `textures/`,并以
`manifest.json``index.html` 提供机器与人工检查入口。
- 已完成的车辆连续路线系统读取预览车辆 glTF新资产库在验收前不得破坏该默认车辆或
现有路线播放。
## Requirements
1. 稳定读取源 `.blend`,记录其集合、对象、材质、几何指标、坐标朝向和许可/来源信息。
2. 按可独立使用的车辆变体拆分为 glTF 资产,保留正确比例、轮胎落地基线、前进朝向与
原始材质语义;共享贴图不得为每辆车重复复制。
3.`assets/models/custom/` 下创建与植物包一致的清晰目录、JSON manifest 与简洁的
HTML 图库预览;每辆车显示缩略图、三角面数、尺寸与材质信息。
4. 预览页必须可通过本地 HTTP 查看,不依赖主场景或 QGIS缩略图应能帮助比较车款、颜色
和比例。
5. 新库经过 Blender/glTF 结构检查;从普通车中固定选 3 个、从卡车中选前 3 个组成候选集,
Cesium 每次打开预览时从该 6 个模型随机选一辆作为巡航车辆。
## Acceptance Criteria
- [ ] 能稳定枚举源文件内的可导出车辆,并把每个产物与源对象/集合对应起来。
- [ ] 每个可用车辆有独立 `model.gltf`/二进制数据、预览图与 manifest 条目;预览图和 HTML
能正确加载。
- [ ] manifest 为每项记录稳定 ID、名称、来源、几何指标、尺寸、材质和相对路径。
- [ ] 人工查看图库可比较全部导出的车辆;没有重复贴图、明显错误的坐标轴或悬空/埋地车轮。
- [ ] 现有目标区域的 Cesium 巡航预览仍可生成和播放。
- [ ] 目标区域预览目录包含选定的 6 个车辆模型与一张共享贴图;页面刷新会从候选集随机选车。
## Out of Scope
- 不在本任务中制作车辆动画、转向轮、交通行为、LOD 或性能压缩策略。
- 不修改 QGIS 工程、道路生成或上一轮已经确认的车辆连续路线逻辑。
- 不引入车辆动画、逐车随机分配、性能压缩或车辆选择控件;本轮只在页面加载时随机确定一种车。

View File

@@ -0,0 +1,26 @@
{
"id": "lowpoly-cars-assets",
"name": "lowpoly-cars-assets",
"title": "拆分 LowPoly Cars 车辆资产",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-05",
"completedAt": "2026-08-05",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,31 @@
# 设计:车辆连续巡航与转弯
## 数据与图模型
`vehicle-route.js` 继续只读取 OSM XML但解析改为保留 node ID、坐标、way 标签和 node
引用。每条可行驶 way 产生一个或两个有向 edge`oneway=yes` 只保留原始方向,其他道路增加
反向 edge。edge 的终点 node 连接其他以该 node 为起点的 edge。
入边在路口前的最后一个线段给出入射方位,出边第一个线段给出离去方位。二者的有符号夹角
分类为 `left``through``right`;接近 180 度的 U-turn 一律排除。读取去程入边方向对应的
`turn:lanes:forward/backward`,拆分 `|``;` 后得到允许动作集合。无 `turn:lanes`
道路保守允许三种非 U-turn 动作,避免未标注区域没有路线。返程沿反向 edge 回走,不用反向
`turn:lanes` 过滤;这是一项预览展示边界,不是交通规则模拟。
## 路线选择与几何
南台子湖主道路图是树状网络,不能生成真实 cycle。改为枚举连接两个端点、长度足够的有向
道路路径,并按稳定的 road ID / maneuver 序列排序。选择彼此不完全相同、且合计覆盖
left / through / right 的前 5 条。路径在两个端点各接一段平滑调头曲线,再沿反向道路返回;
这使得路线可循环播放而不在端点瞬移。
每个路口连接把入边末段和出边首段裁去固定距离,用三次 Bezier 采样 6 个点衔接。偏移在
整条连续路线完成后计算,避免每个 way 单独偏移在路口产生断裂。
route JSON 升级为 `routes`,每项有 `id``maneuvers``coordinates``lengthMeters`;同时
继续写 `segments` 作为旧预览的兼容别名。Cesium 运行时优先读取 `routes`,退回 `segments`
## 边界
没有 `restriction` relation 时不能声称交通法规完全正确。它只影响未来候选出口过滤,不改变
路线格式或 Cesium 播放逻辑。路口曲线是视觉轨迹,车道级精确曲率与道路 polygon 不在首版。

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,10 @@
# 实施计划:车辆连续巡航与转弯
1. 重构 `vehicle-route.js` 的 OSM 读取和纯几何 helper构造有向 road graph、动作分类与
`turn:lanes` 过滤。
2. 枚举稳定的端点间道路路径、选择不重复且覆盖三种动作的至多 5 条路线;在端点添加
平滑调头并生成路口连接。
3. 扩展 route JSON保留 `segments` 兼容字段预览优先消费新路线数组UI 标识路线及动作。
4.`test-preview-assets.js` 增加单行、标签过滤、左直右分类、闭环和曲线连续性夹具。
5. 运行 Node 语法检查、预览测试、现有箭头测试和目标区域 `preview` 重建;在浏览器观察
普通与压缩预览的多车转弯。

View File

@@ -0,0 +1,49 @@
# 车辆连续巡航与转弯
## Goal
让 Cesium 预览中的车辆沿连续道路路线穿越路口,并以可见的平滑曲线完成左转、右转和直行;
同屏展示多条确定性路线以核对效果。
## Confirmed Facts
- 当前 `scripts/lib/vehicle-route.js` 将每条可行驶 OSM way 独立导为折返巡航段;车辆不会跨
way 行驶。
- Cesium 预览最多加载 5 辆车辆,已有位置插值、朝向计算、路线可见性和 Follow 控制。
- 南台子湖 OSM 使用单引号 XML 属性,含 15 处 `turn:lanes:forward`、15 处
`turn:lanes:backward` 和 4 条 `oneway=yes`
- 没有 `restriction` relation首版不能承诺处理禁止左转等限制关系。
- 路口 `1140799725``1140799919` 各连接四条道路,另有多个三岔连接,足以形成多条
左转、右转、直行的连续候选路线。
## Requirements
1. 从 OSM 可行驶 way 构建有向道路图:单行道只能按 tag 方向通行,双向道路提供两个方向。
2. 在共享端点连接道路,依据入射和出射方向将候选动作分类为 left、through、right去程
只有动作出现在该方向 `turn:lanes:*` 的任一车道时才可通行。返程原路返回,不用反向
`turn:lanes` 二次否决路线。
3. 确定性地产生至多 5 条连续往返路线,覆盖至少一条左转、一条右转和一条直行路线。
4. 路口与路线端点均用平滑连接补充路线点;车辆位置、朝向和可见路线必须连续,不能在
way 端点跳回起点。
5. 保持现有路线 JSON 是预览的可选输入,旧 route JSON 仍可由预览加载。
## Out of Scope
- 不做实时交通仿真、避碰、红绿灯、变道或速度控制。
- 不使用 QGIS 图层或改动 Blender/GLB。
- 不支持 OSM `restriction` relation后续数据具备时再接入。
- 不承诺为每条车道生成独立精确轨迹;`turn:lanes` 首版用于许可转向过滤。
## Acceptance Criteria
- [ ] 输出最多 5 条连续往返路线,且每条均有多个跨 way 的路口连接和端点平滑调头。
- [ ] 南台子湖预览同屏可见左转、右转、直行三类路口动作,车辆不在路口或 way 端点瞬移。
- [ ] `oneway=yes` 的路段不会逆向进入;去程没有匹配 `turn:lanes` 动作的出口不会被选入路线。
- [ ] 路线生成对同一 OSM 输入稳定,自动化测试覆盖单行、动作过滤、转向分类、曲线连接和闭环。
- [ ] 旧 route JSON 的预览兼容性不回归,现有 Pause、Follow、路线开关和车辆选择仍可用。
## Open Questions
已决南台子湖可行驶道路主连通分量是树状网络23 个端点节点、19 条道路连接),没有
真实闭环;首版以端点平滑调头的往返路线替代闭环。为展示多条路线,返程不以反向
`turn:lanes` 过滤,但仍不允许逆行单行道。

View File

@@ -0,0 +1,26 @@
{
"id": "vehicle-turn-routes",
"name": "vehicle-turn-routes",
"title": "车辆连续巡航与转弯",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-05",
"completedAt": "2026-08-05",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -8,8 +8,8 @@
<!-- @@@auto:current-status --> <!-- @@@auto:current-status -->
- **Active File**: `journal-1.md` - **Active File**: `journal-1.md`
- **Total Sessions**: 13 - **Total Sessions**: 24
- **Last Active**: 2026-08-03 - **Last Active**: 2026-08-05
<!-- @@@/auto:current-status --> <!-- @@@/auto:current-status -->
--- ---
@@ -19,7 +19,7 @@
<!-- @@@auto:active-documents --> <!-- @@@auto:active-documents -->
| File | Lines | Status | | File | Lines | Status |
|------|-------|--------| |------|-------|--------|
| `journal-1.md` | ~280 | Active | | `journal-1.md` | ~512 | Active |
<!-- @@@/auto:active-documents --> <!-- @@@/auto:active-documents -->
--- ---
@@ -29,6 +29,17 @@
<!-- @@@auto:session-history --> <!-- @@@auto:session-history -->
| # | Date | Title | Commits | Branch | | # | Date | Title | Commits | Branch |
|---|------|-------|---------|--------| |---|------|-------|---------|--------|
| 24 | 2026-08-05 | 拆分 LowPoly Cars 车辆资产 | `3108336`, `2489b8a` | `main` |
| 23 | 2026-08-05 | 车辆连续巡航与转弯 | `30846b6` | `main` |
| 22 | 2026-08-05 | Cesium semantic inspection preview | `607d8fc` | `main` |
| 21 | 2026-08-05 | Add OSM turn lane arrows | `9a23f74` | `main` |
| 20 | 2026-08-04 | Split area preview orchestration | `396df51` | `main` |
| 19 | 2026-08-04 | Add configurable area asset budgets | `463cb06` | `main` |
| 18 | 2026-08-04 | Add OSM area preflight | `e1f5207` | `main` |
| 17 | 2026-08-04 | Add full stage manifests | `5874cf9` | `main` |
| 16 | 2026-08-04 | Add area quality gate | `7fcc4ee` | `main` |
| 15 | 2026-08-04 | Add GLB stage manifests | `2832383` | `main` |
| 14 | 2026-08-04 | Add area diagnostics command | `d4c3baf` | `main` |
| 13 | 2026-08-03 | Fix OSM multipolygon buildings | `95f8458` | `main` | | 13 | 2026-08-03 | Fix OSM multipolygon buildings | `95f8458` | `main` |
| 12 | 2026-08-03 | Optional GLB compression stage | `7ba8594` | `main` | | 12 | 2026-08-03 | Optional GLB compression stage | `7ba8594` | `main` |
| 11 | 2026-08-03 | Adopt Shapespark vegetation | `dbb5705` | `main` | | 11 | 2026-08-03 | Adopt Shapespark vegetation | `dbb5705` | `main` |

View File

@@ -278,3 +278,235 @@ Parsed building multipolygon relations, generated building holes, and respected
### Status ### Status
[OK] **Completed** [OK] **Completed**
## Session 14: Add area diagnostics command
**Date**: 2026-08-04
**Task**: Add area diagnostics command
**Branch**: `main`
### Summary
Added a fast diagnose:area command for OSM structure, building relation health, artifact status, and GLB digest checks; shared area config normalization between build and diagnostics.
### Git Commits
| Hash | Message |
|------|---------|
| `d4c3baf` | (see git log) |
### Status
[OK] **Completed**
## Session 15: Add GLB stage manifests
**Date**: 2026-08-04
**Task**: Add GLB stage manifests
**Branch**: `main`
### Summary
Added first-version cesium and compress stage manifests under _pipeline/stages, shared manifest helpers, and diagnose:area reporting for missing/stale manifests.
### Git Commits
| Hash | Message |
|------|---------|
| `2832383` | (see git log) |
### Status
[OK] **Completed**
## Session 16: Add area quality gate
**Date**: 2026-08-04
**Task**: Add area quality gate
**Branch**: `main`
### Summary
Added a read-only check:area command backed by shared area diagnostics, documented the quality-gate failure policy, and verified nantaizi pass plus controlled missing-output failure.
### Git Commits
| Hash | Message |
|------|---------|
| `7fcc4ee` | (see git log) |
### Status
[OK] **Completed**
## Session 17: Add full stage manifests
**Date**: 2026-08-04
**Task**: Add full stage manifests
**Branch**: `main`
### Summary
Extended stage manifests across intermediates, reimport, blender, and preview; added alternate ownership handling and validated the complete nantaizi pipeline.
### Git Commits
| Hash | Message |
|------|---------|
| `5874cf9` | (see git log) |
### Status
[OK] **Completed**
## Session 18: Add OSM area preflight
**Date**: 2026-08-04
**Task**: Add OSM area preflight
**Branch**: `main`
### Summary
Added the read-only preflight:area command, a successful preflight manifest, shared OSM structural checks, and quality-gate freshness validation. OSM diagnostics now ignore action=delete historical objects; verified only nantaizi with unit fixtures, preflight, diagnose, check, and a controlled stale-manifest failure.
### Git Commits
| Hash | Message |
|------|---------|
| `e1f5207` | (see git log) |
### Status
[OK] **Completed**
## Session 19: Add configurable area asset budgets
**Date**: 2026-08-04
**Task**: Add configurable area asset budgets
**Branch**: `main`
### Summary
Added configurable GLB budgets with documented exceptions, shared budget evaluation for diagnostics, quality gates, and Cesium/compress manifests, plus render-triangle, embedded-image, and source attribution diagnostics. Verified budget tests, preflight tests, nantaizi diagnostics/check, and a controlled low-budget failure without running heavy stages.
### Git Commits
| Hash | Message |
|------|---------|
| `463cb06` | (see git log) |
### Status
[OK] **Completed**
## Session 20: Split area preview orchestration
**Date**: 2026-08-04
**Task**: Split area preview orchestration
**Branch**: `main`
### Summary
Extracted preview route, vehicle glTF, and HTML/runtime assembly from build-area into focused modules; added preview asset tests and verified nantaizi preview, diagnostics, and quality gate.
### Git Commits
| Hash | Message |
|------|---------|
| `396df51` | (see git log) |
### Status
[OK] **Completed**
## Session 21: Add OSM turn lane arrows
**Date**: 2026-08-05
**Task**: Add OSM turn lane arrows
**Branch**: `main`
### Summary
Added vendored lane-icons assets, tested OSM turn-lane arrow generation and lane-center placement, and normalized crosswalk and stop-line generation around rendered Driving lanes.
### Git Commits
| Hash | Message |
|------|---------|
| `9a23f74` | (see git log) |
### Status
[OK] **Completed**
## Session 22: Cesium semantic inspection preview
**Date**: 2026-08-05
**Task**: Cesium semantic inspection preview
**Branch**: `main`
### Summary
Added semantic Cesium inspection assets and controls; fixed compressed metadata to preserve inspection assets; verified the rebuilt compressed preview.
### Git Commits
| Hash | Message |
|------|---------|
| `607d8fc` | (see git log) |
### Status
[OK] **Completed**
## Session 23: 车辆连续巡航与转弯
**Date**: 2026-08-05
**Task**: 车辆连续巡航与转弯
**Branch**: `main`
### Summary
实现 OSM 有向道路图驱动的连续往返车辆路线,支持 turn:lanes 转向筛选、单行限制、路口曲线与端点调头Cesium 路线选择显示编号、长度及左/右/直统计,并补充路线 JSON 中文契约与自动化验证。
### Git Commits
| Hash | Message |
|------|---------|
| `30846b6` | (see git log) |
### Status
[OK] **Completed**
## Session 24: 拆分 LowPoly Cars 车辆资产
**Date**: 2026-08-05
**Task**: 拆分 LowPoly Cars 车辆资产
**Branch**: `main`
### Summary
拆分 LowPoly Cars 资产库并接入 Cesium 预览候选集;每条巡航路线独立随机选模型,修正贴图、亮度、比例和 Truck A03 行驶方向;导入脚本支持脱离 Blender 验证已入库资产。
### Git Commits
| Hash | Message |
|------|---------|
| `3108336` | (see git log) |
| `2489b8a` | (see git log) |
### Status
[OK] **Completed**

View File

@@ -8,7 +8,8 @@
- `<area-id>.blend`Blender 场景,包含道路、建筑、水体、植被等 - `<area-id>.blend`Blender 场景,包含道路、建筑、水体、植被等
- `<area-id>.png`Blender 预览渲染 - `<area-id>.png`Blender 预览渲染
- `<area-id>.glb`Cesium 可加载的 3D 模型 - `<area-id>.glb`Cesium 可加载的完整 3D 模型(基线产物)
- `<area-id>-roads.glb``-buildings.glb``-vegetation.glb``-water.glb`Cesium 分类检查用的辅助模型
- `<area-id>.json`Cesium 放置元数据和示例代码 - `<area-id>.json`Cesium 放置元数据和示例代码
- `<area-id>-cesium-preview.html`Cesium 本地预览页 - `<area-id>-cesium-preview.html`Cesium 本地预览页
- `<area-id>-compressed-webp768.glb/json/html`:显式 `compress` 阶段生成的可选压缩预览产物 - `<area-id>-compressed-webp768.glb/json/html`:显式 `compress` 阶段生成的可选压缩预览产物
@@ -57,12 +58,89 @@ npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json
`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-手工修正工作流)。 `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 后立刻生成压缩产物时,显式跑: `compress` 不含在 `all` 里,也不能从配置文件默认开启。需要重导出 Cesium 后立刻生成压缩产物时,显式跑:
```bash ```bash
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium,compress 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/<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 countsBlender 记录 `.blend` / renderCesium/压缩记录 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不阻断。
## 区域配置 ## 区域配置
新区域从模板复制: 新区域从模板复制:

View File

@@ -0,0 +1,24 @@
{
"upstream": {
"repository": "https://github.com/openstreetmap/lane-icons",
"revision": "db3cab321da93a757c2cf3cdb4f1f1db6b1a091c",
"license": "CC0-1.0",
"license_file": "upstream/LICENSE.md"
},
"assets": [
{"id":"through","source":"upstream/through.svg","anchor_x":12.5,"supported":true,"tested":true,"template":"through"},
{"id":"left","source":"upstream/left.svg","anchor_x":17,"supported":true,"tested":true,"template":"left"},
{"id":"right","source":"upstream/left.svg","derived_from":"left","mirror_x":true,"anchor_x":17,"supported":true,"tested":true,"template":"right"},
{"id":"through;left","source":"upstream/left-through.svg","anchor_x":17,"supported":true,"tested":true,"template":"through_left"},
{"id":"through;right","source":"upstream/left-through.svg","derived_from":"through;left","mirror_x":true,"anchor_x":17,"supported":true,"tested":true,"template":"through_right"},
{"id":"through;left;right","source":"upstream/left-slight_left-through.svg","derived_from":"through;left + through;right","anchor_x":17,"supported":true,"tested":true,"template":"through_left_right"},
{"id":"slight_left","source":"upstream/slight_left.svg","supported":false,"tested":false},
{"id":"slight_left;through","source":"upstream/slight_left-through.svg","supported":false,"tested":false},
{"id":"left;slight_left;through","source":"upstream/left-slight_left-through.svg","supported":false,"tested":false},
{"id":"sharp_left","source":"upstream/sharp_left.svg","supported":false,"tested":false},
{"id":"sharp_left;through","source":"upstream/sharp_left-through.svg","supported":false,"tested":false},
{"id":"reverse_left","source":"upstream/reverse_left.svg","supported":false,"tested":false},
{"id":"reverse_left;through","source":"upstream/reverse_left-through.svg","supported":false,"tested":false},
{"id":"reverse_left;left;slight_left;through","source":"upstream/reverse_left-left-slight_left-through.svg","supported":false,"tested":false}
]
}

View File

@@ -0,0 +1,98 @@
## Design Guidelines
The icons in this project follow closely the design guidelines established in the
[MUTCD Standard Highway Signs guide](http://mutcd.fhwa.dot.gov/ser-shs_millennium.htm),
2009 edition published by the US Federal Highway Administration.
The MUTCD is [in the public domain](http://mutcd.fhwa.dot.gov/knowledge/faqs/faq_general.htm#printq5)
and as such it is not copyrighted.
Each icon should be
* 25px x 25px
* SVG
* Single color fill
* Path outline only, no stroke
* Pixel grid aligned (where possible)
### Arrows
Arrows use the "Standard Arrow" described in MUTCD Standard Highway Signs
[Appendix](http://mutcd.fhwa.dot.gov/ser-shs_millennium.htm).
An upward facing Standard Arrow is included in this icon pack, and forms the basis of all turn
lane arrows.
<img alt='Standard Arrow' width='300px' src='docs/standard_arrow.png'/>
Arrowhead rotation angles are chosen based on the turn lane indication:
<table>
<tr>
<th>Through</th>
<th>Slight Left</th>
<th>Left</th>
<th>Sharp Left</th>
<th>Reverse Left</th>
</tr>
<tr>
<td>no rotation<br/>
<img alt='Through' width='100px' src='https://cdn.rawgit.com/openstreetmap/lane-icons/master/docs/through.svg'/>
</td>
<td>45 degrees<br/>
<img alt='Slight Left' width='100px' src='https://cdn.rawgit.com/openstreetmap/lane-icons/master/docs/slight_left.svg'/>
</td>
<td>70 or 90 degrees<br/>
<img alt='Left' width='100px' src='https://cdn.rawgit.com/openstreetmap/lane-icons/master/docs/left.svg'/>
</td>
<td>135 degrees<br/>
<img alt='Sharp Left' width='100px' src='https://cdn.rawgit.com/openstreetmap/lane-icons/master/docs/sharp_left.svg'/>
</td>
<td>180 degrees<br/>
<img alt='Reverse Left' width='100px' src='https://cdn.rawgit.com/openstreetmap/lane-icons/master/docs/reverse_left.svg'/>
</td>
</tr>
</table>
As the number of arrows increases, the arrows scale down:
<table>
<tr>
<th>1 Arrow</th>
<th>2 Arrows</th>
<th>3 Arrows</th>
<th>4 Arrows</th>
</tr>
<tr>
<td>
<ul><li>3px stroke</li><li>standard arrowhead</li></ul>
<img alt='Through' width='100px' src='https://cdn.rawgit.com/openstreetmap/lane-icons/master/docs/through.svg'/>
</td>
<td>
<ul><li>2.5px stroke</li><li>83% scaled arrowhead</li></ul>
<img alt='Left, Through' width='100px' src='https://cdn.rawgit.com/openstreetmap/lane-icons/master/docs/left-through.svg'/>
</td>
<td>
<ul><li>2.25px stroke</li><li>75% scaled arrowhead</li></ul>
<img alt='Left, Slight Left, Through' width='100px' src='https://cdn.rawgit.com/openstreetmap/lane-icons/master/docs/left-slight_left-through.svg'/>
</td>
<td>
<ul><li>2px stroke</li><li>66% scaled arrowhead</li></ul>
<img alt='Reverse Left, Left, Slight Left, Through' width='100px' src='https://cdn.rawgit.com/openstreetmap/lane-icons/master/docs/reverse_left-left-slight_left-through.svg'/>
</td>
</tr>
</table>
Other important rules for arrows:
* Bottom aligned with each other, 2px from icon bottom edge
* Left-right centered
* Smooth curves, no kinks
* Arrow shaft path connects to the arrowhead anchor point
### Other Icons
Other non-arrow icons (bus, hov, pedestrian, tram, etc) also imitiate the icons found
in the MUTCD Standard Highway Signs [Appendix](http://mutcd.fhwa.dot.gov/ser-shs_millennium.htm).

View File

@@ -0,0 +1,118 @@
[![CC0-1.0](http://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](http://creativecommons.org/publicdomain/zero/1.0/)
### CC0 1.0 Universal
#### Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
**1. Copyright and Related Rights.** A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
**2. Waiver.** To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
**3. Public License Fallback.** Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
**4. Limitations and Disclaimers.**
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>

View File

@@ -0,0 +1 @@
<svg id="left-slight_left-through" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><line x1="18.1" y1="5.17" x2="18.11" y2="23" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.25"/><path d="M18.11,19.69c0-3.65-5.82-6.62-9.2-6.62" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.25"/><path d="M11.89,7.16S18.11,12.74,18.11,19" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.25"/><path d="M18.16,1.09a0.07,0.07,0,0,0-.12,0L15.26,5.36a0.42,0.42,0,0,0,.48.62l2.36-.81L20.47,6A0.42,0.42,0,0,0,21,5.37Z"/><path d="M4.83,13a0.07,0.07,0,0,0,0,.12L9.1,15.91a0.42,0.42,0,0,0,.62-0.48L8.91,13.07,9.72,10.7a0.42,0.42,0,0,0-.62-0.48Z"/><path d="M9.05,4.24A0.07,0.07,0,0,0,9,4.32l1,5a0.42,0.42,0,0,0,.78.1l1.1-2.24,2.25-1.1A0.42,0.42,0,0,0,14,5.28Z"/></svg>

After

Width:  |  Height:  |  Size: 822 B

View File

@@ -0,0 +1 @@
<svg id="left-through" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><line x1="17" y1="23" x2="17" y2="6.81" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.5"/><path d="M17,19c0-5.32-4.66-8.23-7.83-9.1" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.5"/><path d="M17.06,2.3a0.08,0.08,0,0,0-.13,0L13.85,7a0.47,0.47,0,0,0,.53.69L17,6.81l2.62,0.9A0.46,0.46,0,0,0,20.15,7Z"/><path d="M5,8.29a0.08,0.08,0,0,0,0,.12l3.38,4.51a0.47,0.47,0,0,0,.83-0.27L9.17,9.9l1.74-2.16A0.46,0.46,0,0,0,10.45,7Z"/></svg>

After

Width:  |  Height:  |  Size: 562 B

View File

@@ -0,0 +1 @@
<svg id="left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><path d="M17,23V18c0-6.78-3.24-8.11-5.85-8.86" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="3"/><path d="M6.07,7.21A0.1,0.1,0,0,0,6,7.36l4.08,5.44a0.56,0.56,0,0,0,1-.32l0.06-3.33,2.1-2.6a0.56,0.56,0,0,0-.55-0.88Z"/></svg>

After

Width:  |  Height:  |  Size: 329 B

View File

@@ -0,0 +1 @@
<svg id="reverse_left-left-slight_left-through" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><line x1="19" y1="4" x2="19" y2="23" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/><path d="M7.21,12h5.5A15.61,15.61,0,0,1,19,13" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/><path d="M13.7,5.85S19,9.23,19,15" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/><path d="M19.05,0.43a0.07,0.07,0,0,0-.1,0L16.49,4.18a0.37,0.37,0,0,0,.42.55L19,4l2.09,0.71a0.37,0.37,0,0,0,.42-0.54Z"/><path d="M11.2,3.28a0.07,0.07,0,0,0-.07.07l0.92,4.39a0.37,0.37,0,0,0,.68.09l1-2,2-1A0.37,0.37,0,0,0,15.6,4.2Z"/><path d="M3.68,11.95a0.07,0.07,0,0,0,0,.1L7.44,14.5A0.37,0.37,0,0,0,8,14.08L7.21,12,8,9.91a0.37,0.37,0,0,0-.54-0.42Z"/><path d="M10,23.06a0.07,0.07,0,0,0,.1,0l2.45-3.76a0.37,0.37,0,0,0-.42-0.55L10,19.48l-2.09-.71a0.37,0.37,0,0,0-.42.54Z"/><path d="M19,16c0-5.42-9-4.84-9,0l0,3.48" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg id="reverse_left-through" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><path d="M8.36,23a0.08,0.08,0,0,0,.13,0l3.08-4.72A0.47,0.47,0,0,0,11,17.56l-2.62.9-2.62-.9a0.46,0.46,0,0,0-.53.68Z"/><line x1="17" y1="23.19" x2="17" y2="6.08" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.5"/><path d="M17.06,1.57a0.08,0.08,0,0,0-.13,0L13.85,6.29a0.47,0.47,0,0,0,.53.69L17,6.08,19.62,7a0.46,0.46,0,0,0,.53-0.68Z"/><path d="M17,16c0-5-8.58-5.08-8.58,0v2.46" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.5"/></svg>

After

Width:  |  Height:  |  Size: 574 B

View File

@@ -0,0 +1 @@
<svg id="reverse_left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><path d="M7.8,23A0.1,0.1,0,0,0,8,23l3.72-5.69A0.56,0.56,0,0,0,11,16.45L7.88,17.53,4.72,16.44a0.56,0.56,0,0,0-.64.82Z"/><path d="M18,23V13c0-8-10.08-7.77-10.12.1v5.42" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="3"/></svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@@ -0,0 +1 @@
<svg id="sharp_left-through" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><path d="M5.38,17.17a0.08,0.08,0,0,0,.09.09L11,16.1a0.47,0.47,0,0,0,.11-0.86L8.62,14,7.4,11.53a0.46,0.46,0,0,0-.86.11Z"/><line x1="17" y1="23.19" x2="17" y2="6.08" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.5"/><path d="M17.06,1.57a0.08,0.08,0,0,0-.13,0L13.85,6.29a0.47,0.47,0,0,0,.53.69L17,6.08,19.62,7a0.46,0.46,0,0,0,.53-0.68Z"/><path d="M17,15.65c0-4.75-5.12-5.25-8.38-1.63" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.5"/></svg>

After

Width:  |  Height:  |  Size: 580 B

View File

@@ -0,0 +1 @@
<svg id="sharp_left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><path d="M4.1,15.8a0.1,0.1,0,0,0,.11.11l6.65-1.4a0.56,0.56,0,0,0,.13-1L8,12,6.53,9a0.56,0.56,0,0,0-1,.13Z"/><path d="M18,23V15c0-5.78-4-7.79-8.61-4.11L8,12" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="3"/></svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@@ -0,0 +1 @@
<svg id="slight_left-through" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><line x1="17" y1="23.19" x2="17" y2="6.08" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.5"/><path d="M17.06,1.57a0.08,0.08,0,0,0-.13,0L13.85,6.29a0.47,0.47,0,0,0,.53.69L17,6.08,19.62,7a0.46,0.46,0,0,0,.53-0.68Z"/><path d="M17,19c0-4.22-4.84-9.36-6.54-10.68" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2.5"/><path d="M7.3,5.09a0.08,0.08,0,0,0-.09.09L8.37,10.7a0.47,0.47,0,0,0,.86.11l1.22-2.48,2.49-1.22a0.46,0.46,0,0,0-.11-0.86Z"/></svg>

After

Width:  |  Height:  |  Size: 581 B

View File

@@ -0,0 +1 @@
<svg id="slight_left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><path d="M15,23V20c0-4.12-.1-9.29-3-12.74" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="3"/><path d="M8.26,3.36a0.1,0.1,0,0,0-.11.11l1.4,6.65a0.56,0.56,0,0,0,1,.13l1.46-3,3-1.47a0.56,0.56,0,0,0-.13-1Z"/></svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@@ -0,0 +1 @@
<svg id="through" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><title>icons</title><line x1="12.5" y1="23" x2="12.5" y2="8.47" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="3"/><path d="M12.58,3a0.1,0.1,0,0,0-.16,0L8.7,8.73a0.56,0.56,0,0,0,.64.83L12.5,8.47l3.16,1.08a0.56,0.56,0,0,0,.64-0.82Z"/></svg>

After

Width:  |  Height:  |  Size: 328 B

View File

@@ -19,3 +19,21 @@
旧的 SpeedTree apple、Lyrog fattree、Poly Haven `shrub_02`、原始高模 旧的 SpeedTree apple、Lyrog fattree、Poly Haven `shrub_02`、原始高模
`custom/bush` 和 rejected `custom/bush_low` 已删除。相关实验结论保留在 `custom/bush` 和 rejected `custom/bush_low` 已删除。相关实验结论保留在
`docs/changelog.md` 和归档 Trellis 任务中。 `docs/changelog.md` 和归档 Trellis 任务中。
## LowPoly Cars
`assets/models/custom/lowpoly_cars/` 是从用户本地下载的
`011.+LowPoly_Cars_01_blend.blend` 拆分出的车辆图库。目录结构与植物包一致:每辆车
拥有 `model.gltf``model.bin``preview.png`;全部资产共用
`textures/color_512x512.jpg`,以保留颜色图集中的各车型车漆区域。
`manifest.json` 记录源 Empty 根节点、分类、尺寸、顶点/三角面数和相对文件路径,
`index.html` 用于人工选择车款。源文件许可证尚未确认;在重新分发资产前必须核对原始下载
页面的许可。
拆分由 `blender/tools/split_lowpoly_cars.py` 执行。该工具只读取源 Blend 与指定贴图,
不会接入或替换当前 Cesium 巡航车辆。
区域构建**不依赖** Downloads 中的原始 `.blend`:它只读取本目录已提交的 glTF/bin/贴图。
因此原始 Blend 可以在验收后删除;代价是日后不能从零重跑该导入脚本,除非重新取得同一源文件
与颜色贴图。

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More