Compare commits
54 Commits
78cb69edb6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 96a99a671b | |||
| e89b649256 | |||
| 5658e7337d | |||
| aeb2cec021 | |||
| a749abf00c | |||
| 8237c7ed27 | |||
| 808266127d | |||
| 22f306249d | |||
| d382bc4462 | |||
| 208ed055ac | |||
| e153a1c57d | |||
| 72fa04ddeb | |||
| 1c077a312e | |||
| 950cd1c4cd | |||
| 2afab19463 | |||
| c930dff6c4 | |||
| 043766b84e | |||
| 9fbc218e10 | |||
| 8b3a1d79d9 | |||
| c8ca009d13 | |||
| 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 |
@@ -205,6 +205,28 @@ tilt_y = TILT_JITTER * math.cos(index * 0.927295)
|
||||
模型保持在**局部 ENU 坐标系**(X 东、Y 北、Z 上),靠伴生 JSON 配合
|
||||
`Cesium.Transforms.eastNorthUpToFixedFrame` 摆放。
|
||||
|
||||
### WGS84 ENU 坐标契约
|
||||
|
||||
Blender 中所有经纬度几何必须通过 `osmassets.osm.Projector` 转换。该转换必须与 Cesium
|
||||
的 `eastNorthUpToFixedFrame(anchor)` 使用同一个 WGS84 椭球语义:先将经纬度转换为
|
||||
ECEF,再将相对锚点的向量投影到 East/North 轴。禁止用固定 `111320 m/deg` 的
|
||||
equirectangular(等距圆柱)近似生成场景坐标。
|
||||
|
||||
固定米/度近似只会在锚点附近碰巧重合;纬向比例与 WGS84 实际比例不同,误差会随离锚点
|
||||
距离增长。表现为 Cesium Entity 路线在部分道路居中、在其他道路相对整个 GLB 路面同向
|
||||
平移。只验证 route 与 GeoJSON 自洽无法发现此问题,必须重新生成
|
||||
`blender,cesium,preview` 并在最终 Cesium 画面中核对。
|
||||
|
||||
修改 `Projector` 后至少执行:
|
||||
|
||||
```bash
|
||||
python3 -m unittest blender.tests.test_pure
|
||||
npm run build:area -- --config config/areas/<area>.json --stages blender,cesium,preview
|
||||
```
|
||||
|
||||
`blender.tests.test_pure.ProjectorTest` 必须断言锚点为原点、East/North 方向正确,以及局部
|
||||
经纬度增量符合 WGS84 椭球曲率半径。
|
||||
|
||||
### Cesium contract
|
||||
|
||||
新生成场景的 Cesium 导出调色写在 `catalog.MATERIALS[*]["cesium"]`,由
|
||||
@@ -224,6 +246,29 @@ tilt_y = TILT_JITTER * math.cos(index * 0.927295)
|
||||
`EXPORT_BASE_COLOR_OVERRIDES`、`EXPORT_EMISSION_OVERRIDES` 四张按材质名字符串匹配的表,
|
||||
但它们只是旧 `.blend` 兼容回退。新材质不要只写旧表。
|
||||
|
||||
### 交通信号倒计时字体
|
||||
|
||||
`assets/fonts/7LED-1.ttf` 是项目纳入版本管理的倒计时字体。它的字形是反向轮廓:可见
|
||||
的 LED 段是字体轮廓里的孔,而不是普通实心文字。因此 Blender 侧不能直接把文字曲线
|
||||
转成普通填充面(会得到“发光背景+黑色数字”),也不能依赖曲线描边。正确做法是在
|
||||
`blender/osmassets/traffic_signals.py` 中采样负 Bezier 轮廓,构造带前后盖面的挤出棱柱,
|
||||
使 LED 段成为实心发光几何。数字 mesh 必须先在 Blender 中单独渲染确认,再进入 Cesium
|
||||
导出;导出器出现“Could not calculate tangents”只表示这些无 UV 的纯色网格没有切线,
|
||||
不等同于倒计时集合为空或几何失败。
|
||||
|
||||
### 共享与拆分动态资产
|
||||
|
||||
倒计时数字按 phase group 共享 20 个数字 mesh(0-19),不要按信号灯复制网格。Cesium
|
||||
阶段必须生成三个动态 GLB:`traffic-signals-dynamic.glb` 只含灯珠,
|
||||
`traffic-signals-countdown-0.glb` 和 `traffic-signals-countdown-1.glb` 分别含两个相位组的
|
||||
倒计时节点。两个倒计时模型与灯珠模型使用同一个 `modelMatrix`,浏览器只切换当前数字
|
||||
节点,并给整个倒计时模型设置 `color` + `ColorBlendMode.REPLACE`,从而让字色跟随当前
|
||||
红/黄/绿相位且不增加每个灯的材质/几何副本。
|
||||
|
||||
导出器按完整材质名包含 `Countdown Group 0` / `Countdown Group 1` 判断分组;不能用
|
||||
集合名的精确相等比较,否则实际材质名 `Traffic Signal Countdown Group 0` 会被误判为
|
||||
空集合。
|
||||
|
||||
### 为什么新资产总是"发黑"
|
||||
|
||||
`export_cesium.py:38-54` 记录了这个反复出现的问题:
|
||||
@@ -255,9 +300,23 @@ tilt_y = TILT_JITTER * math.cos(index * 0.927295)
|
||||
| 直接 append vendored 资产的材质 | alpha-clip 缺失,树冠渲染成一块 |
|
||||
| 删掉"试过不行"的注释 | 下一个人重新踩同一个坑 |
|
||||
| 从 `scene-layers.js` 的 hex 换算 Blender 颜色 | 抹掉独立调过的配色 |
|
||||
| 用固定米/度比例投影经纬度 | GLB 与 Cesium Entity 随离锚点距离产生位置漂移 |
|
||||
| 加新资产不配 Cesium 调色 | Cesium 里显得发黑 |
|
||||
| 靠调 `FOLIAGE_EMISSION` 提亮植被 | 用错了旋钮,该调 albedo gain |
|
||||
| 在 `MATERIALS` 中间插入条目 | GLB 材质索引整体平移 |
|
||||
| 直接用 Cesium `Model.getMaterial().setValue()` 改普通 glTF PBR 材质 | 运行时数字仍保持原色,不能实现相位字色 |
|
||||
| 每个信号灯各自生成 0-19 全套倒计时 mesh | 节点和几何按信号数量线性膨胀;应按两个 phase group 共享 |
|
||||
|
||||
## 第三方资产导入的源文件边界
|
||||
|
||||
第三方 Blend 只属于一次性入库工具的输入,不能成为 `build:area` 或 Cesium 预览的运行时依赖。
|
||||
完成拆分后,仓库必须包含可直接消费的 glTF/bin、共享贴图、manifest 和人工预览;原始下载文件可
|
||||
以删除。导入工具应将源文件路径作为显式 `--source` 参数,manifest 最多保留源文件名作溯源,
|
||||
不能写死用户 Downloads 目录。
|
||||
|
||||
删除原始文件前必须确认入库资产已通过结构验证和人工预览;删除后若需重新导入,必须重新取得
|
||||
同一 Blend 与其外部贴图。`blender/tools/split_lowpoly_cars.py` 是这一约定的实例,不参与区域
|
||||
构建阶段。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
| | `generate_scene.py` | `export_cesium.py` |
|
||||
|---|---|---|
|
||||
| 行数 | 895 | 647 |
|
||||
| 调用 | `--background --factory-startup --python` | `--background --python` |
|
||||
| 调用 | `--background --factory-startup --python` | `--background --factory-startup --python` |
|
||||
| 输入 | `--osm` + `--geojson`(可选) | `--blend` |
|
||||
| 输出 | `--output`(.blend)、`--render`(.png) | `--glb`、`--metadata`(.json) |
|
||||
| 完成标记 | `SCENE_DONE` | `CESIUM_EXPORT_DONE` |
|
||||
@@ -70,9 +70,9 @@
|
||||
两个 stdout 标记是 [parity 契约](../guides/artifact-parity-guide.md)的一部分
|
||||
(`scripts/parity.js:121` 解析它们),**改动打印格式等于改动契约**。
|
||||
|
||||
### `--factory-startup` 只在 generate 阶段用
|
||||
### 后台阶段使用 `--factory-startup`
|
||||
|
||||
它屏蔽本机 Blender 的 preferences 和 addon,保证场景生成不受用户配置影响。
|
||||
它屏蔽本机 Blender 的 preferences 和 addon,保证场景生成与 Cesium 导出不受用户配置影响。
|
||||
副作用是脚本自己的目录不在 `sys.path` 上,所以两个入口开头都有那段
|
||||
`sys.path.insert` 样板 + `# noqa: E402`——**不是可以整理掉的坏味道**。
|
||||
|
||||
|
||||
@@ -165,6 +165,16 @@ def test_spacing_carries_across_segment_joins(self):
|
||||
**推论**:能挪进纯 Python 层的逻辑就挪。一个函数只要不碰 `bpy`,
|
||||
放进 `geom.py` 就立刻获得测试覆盖的资格。
|
||||
|
||||
### 以 MeshBatch 为边界的静态设施测试
|
||||
|
||||
少数 bpy 要素模块的价值在于确定性地向 `MeshBatch` 追加顶点与面,而不是调用 bpy API
|
||||
本身。对这类模块(例如 `osmassets/traffic_signals.py`),应在 `blender/tests/` 用假的
|
||||
`osmassets.mesh.MeshBatch` 导入模块,断言有效输入的装配数量和关键几何方向。这样可覆盖
|
||||
“校验函数意外返回空、所有要素被静默跳过”这一类错误,不必依赖可用的 Blender 进程。
|
||||
|
||||
测试必须在本文件列出的 `python3 -m unittest discover blender/tests` 命令下独立运行;测试
|
||||
文件自己添加 `blender/` 到 `sys.path`,不能依赖其他测试的导入顺序。
|
||||
|
||||
---
|
||||
|
||||
## 反模式
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
|
||||
```
|
||||
config/areas/<id>.json ← 你写的
|
||||
│ build-area.js: normalizeAreaConfig() 补默认值 + 推导输出路径
|
||||
│ scripts/lib/area-config.js: normalizeAreaConfig()
|
||||
│ 补默认值 + 推导输出路径
|
||||
▼
|
||||
<areaDir>/_pipeline/osm2streets-qgis.config.json ← 生成的,不要手改
|
||||
│
|
||||
@@ -50,9 +51,11 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
| `blenderApp` | | `/Applications/Blender.app` | |
|
||||
| `stages` | | 见下 | 各阶段默认开关 |
|
||||
| `qgis` | | 见下 | QGIS/osm2streets 旋钮 |
|
||||
| `turnLaneArrows` | | 见下 | 从 OSM `turn:lanes:*` 生成自定义车道箭头的发布开关 |
|
||||
| `osm2streets` | | 见下 | 透传给 osm2streets 的选项 |
|
||||
| `blender` | | 见下 | Blender 侧选项 |
|
||||
| `compress` | | 见下 | 显式 `compress` 阶段的 GLB 压缩选项 |
|
||||
| `budget` | | 见下 | 区域 GLB 性能与体量预算 |
|
||||
| `outputs` | | 从 `id` 推导 | 输出路径覆盖,逃生舱 |
|
||||
|
||||
**路径一律绝对**。`normalizeAreaConfig` 对每一项都做 `path.resolve`,相对路径会
|
||||
@@ -95,6 +98,15 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
> 这四个 arrow/corner 旋钮的默认值都是调出来的,**改之前先看 README 里记的理由**。
|
||||
> 尤其 `arrowOutlineSimplifyMeters`——调大会开始削箭头头部。
|
||||
|
||||
### `turnLaneArrows`
|
||||
|
||||
| 字段 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `enabled` | `false` | 仅在样张经用户确认后启用。启用时从 `turn:lanes:forward` / `turn:lanes:backward` 追加经过测试的自定义箭头;未测试素材永不参与映射。 |
|
||||
|
||||
该开关经 `normalizeAreaConfig()` 和 `writeDerivedConfig()` 传入 intermediates 阶段。必须用
|
||||
`??` 保留 `false`;不要将它改为按隐式标签或环境变量自动启用。
|
||||
|
||||
### `osm2streets`
|
||||
|
||||
原样透传给 `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 自己的默认值。
|
||||
|
||||
### `blender`
|
||||
@@ -131,6 +143,23 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
| `effort` | `80` | WebP 编码 effort,范围 `0..100` |
|
||||
| `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`(逃生舱)
|
||||
|
||||
默认全部从 `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`、
|
||||
`compressedFileStem`、`compressedGlb`、`compressedMetadata`、`compressedCesiumPreview`、
|
||||
`vehicleRoute`、`vehicleModel`、`pipelineDir`。
|
||||
`vehicleRoute`、`vehicleModel`、`pipelineDir`、`stageManifestDir`。
|
||||
|
||||
**优先改 `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` 可能是合法值)
|
||||
2. 只写两级 fallback:`raw.<group>?.<key> ?? 默认值`。
|
||||
**不要**制造新的顶层平铺别名——那三级写法是历史兼容,不是模式
|
||||
3. 若要传给低层脚本,加进 `writeDerivedConfig`(`:189`)的 `derivedConfig` 对象
|
||||
3. 若要传给低层脚本,加进 `writeDerivedConfig` 的 `derivedConfig` 对象
|
||||
4. 若是数值,在消费侧加 `Number.isFinite` + 范围校验,**在任何副作用之前**
|
||||
5. 更新 `config/examples/template.json`
|
||||
6. 更新本文档的字段表
|
||||
6. 若字段影响区域质量门,确认 `diagnose:area`、`check:area` 和 stage manifest 共用同一
|
||||
个评估 helper,不能在入口脚本各自比较阈值
|
||||
7. 更新本文档的字段表
|
||||
|
||||
若新字段产出新文件,同时在 `outputs` 里加一行路径推导。
|
||||
|
||||
|
||||
@@ -42,13 +42,14 @@ Blender 高度与线性颜色。两侧靠 `catalog.check_layers()` 对账集合
|
||||
|
||||
### 区域输出路径
|
||||
|
||||
输出路径只在 `scripts/build-area.js:74` 的 `normalizeAreaConfig()` 推导。
|
||||
低层脚本读取 `_pipeline/osm2streets-qgis.config.json`,不要重新读取
|
||||
输出路径只在 `scripts/lib/area-config.js` 的 `normalizeAreaConfig()` 推导。
|
||||
`scripts/build-area.js` 和 `scripts/diagnose-area.js` 都必须通过 `readAreaConfig()` 读取
|
||||
区域配置。低层脚本读取 `_pipeline/osm2streets-qgis.config.json`,不要重新读取
|
||||
`config/areas/*.json` 或在阶段函数里现场拼路径。
|
||||
|
||||
新增产物时,在 `normalizeAreaConfig` 的 `outputs` 里加一项,再按需写入
|
||||
`writeDerivedConfig()`(`build-area.js:189`)。这样 `intermediates`、`reimport`、
|
||||
`blender`、`cesium`、`preview` 仍然只通过磁盘产物耦合。
|
||||
新增产物时,在 `area-config.js` 的 `outputs` 里加一项,再按需写入
|
||||
`writeDerivedConfig()`。这样 `intermediates`、`reimport`、`blender`、`cesium`、
|
||||
`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/reimport-gpkg.js:93`
|
||||
- `scripts/compress-glb.js:16`
|
||||
- `scripts/diagnose-area.js:17`
|
||||
|
||||
语义一致:`--kebab-case value` 变 `kebabCase: "value"`,无值 flag 变字符串 `"true"`。
|
||||
|
||||
这份重复目前是可接受技术债,因为三个脚本都能独立运行。改其中一处解析语义时,不要顺手
|
||||
只改一份;要么保持三份一致,要么把"抽公共模块"作为独立重构并跑 parity。
|
||||
这份重复目前是可接受技术债,因为这些脚本都能独立运行。改其中一处解析语义时,不要顺手
|
||||
只改一份;要么保持全部入口一致,要么把"抽公共模块"作为独立重构并跑对应入口检查。
|
||||
|
||||
### JS 与 Python 的图层颜色
|
||||
|
||||
@@ -115,7 +118,7 @@ Cesium 导出调色也属于同一个材质声明:新场景把 `catalog.MATERI
|
||||
- OSM XML → `osmassets/osm.py:parse_osm()`
|
||||
- 米制几何 → `osmassets/geom.py`
|
||||
- GeoJSON 场景合并 → `scene-layers.js:mergeScene(getCollection)`
|
||||
- 区域配置 → `build-area.js:normalizeAreaConfig()`
|
||||
- 区域配置 → `scripts/lib/area-config.js:normalizeAreaConfig()`
|
||||
|
||||
如果确实需要新解析器,把输入格式、容错语义和调用者写清楚,并给纯 Python 逻辑补测试。
|
||||
|
||||
@@ -132,7 +135,7 @@ Cesium 导出调色也属于同一个材质声明:新场景把 `catalog.MATERI
|
||||
|
||||
不要因为代码相似就抽象:
|
||||
|
||||
- 三份 `parseArgs` 当前保持独立入口价值
|
||||
- 多份 `parseArgs` 当前保持独立入口价值
|
||||
- `ROAD_LAYERS` 与 `SCENE_LAYERS` 跨语言且承载不同字段
|
||||
- 每个要素模块各自调用 `clip_polygon` 是模块边界,不是可消除重复
|
||||
|
||||
@@ -156,6 +159,6 @@ Cesium 导出调色也属于同一个材质声明:新场景把 `catalog.MATERI
|
||||
| 新增一份图层名列表 | 回到旧的四份同步,漏改静默错栈 |
|
||||
| 把两套颜色表统一 | 破坏 QGIS 与 Blender 各自调过的视觉结果 |
|
||||
| 低层脚本直接读 `config/areas/*.json` | 两层配置边界失效 |
|
||||
| 只改一份 `parseArgs` 的语义 | 三个入口行为分裂 |
|
||||
| 只改一份 `parseArgs` 的语义 | 独立入口行为分裂 |
|
||||
| 把要素模块裁剪逻辑挪到调用方 | 不同要素的越界处理开始漂移 |
|
||||
| 只改 `export_cesium.py` 的旧回退表,不写 `MATERIALS[*]["cesium"]` | 新 `.blend` 不会携带 Cesium 导出契约 |
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
```
|
||||
config/areas/*.json JSON 数据
|
||||
↓ ①
|
||||
lib/area-config.js 区域配置归一化
|
||||
↓
|
||||
build-area.js Node(宿主机)
|
||||
↓ ② 派生配置 JSON
|
||||
build-osm2streets-qgis.js Node + osm2streets WASM
|
||||
@@ -26,7 +28,7 @@ cesium-preview.js 浏览器
|
||||
|
||||
| # | 边界 | 常见问题 |
|
||||
|---|---|---|
|
||||
| ① | 用户配置 → 归一化 | `??` vs `\|\|`、相对路径、字段整体替换 |
|
||||
| ① | 用户配置 → 归一化 | `??` vs `\|\|`、相对路径、字段整体替换、绕开共享归一化 |
|
||||
| ② | 两层配置 | 低层脚本读错配置源 |
|
||||
| ③ | Node → 外部进程 | 环境变量缺失、退出码与信号、0 字节产物 |
|
||||
| ④ | 文件交换 | 图层集合/顺序漂移、精度丢失 |
|
||||
@@ -44,6 +46,7 @@ cesium-preview.js 浏览器
|
||||
- [ ] 你在改任何被 `execFileSync` / `spawnSync` 调起的东西
|
||||
- [ ] 你在改 stage 的 stdout 打印
|
||||
- [ ] 你要新增一种在 Blender 里生成、要在 Cesium 里看的资产
|
||||
- [ ] Blender GLB 与 Cesium Entity、polyline 或 label 必须在地理位置上重合
|
||||
|
||||
---
|
||||
|
||||
@@ -81,6 +84,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:同一份事实存了四份
|
||||
|
||||
九个图层的顺序曾同时存在于 z_index 表、样式 JSON、QGIS 工程、README。
|
||||
@@ -130,6 +145,36 @@ cesium-preview.js 浏览器
|
||||
|
||||
**教训**:**跨阶段契约必须随产物保存;兼容旧产物的字符串回退也要被审查**。
|
||||
|
||||
### 坑 6:部分构建复用了过期的交通信号运行时数据
|
||||
|
||||
`intermediates` 才能根据当前 OSM 的 `highway=traffic_signals` 控制节点、
|
||||
`vehicle_stop_lines.geojson` 和 `intersection_surface.geojson` 初始化可编辑的
|
||||
`traffic_signal_assemblies.geojson`。此后该 GeoJSON 是 QGIS 编辑生命周期内的事实源;
|
||||
`traffic_signals.json` 只是严格校验后派生的运行时数据。Blender 将其中每个稳定的
|
||||
`signal_uid` 导出为静态设施、三个动态灯节点和一组倒计时节点,
|
||||
`scripts/lib/cesium-preview.js` 再按相同 id 控制它们。
|
||||
|
||||
因此执行 `blender,cesium,preview` 这类部分构建时,必须在 Blender stage 入口从当前
|
||||
`traffic_signal_assemblies.geojson` 重建运行时 JSON,但绝不能重新从 OSM 初始化位置,
|
||||
否则会覆盖 QGIS 中移动、旋转或禁用设施的编辑。该刷新由
|
||||
`scripts/build-area.js:buildBlenderScene()` 负责。
|
||||
|
||||
**教训**:**跨阶段运行时 JSON 必须在最早消费它的 stage 从当前权威产物重建;同时要
|
||||
区分“初始化来源”和“编辑后的事实源”,不能用早期输入覆盖人工编辑。**
|
||||
|
||||
### 坑 7:GeoJSON 内部正确,但 GLB 与 Cesium 路线仍然错位
|
||||
|
||||
巡航路线曾经与 osm2streets Driving polygon 中轴逐点吻合到厘米级,仍在最终预览中出现
|
||||
部分路段偏离车道中心。原因是路线由 Cesium 直接按 WGS84 经纬度放置,而 Blender GLB
|
||||
使用固定 `111320 m/deg` 的近似投影后再放到 WGS84 ENU 锚点。两套坐标仅在锚点附近
|
||||
重合,误差随距离增长。
|
||||
|
||||
**教训**:跨坐标运行时不能只验证源数据内部自洽。凡是 Blender GLB 与 Cesium Entity
|
||||
需要重合,必须检查 `GeoJSON -> Blender local ENU -> GLB modelMatrix` 与
|
||||
`GeoJSON -> Cesium Cartesian3` 的端到端契约,并在最终画面做横截面对齐验证。
|
||||
|
||||
→ [资产生成:WGS84 ENU 坐标契约](../blender/asset-generation.md#wgs84-enu-坐标契约)
|
||||
|
||||
---
|
||||
|
||||
## 加东西时的检查清单
|
||||
|
||||
@@ -23,15 +23,16 @@
|
||||
- [ ] 改 `blender/osmassets/catalog.py:28` 的 `ROAD_LAYERS` 或 `catalog.py:56` 的 `MATERIALS`
|
||||
- [ ] 改 `catalog.MATERIALS[*]["cesium"]`、`material["cesium_export"]` 或
|
||||
`export_cesium.py` 的旧材质名回退表
|
||||
- [ ] 改 `build-area.js:74` 的 `normalizeAreaConfig()` 或 `config/examples/template.json`
|
||||
- [ ] 改 `scripts/lib/area-config.js` 的 `normalizeAreaConfig()` 或 `config/examples/template.json`
|
||||
- [ ] 改任何 `execFileSync` / `spawnSync` 调起的脚本或参数
|
||||
- [ ] 改 `SCENE_DONE` / `CESIUM_EXPORT_DONE` 的 stdout 标记
|
||||
|
||||
### 读代码复用思考指南
|
||||
|
||||
- [ ] 准备新增第二份或第三份图层、材质、配置字段枚举
|
||||
- [ ] 修改三份重复的 `parseArgs` 之一:
|
||||
`build-area.js:50`、`build-osm2streets-qgis.js:153`、`reimport-gpkg.js:93`
|
||||
- [ ] 修改多份重复的 `parseArgs` 之一:
|
||||
`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`
|
||||
- [ ] 低层脚本想直接读取 `config/areas/*.json`,绕开派生配置
|
||||
@@ -66,7 +67,7 @@ grep -rn "要改的值" scripts blender config
|
||||
- 先看它有没有读到对应包的 index 和本目录指南
|
||||
- 对任何"行为没变"的结论,要求说明是否需要 parity;需要却没跑就是风险
|
||||
- 对任何"可以合并重复"的建议,先判断重复是不是刻意边界:
|
||||
三份 `parseArgs` 目前是可接受技术债,JS/Python 图层颜色则是刻意不同步
|
||||
多份 `parseArgs` 目前是可接受技术债,JS/Python 图层颜色则是刻意不同步
|
||||
- 对任何"加精度、加默认值、直接覆盖文件"的建议,回到真实代码注释验证;
|
||||
`reimport-gpkg.js:152-156` 和 `reimport-gpkg.js:11-13` 都是反直觉约束
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
|
||||
本仓库不是前端应用,而是 **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 侧事实源
|
||||
- `blender/osmassets/catalog.py:28` 的 `ROAD_LAYERS` 是 Blender 侧道路图层与材质顺序事实源
|
||||
- `scripts/lib/cesium-preview.js:1` 是无构建步骤的浏览器预览 IIFE
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
|
||||
---
|
||||
|
||||
## 三个入口脚本
|
||||
## 命令入口脚本
|
||||
|
||||
| 脚本 | 角色 | 入口方式 |
|
||||
|---|---|---|
|
||||
| `scripts/build-area.js` | **主入口**。读区域配置,按阶段调度 | `npm run build` / `build:area` |
|
||||
| `scripts/build-osm2streets-qgis.js` | intermediates 阶段的实现 | 由 build-area 调起;`npm run build:qgis` 可单跑 |
|
||||
| `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` 是校验工具,不属于构建链,见
|
||||
[产物一致性指南](../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.json`
|
||||
- `<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` /
|
||||
`metadataName` 和 loading 文案,不改 preview runtime。
|
||||
- 成功时 stdout 打印 `GLB_COMPRESS_DONE <json>`,包含压缩前后大小、image /
|
||||
@@ -97,6 +101,7 @@ npm run compress:glb -- --input in.glb --output out.glb [options]
|
||||
### 6. Tests Required
|
||||
|
||||
- `node --check scripts/compress-glb.js`
|
||||
- `npm run test:compress-glb`:断言压缩 metadata 只替换主资产,不丢失语义资产。
|
||||
- `node --check scripts/build-area.js`
|
||||
- 对目标区域跑一次 `npm run compress:glb -- ... --metadata --preview`
|
||||
- 对目标区域跑一次 `npm run build:area -- --stages compress`
|
||||
@@ -119,10 +124,733 @@ 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
|
||||
|
||||
路口信号设施需要同时被 Blender 主 GLB 和 Cesium 预览消费时,使用
|
||||
`<geojsonDir>/traffic_signal_assemblies.geojson` 是 GeoPackage/QGIS 中的附属可编辑点图层;
|
||||
`<geojsonDir>/traffic_signals.json` 是从它严格校验并派生的运行时产物。前者不属于九个
|
||||
`SCENE_LAYERS`,后者不进入 GeoPackage。
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
```bash
|
||||
npm run build:area -- --config config/areas/<area>.json --stages intermediates,blender,cesium,preview
|
||||
```
|
||||
|
||||
`normalizeAreaConfig()` 将默认路径归一化为:
|
||||
|
||||
```js
|
||||
area.outputs.trafficSignals
|
||||
// <areaDir>/osm2streets_web_out/traffic_signals.json
|
||||
```
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- `intermediates` 从 OSM control、停止线和路口面初始化 `traffic_signal_assemblies.geojson`,
|
||||
并将其作为附属点层导入 GeoPackage;完整重跑 intermediates 会像道路图层一样覆盖人工编辑。
|
||||
- `reimport` 必须与九个场景层一起暂存导出附属层,先校验全部信号要素,再替换任何输出。
|
||||
- `build-area.js:writeTrafficSignals()` 只从当前 `traffic_signal_assemblies.geojson` 重建运行时
|
||||
`traffic_signals.json`。Blender 入口也执行这一步,但不得重新从 OSM 初始化位置。
|
||||
- `blender` 和 `preview` 在启动前必须检查该文件存在;前者把静态设施写进 `05_Props`,
|
||||
后者只叠加动态灯珠、倒计时和车辆相位。
|
||||
- `traffic_signal_assemblies.geojson` 必须加入 GeoPackage/QGIS 工程,但不得加入
|
||||
`SCENE_LAYERS`、合并道路场景或栅格预览;`traffic_signals.json` 仍不得加入 GeoPackage。
|
||||
- `signal_uid` 必须由 control id、source way id 和相邻 arm node id 确定性生成;运行时 `id`
|
||||
使用该技术 id。`display_id` 可编辑且非空时唯一,修改它不得重命名 GLB 节点。
|
||||
- Point 几何是灯杆地面点;`stop_lon`/`stop_lat` 独立保存,移动杆件不得移动车辆停止点。
|
||||
- `enabled=false` 的要素保留在编辑层但不进入运行时 signals。
|
||||
- `layout.countdownLateralMeters` 等几何字段是 Blender/preview 的共同事实源;横向正值统一
|
||||
表示相对来车方向的右侧。不得在任一消费方用独立的负号约定替代它。
|
||||
- `layout.mastHeightMeters` 与 `layout.headCenterHeightMeters` 必须相等,表示横杆与灯壳的
|
||||
中心对齐;`lensVerticalOffsetsMeters` 以灯壳中心为基准,正值向上、负值向下。当前倒计时牌
|
||||
垂直偏移为 `0`,必须贴在横杆上而非悬挂。
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| `intermediates` 或 `reimport` 有合法编辑层 | 写出 `version` 与 `signals` 数组,即使数组为空 |
|
||||
| 直接运行 `blender` / `preview` 但锚点不存在 | 在启动外部工具前报 `Traffic signal anchors not found` |
|
||||
| `signal_uid` 缺失/重复、非空 `display_id` 重复、字段或 Point 无效 | 重导入在替换任何输出前失败 |
|
||||
| 用户仅修改 QGIS 后运行 `reimport` | 重新生成锚点,不沿用旧坐标 |
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good:完整构建后,GLB 的静态灯杆/灯壳和 Cesium 动态灯珠使用同一份 anchor。
|
||||
- Base:没有可用进口时写出空 `signals`,Blender 继续生成其余场景。
|
||||
- Bad:在 Cesium 中再次推导灯杆位置,或把 anchors 导入 GeoPackage;两者都会产生位置
|
||||
漂移或污染人工 QGIS 工作流。
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- `npm run test:budgets`:断言默认锚点路径位于 `osm2streets_web_out/`。
|
||||
- `npm run test:preview-assets`:断言预览配置仍传递相对锚点 URL。
|
||||
- 目标区域完整构建:确认 `traffic_signals.json` 与 Blender/preview stage manifest 均存在。
|
||||
- Blender 可运行环境:检查 `SCENE_DONE.traffic_signals`、主 GLB 的 `05_Props` 设施,
|
||||
以及 Cesium 动态叠层与静态灯壳对齐。
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
错误:
|
||||
|
||||
```js
|
||||
// preview 运行时再次从两份 GeoJSON 推导另一组锚点。
|
||||
const signals = buildTrafficSignals(stopLines, intersections);
|
||||
```
|
||||
|
||||
正确:
|
||||
|
||||
```js
|
||||
// Blender 与 preview 都消费 intermediates 写出的同一份文件。
|
||||
ensureFile(area.outputs.trafficSignals, "Traffic signal anchors");
|
||||
```
|
||||
|
||||
## 区域诊断命令
|
||||
|
||||
### 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.lanePolygons`
|
||||
- `inputs.network`
|
||||
- `inputs.intersectionSurface`
|
||||
- `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 参数解析
|
||||
|
||||
三个脚本各有一份**完全相同**的 `parseArgs`
|
||||
(`build-area.js:50`、`build-osm2streets-qgis.js:153`、`reimport-gpkg.js:93`):
|
||||
独立入口脚本各有一份同语义的 `parseArgs`:
|
||||
|
||||
- `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
|
||||
--kebab-case value → { kebabCase: "value" }
|
||||
@@ -136,9 +864,9 @@ npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a-compressed-
|
||||
- **不做校验**。未知参数被静默收集,缺失参数由下游的 `requireText` / `Number.isFinite`
|
||||
报错
|
||||
|
||||
> 这份重复是已知的、**当前被接受的**技术债:三个脚本要能各自独立运行,抽公共模块的
|
||||
> 收益还不抵引入一层依赖。改其中一份时**不要**顺手把另外两份重构掉——那是独立的决定,
|
||||
> 且会扩大 diff。真要抽取,三处一起改并跑 parity。
|
||||
> 这份重复是已知的、**当前被接受的**技术债:这些脚本要能各自独立运行,抽公共模块的
|
||||
> 收益还不抵引入一层依赖。改其中一份解析语义时**不要**只改一份;要么保持全部一致,
|
||||
> 要么把抽公共解析器作为独立重构并跑对应入口检查。
|
||||
|
||||
---
|
||||
|
||||
@@ -146,7 +874,7 @@ npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a-compressed-
|
||||
|
||||
```
|
||||
config/areas/<id>.json 用户写的区域配置(面向人)
|
||||
│ build-area.js: normalizeAreaConfig() —— 补默认值、推导全部输出路径
|
||||
│ lib/area-config.js: normalizeAreaConfig() —— 补默认值、推导全部输出路径
|
||||
▼
|
||||
area(内存中的归一化对象)
|
||||
│ writeDerivedConfig()
|
||||
@@ -158,14 +886,15 @@ build-osm2streets-qgis.js / reimport-gpkg.js
|
||||
```
|
||||
|
||||
**低层脚本从不读区域配置**,只读派生配置。这条边界让低层脚本能被独立调试,也让
|
||||
"输出路径怎么算出来的"只有一处答案(`normalizeAreaConfig`,`build-area.js:74`)。
|
||||
"输出路径怎么算出来的"只有一处答案(`scripts/lib/area-config.js` 的
|
||||
`normalizeAreaConfig`)。
|
||||
|
||||
派生配置**落在 `_pipeline/` 目录里而不是临时目录**——构建失败时它还在,可以直接拿去
|
||||
复现(`writeDerivedConfig`,`build-area.js:189`)。
|
||||
复现。
|
||||
|
||||
### 输出路径全部从 `id` 推导
|
||||
|
||||
`normalizeAreaConfig` 一次性算出 14 个输出路径(`build-area.js:87-102`),规则统一是
|
||||
`normalizeAreaConfig` 一次性算出输出路径,规则统一是
|
||||
`<outputRoot>/<id>/<fileStem>.<ext>`,`fileStem` 默认等于 `id`。
|
||||
|
||||
每一项都可以被 `outputs.*` 单独覆盖,写法固定:
|
||||
@@ -279,7 +1008,7 @@ parity 校验依赖 stage 的 stdout 标记来判断阶段是否跑到(如 `SC
|
||||
| 让 `reimport` / `preview` 能从配置文件默认开启 | 恢复动作变成常规行为 |
|
||||
| 新阶段忘了 `ensureFile` 前置校验 | 单跑时报底层堆栈而非人话 |
|
||||
| 改 stage 的 stdout 标记 | 静默破坏 parity 契约 |
|
||||
| 顺手把三份 `parseArgs` 合并 | 扩大 diff,且三个脚本的独立性是刻意的 |
|
||||
| 顺手把多份 `parseArgs` 合并 | 扩大 diff,且独立入口的独立性是刻意的 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -125,10 +125,10 @@ SCENE_LAYERS.forEach((layer, index) => {
|
||||
| 阶段 | 参数 | 出处 |
|
||||
|---|---|---|
|
||||
| `blender` | `--background --factory-startup --python generate_scene.py --` | `build-area.js:242-247` |
|
||||
| `cesium` | `--background --python export_cesium.py --` | `build-area.js:276-280` |
|
||||
| `cesium` | `--background --factory-startup --python export_cesium.py --` | `build-area.js:276-280` |
|
||||
|
||||
**`--factory-startup` 只在 generate 阶段用**:它屏蔽用户的 preferences 和 addon,
|
||||
保证场景生成不受本机 Blender 配置影响。export 阶段不带,因为它要读已经建好的 `.blend`。
|
||||
**两个后台阶段都使用 `--factory-startup`**:它屏蔽用户的 preferences 和 addon,
|
||||
保证场景生成与导出不受本机 Blender 配置影响。export 阶段仍会显式读取已经建好的 `.blend`。
|
||||
|
||||
`--` 之后才是脚本自己的参数,Blender 不解析它们。脚本侧用
|
||||
`sys.argv[sys.argv.index("--") + 1:]` 取。
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
| 改九个 osm2streets 图层(增/删/改顺序/改色) | [图层表](./layer-registry.md) ← **最容易出静默错误** |
|
||||
| 调 QGIS / GDAL / Blender 子进程 | [外部工具调用](./external-tools.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) |
|
||||
| 声称"纯重构,产物不变" | [产物一致性指南](../guides/artifact-parity-guide.md) |
|
||||
|
||||
@@ -23,7 +25,9 @@
|
||||
```
|
||||
config/areas/<id>.json
|
||||
│
|
||||
▼ build-area.js — normalizeAreaConfig() 推导全部输出路径
|
||||
▼ lib/area-config.js — normalizeAreaConfig() 推导全部输出路径
|
||||
│
|
||||
▼ build-area.js — 阶段调度
|
||||
_pipeline/osm2streets-qgis.config.json (派生配置)
|
||||
│
|
||||
├─[intermediates]─▶ build-osm2streets-qgis.js
|
||||
@@ -34,22 +38,30 @@ config/areas/<id>.json
|
||||
│ → osm2streets_scene.geojson + _scene_style.json
|
||||
│ → ogr2ogr 导入 <id>.gpkg
|
||||
│ → QGIS 生成 .qgz + -preview.png
|
||||
│ → _pipeline/stages/intermediates.manifest.json
|
||||
│
|
||||
├─[reimport]──────▶ reimport-gpkg.js (反向,与 intermediates 互斥)
|
||||
│ ogr2ogr 从 .gpkg 导出 → 校验 → 覆写 *.geojson
|
||||
│ → 重建 scene.geojson + scene_style.json
|
||||
│ → _pipeline/stages/reimport.manifest.json
|
||||
│
|
||||
├─[blender]───────▶ Blender + blender/generate_scene.py
|
||||
│ 读 .osm + osm2streets_web_out/
|
||||
│ → <id>.blend + <id>.png
|
||||
│ → _pipeline/stages/blender.manifest.json
|
||||
│
|
||||
├─[cesium]────────▶ Blender + blender/export_cesium.py
|
||||
│ 读 .blend → <id>.glb + <id>.json
|
||||
│ → 并自动执行 preview
|
||||
│ → _pipeline/stages/cesium.manifest.json
|
||||
│
|
||||
└─[preview]───────▶ 生成 <id>-cesium-preview.html
|
||||
+ 拷贝 lib/cesium-preview.{js,css}
|
||||
+ 车辆巡航路线与模型
|
||||
├─[preview]───────▶ 生成 <id>-cesium-preview.html
|
||||
│ + 拷贝 lib/cesium-preview.{js,css}
|
||||
│ + 车辆巡航路线与模型
|
||||
│ → _pipeline/stages/preview.manifest.json
|
||||
│
|
||||
└─[compress]──────▶ 生成并列压缩 GLB / metadata / preview
|
||||
→ _pipeline/stages/compress.manifest.json
|
||||
```
|
||||
|
||||
**阶段之间只通过磁盘产物耦合**,不传内存状态。这是单跑任意阶段能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 工程生成 |
|
||||
| `reimport-gpkg.js` | 179 | reimport:GeoPackage → GeoJSON 反向导出 |
|
||||
| `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) |
|
||||
| `normalize-lane-arrows.py` | 182 | 合并 osm2streets 的三角网箭头(跑在 QGIS Python 里) |
|
||||
| `parity.js` | 270 | 产物一致性校验驱动 |
|
||||
| `glb-digest.js` | 121 | GLB 结构摘要 |
|
||||
| `glb-digest.js` | 132 | GLB 结构摘要,CLI、build manifest 和诊断脚本共用 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# 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 环境。
|
||||
|
||||
---
|
||||
@@ -19,9 +20,9 @@
|
||||
```
|
||||
scripts/lib/cesium-preview.js ─── 原样 copyFileSync ──▶ outputs/<area>/cesium-preview.js
|
||||
scripts/lib/cesium-preview.css ─── 原样 copyFileSync ──▶ outputs/<area>/cesium-preview.css
|
||||
(build-area.js:328-335)
|
||||
(area-preview.js:writeCesiumPreviewSupportFiles)
|
||||
<area>-cesium-preview.html ─── 模板字符串生成 ────▶ 同目录
|
||||
(build-area.js:697)
|
||||
(area-preview.js:cesiumPreviewHtml)
|
||||
```
|
||||
|
||||
所以:**没有打包、没有转译、没有 npm 依赖、没有模块系统**。浏览器直接吃。
|
||||
@@ -40,18 +41,22 @@ const config = window.OSM_ASSET_PREVIEW_CONFIG || {}; // :4
|
||||
// config.areaId / .glbName / .metadataName / .routeName / .vehicleModelName
|
||||
```
|
||||
|
||||
生成侧在 `build-area.js:697 cesiumPreviewHtml()`,注入时**必须转义**:
|
||||
生成侧在 `scripts/lib/area-preview.js` 的 `cesiumPreviewHtml()`,注入时**必须转义**:
|
||||
|
||||
| 场景 | 用 |
|
||||
|---|---|
|
||||
| HTML 文本/属性 | `escapeHtml()`(`build-area.js:759`) |
|
||||
| `<script>` 里的 JSON | `escapeScriptJson()`(`:767`) |
|
||||
| HTML 文本/属性 | `escapeHtml()` |
|
||||
| `<script>` 里的 JSON | `escapeScriptJson()` |
|
||||
|
||||
`|| {}` 的兜底不能删——它让 JS 在没有配置块时也不至于在第一行就崩。
|
||||
|
||||
**加一个新的可配置项**:`cesiumPreviewHtml()` 里加进注入的 JSON,JS 侧从 `config` 读,
|
||||
两边都要动。
|
||||
|
||||
`build-area.js` 只保留 GLB / metadata 依赖检查、写入顺序和 preview manifest ownership;
|
||||
不要把 HTML 模板、runtime copy 或转义实现移回阶段调度器。路线 JSON 与车辆 glTF 分别由
|
||||
`vehicle-route.js` 和 `vehicle-model.js` 生成,二者都是不启动外部工具的 Node 模块。
|
||||
|
||||
---
|
||||
|
||||
## 加载流程
|
||||
@@ -107,6 +112,9 @@ setLoadingMessage("Preparing view")
|
||||
window.osmPreview = { viewer, metadata, placement, assets, cruise, cameras };
|
||||
```
|
||||
|
||||
预览加载的生成式 JSON(路线和交通信号)使用 `fetch(..., { cache: "no-store" })`,因为
|
||||
这些文件保持稳定文件名但会被单独重生成;浏览器不得继续显示旧的巡航路线。
|
||||
|
||||
调试和无头检查都靠它。**加新的顶层对象就往这里挂**,不要再开新全局。
|
||||
|
||||
---
|
||||
@@ -196,6 +204,87 @@ GLB 停留在**局部 ENU 坐标系**(X 东、Y 北、Z 上),靠伴生 JSO
|
||||
|
||||
`scenePlacement(metadata)`(`:131`)负责这一步。**改动导出侧的坐标约定必须同步改这里。**
|
||||
|
||||
## 交通信号动态覆盖层
|
||||
|
||||
metadata 的动态资产契约如下:
|
||||
|
||||
- `category="dynamic"`:灯珠节点,继续按相位切换红/黄/绿 lens 的 `show`。
|
||||
- `category="countdown"` 且 `phaseGroup` 为 `0` 或 `1`:对应相位组的倒计时模型;模型内
|
||||
共享 20 个数字节点,不按每个信号复制数字。
|
||||
|
||||
三个模型必须使用完全相同的 `placement.modelMatrix`。倒计时颜色只能通过模型级
|
||||
`model.color` 配合 `Cesium.ColorBlendMode.REPLACE` 设置;普通 glTF PBR 材质的
|
||||
`getMaterial().setValue()` 在本项目验证中不能可靠修改运行时字色,禁止作为实现路径。
|
||||
倒计时数字的显示逻辑只改变当前数字节点的 `show`,颜色由该 phase group 的当前灯色
|
||||
统一设置。加载失败属于部分资产失败:应进入诊断而不清空主场景。
|
||||
|
||||
## 语义检查资产
|
||||
|
||||
### 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
|
||||
@@ -234,4 +323,5 @@ python3 -m http.server 8765
|
||||
|
||||
- [CLI 与阶段](../pipeline/cli-and-stages.md):`cesium` / `preview` 阶段如何生成这些文件
|
||||
- [资产生成](../blender/asset-generation.md):GLB 里的材质为什么要单独调色
|
||||
- [车辆连续路线](vehicle-routes.md):路线 JSON、转向选择与预览标签契约
|
||||
- README「实验:车辆巡航」节:面向使用者的说明
|
||||
|
||||
120
.trellis/spec/preview/vehicle-routes.md
Normal file
120
.trellis/spec/preview/vehicle-routes.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# 车辆连续路线
|
||||
|
||||
## 1. Scope / Trigger
|
||||
|
||||
适用于 `scripts/lib/vehicle-route.js` 生成的路线 JSON,以及
|
||||
`scripts/lib/cesium-preview.js` 对车辆巡航路线的读取与展示。
|
||||
|
||||
触发:修改路线生成、OSM 转向标签解析、车辆选择菜单或路线 JSON 字段时。
|
||||
路线仅用于 Cesium 验证预览,不构成交通仿真或法规级导航。
|
||||
|
||||
## 2. Signatures
|
||||
|
||||
```js
|
||||
buildVehicleRoute(osmPath, lanePolygonsPath, networkPath, intersectionSurfacePath) => {
|
||||
source, laneSource, networkSource, intersectionSource, bounds, generatedAt, speedMetersPerSecond, loop,
|
||||
routes, segments, diagnostics
|
||||
}
|
||||
|
||||
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`、`laneSegments` 与 `connectors`。道路区间来自匹配的 Driving lane polygon 中轴。
|
||||
- 路线拓扑以 `network.json` 的 internal road 和 intersection 为准;禁止把整个 OSM way 直接当作一条不可分割 edge。
|
||||
- connector 必须绑定同一个 internal intersection,并位于对应 `intersection_surface.geojson` 内或允许的边界容差内;越界时拒绝候选路线。
|
||||
- preview 必须将 `lane_polygons.geojson`、`network.json` 和 `intersection_surface.geojson` 作为强制输入;缺失或无效时在写产物前失败。
|
||||
- route 经纬度由 Cesium 按 WGS84 直接放置;最终道路 GLB 必须由 WGS84 ECEF→ENU
|
||||
`Projector` 生成。禁止以固定米/度近似投影道路,否则即使 route 与 lane polygon
|
||||
完全一致,最终画面仍会随离锚点距离产生横向偏移。
|
||||
- 单条路线无法可靠匹配时跳过并写结构化 `diagnostics`,不得回退固定或默认车道宽度。
|
||||
- 有 `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`,因为一条路线可跨越多个道路名称。
|
||||
- 预览只显示当前下拉框选中车辆的 route polyline,避免多条闭环轨迹在路口重叠造成错误的偏移判断。
|
||||
|
||||
## 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 下拉标签显示编号、长度与动作统计。
|
||||
- 修改地理投影时必须运行 `blender,cesium,preview`,不能只重跑 preview;最终检查青色路线
|
||||
到黄色中心线及道路边缘的横截面距离,确认两侧路线分别位于各自车道中心。
|
||||
|
||||
## 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"));
|
||||
```
|
||||
|
||||
## 信号动态 GLB 契约
|
||||
|
||||
`traffic_signals.json` 的 `pose.*` 是 Blender 静态设施、动态灯珠和倒计时共享的锚点。Blender
|
||||
把发光灯珠导出为独立的 `*-traffic-signals-dynamic.glb`,preview 必须使用与主 GLB 相同的
|
||||
`scenePlacement(metadata).modelMatrix` 加载它;Cesium 仅按命名灯珠节点切换 `show`。倒计时
|
||||
例外:它由 Cesium Entity 从 `pose.countdown` 的 ENU 坐标与面向直接绘制,避免 glTF 轴变换
|
||||
反转七段字形。
|
||||
|
||||
动态表面不能与静态镜片或倒计时外壳共面:镜片和数码管必须沿本地 `face` 轴前移
|
||||
`(static_depth + dynamic_depth) / 2 + epsilon`。这是模型局部几何关系,不是经纬度修正;
|
||||
否则静态网格会通过深度测试遮住发光状态,表现为灯不切换或数字不可见。
|
||||
|
||||
错误:在 Cesium 用 `fromDegrees`/Entity 重新计算动态设施,或将动态网格中心与静态表面中心
|
||||
重合。
|
||||
|
||||
正确:Blender 生成命名节点 `TrafficSignalDynamic_<signal-id>_<state>`;浏览器在同一 model
|
||||
matrix 下加载该 GLB,并只切换这些灯珠节点。倒计时 Entity 使用 `pose.countdown` 的经纬度、
|
||||
高度、`faceHeadingDegrees` 生成与牌面相同的 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,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,44 @@
|
||||
# 设计:Cesium 路口信号灯可视化
|
||||
|
||||
## 分层边界
|
||||
|
||||
信号锚点不是 QGIS 业务图层:它不进入 GeoPackage、场景九图层或 QGIS 工程。`intermediates`
|
||||
在 `osm2streets_web_out/traffic_signals.json` 写出它;Blender 和 preview 都消费同一份文件。
|
||||
|
||||
Blender 的 `05_Props` 负责所有静态设施:灯杆、横杆、灯头、熄灭灯珠和倒计时牌外壳。它们
|
||||
随 `.blend` 和主 GLB 导出,成为正式场景的一部分。Cesium 预览只负责动态覆盖层:当前相位的
|
||||
发光灯珠、七段倒计时数字、Signals 显示开关,以及车辆在红黄灯前的等待。这样静态造型只有
|
||||
一份,浏览器不再用临时 Entity 重复搭建设施。
|
||||
|
||||
## 锚点与几何
|
||||
|
||||
中间阶段已有 `vehicle_stop_lines` 和 `intersection_surface`。信号锚点生成器将停止线的
|
||||
中点作为进口横向基准,依据相交路口面和停止线两端的方向判定来车朝向;灯杆置于停止线的
|
||||
侧后方、道路外缘一侧,且朝向来车。没有可唯一关联的路口面、停止线过短或无法确定外侧时,
|
||||
不输出锚点。
|
||||
|
||||
输出保存灯杆坐标、对应停止线坐标、朝向、稳定 ID 和 `layout` 几何契约,避免 Blender 或
|
||||
浏览器重新解析 GeoJSON 或 OSM。`layout` 包含灯头、灯珠、横杆和倒计时牌的尺寸与偏移;
|
||||
其中横向偏移以车辆行驶方向为基准,正值表示驾驶员右侧;`mastHeightMeters` 与
|
||||
`headCenterHeightMeters` 是横杆和灯壳的共同中心高度。三颗灯珠相对灯壳中心排列,而倒计时牌
|
||||
的垂直偏移为零、固定在横杆上。
|
||||
Blender 使用
|
||||
`Projector.xy((longitude, latitude))` 转成本地米制坐标,并以 `headingDegrees` 旋转;Cesium
|
||||
以同一字段派生地理位置与灯面朝向。
|
||||
每个路口按相对进口方向分为两组对向相位;统一循环绿、黄、全红切换。
|
||||
|
||||
预览将每条路线按累计米数投影到信号停止线。只有距离阈值内且行驶方向与信号进口一致的
|
||||
匹配才形成停车点。车辆的累计里程由 `clock.onTick` 推进;下一停车点为红或黄时将里程夹在
|
||||
停止线前,绿灯后从同一位置继续。没有停车点的路线保留原速度循环。
|
||||
|
||||
## 预览交互
|
||||
|
||||
预览加载锚点 JSON 失败时记录 warning,场景、路线与车辆仍可用。加载成功时信号灯默认显示,
|
||||
并在现有 View 控件中提供独立 Signals 复选框。动态灯珠和数字以 emissive 材质区分点亮和
|
||||
熄灭状态,不依赖环境光;静态部分由 GLB 的低多边形 MeshBatch 几何承载。
|
||||
|
||||
## 风险与回退
|
||||
|
||||
信号灯是示意设施,不能视为 OSM 语义。复杂交叉口或人工修补后的不完整标线宁可跳过,也不
|
||||
摆放到行车道中央。回退时删除附属锚点输出、`05_Props` 信号构件和 Cesium 动态覆盖层即可;
|
||||
QGIS、道路/标线和既有路线 JSON 不受影响。
|
||||
@@ -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,42 @@
|
||||
# 实施计划:Cesium 路口信号灯可视化
|
||||
|
||||
1. 复用 `traffic-signals.js` 的锚点推导,在 `intermediates` 阶段把附属
|
||||
`traffic_signals.json` 写入 `geojsonDir`;不修改 `SCENE_LAYERS`、GeoPackage 或 QGIS。
|
||||
2. 在 `blender/osmassets/traffic_signals.py` 用共享低多边形 MeshBatch 几何装配静态信号设施,
|
||||
并在 `generate_scene.py` 读取锚点、投影坐标、置入 `05_Props` 和写入计数。
|
||||
3. 在 `catalog.MATERIALS` 末尾追加信号设施材质和 Cesium 导出补偿,保证新 GLB 在 Cesium
|
||||
中不会发黑或材质索引漂移。
|
||||
4. 让 preview 直接读取 intermediates 的锚点文件;删除 Cesium 对立杆、横杆、灯头、熄灭灯珠
|
||||
和外壳的构造,只保留精确对齐的动态灯珠、数字与既有相位/车辆等待。
|
||||
5. 更新 Node 与纯 Python 测试,构建目标区域并用 parity 检查 GLB 差异只包含预期新增设施;
|
||||
人工核对主 GLB 静态构件和 preview 动态覆盖层。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
npm run test:preview-assets
|
||||
node --check scripts/lib/cesium-preview.js
|
||||
python3 -m unittest discover blender/tests
|
||||
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages intermediates,blender,cesium,preview
|
||||
git diff --check
|
||||
```
|
||||
|
||||
## 已验证决策与故障记录
|
||||
|
||||
- `assets/fonts/7LED-1.ttf` 已纳入版本管理;该字体是反向 LED 轮廓。直接填充文字会得到
|
||||
绿色背景/黑色字,曲线描边会得到空心描边字;当前实现改为采样负轮廓并构造实心挤出
|
||||
棱柱,独立 Blender 渲染已确认数字形状正确。
|
||||
- 为支持相位字色且避免复制几何,动态导出拆为灯珠 GLB + countdown group 0/1 两个 GLB。
|
||||
预览三者共用同一 placement,倒计时模型使用 `colorBlendMode=REPLACE` 做模型级换色。
|
||||
- 失败方案:Cesium `Model.getMaterial().setValue()` 修改普通 glTF PBR uniforms,用户实测
|
||||
数字仍为绿色,不能恢复使用。
|
||||
- 失败陷阱:导出器的 `groups` 是完整材质名集合,不能检查精确字符串 `"Countdown Group 0"`;
|
||||
必须用 `"Countdown Group 0" in name` 的包含判断,否则导出阶段报
|
||||
`Traffic countdown collection 0 is empty`。
|
||||
- glTF 的 `Could not calculate tangents` 警告来自无 UV 的纯色倒计时 mesh;只要三个 GLB
|
||||
均成功生成,它不是阻断错误。
|
||||
|
||||
## 回退
|
||||
|
||||
删除静态信号 Blender 模块、附属锚点输出和浏览器动态覆盖层;QGIS、道路/标线和既有路线
|
||||
JSON 不受影响。
|
||||
@@ -0,0 +1,55 @@
|
||||
# Cesium 路口信号灯可视化
|
||||
|
||||
## Goal
|
||||
|
||||
在 Cesium 预览中加入可直接观察的路口交通信号灯,使已完成的道路、停止线、斑马线、
|
||||
转向箭头和车辆巡航有清晰的交通控制参照。车辆应在对应停止线前遵守同一套相位,并在
|
||||
绿灯放行后继续巡航。
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- `scripts/lib/cesium-preview.js` 目前只加载场景资产、巡航路线与车辆;没有信号灯图层、
|
||||
模型或相位状态。
|
||||
- 路线 JSON 包含连续的左、右、直转向曲线,但没有路口 ID 或信号相位字段。
|
||||
- 现有停止线、斑马线和转向箭头已由区域构建确认,且用户要求暂不触及 QGIS 的人工修补
|
||||
边界、道路生成与既有连续路线逻辑。
|
||||
- Cesium 当前同时绘制灯杆、横杆、灯头、熄灭灯珠、倒计时外壳,以及随相位变化的灯珠和
|
||||
七段数字。这使静态设施只存在于验证层,难以随主场景维护。
|
||||
- `05_Props` 集合已经进入主 `.blend` 和 Cesium GLB;`traffic-signals.js` 已是停止线和
|
||||
路口面推导信号锚点的唯一事实源。
|
||||
|
||||
## Requirements
|
||||
|
||||
- 在道路进口侧、停止线附近呈现简洁而可辨识的交通信号灯,并在不遮挡车道标线的前提下
|
||||
面向来车方向。
|
||||
- 红、黄、绿灯应有明确的点亮状态和周期性相位切换,使静态截图与实时预览都能看出其作用。
|
||||
- 信号灯应能独立显示/隐藏,遵循现有 Cesium 控件的克制设计语言。
|
||||
- 车辆接近已匹配的停止线时,红灯和黄灯必须停车,绿灯继续通行;相位切换后应自然恢复
|
||||
移动,不得瞬移到路口另一侧。
|
||||
- 等待逻辑只模拟单车对信号的响应,不做车辆间跟车距离、排队或碰撞避让。
|
||||
- 首版以主要路口的程序化示意灯覆盖为准:从已生成的停止线和路口面推导进口,缺少可靠
|
||||
几何锚点时跳过。它不宣称复刻 OSM 中逐节点标注的真实信号设施。
|
||||
- 灯杆、横杆、灯壳、熄灭灯珠和倒计时牌外壳必须成为 `05_Props` 中的静态场景几何,随
|
||||
主 GLB 导出;Cesium 只保留与这些几何严格对齐的发光灯珠、七段倒计时数字、显示开关和
|
||||
车辆相位等待。
|
||||
- 信号锚点必须在 `intermediates` 阶段写入 `osm2streets_web_out/traffic_signals.json`,由
|
||||
Blender 与 preview 共用;不得纳入 `SCENE_LAYERS`、GeoPackage 或 QGIS 工程。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 仅运行 `intermediates,blender,cesium` 后,主 GLB 已包含位于路口进口侧的灯杆、横杆、
|
||||
灯头、熄灭灯珠和倒计时牌外壳;静态几何位置与停止线/
|
||||
斑马线关系清楚,且不会漂浮在道路中央或遮挡车道箭头。
|
||||
- [ ] 灯组以可见状态呈现红、黄、绿的相位切换;未点亮灯珠明显较暗。
|
||||
- [ ] 页面提供独立的 Signals 显示开关,关闭后不影响场景、路线与车辆。
|
||||
- [ ] 匹配到信号停止线的巡航车辆会在红/黄灯时停在线前,绿灯后连续通过;没有可靠匹配的
|
||||
路线保持原有循环巡航,不因信号锚点缺失而卡住。
|
||||
- [ ] 不修改 QGIS 工程、道路/标线生成或既有路线 JSON 的基本契约。
|
||||
- [ ] preview 重建后,动态灯珠和数字与 GLB 中相应灯头、倒计时外壳对齐,且无 Cesium
|
||||
重复的杆、横杆、灯壳或外壳实体。
|
||||
|
||||
## 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": "cesium-traffic-signals",
|
||||
"name": "cesium-traffic-signals",
|
||||
"title": "Cesium 路口信号灯可视化",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-05",
|
||||
"completedAt": "2026-08-06",
|
||||
"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": {}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Check stage ownership, diagnostics, and traffic signal contract compliance"}
|
||||
{"file":".trellis/spec/pipeline/external-tools.md","reason":"Check atomic reimport behavior and external-tool handling"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"Check full editable-layer to runtime JSON to Blender/Cesium data flow"}
|
||||
{"file":".trellis/spec/guides/artifact-parity-guide.md","reason":"Check intended and unintended scene/GLB structural differences"}
|
||||
{"file":".trellis/spec/blender/testing.md","reason":"Check appropriate pure and Blender validation coverage"}
|
||||
@@ -0,0 +1,100 @@
|
||||
# Design: QGIS Traffic Signal Overrides
|
||||
|
||||
## Architecture
|
||||
|
||||
Introduce a separate auxiliary-edit-layer registry rather than adding traffic signals to `SCENE_LAYERS`. The initial registry contains one layer:
|
||||
|
||||
```text
|
||||
traffic_signal_assemblies.geojson
|
||||
geometry: Point (pole ground position, EPSG:4326)
|
||||
properties: stable identity, source identity, heading, phase, reach, stop point, enabled, z offset
|
||||
```
|
||||
|
||||
The existing runtime file remains:
|
||||
|
||||
```text
|
||||
traffic_signals.json
|
||||
version/layout/signals[] with full pose.* data
|
||||
```
|
||||
|
||||
The editable GeoJSON is the placement source; the runtime JSON is a derived consumer artifact.
|
||||
|
||||
## Data Flow
|
||||
|
||||
```text
|
||||
OSM controls + topology + stop lines + intersections
|
||||
|
|
||||
intermediates only
|
||||
v
|
||||
traffic_signal_assemblies.geojson
|
||||
|
|
||||
import into GeoPackage
|
||||
|
|
||||
edit in QGIS
|
||||
|
|
||||
reimport
|
||||
v
|
||||
traffic_signal_assemblies.geojson
|
||||
|
|
||||
validate + derive pose
|
||||
v
|
||||
traffic_signals.json
|
||||
/ \
|
||||
Blender preview/Cesium
|
||||
```
|
||||
|
||||
`blender` reruns the final validation/derivation arrow from the editable GeoJSON so derived JSON cannot be stale, but it never reruns the OSM initialization arrow.
|
||||
|
||||
## Editable Feature Contract
|
||||
|
||||
Recommended properties:
|
||||
|
||||
| Property | Type | Ownership |
|
||||
|---|---|---|
|
||||
| `signal_uid` | string | generated, immutable technical identity |
|
||||
| `display_id` | string | user-editable unique label/number |
|
||||
| `control_id` | string | generated OSM control id |
|
||||
| `approach_id` | string | generated physical approach identity |
|
||||
| `source_way_id` | string | generated matching/diagnostic field |
|
||||
| `heading_deg` | number | user-editable assembly facing direction |
|
||||
| `phase_group` | integer 0/1 | user-editable current two-phase group |
|
||||
| `mast_reach_m` | positive number | user-editable arm reach |
|
||||
| `stop_lon`, `stop_lat` | finite numbers | generated vehicle stop point, preserved when pole moves |
|
||||
| `enabled` | boolean/integer | user-editable suppression flag |
|
||||
| `z_offset_m` | finite number | user-editable vertical adjustment |
|
||||
|
||||
Point geometry is the pole longitude/latitude. The runtime `id` should be derived from `signal_uid`, not display numbering, so changing `display_id` does not rename GLB nodes or break preview control.
|
||||
|
||||
## Stable Identity
|
||||
|
||||
Extend parsed OSM arm data to retain enough deterministic source identity (control node, way, adjacent arm direction/node). Generate a technical key from those source values. Do not use sorted array index or rounded heading as the primary key.
|
||||
|
||||
If topology changes on a future `intermediates` run, the rebuilt GeoPackage may produce new identities. This is consistent with current road-edit lifecycle and is explicitly out of scope for MVP migration. Validation still reports duplicate identities and malformed source fields.
|
||||
|
||||
## QGIS Integration
|
||||
|
||||
- Add an auxiliary layer definition separate from the nine render layers.
|
||||
- Import it into the same GeoPackage after render layers.
|
||||
- Include it in the generated project but exclude it from the merged scene and 2D raster preview unless deliberately enabled for editing visibility.
|
||||
- Use a point marker plus rotated direction indicator driven by `heading_deg` and label by `display_id`, falling back to `signal_uid`.
|
||||
- Configure read-only/editor widgets where practical: technical/source ids read-only; phase group constrained to 0/1; numeric fields constrained to valid ranges; enabled as checkbox.
|
||||
|
||||
## Reimport and Atomicity
|
||||
|
||||
Extend the reimport layer manifest to include auxiliary editable layers while keeping render-scene merge derived only from `SCENE_LAYERS`. Export every layer into staging, parse and validate all editable features, then replace output files. Runtime JSON is written only after the staged auxiliary layer passes validation.
|
||||
|
||||
## Compatibility
|
||||
|
||||
- The next `intermediates` run bootstraps existing areas; no old JSON migration is required.
|
||||
- Main `.blend`/GLB geometry changes intentionally when a QGIS edit changes a signal.
|
||||
- Dynamic and countdown GLBs continue using runtime signal ids, now stable across ordinary reimport edits.
|
||||
- Current two-phase simulation remains unchanged.
|
||||
|
||||
## Risks and Controls
|
||||
|
||||
- **OSM way splitting changes source ids:** accepted across a full intermediates rebuild; ordinary reimport is stable.
|
||||
- **QGIS boolean/string coercion:** normalize known GDAL representations before strict validation and test the round-trip output.
|
||||
- **Accidental source-field editing:** mark technical fields read-only in QGIS and validate identity format during reimport.
|
||||
- **Partial overwrite on invalid auxiliary data:** retain the existing staging-before-replace discipline.
|
||||
- **Old spec conflict:** update pipeline specs that currently forbid traffic-signal anchors in GeoPackage, clarifying the distinction between editable assembly points and derived runtime anchors.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Traffic signal stage ownership, reimport lifecycle, manifests, and current anchor contract"}
|
||||
{"file":".trellis/spec/pipeline/layer-registry.md","reason":"Keep the auxiliary editable layer separate from the nine render layers and preserve their order"}
|
||||
{"file":".trellis/spec/pipeline/external-tools.md","reason":"GeoPackage import/export and staging-before-replace requirements"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"OSM to GeoJSON/GPKG to Blender/Cesium contract review"}
|
||||
{"file":".trellis/spec/blender/asset-generation.md","reason":"Signal pose and dynamic asset generation constraints"}
|
||||
{"file":".trellis/spec/preview/vehicle-routes.md","reason":"Vehicle stop coordinates and runtime signal data coupling"}
|
||||
@@ -0,0 +1,53 @@
|
||||
# Implementation Plan: QGIS Traffic Signal Overrides
|
||||
|
||||
## 1. Contracts and Pure Logic
|
||||
|
||||
- [x] Add an auxiliary editable-layer definition without modifying `SCENE_LAYERS` ordering.
|
||||
- [x] Extend OSM arm parsing with deterministic approach identity inputs.
|
||||
- [x] Split traffic-signal logic into automatic editable-feature generation, feature validation/normalization, and runtime pose derivation.
|
||||
- [x] Use stable technical ids for runtime signal ids; keep `display_id` as editable metadata.
|
||||
- [x] Add pure Node tests for T/cross counts, stable ids, movement/heading reconstruction, disabled features, duplicate ids, and invalid values.
|
||||
|
||||
## 2. Intermediates and QGIS
|
||||
|
||||
- [x] Write `traffic_signal_assemblies.geojson` after stop-line/intersection outputs are stable.
|
||||
- [x] Import the auxiliary point layer into the GeoPackage after the nine render layers.
|
||||
- [x] Extend generated QGIS project code with point/direction styling, labels, and field widgets/constraints.
|
||||
- [x] Confirm the auxiliary layer is excluded from merged road scene ordering and raster preview behavior.
|
||||
|
||||
## 3. Reimport and Stage Ownership
|
||||
|
||||
- [x] Extend `reimport-gpkg.js` to discover/export render and auxiliary layers through staging.
|
||||
- [x] Validate the staged editable layer before replacing any output artifact.
|
||||
- [x] Rebuild runtime `traffic_signals.json` from editable GeoJSON after `intermediates`, `reimport`, and at Blender entry.
|
||||
- [x] Remove Blender-entry OSM placement regeneration so QGIS edits remain authoritative.
|
||||
- [x] Extend stage manifests and diagnostics with auxiliary input/output records and feature counts.
|
||||
|
||||
## 4. Cross-Layer Consumers
|
||||
|
||||
- [x] Preserve `display_id` and stable runtime ids through Blender and Cesium metadata where useful.
|
||||
- [x] Verify static signal objects, dynamic lenses, countdown nodes, and vehicle stop behavior all consume the same enabled runtime records.
|
||||
- [x] Update pipeline specifications to replace the old prohibition with the editable-layer/derived-runtime distinction.
|
||||
|
||||
## 5. Validation
|
||||
|
||||
- [x] Run Node syntax checks and focused unit tests.
|
||||
- [x] Run existing preview-assets, preflight, budget, and relevant pipeline tests.
|
||||
- [x] Run `intermediates` and inspect the GeoPackage/QGIS project feature schema and styling.
|
||||
- [ ] Make a controlled QGIS edit to one signal (display id, point, heading), run `reimport,blender,cesium,preview`, and verify only the intended assembly changes. (`reimport` and Blender passed; Cesium/preview refresh was not repeated.)
|
||||
- [x] Confirm an invalid/duplicate edit fails before overwriting valid outputs.
|
||||
- [ ] Inspect Blender/Cesium structural digests and Safari preview for T and cross junctions. (Blocked this run by Blender 4.5.12 Metal startup SIGSEGV before project Python.)
|
||||
|
||||
## Risky Files / Rollback Points
|
||||
|
||||
- `scripts/lib/traffic-signals.js`: identity and pose contract; land pure tests before pipeline integration.
|
||||
- `scripts/build-osm2streets-qgis.js`: GeoPackage recreation and generated QGIS Python; verify auxiliary import independently before styling.
|
||||
- `scripts/reimport-gpkg.js`: atomic overwrite boundary; preserve staging semantics.
|
||||
- `scripts/build-area.js`: stage ownership; ensure Blender derives from editable GeoJSON rather than overwriting it.
|
||||
- `blender/osmassets/traffic_signals.py` and preview runtime should require minimal or no geometry changes; unexpected edits here indicate contract leakage.
|
||||
|
||||
## Review Gate Before Start
|
||||
|
||||
- [ ] User approves the final planning summary.
|
||||
- [ ] `prd.md`, `design.md`, and `implement.md` agree on full editable layer ownership and out-of-scope intermediates persistence.
|
||||
- [ ] No unresolved product decision remains.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Debug Notes
|
||||
|
||||
## 2026-08-07 countdown node-name regression
|
||||
|
||||
Stable `signal_uid` values are intentionally descriptive and can exceed Blender's
|
||||
63-byte object-name limit. Using them directly in dynamic lens/countdown node
|
||||
names caused Blender to truncate names while Cesium looked up the untruncated
|
||||
strings. The countdown GLBs then exposed all digits without the runtime being
|
||||
able to hide the inactive values, appearing as overlapping/blurred numbers.
|
||||
|
||||
Runtime signal records now carry a deterministic short `nodeKey` (`ts_` plus
|
||||
the first 16 hex characters of SHA-256 of `signal_uid`). Blender uses it for
|
||||
dynamic object names and Cesium uses the same key for lookups. Preview keeps a
|
||||
fallback to `signal.id` for older metadata files.
|
||||
@@ -0,0 +1,67 @@
|
||||
# QGIS Traffic Signal Overrides
|
||||
|
||||
## Goal
|
||||
|
||||
Make every automatically generated vehicle traffic signal independently identifiable and editable in QGIS. A user must be able to assign a display number, move a pole, rotate its assembly, adjust supported placement attributes, run `reimport`, and have Blender and Cesium consume that edited result without OSM regeneration overwriting it.
|
||||
|
||||
## Background
|
||||
|
||||
- Current traffic signals are derived from OSM `highway=traffic_signals` controls, road topology, `vehicle_stop_lines.geojson`, and `intersection_surface.geojson` by `scripts/lib/traffic-signals.js`.
|
||||
- Current sequential ids such as `signal-1` depend on generation order and are not suitable as persistent edit identities.
|
||||
- Current `<geojsonDir>/traffic_signals.json` contains fully derived `pose.*` data but is deliberately excluded from the GeoPackage and QGIS project.
|
||||
- Existing road editing establishes the desired lifecycle: `intermediates` initializes a GeoPackage, the user edits it in QGIS, and `reimport` exports the edited data back to GeoJSON. Running `intermediates` again may discard manual edits; that behavior remains explicit and unchanged.
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1. Editable auxiliary layer
|
||||
|
||||
- `intermediates` must create a point FeatureCollection containing one feature per physical signal assembly and import it into the area GeoPackage.
|
||||
- The generated QGIS project must expose the layer with a visible directional symbol and a label suitable for identifying individual signals.
|
||||
- The auxiliary layer must not join `SCENE_LAYERS` or the merged road scene because it is an editing/control artifact, not a road render layer.
|
||||
|
||||
### R2. Stable identity and numbering
|
||||
|
||||
- Every generated feature must contain an immutable technical `signal_uid` derived deterministically from its OSM control and physical approach identity, rather than array order.
|
||||
- Every feature must contain an editable `display_id` intended for user-facing numbering.
|
||||
- Build/reimport validation must reject duplicate or missing `signal_uid` values and duplicate non-empty `display_id` values with an actionable error.
|
||||
|
||||
### R3. Editable placement contract
|
||||
|
||||
- Point geometry represents the pole ground position.
|
||||
- Editable attributes must include at least `display_id`, `heading_deg`, `phase_group`, `mast_reach_m`, `enabled`, and `z_offset_m`.
|
||||
- Source/control attributes required for matching and diagnostics must be preserved, including `control_id` and approach identity.
|
||||
- Vehicle stop coordinates remain independent attributes; moving the pole must not silently move the vehicle stop point.
|
||||
- After reimport, the pipeline must deterministically rebuild `pose.pole`, `pose.arm`, `pose.head`, `pose.lenses`, and `pose.countdown` from the edited point and attributes.
|
||||
|
||||
### R4. Stage ownership
|
||||
|
||||
- `intermediates` initializes the editable signal layer from current OSM/topology and derives the runtime `traffic_signals.json` from it.
|
||||
- `reimport` must stage, validate, and export the editable signal layer along with the existing road layers, then rebuild the runtime JSON.
|
||||
- `blender` must rebuild runtime `traffic_signals.json` from the current editable signal GeoJSON. It must not recompute signal placement directly from OSM and erase QGIS edits.
|
||||
- `cesium` and `preview` continue consuming artifacts derived from the same runtime JSON and retain matching signal node ids.
|
||||
|
||||
### R5. Diagnostics and compatibility
|
||||
|
||||
- Invalid geometry, invalid numeric fields, duplicate identities, unsupported phase groups, and unmatched source references must fail before replacing valid output artifacts.
|
||||
- `enabled=false` suppresses a signal without requiring feature deletion, so automatic regeneration cannot accidentally resurrect an intentionally disabled assembly within the same edit lifecycle.
|
||||
- Existing areas without an editable signal layer must receive one on their next `intermediates` run. No migration of previously hand-edited traffic signal JSON is required.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] A clean `intermediates` run creates the editable traffic-signal GeoJSON, a GeoPackage layer with the same feature count, and a QGIS project layer with labels and directional symbols.
|
||||
- [ ] T junctions produce three editable features and cross junctions produce four, each with a unique deterministic `signal_uid`.
|
||||
- [ ] Moving one point in QGIS and changing its `display_id` and `heading_deg`, followed by `reimport,blender,cesium,preview`, changes only that signal assembly's placement/identity-facing metadata while preserving its vehicle stop point.
|
||||
- [ ] Re-running `blender` after reimport does not overwrite the QGIS-edited pole position or heading from OSM.
|
||||
- [ ] Setting one feature to disabled removes its static and dynamic signal assets while leaving the other signals intact.
|
||||
- [ ] Duplicate `signal_uid` or non-empty `display_id`, invalid geometry, and invalid placement fields abort reimport without partially replacing GeoJSON outputs.
|
||||
- [ ] Blender/Cesium node counts and ids match the enabled features in the final runtime JSON; lights and countdowns continue switching correctly.
|
||||
- [ ] Existing road GeoPackage import/reimport behavior and merged scene layer order remain unchanged.
|
||||
- [ ] Unit/integration tests cover stable ids, editable-feature validation, override-to-pose reconstruction, auxiliary GeoPackage round-trip, and stage ownership.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Preserving QGIS edits across a subsequent full `intermediates` rebuild; as with road edits, users must preserve or reapply edits before regenerating the GeoPackage.
|
||||
- A complete traffic-controller timing editor or arbitrary multi-phase signal program.
|
||||
- Independent editing of each lens or countdown glyph position; those remain derived from the assembly point, heading, and shared layout.
|
||||
- Automatically assigning a stable identity to a brand-new signal feature drawn manually in QGIS.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "qgis-traffic-signal-overrides",
|
||||
"name": "qgis-traffic-signal-overrides",
|
||||
"title": "QGIS traffic signal overrides",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-07",
|
||||
"completedAt": "2026-08-07",
|
||||
"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,130 @@
|
||||
# 自动匹配 Cesium 巡航车道中心:技术设计
|
||||
|
||||
## 设计目标
|
||||
|
||||
巡航路线以 osm2streets internal road topology 与已渲染的 Driving lane polygon 为几何事实源。道路区间直接使用 polygon 的中轴,不再对 OSM way 中心线施加固定米数偏移;OSM 只提供原始标签和转向语义,不再作为最终路口拓扑。
|
||||
|
||||
## 数据流
|
||||
|
||||
```text
|
||||
network.json ──▶ internal road/intersection directed graph
|
||||
OSM XML ──▶ turn:lanes semantics
|
||||
│
|
||||
lane_polygons.geojson ──▶ validated Driving lane centerlines
|
||||
│
|
||||
▼
|
||||
maneuver-aware lane selection
|
||||
│
|
||||
▼
|
||||
internal road lane sections + surface-constrained junction curves
|
||||
│
|
||||
▼
|
||||
vehicle-route.json + diagnostics
|
||||
```
|
||||
|
||||
`scripts/build-area.js` 在 preview stage 调用 `buildVehicleRoute(osmPath, lanePolygonsPath)`。`lane_polygons.geojson` 同时写入 preview manifest inputs,`scripts/lib/area-diagnostics.js` 使用既有 SHA-256 freshness 检查自动识别过期路线。
|
||||
|
||||
## 模块边界
|
||||
|
||||
### `scripts/lib/lane-geometry.js`
|
||||
|
||||
新增纯几何共享模块,承载:
|
||||
|
||||
- 校验 Driving `Polygon` ring;
|
||||
- 通过 polygon 两侧对应顶点中点提取 lane centerline;
|
||||
- 米制距离、方向对齐、端点排序和 polyline 拼接所需的无副作用 helper。
|
||||
|
||||
`scripts/lib/turn-lane-arrows.js` 改为导入共享 `laneCenterline()`,确保箭头和巡航对 osm2streets polygon 顺序使用同一契约。
|
||||
|
||||
### `scripts/lib/vehicle-route.js`
|
||||
|
||||
保留现有 OSM 路线拓扑搜索,替换 `LANE_OFFSET_METERS` / `offsetClosedRouteRight()` 路径:
|
||||
|
||||
1. 加载并严格校验 `lane_polygons.geojson` 为 FeatureCollection。
|
||||
2. 按 `road`、lane `index` 和 `direction=Fwd|Back` 建立 Driving lane 索引;`osm_way_ids` 仅用于追溯 OSM 标签。
|
||||
3. 对每条 directed edge,使用实际横向位置按行驶方向排列同向车道;renderer `index` 只作为稳定 tie-breaker,不单独决定车道顺序。
|
||||
4. 将 edge 末端的 maneuver 与 `turn:lanes:forward|backward` 对齐。左转/掉头选最左兼容车道,右转选最右兼容车道,直行选最右兼容车道;无显式 lane restriction 时按相同位置规则选择。
|
||||
5. 每个 edge 对应一个 osm2streets internal road;禁止把同一 OSM way 下多个 internal road 当作无语义 fragment 直接拼接。
|
||||
6. 车道中轴端点之间使用 tangent Bezier / U-turn 连接,并绑定共同的 internal intersection;全部采样点必须落在对应 `intersection_surface` 内或边界容差内。
|
||||
7. 任一 edge 无法可靠匹配时丢弃该候选 route;继续搜索其他候选,最多输出 5 条。
|
||||
|
||||
## 输出契约
|
||||
|
||||
保留顶层 `routes`、兼容别名 `segments`、`loop`、`speedMetersPerSecond`,以及每条路线的 `coordinates`、`centerlineCoordinates`、`edgeIds`、`maneuvers` 和 `lengthMeters`。
|
||||
|
||||
每条路线新增 `laneSegments`,每段至少记录:
|
||||
|
||||
- `edgeId`、`osmWayId`、`direction`;
|
||||
- `laneIndex`、`widthMeters`、`centerOffsetMeters`;
|
||||
- `maneuver`、`source="lane_polygon_centerline"`;
|
||||
- 参与拼接的 polygon/road 标识。
|
||||
|
||||
顶层新增 `diagnostics`,按稳定 reason code 汇总被拒绝的 edge/route,例如 `missing_lane_polygon`、`invalid_lane_polygon`、`ambiguous_lane_order`、`no_compatible_turn_lane` 和 `disconnected_lane_fragments`。固定 `laneOffsetMeters` 不再作为几何输入;为避免伪造单值,不以平均偏移替代逐段事实。
|
||||
|
||||
## 错误与降级语义
|
||||
|
||||
- 整个 lane polygon 文件缺失、JSON 无法解析或不是 FeatureCollection:preview stage 在写产物前失败。
|
||||
- 单个 polygon 无效:记录诊断,该 polygon 不参与匹配。
|
||||
- directed edge 缺少唯一可信车道:候选 route 被拒绝,生成器继续选择其他 route。
|
||||
- 所有候选都被拒绝:生成合法的空 `routes` 和完整诊断;Cesium 场景仍可加载,但不显示巡航车辆。
|
||||
- 禁止回退到固定 `1.3 m`、固定 `1.5 m` 或默认 lane width。
|
||||
|
||||
## 兼容性
|
||||
|
||||
- Blender scene 经纬度投影改为 WGS84 ECEF→ENU,与 Cesium
|
||||
`eastNorthUpToFixedFrame` 的锚点坐标系一致;这是 route 与最终 GLB 道路重合的必要
|
||||
跨层契约。
|
||||
- `segments` 继续与 `routes` 引用相同数组。
|
||||
- Cesium runtime 当前只消费 `coordinates` 等既有字段,无需理解 `laneSegments` 即可运行。
|
||||
- preview manifest 新增 lane polygon input 后,旧 manifest 会被诊断为缺少记录并要求重建,这是预期迁移行为。
|
||||
|
||||
## 验证策略
|
||||
|
||||
- 纯几何测试:四边形、曲线 polygon、反向 geometry、坏 ring。
|
||||
- 路线 fixture:不同宽度、双向/单向、多车道和转向车道选择。
|
||||
- 精度断言:路线道路区间采样点到 polygon 中轴距离不超过 `0.10 m`。
|
||||
- 连续性断言:车道变化和路口连接处没有由数据拼接产生的异常横跳。
|
||||
- 集成验证:目标区域重跑 preview,检查 route JSON diagnostics、manifest freshness、区域质量门和 Cesium 实际显示。
|
||||
- 端到端视觉验证:重跑 `blender,cesium,preview`,对最终画面做道路横截面检查;不能用
|
||||
route 与 lane polygon 的厘米级一致性代替 GLB/Entity 对齐验证。
|
||||
|
||||
## Bug Analysis:GLB 与巡航路线随距离漂移
|
||||
|
||||
### 1. Root Cause Category
|
||||
|
||||
- **Category**:B - Cross-Layer Contract;D - Test Coverage Gap。
|
||||
- **Specific Cause**:Blender 使用固定米/度的平面近似,Cesium 使用 WGS84 椭球 ENU;
|
||||
两层没有共享坐标转换契约。
|
||||
|
||||
### 2. Why Fixes Failed
|
||||
|
||||
1. 固定车道偏移:只处理症状,且假定所有车道宽度相同。
|
||||
2. 从 Driving polygon 重建中轴:解决了车道宽度与 lane 选择,但只证明 GeoJSON 内部正确。
|
||||
3. 路口切线连接:改善了 connector,却没有解释直线路段整套坐标同向平移。
|
||||
|
||||
### 3. Prevention Mechanisms
|
||||
|
||||
| Priority | Mechanism | Specific Action | Status |
|
||||
|---|---|---|---|
|
||||
| P0 | Architecture | `Projector` 使用 WGS84 ECEF→ENU,与 Cesium 锚点一致 | DONE |
|
||||
| P0 | Test Coverage | 测试 WGS84 局部经纬度比例与 ENU 方向 | DONE |
|
||||
| P1 | Documentation | 在 Blender、preview 与 cross-layer spec 固化契约 | DONE |
|
||||
| P1 | Integration | 坐标变更后强制重跑 `blender,cesium,preview` 并视觉核对 | DONE |
|
||||
|
||||
### 4. Systematic Expansion
|
||||
|
||||
- **Similar Issues**:交通信号、语义模型、route polyline 等所有叠加在 GLB 上的 Cesium
|
||||
Entity 都依赖同一契约。
|
||||
- **Design Improvement**:坐标转换只有 `Projector` 一个 Blender 事实源。
|
||||
- **Process Improvement**:跨运行时几何必须验证最终组合画面,不能停在单层数值测试。
|
||||
|
||||
### 5. Knowledge Capture
|
||||
|
||||
- [x] 更新 Blender asset generation spec。
|
||||
- [x] 更新 preview vehicle route spec。
|
||||
- [x] 更新 cross-layer thinking guide。
|
||||
- [x] 增加 `ProjectorTest` WGS84 断言。
|
||||
|
||||
## 回滚
|
||||
|
||||
代码回滚只涉及 preview 路线生成和共享 JS helper;重跑 preview 即可恢复旧路线产物,不需要重建 Blender/GLB。用户已有区域配置修改保持不动。
|
||||
@@ -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,43 @@
|
||||
# 自动匹配 Cesium 巡航车道中心:实施计划
|
||||
|
||||
## 实施顺序
|
||||
|
||||
- [x] 1. 新增 `scripts/lib/lane-geometry.js`,从 `turn-lane-arrows.js` 迁移并导出 Driving polygon 中轴提取与必要的纯几何 helper;补充共享几何测试,确认箭头行为不变。
|
||||
- [x] 2. 扩展 `scripts/lib/vehicle-route.js` 输入校验和车道索引,按 OSM way、行驶方向、实际横向位置及 maneuver 选择目标 Driving lane。
|
||||
- [x] 3. 实现同车道多 fragment 的方向校验、确定性拼接和 lane centerline 路线生成;删除固定偏移路径,加入稳定 reason-code diagnostics 与 `laneSegments` 溯源。
|
||||
- [x] 4. 更新 `scripts/test-preview-assets.js` fixture,覆盖 `3.0 m` / `3.5 m`、双向单车道、同向多车道、oneway、正反方向、左/直/右选择、坏 polygon 和缺失匹配。
|
||||
- [x] 5. 修改 `scripts/build-area.js`,在任何 preview 写入前校验 `lane_polygons.geojson`,传给路线生成器并记录到 stage manifest inputs。
|
||||
- [x] 6. 修改 `scripts/lib/area-diagnostics.js` 的 preview expected inputs,并补充 manifest freshness 回归测试或等价断言。
|
||||
- [x] 7. 更新 README 巡航说明,移除固定 `1.3 m` 描述,说明实际车道中轴、跳过语义和诊断字段。
|
||||
- [x] 8. 重生成目标区域 preview 路线,检查所有 route 的 lane 溯源、空/拒绝诊断和直线路段 `<= 0.10 m` 中心误差。
|
||||
- [x] 9. 将路线拓扑切换到 `network.json` internal road/intersection,并对 connector 执行 `intersection_surface` 越界拒绝;预览仅显示当前选中 route,等待用户做最终视觉复核。
|
||||
|
||||
## 当前验证状态
|
||||
|
||||
- 目标区域只读生成验证通过:`5` 条路线、`56` 个 lane segment、`116` 个中轴顶点最大误差 `0 m`,覆盖 left/right/through/u_turn,实际 center offset 范围 `1.415–1.588 m`。
|
||||
- 第 8、9 步暂未完成:preview stage 在写产物前因既有 `traffic_signal_assemblies.geojson` 和 `traffic_signals.json` 缺失而失败。
|
||||
- `check:area` 的 3 个 failure 均来自既有 intermediates/blender/preview manifest stale;修复需要重跑会重建 GeoPackage 的 intermediates,未获用户授权前不执行。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
node --check scripts/lib/lane-geometry.js
|
||||
node --check scripts/lib/vehicle-route.js
|
||||
node --check scripts/lib/turn-lane-arrows.js
|
||||
node --check scripts/build-area.js
|
||||
npm run test:turn-lane-arrows
|
||||
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
|
||||
```
|
||||
|
||||
若仓库已有覆盖 manifest freshness 的独立测试入口,一并运行;否则在现有最接近的 Node 测试中加入定向断言。
|
||||
|
||||
## 风险与检查点
|
||||
|
||||
- polygon fragment 拼接是最高风险点:完成第 3 步后先用目标区域做只读匹配统计,确认不会因 osm2streets 分段导致全部路线被拒绝,再继续 stage 集成。
|
||||
- 多车道 lane order 不得只依赖 renderer `index`;必须用行驶方向下的实际横向位置验证顺序。
|
||||
- 不把 OSM `width` 或默认宽度作为无声回退;任何覆盖率下降必须能从 diagnostics 定位。
|
||||
- 不修改用户已有的 `config/areas/nantaizi-lake-innovation-valley.json` Linux 路径变更。
|
||||
- 不需要 GLB parity;本任务不改变 Blender/GLB。但必须重跑 preview manifest 和区域质量门。
|
||||
@@ -0,0 +1,49 @@
|
||||
# 自动匹配 Cesium 巡航车道中心
|
||||
|
||||
## Goal
|
||||
|
||||
Cesium 预览巡航路线应根据实际车道数据自动落在所选行车道中心,消除固定横向偏移带来的位置误差,为后续车辆仿真提供可靠的几何基础。
|
||||
|
||||
## Background
|
||||
|
||||
- `scripts/lib/vehicle-route.js:9` 当前使用固定 `LANE_OFFSET_METERS = 1.3`,并在 `makeRoute()` 中对整条平滑后的道路中心线统一向右偏移。
|
||||
- 当前区域的 `lane_polygons.geojson` 中 Driving lane 宽度为 `3.0 m`,单车道中心距道路中心线应为 `1.5 m`,现有路线存在约 `0.2 m` 横向误差。
|
||||
- `lane_polygons.geojson` 已包含 `direction`、`width`、`index`、`osm_way_ids` 和车道 polygon;这些数据与最终渲染道路来自同一 osm2streets 中间产物。
|
||||
- OSM 输入包含 `lanes`、`lanes:forward`、`lanes:backward` 和 `turn:lanes:*`,但不保证包含明确的 `width`,不能单独作为所有区域的精确宽度来源。
|
||||
- 历史提交 `30846b6` 引入连续巡航路线时沿用了实验阶段的 `1.3 m` 固定值,没有建立路线与 osm2streets 车道几何之间的契约。
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1:路线生成以 osm2streets 实际 Driving lane 数据为主事实源,不再使用全局固定偏移常量。
|
||||
- R2:按 OSM way、行驶方向和车道顺序匹配目标车道,并根据各路段的真实宽度及横向位置计算车道中心。
|
||||
- R3:不同宽度、不同车道数或不同方向配置的连续道路必须逐路段计算偏移,路口连接处保持连续且不产生横向跳变。
|
||||
- R4:多车道路段必须选择一条明确的目标车道;车道选择和转向可行性应使用 `direction`、`index`、`allowed_turns` / `turn:lanes:*` 数据,而不是只看总车道数。
|
||||
- R4.1:同向多车道按下一次 maneuver 选择兼容车道;左转/掉头优先最左侧兼容车道,右转优先最右侧兼容车道,直行默认最右侧兼容车道。没有兼容车道时跳过该候选路线并记录诊断。
|
||||
- R5:每条输出路线记录所用车道、宽度/偏移来源及诊断信息,使下游能够识别精确匹配、次级推导和跳过的路段。
|
||||
- R5.1:缺少、歧义或无法验证车道数据的路段必须跳过并输出结构化诊断;禁止回退到固定偏移或默认车道宽度。
|
||||
- R6:保留现有 `routes`、兼容别名 `segments`、闭环路线、信号灯停车和 Cesium 动画消费契约。
|
||||
- R7:preview stage manifest 将参与路线计算的车道数据列为输入,使车道几何变化能够正确判定预览产物 stale。
|
||||
- R8:不得修改 Blender/GLB 主资产;本任务只修正预览巡航路线及其生成契约。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] AC1:当前南台子湖区域重新生成后,普通直线路段的巡航点位于匹配 Driving lane 的几何中心,允许误差不超过 `0.10 m`。
|
||||
- [x] AC2:测试 fixture 覆盖至少 `3.0 m`、`3.5 m` 两种车道宽度,输出中心偏移分别随实际车道数据变化,不存在 `1.3 m` 或 `1.5 m` 全局常量依赖。
|
||||
- [x] AC3:测试 fixture 覆盖双向单车道、同向多车道、`oneway=yes` 和正反方向,验证目标车道选择及偏移方向正确。
|
||||
- [x] AC4:连续路段车道宽度变化或车道数变化时,路线在衔接区连续;相邻采样点不得出现由偏移切换导致的异常横跳。
|
||||
- [x] AC5:路线 JSON 能追溯每段匹配到的 `osm_way_ids`、方向、lane index、width、center offset 和数据来源。
|
||||
- [ ] AC6:既有 preview asset tests、路线闭环测试和区域质量门通过,信号灯停车与车辆朝向行为不回归。
|
||||
- [x] AC7:preview manifest 记录车道数据文件;该文件改变后诊断能够将 preview 标记为 stale。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 车辆在运行时动态换道、超车或避障。
|
||||
- 交通流量、车辆间距和碰撞模型。
|
||||
- 修改 osm2streets 生成的车道 polygon 或 Blender 道路网格。
|
||||
- 将实验性预览巡航升级为通用交通仿真引擎。
|
||||
|
||||
## Technical Constraints
|
||||
|
||||
- 本任务涉及 OSM、osm2streets GeoJSON、stage manifest 和 Cesium route JSON 的跨层契约,按复杂任务处理;规划收敛后需要 `design.md` 和 `implement.md`。
|
||||
- Driving lane polygon 的相对两边中点算法已在 `scripts/lib/turn-lane-arrows.js:265` 验证;巡航路线应复用同一纯几何实现,不维护第二份 polygon 解析逻辑。
|
||||
- `preview` 保持可独立执行,但现在明确依赖已有 `lane_polygons.geojson`;车道文件整体缺失或格式无效属于 stage 输入错误,单个路段无法可靠匹配则跳过并写诊断。
|
||||
@@ -0,0 +1,23 @@
|
||||
# osm2streets 车道中心线研究
|
||||
|
||||
## 上游实现
|
||||
|
||||
- 调研版本:`osm2streets-js-node 0.1.4`,上游 `osm2streets` commit `fc119c47dac567d030c6ce7c24a48896f58ed906`。
|
||||
- `Road::get_untrimmed_center_line()` 先根据 OSM reference line、`reference_line_placement`、道路总宽度和驾驶方向生成 road full-width centerline。
|
||||
- `Road::get_lane_center_lines()` 按 `lane_specs_ltr` 从左到右累计真实 lane width,再调用 `center_line.shift_from_center(total_width, width_from_left_side)` 生成每条 lane 的中心线。
|
||||
- `to_lane_polygons_geojson()` 先取得上述 lane centerline,再调用 `pl.make_polygons(lane.width)` 生成 Driving polygon。因此 lane polygon 是 lane centerline 的派生产物。
|
||||
- `PolyLine::make_polygons()` 使用两侧等距平移和 miter 交点生成 polygon ring;ring 前半边与反向后的后半边一一对应,其中点可恢复原始 lane centerline。
|
||||
|
||||
## 当前区域审计
|
||||
|
||||
- `54` 个 Driving polygon:`32` 个四边形,其余包含 `6/8/10/18` 个非闭合顶点。
|
||||
- 使用 `network.json` 的 `road.center_line`、`lane_specs_ltr` 和 geom miter 算法重建所有 lane centerline。
|
||||
- 与 `scripts/lib/lane-geometry.js:laneCenterline()` 结果逐点比较,最大误差为 `0.006 m`。
|
||||
- 结论:道路区间的 polygon 中轴提取与 osm2streets 权威 lane centerline 一致,不是肉眼所见大偏移的来源。
|
||||
|
||||
## 路口 movement 限制
|
||||
|
||||
- osm2streets 的公开 JS API 没有导出可直接用于车辆行驶的 lane-to-lane movement centerline。
|
||||
- `debugMovementsFromLaneGeojson()` 只是调试箭头:在双向 road centerline 上使用固定 `1.3 m` 偏移,再用直线连接 road endpoints;它不是 lane-aware 仿真轨迹,不能复用。
|
||||
- 当前项目的大偏移排查应限定在:selected lane 与 internal road 的映射、junction connector、U-turn,以及多条闭环 route 同时显示造成的视觉混淆。
|
||||
- 后续 connector 必须显式关联 `network.json` 的 internal road/intersection,并受 `intersection_surface.geojson` 约束;不得修改已验证的 lane section centerline。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "cesium-lane-centered-route",
|
||||
"name": "cesium-lane-centered-route",
|
||||
"title": "自动匹配 Cesium 巡航车道中心",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "que01",
|
||||
"assignee": "que01",
|
||||
"createdAt": "2026-08-08",
|
||||
"completedAt": "2026-08-08",
|
||||
"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 -->
|
||||
- **Active File**: `journal-1.md`
|
||||
- **Total Sessions**: 13
|
||||
- **Last Active**: 2026-08-03
|
||||
- **Total Sessions**: 27
|
||||
- **Last Active**: 2026-08-07
|
||||
<!-- @@@/auto:current-status -->
|
||||
|
||||
---
|
||||
@@ -19,7 +19,7 @@
|
||||
<!-- @@@auto:active-documents -->
|
||||
| File | Lines | Status |
|
||||
|------|-------|--------|
|
||||
| `journal-1.md` | ~280 | Active |
|
||||
| `journal-1.md` | ~575 | Active |
|
||||
<!-- @@@/auto:active-documents -->
|
||||
|
||||
---
|
||||
@@ -29,6 +29,20 @@
|
||||
<!-- @@@auto:session-history -->
|
||||
| # | Date | Title | Commits | Branch |
|
||||
|---|------|-------|---------|--------|
|
||||
| 27 | 2026-08-07 | QGIS traffic signal editing and countdown stability | `e153a1c` | `main` |
|
||||
| 26 | 2026-08-07 | 交通信号拓扑与部分构建同步修复 | `1c077a3` | `main` |
|
||||
| 25 | 2026-08-06 | Cesium traffic signal countdowns | `0e1574f` | `main` |
|
||||
| 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` |
|
||||
| 12 | 2026-08-03 | Optional GLB compression stage | `7ba8594` | `main` |
|
||||
| 11 | 2026-08-03 | Adopt Shapespark vegetation | `dbb5705` | `main` |
|
||||
|
||||
@@ -278,3 +278,298 @@ Parsed building multipolygon relations, generated building holes, and respected
|
||||
### Status
|
||||
|
||||
[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**
|
||||
|
||||
|
||||
## Session 25: Cesium traffic signal countdowns
|
||||
|
||||
**Date**: 2026-08-06
|
||||
**Task**: Cesium traffic signal countdowns
|
||||
**Branch**: `main`
|
||||
|
||||
### Summary
|
||||
|
||||
Added shared 7LED countdown geometry, split dynamic Cesium assets by phase group for reliable lamp-matched colors, documented the inverse-font and exporter grouping contracts, and verified the preview asset and Blender Python test suites.
|
||||
|
||||
### Git Commits
|
||||
|
||||
| Hash | Message |
|
||||
|------|---------|
|
||||
| `0e1574f` | (see git log) |
|
||||
|
||||
### Status
|
||||
|
||||
[OK] **Completed**
|
||||
|
||||
|
||||
## Session 26: 交通信号拓扑与部分构建同步修复
|
||||
|
||||
**Date**: 2026-08-07
|
||||
**Task**: 交通信号拓扑与部分构建同步修复
|
||||
**Branch**: `main`
|
||||
|
||||
### Summary
|
||||
|
||||
基于 OSM highway=traffic_signals 控制节点生成 T/十字路口信号,复用共享 OSM 解析;Blender stage 每次重写交通信号锚点,避免部分构建沿用旧 JSON 导致 GLB 与 Cesium 预览 signal id 错位。验证了 nantaizi 区域 35 盏信号、动态/倒计时 GLB、Safari 预览和 preview-assets 测试。
|
||||
|
||||
### Git Commits
|
||||
|
||||
| Hash | Message |
|
||||
|------|---------|
|
||||
| `1c077a3` | (see git log) |
|
||||
|
||||
### Status
|
||||
|
||||
[OK] **Completed**
|
||||
|
||||
|
||||
## Session 27: QGIS traffic signal editing and countdown stability
|
||||
|
||||
**Date**: 2026-08-07
|
||||
**Task**: QGIS traffic signal editing and countdown stability
|
||||
**Branch**: `main`
|
||||
|
||||
### Summary
|
||||
|
||||
Implemented editable traffic signal assemblies in QGIS with stable IDs, position/heading overrides, reimport ownership, source validation, and Blender/Cesium runtime derivation. Fixed long signal IDs overflowing Blender node names with short nodeKey values, and changed QGIS SVG symbols to direct heading_deg field rotation so QGIS rotation edits write back to the field. Verified real GeoPackage round-trip, focused Node tests, preview tests, and 59 Blender tests. Safari cache caused stale GLB symptoms and was resolved with a hard refresh.
|
||||
|
||||
### Git Commits
|
||||
|
||||
| Hash | Message |
|
||||
|------|---------|
|
||||
| `e153a1c` | (see git log) |
|
||||
|
||||
### Status
|
||||
|
||||
[OK] **Completed**
|
||||
|
||||
41
.trellis/workspace/que01/index.md
Normal file
41
.trellis/workspace/que01/index.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Workspace Index - que01
|
||||
|
||||
> Journal tracking for AI development sessions.
|
||||
|
||||
---
|
||||
|
||||
## Current Status
|
||||
|
||||
<!-- @@@auto:current-status -->
|
||||
- **Active File**: `journal-1.md`
|
||||
- **Total Sessions**: 1
|
||||
- **Last Active**: 2026-08-08
|
||||
<!-- @@@/auto:current-status -->
|
||||
|
||||
---
|
||||
|
||||
## Active Documents
|
||||
|
||||
<!-- @@@auto:active-documents -->
|
||||
| File | Lines | Status |
|
||||
|------|-------|--------|
|
||||
| `journal-1.md` | ~28 | Active |
|
||||
<!-- @@@/auto:active-documents -->
|
||||
|
||||
---
|
||||
|
||||
## Session History
|
||||
|
||||
<!-- @@@auto:session-history -->
|
||||
| # | Date | Title | Commits | Branch |
|
||||
|---|------|-------|---------|--------|
|
||||
| 1 | 2026-08-08 | 修正 Cesium 巡航车道中心对齐 | `5658e73` | `main` |
|
||||
<!-- @@@/auto:session-history -->
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Sessions are appended to journal files
|
||||
- New journal file created when current exceeds 2000 lines
|
||||
- Use `add_session.py` to record sessions
|
||||
28
.trellis/workspace/que01/journal-1.md
Normal file
28
.trellis/workspace/que01/journal-1.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Journal - que01 (Part 1)
|
||||
|
||||
> AI development session journal
|
||||
> Started: 2026-08-08
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## Session 1: 修正 Cesium 巡航车道中心对齐
|
||||
|
||||
**Date**: 2026-08-08
|
||||
**Task**: 修正 Cesium 巡航车道中心对齐
|
||||
**Branch**: `main`
|
||||
|
||||
### Summary
|
||||
|
||||
巡航路线改用 osm2streets Driving lane polygon 中轴和 internal road 拓扑;Blender 改用与 Cesium 一致的 WGS84 ECEF 到 ENU 投影,修复随锚点距离增长的整体偏移,并补齐连接器、预览、manifest、投影测试与项目规范。
|
||||
|
||||
### Git Commits
|
||||
|
||||
| Hash | Message |
|
||||
|------|---------|
|
||||
| `5658e73` | (see git log) |
|
||||
|
||||
### Status
|
||||
|
||||
[OK] **Completed**
|
||||
92
README.md
92
README.md
@@ -8,7 +8,8 @@
|
||||
|
||||
- `<area-id>.blend`: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>-cesium-preview.html`:Cesium 本地预览页
|
||||
- `<area-id>-compressed-webp768.glb/json/html`:显式 `compress` 阶段生成的可选压缩预览产物
|
||||
@@ -19,10 +20,14 @@
|
||||
|
||||
需要:
|
||||
|
||||
- macOS QGIS:默认 `/Applications/QGIS.app`
|
||||
- Blender:默认 `/Applications/Blender.app`
|
||||
- QGIS 与 GDAL 工具链
|
||||
- Blender
|
||||
- Node.js / npm
|
||||
|
||||
macOS 默认使用 QGIS `/Applications/QGIS.app` 与 Blender
|
||||
`/Applications/Blender.app`。Linux 配置中,`qgisApp` 是安装前缀(通常为
|
||||
`/usr`),`blenderApp` 是可执行文件路径(通常为 `/usr/bin/blender`)。
|
||||
|
||||
首次使用:
|
||||
|
||||
```bash
|
||||
@@ -57,12 +62,91 @@ 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-手工修正工作流)。
|
||||
|
||||
Cesium 预览默认显示完整场景。点击 `Inspect` 后会按道路、建筑、绿化与设施、水体加载辅助 GLB;该模式用于单独检查生成结果,主 GLB 仍是完整场景和下游使用的基线。
|
||||
压缩预览同样保留这些分类检查资产;重跑 `compress` 后应打开新生成的压缩预览 HTML。
|
||||
|
||||
`compress` 不含在 `all` 里,也不能从配置文件默认开启。需要重导出 Cesium 后立刻生成压缩产物时,显式跑:
|
||||
|
||||
```bash
|
||||
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium,compress
|
||||
```
|
||||
|
||||
## 区域诊断
|
||||
|
||||
手工编辑 OSM 或怀疑产物变大时,先跑快速诊断。它只读取区域配置、OSM XML 和已有输出
|
||||
文件,不会启动 QGIS、Blender 或 Cesium 构建:
|
||||
|
||||
```bash
|
||||
npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
```
|
||||
|
||||
每次手工编辑 OSM 后、进入重型构建前,先跑预检。它只读取区域配置和 OSM XML,检查
|
||||
bounds、缺失 node 引用、building way 闭合性、building multipolygon member/ring、以及
|
||||
显式 `height` / `building:levels` 标签:
|
||||
|
||||
```bash
|
||||
npm run preflight:area -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
```
|
||||
|
||||
预检有 error 时退出非零且不会更新记录;通过后写 `preflight.manifest.json`,保留本次
|
||||
验证的 config / OSM 文件摘要和检查结果。
|
||||
|
||||
诊断会输出 OSM bounds、building way / multipolygon relation、显式高度、植被数量、
|
||||
现有产物状态,以及 GLB 的 size / nodes / meshes / materials / images / extensions。
|
||||
缺少已期望的基线产物、异常 building relation、GLB 超过保守预算等会进入 `Warnings`。
|
||||
|
||||
预检和每个成功的区域构建阶段都会写机器可读的 manifest:
|
||||
|
||||
```text
|
||||
outputs/<area-id>/_pipeline/stages/preflight.manifest.json
|
||||
outputs/<area-id>/_pipeline/stages/intermediates.manifest.json
|
||||
outputs/<area-id>/_pipeline/stages/reimport.manifest.json
|
||||
outputs/<area-id>/_pipeline/stages/blender.manifest.json
|
||||
outputs/<area-id>/_pipeline/stages/cesium.manifest.json
|
||||
outputs/<area-id>/_pipeline/stages/preview.manifest.json
|
||||
outputs/<area-id>/_pipeline/stages/compress.manifest.json
|
||||
```
|
||||
|
||||
`intermediates` 和 `reimport` 是同一批 GeoJSON 的两种所有权路径:前者从 OSM 重建,
|
||||
后者从手工编辑的 GeoPackage 回导。成功运行其中一个会清掉另一个的 manifest,避免旧路径
|
||||
造成假 stale。
|
||||
|
||||
manifest 记录阶段输入/输出文件的 bytes、mtime、sha256、耗时和结构摘要:前段记录 OSM /
|
||||
GeoJSON feature counts,Blender 记录 `.blend` / render,Cesium/压缩记录 GLB digest,
|
||||
preview 记录 GLB、metadata、`lane_polygons.geojson`、`network.json`、`intersection_surface.geojson`、
|
||||
车辆路线和 runtime 文件。巡航道路区间按 osm2streets internal road 匹配真实 Driving lane 中轴,
|
||||
路口 connector 必须通过 intersection surface 越界检查。`diagnose:area` 会读取这些
|
||||
manifest;缺失或当前输入/输出 sha/bytes 不一致会在 `Stage manifests` 和 `Warnings`
|
||||
里标出来。
|
||||
|
||||
## 区域质量门
|
||||
|
||||
提交或交付某个区域前,跑只读质量门:
|
||||
|
||||
```bash
|
||||
npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
```
|
||||
|
||||
`check:area` 复用 `diagnose:area` 的 OSM、产物、metadata、GLB digest 和 stage manifest
|
||||
检查,但输出更短的 PASS/FAIL 报告。它不会启动 QGIS、Blender、Cesium、压缩或任何重建阶段。
|
||||
|
||||
GLB 预算默认阻断 size、nodes、images、实例化后的 render triangles 和嵌入贴图字节。
|
||||
`diagnose:area` 会显示有效预算、top source 和最大贴图。区域可在配置中收紧预算;要放宽
|
||||
默认值,必须写明原因:
|
||||
|
||||
```json
|
||||
"budget": {
|
||||
"nodes": 1400,
|
||||
"triangles": 350000,
|
||||
"reason": "Dense campus vegetation approved for this area"
|
||||
}
|
||||
```
|
||||
|
||||
第一版会在这些条件下退出非零:OSM bounds 缺失/无效、building multipolygon relation
|
||||
异常、建筑 `height` 无法按正数米解析、Cesium GLB / metadata / preview 缺失或类型错误、
|
||||
metadata JSON 损坏、GLB size / nodes / images / render triangles / embedded image bytes 超过保守预算、期望存在的 stage manifest
|
||||
缺失/损坏/stale。缺 QGIS preview 目前只作为 warning,不阻断。
|
||||
|
||||
## 区域配置
|
||||
|
||||
新区域从模板复制:
|
||||
@@ -142,7 +226,7 @@ python3 -m http.server 8765
|
||||
|
||||
## 实验:车辆巡航
|
||||
|
||||
`preview` 和 `cesium` 阶段会额外生成 `<area-id>-vehicle-route.json` 和 `<area-id>-vehicle-car.gltf`。路线文件从 OSM bounds 内的可行驶 `highway` way 提取道路中心线,并向右偏移约 1.3 米作为车辆行驶线,避免车辆压道路中心线。Cesium 预览页会加载多条道路段并显示多辆实验车辆循环巡航;`Vehicle` 下拉框决定 `Follow` 跟随哪一辆车。
|
||||
`preview` 和 `cesium` 阶段会额外生成 `<area-id>-vehicle-route.json` 和 `<area-id>-vehicle-car.gltf`。OSM 提供可行驶拓扑与转向语义,实际巡航坐标来自 osm2streets 的 `Driving` lane polygon 中轴;同向多车道按下一次 maneuver 选择兼容车道。路线中的 `laneSegments` 记录每个 polygon fragment 的 OSM way、方向、lane index、width、center offset 和来源。车道数据缺失、歧义、断裂或没有兼容转向车道时,候选路线会被跳过并写入顶层 `diagnostics`,不会回退到固定偏移或默认宽度。Cesium 预览页会加载多条道路段并显示多辆实验车辆循环巡航;`Vehicle` 下拉框决定 `Follow` 跟随哪一辆车。
|
||||
|
||||
这是用于验证高精度巡航可用性的预览层功能,不会改变 Blender/GLB 主资产本身。车辆模型是无 logo 的轻量预览模型,生成在输出目录中。
|
||||
|
||||
|
||||
BIN
assets/fonts/7LED-1.ttf
Normal file
BIN
assets/fonts/7LED-1.ttf
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user