Compare commits
89 Commits
78cb69edb6
...
feature/na
| Author | SHA1 | Date | |
|---|---|---|---|
| ea8a3622b9 | |||
| 03a8d6159b | |||
| 1b9829d9ed | |||
| 822e6ef936 | |||
| 3eea12c6ea | |||
| 3d57655497 | |||
| 65cf8b96d9 | |||
| e1f3fc10ca | |||
| f43a122ff7 | |||
| 3e22a1872e | |||
| 1da985c932 | |||
| 817e361477 | |||
| 25287777f6 | |||
| a556c0fc97 | |||
| 1876472bf8 | |||
| 707e7f82f9 | |||
| 850e9eb344 | |||
| df81a22ce8 | |||
| b5fa4482f0 | |||
| ddd15f68b3 | |||
| aa5b1f85c8 | |||
| 761a52646c | |||
| f13d890ffb | |||
| 41a0e81a6f | |||
| eff7a01b2b | |||
| 0102ffbb3c | |||
| f2b8d79f5d | |||
| db0fba5bb5 | |||
| c925890848 | |||
| f385009043 | |||
| b4a81331e8 | |||
| e0cdf9e00b | |||
| 0790cbd0a9 | |||
| b68be063ad | |||
| d26921c6d1 | |||
| 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 压缩选项 |
|
||||
| `compress` | | 见下 | 默认交付压缩阶段的 GLB 压缩选项 |
|
||||
| `budget` | | 见下 | 区域 GLB 性能与体量预算 |
|
||||
| `outputs` | | 从 `id` 推导 | 输出路径覆盖,逃生舱 |
|
||||
|
||||
**路径一律绝对**。`normalizeAreaConfig` 对每一项都做 `path.resolve`,相对路径会
|
||||
@@ -65,12 +68,14 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
| `intermediates` | `true` | 旧名 `qgis` 仍被接受 |
|
||||
| `blender` | `true` | |
|
||||
| `cesium` | `true` | |
|
||||
| `compress` | `true` | 压缩 staged GLB,供随后发布使用 |
|
||||
| `package` | `true` | 将通过校验的静态模型原子发布到 `package/` |
|
||||
| `preview` | `true` | 基于已发布 package 写验证预览及 `_preview/` 动态资源 |
|
||||
|
||||
`reimport`、`preview` 和 `compress` **在这里配也没用**——`normalizeAreaConfig` 把它们
|
||||
硬编码为 `false`,只能靠 `--stages` 显式请求。
|
||||
`reimport` **在这里配也没用**——`normalizeAreaConfig` 把它硬编码为 `false`,只能靠
|
||||
`--stages` 显式请求。`compress`、`package` 和 `preview` 始终默认开启。
|
||||
|
||||
> 恢复动作(reimport)、补丁动作(preview)和替代产物动作(compress)不该被一份
|
||||
> 配置文件变成默认行为。
|
||||
> 恢复动作(reimport)不该被一份配置文件变成默认行为;压缩、发布和验证预览是标准交付链的一部分。
|
||||
|
||||
`--stages` 会整体覆盖这里的默认值。
|
||||
|
||||
@@ -95,6 +100,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 +123,7 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
}
|
||||
```
|
||||
|
||||
⚠️ **给了就整体替换,不做逐字段合并**(`build-area.js:132`:`raw.osm2streets || {...}`)。
|
||||
⚠️ **给了就整体替换,不做逐字段合并**(`raw.osm2streets || {...}`)。
|
||||
只想改一个开关也必须把五个字段全写上,否则其余四个会退到 osm2streets 自己的默认值。
|
||||
|
||||
### `blender`
|
||||
@@ -118,11 +132,13 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
|---|---|---|
|
||||
| `treeStyle` | `"natural"` | 合法值见 `generate_scene.py` 的 `TREE_STYLES`(`natural`、`procedural`、`shapespark`) |
|
||||
| `officeOverrides` | `""` | 旧名 `office_overrides` 仍被接受 |
|
||||
| `roadProvider` | `"osm2streets"` | Blender 道路来源。`"native"` 时仅使用 `native-road/` 的道路、路口和人行道面;可由 `build:area --road-provider native` 临时覆盖。 |
|
||||
|
||||
### `compress`
|
||||
|
||||
只影响显式 `--stages compress`。默认压缩链是 texture resize + WebP transcode,
|
||||
不覆盖默认 `<area-id>.glb`。
|
||||
完整构建和显式 `--stages compress` 都使用此配置。默认压缩链是 texture resize + WebP
|
||||
transcode,成功后替换**package staging** 中的主 GLB 与 manifest;未压缩源只保留在构建临时目录。
|
||||
只有随后的 `package` 阶段才会原子发布到 `package/`。
|
||||
|
||||
| 字段 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
@@ -131,25 +147,48 @@ 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>`。需要定制时逐项覆盖:
|
||||
默认全部从 `id` 推导。发布给下游的静态资产固定在
|
||||
`<outputRoot>/<id>/package/`(含 `models/` 与可控信号灯 `runtime/`),临时静态资产在
|
||||
`_pipeline/package-staging/`,预览专用车辆/路线资源在 `<outputRoot>/<id>/_preview/`。需要定制时逐项覆盖:
|
||||
|
||||
```json
|
||||
{
|
||||
"outputs": {
|
||||
"areaDir": "/absolute/path/to/custom-area",
|
||||
"blend": "/absolute/path/to/custom.blend",
|
||||
"glb": "/absolute/path/to/custom.glb",
|
||||
"packageDir": "/absolute/path/to/custom-package",
|
||||
"cesiumPreview": "/absolute/path/to/custom-preview.html"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
可覆盖的键(`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`。
|
||||
|
||||
静态发布路径另有 `packageDir`、`packageStagingDir`、`packageManifest`、
|
||||
`packageStagingManifest`、`packageModelDir`、`packageStagingModelDir` 与
|
||||
`packagePrimaryGlb`;预览路径另有 `previewDir` 与 `previewDescriptor`。除非在迁移旧调用,
|
||||
不要覆盖 `glb` / `metadata`:它们是 staging 内部路径,不是下游资产入口。
|
||||
|
||||
**优先改 `fileStem` 或 `areaDir`**——它们能一次性影响全部派生路径。逐个覆盖容易漏。
|
||||
|
||||
@@ -157,14 +196,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` 里加一行路径推导。
|
||||
|
||||
@@ -190,7 +231,8 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
| 布尔字段用 `\|\|` 兜底 | `false` 被翻转 |
|
||||
| 给新字段造顶层平铺别名 | 扩大历史包袱 |
|
||||
| 逐个覆盖 `outputs` 而不用 `fileStem` | 漏掉某个产物路径 |
|
||||
| 在 `stages` 里配 `reimport` / `preview` / `compress` | 无效,被硬编码为 false |
|
||||
| 将 `glb` / `metadata` 当作下游入口 | 它们位于 staging;应只读取 `package/manifest.json` |
|
||||
| 在 `stages` 里配 `reimport` | 无效,被硬编码为 false |
|
||||
| 加数值字段不做范围校验 | 错配置在中途才崩,输出已被破坏 |
|
||||
|
||||
---
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -120,15 +120,65 @@ SCENE_LAYERS.forEach((layer, index) => {
|
||||
|
||||
## Blender 调用
|
||||
|
||||
### macOS Blender 4.5 的 Metal 启动兼容
|
||||
|
||||
#### 1. Scope / Trigger
|
||||
|
||||
`export_cesium.py` 在 macOS 的 Blender 4.5.12 后台启动时,可能在 Python 脚本加载前的 Metal 扩展探测中崩溃;这不是场景或道路数据错误。
|
||||
|
||||
#### 2. Signatures
|
||||
|
||||
Cesium 阶段的调用参数必须包含:
|
||||
|
||||
```text
|
||||
--background --factory-startup --debug-gpu-force-workarounds --python blender/export_cesium.py -- ...
|
||||
```
|
||||
|
||||
#### 3. Contracts
|
||||
|
||||
`--debug-gpu-force-workarounds` 是 Blender 的官方 CLI 参数。它只约束导出进程的 GPU 扩展探测,不改变 `.blend`、GeoJSON 或导出脚本的输入输出契约。
|
||||
|
||||
#### 4. Validation & Error Matrix
|
||||
|
||||
| 情况 | 结果 |
|
||||
|---|---|
|
||||
| 缺少该参数且启动时崩在 Metal 初始化 | 不应归因于道路数据;补齐参数后重跑 Cesium 阶段 |
|
||||
| 参数存在且 `CESIUM_EXPORT_DONE` / stage manifest 写出 | 继续 GLB digest 与预览验证 |
|
||||
|
||||
#### 5. Good / Base / Bad Cases
|
||||
|
||||
- Good: 保留 `--factory-startup`,并在 Cesium 导出加入 workaround。
|
||||
- Base: Blender 场景阶段未受影响时,不额外改变其启动参数。
|
||||
- Bad: 为绕过启动崩溃删除 `--factory-startup`,这会重新引入本机偏好和 addon 的不确定性。
|
||||
|
||||
#### 6. Tests Required
|
||||
|
||||
- `npm run test:build-stages` 断言导出参数仍包含 workaround。
|
||||
- 对目标区域运行 `--stages blender,cesium,preview`,并用 `glb-digest.js` 解析输出。
|
||||
|
||||
#### 7. Wrong vs Correct
|
||||
|
||||
Wrong:
|
||||
|
||||
```text
|
||||
--background --python blender/export_cesium.py
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```text
|
||||
--background --factory-startup --debug-gpu-force-workarounds --python blender/export_cesium.py
|
||||
```
|
||||
|
||||
### 两种调用姿势
|
||||
|
||||
| 阶段 | 参数 | 出处 |
|
||||
|---|---|---|
|
||||
| `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,34 @@ 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
|
||||
│ 读 .blend → _pipeline/package-staging/models/<id>.glb
|
||||
│ + staged manifest,动态预览 GLB 写入 _preview/
|
||||
│ → _pipeline/stages/cesium.manifest.json
|
||||
│
|
||||
├─[compress]──────▶ 压缩 staging 内主 GLB 并更新 staged manifest
|
||||
│ → _pipeline/stages/compress.manifest.json
|
||||
│
|
||||
├─[package]───────▶ 校验 manifest 与全部静态模型,原子发布 package/
|
||||
│ → package/manifest.json + package/models/*.glb
|
||||
│ → _pipeline/stages/package.manifest.json
|
||||
│
|
||||
└─[preview]───────▶ 生成 <id>-cesium-preview.html
|
||||
+ 拷贝 lib/cesium-preview.{js,css}
|
||||
+ 车辆巡航路线与模型
|
||||
│ + 拷贝 lib/cesium-preview.{js,css}
|
||||
+ _preview/ 车辆巡航路线、模型、动态信号
|
||||
│ → _pipeline/stages/preview.manifest.json
|
||||
```
|
||||
|
||||
**阶段之间只通过磁盘产物耦合**,不传内存状态。这是单跑任意阶段能work 的前提。
|
||||
@@ -75,14 +91,23 @@ config/areas/<id>.json
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|---|---|---|
|
||||
| `build-area.js` | 774 | 主入口:配置归一化、阶段调度、Cesium 预览页与车辆巡航生成 |
|
||||
| `build-area.js` | 主入口:区域配置读取、阶段调度、package 发布、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/package-contract.js` | package manifest 校验、相对 URI 与 SHA-256 完整性记录 |
|
||||
| `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,14 +1,15 @@
|
||||
# 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 环境。
|
||||
|
||||
---
|
||||
|
||||
## 定位
|
||||
|
||||
预览层是**验证性的,不是产物本身**。它加载 `cesium` 阶段导出的 `.glb` + `.json`,
|
||||
用来确认资产在真实 Cesium 里的样子。改这一层**不会**改变 Blender/GLB 主资产。
|
||||
预览层是**验证性的,不是产物本身**。它加载 `package/manifest.json`,用来确认已发布资产
|
||||
在真实 Cesium 里的样子。改这一层**不会**改变 package 内的 Blender/GLB 静态资产。
|
||||
|
||||
车辆巡航同理——README 里写明它是"用于验证高精度巡航可用性的预览层功能"。
|
||||
|
||||
@@ -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,27 @@ 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` 读,
|
||||
两边都要动。
|
||||
|
||||
`glbName` 与 `metadataName` 都是 `package/manifest.json`。浏览器读取 package manifest 后,
|
||||
每个 `assets[*].uri` 必须相对**manifest 文件**解析,绝不能相对 preview HTML 解析;否则将
|
||||
错误请求 `outputs/<area>/models/...` 而不是 `outputs/<area>/package/models/...`。
|
||||
|
||||
`build-area.js` 只保留已发布 package / 动态输入的依赖检查、写入顺序和 preview manifest ownership;
|
||||
不要把 HTML 模板、runtime copy 或转义实现移回阶段调度器。路线 JSON 与车辆 glTF 分别由
|
||||
`vehicle-route.js` 和 `vehicle-model.js` 生成,二者都是不启动外部工具的 Node 模块。
|
||||
交通信号灯 runtime 则从 package manifest 的 `runtime` 读取;预览只负责驱动其状态,不拥有这些文件。
|
||||
|
||||
---
|
||||
|
||||
## 加载流程
|
||||
@@ -107,6 +117,9 @@ setLoadingMessage("Preparing view")
|
||||
window.osmPreview = { viewer, metadata, placement, assets, cruise, cameras };
|
||||
```
|
||||
|
||||
预览加载的生成式 JSON(路线和交通信号)使用 `fetch(..., { cache: "no-store" })`,因为
|
||||
这些文件保持稳定文件名但会被单独重生成;浏览器不得继续显示旧的巡航路线。
|
||||
|
||||
调试和无头检查都靠它。**加新的顶层对象就往这里挂**,不要再开新全局。
|
||||
|
||||
---
|
||||
@@ -177,6 +190,21 @@ let baseStatus = "";
|
||||
这会让用户误判材质和模型质量。若确实需要性能模式,应做成显式开关,而不是默认牺牲
|
||||
预览清晰度。
|
||||
|
||||
### 车辆事件仅属于预览会话
|
||||
|
||||
点击车辆的信息卡可在 `normal`、`breakdown`、`accident` 三态间切换。这是验证交互,
|
||||
不得写入 `package/`、路线 JSON 或 OSM。状态必须附着在 `addCruiseVehicle()` 返回的记录上;
|
||||
`createTrafficAwarePositions()` 只在状态为 `normal` 时推进已有的 route distance,恢复正常从
|
||||
当前停点继续。
|
||||
|
||||
- `breakdown`:黄色扳手 label,路线保留原色。
|
||||
- `accident`:红色警示 label,路线设为红色。
|
||||
- `normal`:隐藏 label,恢复原路线颜色。
|
||||
|
||||
车辆实体以 `properties.vehicleId` 标识;点击拾取必须只处理此属性,不能把静态模型、路线或
|
||||
信号灯当作车辆。Cesium InfoBox 在本预览中关闭,信息卡必须使用 HTML/CSS,并把新增 DOM 句柄
|
||||
集中在 `cesium-preview.js` 顶部。
|
||||
|
||||
### 单资产 vs 多资产的开关
|
||||
|
||||
```js
|
||||
@@ -196,6 +224,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 +343,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 坐标轴。
|
||||
3
.trellis/tasks/08-13-native-road-compiler/check.jsonl
Normal file
3
.trellis/tasks/08-13-native-road-compiler/check.jsonl
Normal file
@@ -0,0 +1,3 @@
|
||||
{"file":".trellis/spec/pipeline/index.md","reason":"Check command ownership, disk artifact boundaries, and legacy pipeline compatibility."}
|
||||
{"file":".trellis/spec/preview/index.md","reason":"Check browser workbench state, error handling, and no-build browser constraints."}
|
||||
{"file":".trellis/spec/config/index.md","reason":"Check new area output/config normalization and compatibility."}
|
||||
110
.trellis/tasks/08-13-native-road-compiler/design.md
Normal file
110
.trellis/tasks/08-13-native-road-compiler/design.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Native Road Compiler Workbench Design
|
||||
|
||||
## Architecture
|
||||
|
||||
The native compiler is an additive pipeline path. It owns a separate output
|
||||
directory under an area and never writes into `osm2streets_web_out/`.
|
||||
|
||||
```text
|
||||
OSM XML + native-road-overrides.json
|
||||
|
|
||||
v
|
||||
Canonical Road Model
|
||||
roads / endpoints / junction candidates / provenance
|
||||
|
|
||||
v
|
||||
Native Geometry Compiler
|
||||
road surfaces / initial junction surfaces / diagnostics
|
||||
|
|
||||
+--> native-road/compiled.json
|
||||
+--> native-road/layers/*.geojson
|
||||
+--> native-road/diagnostics.json
|
||||
+--> native-road/comparison.json
|
||||
|
|
||||
v
|
||||
Road Workbench HTTP service
|
||||
browser map + inspect/edit/save API
|
||||
```
|
||||
|
||||
The canonical model is the authority. Render layers, browser display data, and
|
||||
future Blender compatibility adapters are derived from it.
|
||||
|
||||
## Commands And Ownership
|
||||
|
||||
- `npm run road:compile -- --config <area-config>` performs no browser work.
|
||||
It reads OSM plus the persisted override file, writes a staged native-road
|
||||
result, validates it, and atomically promotes the result directory.
|
||||
- `npm run road:workbench -- --config <area-config>` compiles first unless
|
||||
`--no-compile` is supplied, then starts a local HTTP server scoped to that
|
||||
one area.
|
||||
- The server exposes read-only compiler artifacts and one explicit save API
|
||||
for validated overrides. It does not expose arbitrary filesystem paths.
|
||||
- Existing `build:area`, `intermediates`, QGIS, Blender, Cesium, and package
|
||||
paths remain unchanged in the first iteration.
|
||||
|
||||
## Data Contracts
|
||||
|
||||
### Canonical road model
|
||||
|
||||
Each road direction carries a stable ID derived from OSM identifiers, source
|
||||
way IDs, endpoint node IDs, centerline, explicit/inferred attributes, applied
|
||||
override IDs, and diagnostics. Junction candidates likewise use their OSM node
|
||||
ID when available. Values include provenance such as `tag:lanes:forward`,
|
||||
`inferred:highway-default`, or `override:<id>`.
|
||||
|
||||
Each `Movement` is a stable semantic record joining a connection, source and
|
||||
target road/lane, turn class, provenance, and an optional connector geometry.
|
||||
`geometryStatus="connector"` publishes a connector curve;
|
||||
`geometryStatus="continuous"` means the lane centerlines meet at the node and
|
||||
does not invent a zero-length curve; `deferred-too-long` retains the movement
|
||||
while withholding unsafe geometry.
|
||||
|
||||
### Override file
|
||||
|
||||
`<area>/native-road-overrides.json` is versioned and human-reviewable. It
|
||||
contains an array of uniquely identified changes whose targets are stable road
|
||||
or endpoint IDs. Supported v1 records are `road` parameter overrides and
|
||||
`junction-connection` decisions. The save endpoint validates schema, target
|
||||
existence, finite values, and duplicate/conflicting edits before atomic write.
|
||||
|
||||
### Compiler artifacts
|
||||
|
||||
`<area>/native-road/compiled.json` is the workbench's single read model and
|
||||
contains the canonical `movements` list.
|
||||
`layers/` contains generated GeoJSON with source/provenance properties.
|
||||
`diagnostics.json` contains severity, stable subject ID, source IDs, rule,
|
||||
message, and optional geometry. `comparison.json` reports counts and coverage
|
||||
against available osm2streets layers; it does not claim quality solely from
|
||||
visual differences.
|
||||
|
||||
## Browser Workbench
|
||||
|
||||
The browser uses OpenLayers as its sole GIS runtime, served directly from the
|
||||
local allowlisted `node_modules` packages with a browser import map. The map
|
||||
renders fit-to-data OSM centerlines, native surfaces, optional osm2streets
|
||||
reference layers, diagnostics, selected-object provenance, movements, and
|
||||
overrides. This provides mature map selection and hit detection without a
|
||||
framework or bundler.
|
||||
|
||||
The user can select a road or endpoint, edit only v1 fields, inspect the
|
||||
resulting override record, explicitly save it, and recompile/reload. Saved
|
||||
state is visibly differentiated from unsaved state. The workbench must not
|
||||
offer freehand final-polygon editing, since that would break reproducibility.
|
||||
|
||||
## Geometry And Validation
|
||||
|
||||
V1 produces road segments from projected centerline offsets and terminal
|
||||
cross-sections. It only generates a junction surface when endpoints satisfy
|
||||
the supported ordinary T/cross shape and geometry checks; otherwise it emits a
|
||||
diagnostic rather than inventing an invalid polygon. Validation detects
|
||||
dangling endpoints, unclosed/self-intersecting rings, non-finite coordinates,
|
||||
unsupported multi-level intersections, and source/topology ambiguity. Small
|
||||
numerical cleanup may be explicit and recorded; semantic failures are never
|
||||
silently repaired.
|
||||
|
||||
## Compatibility And Rollout
|
||||
|
||||
The first compiler's layers use existing render-layer names where meaningful,
|
||||
but are stored separately. A later, explicitly enabled Blender provider option
|
||||
may consume native layers after comparison gates pass. Delete/replace behavior
|
||||
is out of scope; rollback is selecting the existing osm2streets pipeline.
|
||||
@@ -0,0 +1,3 @@
|
||||
{"file":".trellis/spec/pipeline/index.md","reason":"Native compiler commands, artifacts, and area config extend the Node pipeline while preserving legacy stages."}
|
||||
{"file":".trellis/spec/preview/index.md","reason":"The browser workbench is a new DOM runtime and must follow local preview loading and state conventions where applicable."}
|
||||
{"file":".trellis/spec/config/index.md","reason":"New native-road output paths and config behavior extend the normalized area contract."}
|
||||
32
.trellis/tasks/08-13-native-road-compiler/implement.md
Normal file
32
.trellis/tasks/08-13-native-road-compiler/implement.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Add area output/config normalization and command entrypoints for the native
|
||||
compiler, preserving existing stage behavior and paths.
|
||||
2. Implement a shared OSM road parser and canonical road/endpoints model with
|
||||
source provenance, explicit versus inferred properties, and stable IDs.
|
||||
3. Implement v1 override schema, validation, load/apply behavior, atomic save,
|
||||
and focused unit tests.
|
||||
4. Implement projected road segment geometry, supported T/cross junction
|
||||
detection, native GeoJSON artifact emission, diagnostics, and comparison
|
||||
summary.
|
||||
5. Implement a dependency-free local workbench server and browser UI with
|
||||
selection, provenance display, v1 parameter/topology editing, explicit save,
|
||||
compile/reload, and error states.
|
||||
6. Add native compiler tests using focused fixtures plus nantaizi analysis;
|
||||
run existing relevant Node tests to confirm legacy behavior remains intact.
|
||||
7. Compare nantaizi and at least one supplied problematic OSM sample. Record
|
||||
metrics, unsupported cases, and follow-up work in task research.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run road:workbench -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run test:build-stages
|
||||
npm run test:preflight
|
||||
npm run test:preview-assets
|
||||
```
|
||||
|
||||
Browser validation includes loading the workbench, editing a road parameter,
|
||||
saving, verifying the override file, recompiling, reloading, and confirming
|
||||
provenance identifies the saved override.
|
||||
93
.trellis/tasks/08-13-native-road-compiler/prd.md
Normal file
93
.trellis/tasks/08-13-native-road-compiler/prd.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Native road compiler workbench
|
||||
|
||||
## Goal
|
||||
|
||||
Build an incremental native road compiler for Chinese urban and campus OSM
|
||||
data that can progressively exceed osm2streets in geometry quality,
|
||||
explainability, and repeatable correction. The existing osm2streets pipeline
|
||||
must remain usable while the native compiler is developed and compared.
|
||||
|
||||
The first deliverable is a browser-based Road Workbench. It must expose the
|
||||
native compiler's source data, generated geometry, and diagnostics, allow
|
||||
users to make small semantic/topology corrections, persist those corrections
|
||||
as versionable overrides, and reload them automatically in later runs.
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- Nantaizi currently works relatively well because its OSM data received
|
||||
deliberate supplemental tagging; it still has missing boundaries and
|
||||
polygons that cannot be closed.
|
||||
- Other tested OSM inputs expose osm2streets sensitivity to input structure
|
||||
and leave too much opaque, final-polygon repair work in QGIS.
|
||||
- Existing Blender consumes the nine GeoJSON render layers from
|
||||
`osm2streets_web_out/`; QGIS GeoPackage edits can currently be reimported
|
||||
only as a whole batch.
|
||||
- The repository has no existing interactive browser editing service. Existing
|
||||
Cesium preview is a static, generated verification page.
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1: Add a native-road-compiler path without replacing or regressing the
|
||||
existing osm2streets path.
|
||||
- R2: Parse OSM into a canonical, source-traceable road model with stable
|
||||
references to OSM ways and nodes, explicit values versus inferred values,
|
||||
and diagnostics.
|
||||
- R3: Compile at least ordinary road segments and the initial supported
|
||||
junction subset into the existing render-layer contract, allowing existing
|
||||
Blender/Cesium consumers to be reused.
|
||||
- R4: Provide a browser Road Workbench that overlays raw OSM topology,
|
||||
generated geometry, osm2streets comparison geometry when available, and
|
||||
compiler diagnostics.
|
||||
- R5: The workbench must permit scoped user adjustments and save them to an
|
||||
area-local, human-reviewable override file. Future compile and workbench
|
||||
runs must load that file automatically.
|
||||
- R6: Each generated object and diagnostic must be traceable to OSM source
|
||||
IDs, compiler rule/inference evidence, and relevant override IDs.
|
||||
- R7: Validate topology and geometry before publishing generated layers;
|
||||
report unresolved semantic errors instead of silently disguising them as
|
||||
geometric repair.
|
||||
- R8: Develop against nantaizi plus problem inputs and report native versus
|
||||
osm2streets comparison metrics.
|
||||
|
||||
## Scope Boundaries
|
||||
|
||||
- First implementation targets Chinese urban/campus roads, ordinary road
|
||||
segments, T/cross junctions, directed/multi-lane roads, and data already
|
||||
tagged in nantaizi where possible.
|
||||
- Existing Blender, Cesium export, package format, building, vegetation, and
|
||||
water generators are out of scope unless a compatibility adapter requires a
|
||||
narrowly scoped change.
|
||||
- Directly editing final render polygons is not the intended correction model;
|
||||
generated layers remain derived output.
|
||||
- Complex interchanges, arbitrary multilayer junctions, and full worldwide OSM
|
||||
coverage are deferred until driven by concrete samples.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] A native compile command produces a canonical road model, generated
|
||||
layers, diagnostics, and comparison artifacts for a configured area without
|
||||
changing the osm2streets output path.
|
||||
- [ ] A browser command serves a Road Workbench for an area and clearly shows
|
||||
source topology, generated output, diagnostics, provenance, and saved
|
||||
overrides.
|
||||
- [ ] A user can make the agreed first-scope override edits in the browser,
|
||||
save them explicitly, and receive a durable area-local override artifact.
|
||||
- [ ] Re-running compile or reopening the workbench applies saved overrides
|
||||
automatically and exposes their provenance.
|
||||
- [ ] The compiler reports invalid/unclosed geometry, dangling road ends,
|
||||
and unresolved junction/lane ambiguity with source IDs.
|
||||
- [ ] Nantaizi and at least one known problematic area can run through the
|
||||
native analysis/preview path, with comparison metrics captured rather than
|
||||
a claim based only on visual inspection.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- The first browser editing surface supports road parameters (width, directed
|
||||
lane counts, left/right sidewalk state) plus junction endpoint
|
||||
connect/disconnect decisions.
|
||||
- Turn restrictions, stop lines, and crosswalk placement are deferred until
|
||||
the compiler has a validated road/junction editing loop.
|
||||
- Overrides are a versioned, human-reviewable JSON artifact owned by the area,
|
||||
not edits to generated polygon layers.
|
||||
- Native output and osm2streets output remain parallel during development;
|
||||
neither silently overwrites the other.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Two-Area Native Road Comparison
|
||||
|
||||
## Runs
|
||||
|
||||
2026-08-14:
|
||||
|
||||
```bash
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run build:area -- --config config/areas/hanyang-block.json --stages intermediates
|
||||
npm run road:compile -- --config config/areas/hanyang-block.json
|
||||
```
|
||||
|
||||
`comparison.json` is a coverage and diagnostic record. Feature counts are not
|
||||
a geometry-quality score: osm2streets and the native compiler segment roads at
|
||||
different levels.
|
||||
|
||||
| Area | Directional roads | Native surfaces | Native junctions | Movements | Internal ends | Manual candidates | osm2streets road surfaces |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| nantaizi-lake-innovation-valley | 34 | 19 | 6 | 46 | 4 | 2 | 50 |
|
||||
| hanyang-block | 475 | 352 | 20 | 369 | 161 | 83 | 1826 |
|
||||
|
||||
## Observed Failure Modes
|
||||
|
||||
The hanyang osm2streets/QGIS run completed, but its log reported repeated:
|
||||
|
||||
- roads trimmed into oblivion;
|
||||
- degenerate intersections that could not be collapsed because layers, names,
|
||||
highway types, or lane specifications differ;
|
||||
- intersection polygon requests with no roads.
|
||||
|
||||
The native compiler did not hide the related uncertainty. It reported 161
|
||||
internal road ends. Eighty-three have one or more nearby, direction-compatible
|
||||
candidate departures within 35 metres; these remain suggestions for explicit
|
||||
review rather than automatic topology edits. The initial five one-way junctions
|
||||
that lacked connector curves now publish road-surface envelopes based on their
|
||||
semantic movements. Across hanyang, 369 movements are identified: 243 require
|
||||
a connector curve, while 126 are continuous at their OSM node and intentionally
|
||||
have no separate geometry.
|
||||
|
||||
## Resulting Priorities
|
||||
|
||||
1. Keep manual candidate suggestions and semantic overrides as the correction
|
||||
path for near-miss topology. Do not bulk-connect candidates.
|
||||
2. Expand ordinary junction support from 3/4 physical approaches only after
|
||||
identifying a repeated unsupported topology; one-way movements that are
|
||||
continuous at a node are already supported without fake connector geometry.
|
||||
3. Add an inspectable movement artifact so turn geometry is not the only
|
||||
representation of a road-to-road movement.
|
||||
4. Use a visual review of a few explicit hanyang diagnostics before changing
|
||||
connection-distance or road-class rules.
|
||||
28
.trellis/tasks/08-13-native-road-compiler/task.json
Normal file
28
.trellis/tasks/08-13-native-road-compiler/task.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"id": "native-road-compiler",
|
||||
"name": "native-road-compiler",
|
||||
"title": "Native road compiler workbench",
|
||||
"description": "",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-13",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [
|
||||
"08-14-native-road-lane-markings"
|
||||
],
|
||||
"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,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。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user