Compare commits
34 Commits
78cb69edb6
...
c5956365fa
| Author | SHA1 | Date | |
|---|---|---|---|
| c5956365fa | |||
| c850a0da49 | |||
| 2489b8a10b | |||
| 3108336bf4 | |||
| 436eb482fd | |||
| eb4906b296 | |||
| 30846b6df9 | |||
| eb9e510e13 | |||
| 289c5a29e7 | |||
| 607d8fc0b1 | |||
| 400525dd07 | |||
| 15a8429af9 | |||
| 9a23f74f0f | |||
| 9970e3eeef | |||
| 1f9d99d9b5 | |||
| 396df5127c | |||
| 0ce7d1ea5e | |||
| abfd9e401e | |||
| 463cb06be4 | |||
| f78080bbfa | |||
| d263c02ea7 | |||
| e1f5207e97 | |||
| 34153a31ab | |||
| d36948cd8b | |||
| 5874cf9379 | |||
| 0b7e69770c | |||
| 429e61e51d | |||
| 7fcc4ee8cc | |||
| b791c4350e | |||
| b26fb730e7 | |||
| 2832383f6e | |||
| b7b5530430 | |||
| c4d4bf9db2 | |||
| d4c3baf608 |
@@ -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` 是这一约定的实例,不参与区域
|
||||||
|
构建阶段。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 相关
|
## 相关
|
||||||
|
|||||||
@@ -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` 里加一行路径推导。
|
||||||
|
|
||||||
|
|||||||
@@ -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 导出契约 |
|
||||||
|
|||||||
@@ -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 中有三个以上相连 way;osm2streets 可能把相邻 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。
|
||||||
|
|||||||
@@ -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` 都是反直觉约束
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 和 violations;warnings 来自同一个预算评估
|
||||||
|
|
||||||
|
`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,且独立入口的独立性是刻意的 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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 | intermediates:osm2streets 解析、图层拆分、人行道转角合成、GeoPackage 与 QGIS 工程生成 |
|
| `build-osm2streets-qgis.js` | 1468 | intermediates:osm2streets 解析、图层拆分、人行道转角合成、GeoPackage 与 QGIS 工程生成 |
|
||||||
| `reimport-gpkg.js` | 179 | reimport:GeoPackage → GeoJSON 反向导出 |
|
| `reimport-gpkg.js` | 179 | reimport:GeoPackage → 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 和诊断脚本共用 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Preview:Cesium 预览层
|
# Preview:Cesium 预览层
|
||||||
|
|
||||||
> 覆盖 `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()` 里加进注入的 JSON,JS 侧从 `config` 读,
|
**加一个新的可配置项**:`cesiumPreviewHtml()` 里加进注入的 JSON,JS 侧从 `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「实验:车辆巡航」节:面向使用者的说明
|
||||||
|
|||||||
91
.trellis/spec/preview/vehicle-routes.md
Normal 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"));
|
||||||
|
```
|
||||||
@@ -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."}
|
||||||
@@ -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 中的附加字段可忽略。
|
||||||
@@ -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."}
|
||||||
@@ -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 层覆盖。
|
||||||
@@ -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 总字节伪分配给资产类别。
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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."}
|
||||||
@@ -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."}
|
||||||
@@ -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`.
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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."}
|
||||||
@@ -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."}
|
||||||
@@ -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`.
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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."}
|
||||||
@@ -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.
|
||||||
@@ -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."}
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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."}
|
||||||
@@ -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 是可再生文件,保留或删除均不影响构建。
|
||||||
@@ -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."}
|
||||||
@@ -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。
|
||||||
@@ -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 工件后再开始实现。
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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."}
|
||||||
@@ -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.
|
||||||
@@ -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."}
|
||||||
@@ -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.
|
||||||
@@ -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` 样张的轮廓和视觉语言。
|
||||||
@@ -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.
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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."}
|
||||||
@@ -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."}
|
||||||
@@ -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`.
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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."}
|
||||||
@@ -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.
|
||||||
@@ -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."}
|
||||||
@@ -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`
|
||||||
@@ -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 仍 fresh,route / 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 所需值,避免模块反向读取区域配置或启动外部进程。
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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."}
|
||||||
@@ -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 坐标和材质处理。
|
||||||
@@ -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."}
|
||||||
@@ -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 文件保持不变。若辅助导出或分类预览异常,可删除语义资产声明并恢复为单资产加载,不影响既有场景。
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Cesium 预览控制
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
让 Cesium 预览更便于检查已生成的园区场景,不涉及 QGIS 数据编辑或压缩产物策略。
|
||||||
|
|
||||||
|
## 已确认事实
|
||||||
|
|
||||||
|
- 预览已有 `Overview`、`Oblique`、`Detail`、`Route` 相机预设,以及车辆跟随;重复开发这些控件没有价值。
|
||||||
|
- 预览已有主场景、路线、车辆的显隐开关;多 GLB 时也会自动提供资产级开关。
|
||||||
|
- 当前导出物只有一个主 GLB,metadata 只记录场景统计和一个 `main` 资产,不含可用于点选的要素属性。
|
||||||
|
- 预览是验证层,改动不应改变 Blender 主资产的几何或 QGIS 工作流。
|
||||||
|
|
||||||
|
## 候选范围
|
||||||
|
|
||||||
|
- 按场景类别显隐,例如道路、建筑、植被、水体。
|
||||||
|
- 点击可识别对象后显示基础信息。
|
||||||
|
- 保持既有相机预设、车辆控制和单场景加载的兼容性。
|
||||||
|
|
||||||
|
## 已确认决策
|
||||||
|
|
||||||
|
- 场景按道路、建筑、植被、水体等语义类别额外导出 GLB,Cesium 预览分别加载。
|
||||||
|
- 保留现有主 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、车辆和诊断控件仍可使用。
|
||||||
|
- [ ] 控件在常规桌面与窄屏宽度下无重叠、无溢出,视觉层级与现有预览一致。
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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."}
|
||||||
@@ -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;浏览器页面加载时从文件名列表随机选
|
||||||
|
一项,所有巡航实体在本页使用相同模型。这样构建产物保持确定,随机性只属于用户可见的会话。
|
||||||
@@ -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."}
|
||||||
@@ -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 预览没有依赖它们。
|
||||||
@@ -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 工程、道路生成或上一轮已经确认的车辆连续路线逻辑。
|
||||||
|
- 不引入车辆动画、逐车随机分配、性能压缩或车辆选择控件;本轮只在页面加载时随机确定一种车。
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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."}
|
||||||
@@ -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 不在首版。
|
||||||
@@ -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."}
|
||||||
@@ -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` 重建;在浏览器观察
|
||||||
|
普通与压缩预览的多车转弯。
|
||||||
@@ -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` 过滤,但仍不允许逆行单行道。
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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` |
|
||||||
|
|||||||
@@ -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**
|
||||||
|
|||||||
80
README.md
@@ -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 counts,Blender 记录 `.blend` / render,Cesium/压缩记录 GLB digest,
|
||||||
|
preview 记录 GLB、metadata、车辆路线和 runtime 文件。`diagnose:area` 会读取这些
|
||||||
|
manifest;缺失或当前输入/输出 sha/bytes 不一致会在 `Stage manifests` 和 `Warnings`
|
||||||
|
里标出来。
|
||||||
|
|
||||||
|
## 区域质量门
|
||||||
|
|
||||||
|
提交或交付某个区域前,跑只读质量门:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||||
|
```
|
||||||
|
|
||||||
|
`check:area` 复用 `diagnose:area` 的 OSM、产物、metadata、GLB digest 和 stage manifest
|
||||||
|
检查,但输出更短的 PASS/FAIL 报告。它不会启动 QGIS、Blender、Cesium、压缩或任何重建阶段。
|
||||||
|
|
||||||
|
GLB 预算默认阻断 size、nodes、images、实例化后的 render triangles 和嵌入贴图字节。
|
||||||
|
`diagnose:area` 会显示有效预算、top source 和最大贴图。区域可在配置中收紧预算;要放宽
|
||||||
|
默认值,必须写明原因:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"budget": {
|
||||||
|
"nodes": 1400,
|
||||||
|
"triangles": 350000,
|
||||||
|
"reason": "Dense campus vegetation approved for this area"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
第一版会在这些条件下退出非零:OSM bounds 缺失/无效、building multipolygon relation
|
||||||
|
异常、建筑 `height` 无法按正数米解析、Cesium GLB / metadata / preview 缺失或类型错误、
|
||||||
|
metadata JSON 损坏、GLB size / nodes / images / render triangles / embedded image bytes 超过保守预算、期望存在的 stage manifest
|
||||||
|
缺失/损坏/stale。缺 QGIS preview 目前只作为 warning,不阻断。
|
||||||
|
|
||||||
## 区域配置
|
## 区域配置
|
||||||
|
|
||||||
新区域从模板复制:
|
新区域从模板复制:
|
||||||
|
|||||||
24
assets/lane-icons/manifest.json
Normal 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}
|
||||||
|
]
|
||||||
|
}
|
||||||
98
assets/lane-icons/upstream/DESIGN.md
Normal 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).
|
||||||
118
assets/lane-icons/upstream/LICENSE.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
[](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/>
|
||||||
1
assets/lane-icons/upstream/left-slight_left-through.svg
Normal 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 |
1
assets/lane-icons/upstream/left-through.svg
Normal 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 |
1
assets/lane-icons/upstream/left.svg
Normal 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 |
@@ -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 |
1
assets/lane-icons/upstream/reverse_left-through.svg
Normal 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 |
1
assets/lane-icons/upstream/reverse_left.svg
Normal 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 |
1
assets/lane-icons/upstream/sharp_left-through.svg
Normal 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 |
1
assets/lane-icons/upstream/sharp_left.svg
Normal 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 |
1
assets/lane-icons/upstream/slight_left-through.svg
Normal 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 |
1
assets/lane-icons/upstream/slight_left.svg
Normal 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 |
1
assets/lane-icons/upstream/through.svg
Normal 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 |
@@ -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 可以在验收后删除;代价是日后不能从零重跑该导入脚本,除非重新取得同一源文件
|
||||||
|
与颜色贴图。
|
||||||
|
|||||||
BIN
assets/models/custom/lowpoly_cars/car_a01_002/model.bin
Normal file
1072
assets/models/custom/lowpoly_cars/car_a01_002/model.gltf
Normal file
BIN
assets/models/custom/lowpoly_cars/car_a01_002/preview.png
Normal file
|
After Width: | Height: | Size: 367 KiB |
BIN
assets/models/custom/lowpoly_cars/car_a01_003/model.bin
Normal file
1072
assets/models/custom/lowpoly_cars/car_a01_003/model.gltf
Normal file
BIN
assets/models/custom/lowpoly_cars/car_a01_003/preview.png
Normal file
|
After Width: | Height: | Size: 336 KiB |
BIN
assets/models/custom/lowpoly_cars/car_a02_002/model.bin
Normal file
1149
assets/models/custom/lowpoly_cars/car_a02_002/model.gltf
Normal file
BIN
assets/models/custom/lowpoly_cars/car_a02_002/preview.png
Normal file
|
After Width: | Height: | Size: 351 KiB |