Compare commits
97 Commits
main
...
009fe34f79
| Author | SHA1 | Date | |
|---|---|---|---|
| 009fe34f79 | |||
| 7883a451c5 | |||
| 620127031e | |||
| 5704b51315 | |||
| 969b57106f | |||
| 7d4b3b566a | |||
| 5cde08090e | |||
| b78cb6e990 | |||
| b44c995457 | |||
| 2469cfdbff | |||
| 284d3dbedb | |||
| f542a64924 | |||
| d5c58b5b0c | |||
| 73707dabbe | |||
| 5220c6b2a2 | |||
| d7c2e13124 | |||
| e7bc7f82b1 | |||
| 25cf82e7c7 | |||
| bc845444bb | |||
| bb2a1449ac | |||
| 1c2d091ad2 | |||
| e997ece188 | |||
| b7031f145a | |||
| ee273c5cd6 | |||
| 71ba536c7c | |||
| 903b34b5ae | |||
| f1f829c2c6 | |||
| a16400ff96 | |||
| 8ecd794633 | |||
| 12aeda9a63 | |||
| 9a8dbc1a74 | |||
| 5403936ae4 | |||
| 0bc949bc24 | |||
| 6a0437ff83 | |||
| 3cad384b25 | |||
| 9e0e25aadf | |||
| e9ab1f03eb | |||
| 1154de3ba1 | |||
| c057258085 | |||
| 41d32457f2 | |||
| 5accc0a4b1 | |||
| 7204c28161 | |||
| a63a3ae0d8 | |||
| f7e71cfe9c | |||
| e41bfd13ec | |||
| 46542c5f4e | |||
| 1896884900 | |||
| 9bb97d4507 | |||
| bd38aa55d3 | |||
| b318f7b641 | |||
| d065dcdaf3 | |||
| 472a83c670 | |||
| bc045177a5 | |||
| 8409d2c6c3 | |||
| 5f59ad0ef0 | |||
| 47efb78f1e | |||
| 8bfc02e66e | |||
| 9d42181246 | |||
| fb863dafb2 | |||
| 7dc5c947a3 | |||
| 866b59acf8 | |||
| 4c4f4534c0 | |||
| 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 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ outputs/
|
||||
__pycache__/
|
||||
assets/models/speedtree/
|
||||
assets/models/lyrog/
|
||||
inputs/osm
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
## 两层配置
|
||||
|
||||
用户只写第一层,第二层是机器生成的中间产物:
|
||||
用户只写第一层;legacy QGIS 链路需要时才生成第二层中间产物:
|
||||
|
||||
```
|
||||
config/areas/<id>.json ← 你写的
|
||||
│ scripts/lib/area-config.js: normalizeAreaConfig()
|
||||
│ 补默认值 + 推导输出路径
|
||||
▼
|
||||
<areaDir>/_pipeline/osm2streets-qgis.config.json ← 生成的,不要手改
|
||||
<areaDir>/_pipeline/osm2streets-qgis.config.json ← legacy 生成的,不要手改
|
||||
│
|
||||
▼ build-osm2streets-qgis.js / reimport-gpkg.js
|
||||
```
|
||||
@@ -54,7 +54,9 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
| `turnLaneArrows` | | 见下 | 从 OSM `turn:lanes:*` 生成自定义车道箭头的发布开关 |
|
||||
| `osm2streets` | | 见下 | 透传给 osm2streets 的选项 |
|
||||
| `blender` | | 见下 | Blender 侧选项 |
|
||||
| `compress` | | 见下 | 显式 `compress` 阶段的 GLB 压缩选项 |
|
||||
| `nativeRoad` | | 见下 | 原生道路编译选项 |
|
||||
| `v2xPreview` | | 见下 | 可选的 Cesium 预览实时 V2X 叠加设置 |
|
||||
| `compress` | | 见下 | 默认交付压缩阶段的 GLB 压缩选项 |
|
||||
| `budget` | | 见下 | 区域 GLB 性能与体量预算 |
|
||||
| `outputs` | | 从 `id` 推导 | 输出路径覆盖,逃生舱 |
|
||||
|
||||
@@ -65,15 +67,17 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
|
||||
| 字段 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `intermediates` | `true` | 旧名 `qgis` 仍被接受 |
|
||||
| `intermediates` | `false` | native-only 默认;旧名 `qgis` 仍被接受,显式开启才运行 legacy |
|
||||
| `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` 会整体覆盖这里的默认值。
|
||||
|
||||
@@ -130,11 +134,47 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
|---|---|---|
|
||||
| `treeStyle` | `"natural"` | 合法值见 `generate_scene.py` 的 `TREE_STYLES`(`natural`、`procedural`、`shapespark`) |
|
||||
| `officeOverrides` | `""` | 旧名 `office_overrides` 仍被接受 |
|
||||
| `roadProvider` | `"native"` | Blender 道路来源。`"native"` 时仅使用 `native-road/` 的道路、路口和人行道面;`"osm2streets"` 仅用于显式 legacy/debug 构建。 |
|
||||
|
||||
### `nativeRoad`
|
||||
|
||||
| 字段 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `edgeLines` | `false` | 是否输出道路边缘线。 |
|
||||
| `junctionTemplates.enabled` | `false` | 启用显式绑定的参数化路口模板。 |
|
||||
| `junctionTemplates.references` | `[]` | 仅支持 `cross-v1`;每项必须给出 OSM `nodeId`,可带 GCJ-02 `referenceFile` 作校准与有效性检查。 |
|
||||
| `junctionTemplates.clusters` | `[]` | `cross-cluster-v1` 的相邻 OSM 节点簇。输出各外部进口的参数化渐变道路面,簇内短段与节点级路口面保留;`approachWidthMultiplier` / `approachLengthMeters` 控制渐变。可选 `referenceFile` 记录 GCJ-02 校准来源,不合并节点级道路、connector、信号或停止线语义。 |
|
||||
| `junctionTemplates.clusters[].cornerRadiusMeters` | `12`(4–25) | 仅 `complex-junction-v1`:相邻进口夹角处路缘圆角的半径。圆角切于两侧最外道路边缘,只补齐夹角处的路面,不改变 connector、信号或停止线。 |
|
||||
| `junctionTemplates.clusters[].outerRadiusExtraMeters` | `18`(18–35) | 仅 `complex-junction-v1`:路口中心到外部进口交接边界的额外半径。用于让圆角包住角部斑马线;未配置时保持原有 18 m。 |
|
||||
|
||||
`junctionTemplates.references[*].referenceFile` 与
|
||||
`junctionTemplates.clusters[*].referenceFile` 是唯一允许相对写法的路径:相对当前
|
||||
`config/areas/<id>.json` 解析。`readAreaConfig()` 会先将其规范化为绝对路径,再通过
|
||||
`toRoadCompilerInput()` 传入 road compiler;包内不得自行解析相对路径或读取配置文件。
|
||||
|
||||
### `v2xPreview`
|
||||
|
||||
这是 Cesium 验证预览的可选实时叠加层,不进入发布的 `package/`。默认值:
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": false,
|
||||
"apiBaseUrl": "/api",
|
||||
"wsBaseUrl": "/websocket",
|
||||
"crossCode": ""
|
||||
}
|
||||
```
|
||||
|
||||
`apiBaseUrl` 和 `wsBaseUrl` 应使用同源反向代理路径,不能写入私有上游主机、账号或令牌。
|
||||
V2X 接口返回的地图数据是 GCJ-02;浏览器预览在创建 Cesium entity 前一次性转为 WGS84,
|
||||
而 native package 的 WGS84/ENU 契约保持不变。详见
|
||||
[`docs/v2x-cesium-preview.md`](../../../docs/v2x-cesium-preview.md)。
|
||||
|
||||
### `compress`
|
||||
|
||||
只影响显式 `--stages compress`。默认压缩链是 texture resize + WebP transcode,
|
||||
不覆盖默认 `<area-id>.glb`。
|
||||
完整构建和显式 `--stages compress` 都使用此配置。默认压缩链是 texture resize + WebP
|
||||
transcode,成功后替换**package staging** 中的主 GLB 与 manifest;未压缩源只保留在构建临时目录。
|
||||
只有随后的 `package` 阶段才会原子发布到 `package/`。
|
||||
|
||||
| 字段 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
@@ -162,14 +202,16 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
|
||||
### `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"
|
||||
}
|
||||
}
|
||||
@@ -177,9 +219,14 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
|
||||
可覆盖的键(`scripts/lib/area-config.js`):`areaDir`、`fileStem`、`geojsonDir`、`gpkg`、
|
||||
`qgisProject`、`qgisPreview`、`blend`、`render`、`glb`、`metadata`、`cesiumPreview`、
|
||||
`compressedFileStem`、`compressedGlb`、`compressedMetadata`、`compressedCesiumPreview`、
|
||||
`vehicleRoute`、`vehicleModel`、`pipelineDir`、`stageManifestDir`。
|
||||
|
||||
静态发布路径另有 `packageDir`、`packageStagingDir`、`packageManifest`、
|
||||
`packageStagingManifest`、`packageModelDir`、`packageStagingModelDir` 与
|
||||
`packagePrimaryGlb`;预览路径另有 `previewDir`、`previewDescriptor` 与
|
||||
`trafficSimulation`(native preview 的可迁移仿真描述符)。除非在迁移旧调用,
|
||||
不要覆盖 `glb` / `metadata`:它们是 staging 内部路径,不是下游资产入口。
|
||||
|
||||
**优先改 `fileStem` 或 `areaDir`**——它们能一次性影响全部派生路径。逐个覆盖容易漏。
|
||||
|
||||
---
|
||||
@@ -221,7 +268,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 |
|
||||
| 加数值字段不做范围校验 | 错配置在中途才崩,输出已被破坏 |
|
||||
|
||||
---
|
||||
|
||||
@@ -21,13 +21,14 @@
|
||||
全部是 CommonJS(`package.json` 的 `"type": "commonjs"`),无构建步骤、无 TypeScript、
|
||||
零运行时依赖(唯一依赖 `osm2streets-js-node` 只被 `build-osm2streets-qgis.js` 用)。
|
||||
|
||||
## 可选 GLB 压缩阶段
|
||||
## 默认 GLB 压缩阶段
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
`compress` 是显式请求的替代产物阶段,不属于 `all`,也不覆盖默认 `<area-id>.glb`。
|
||||
它用于在已有 Cesium GLB 上生成并列压缩 GLB、metadata 和预览页。当前主路径是
|
||||
texture resize + WebP transcode。
|
||||
`compress` 是完整构建的默认阶段,位于 `cesium` 与 `package` 之间。它在
|
||||
`_pipeline/package-staging/` 的主 GLB 上执行 texture resize + WebP transcode,并替换 staged
|
||||
主 GLB 与 staged manifest;不触碰已发布的 `package/`,也不重写 preview HTML。未压缩版本只在
|
||||
`<areaDir>/_pipeline/compress-*` 临时目录中存在,成功或失败后都会清理。
|
||||
|
||||
`scripts/compress-glb.js` 是该阶段调用的低层脚本,也可单独运行做实验。
|
||||
|
||||
@@ -62,20 +63,17 @@ npm run compress:glb -- --input in.glb --output out.glb [options]
|
||||
### 3. Contracts
|
||||
|
||||
- 输入必须是现有 `.glb` 文件;`--output` 必须不同于 `--input`。
|
||||
- 输出是并列压缩 GLB;默认构建产物不被替换。
|
||||
- `compress` 阶段依赖默认 `glb`、`metadata` 和 `cesiumPreview` 已存在。
|
||||
- 低层脚本仍输出到与输入不同的路径;`build-area.js` 使用临时输入/输出路径,避免原地压缩。
|
||||
- `compress` 阶段依赖 staged `glb` 与 `metadata` 已存在,并在全部临时交付文件生成和校验后
|
||||
以 `renameSync` 替换它们。
|
||||
- 默认链固定为 `gltf-transform resize -> gltf-transform webp`。
|
||||
- `compress` 阶段默认 `textureSize=768`;低层脚本单独运行时默认 `--texture-size 1024`。
|
||||
- `build-area.js` 默认写:
|
||||
- `<fileStem>-compressed-webp768.glb`
|
||||
- `<fileStem>-compressed-webp768.json`
|
||||
- `<fileStem>-compressed-webp768-cesium-preview.html`
|
||||
- 伴生 metadata 的 `asset` 与 `id="main"` 资产 URL 改为压缩 GLB 文件名;其余资产(包括
|
||||
`category="semantic"` 的 Cesium 分类检查 GLB)必须原样保留。
|
||||
- preview HTML 只替换 `window.OSM_ASSET_PREVIEW_CONFIG` 的 `glbName` /
|
||||
`metadataName` 和 loading 文案,不改 preview runtime。
|
||||
- `build-area.js` 不会写并列 `-compressed-*` 交付物;`package` 是唯一能发布静态文件到
|
||||
`package/` 的阶段。
|
||||
- staged manifest 的 `assets[*].uri` 必须保持 package-relative,如 `models/<area>.glb`;分类
|
||||
layer GLB 必须原样保留。
|
||||
- 成功时 stdout 打印 `GLB_COMPRESS_DONE <json>`,包含压缩前后大小、image /
|
||||
non-image bytes、结构计数、扩展、metadata / preview 输出路径。
|
||||
non-image bytes、结构计数、扩展与 metadata 输出路径。
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
@@ -86,27 +84,24 @@ npm run compress:glb -- --input in.glb --output out.glb [options]
|
||||
| `--output` 等于 `--input` | 抛错,避免覆盖源 GLB |
|
||||
| 数值参数超范围 | 抛错并指出合法范围 |
|
||||
| `gltf-transform` 退出非零 | 抛错并带上 status / signal |
|
||||
| `--preview` 没有 `--metadata` | 抛错,因为 preview 必须指向存在的 metadata |
|
||||
| preview HTML 找不到配置块 | 抛错,不做猜测替换 |
|
||||
| `--stages compress` 但默认 preview 不存在 | `Cesium preview not found: <path>` |
|
||||
| `--stages compress` 但 staged manifest 不存在 | `Cesium metadata not found: <path>` |
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: `--stages cesium,compress` 先重导默认 GLB,再生成并列压缩产物。
|
||||
- Good: `--texture-size 768 --metadata --preview` 生成压缩 GLB、metadata、HTML,
|
||||
源 GLB 保持不变。
|
||||
- Good: 默认构建先导出 Cesium,再压缩 staged 资产,最后由 `package` 发布。
|
||||
- Good: `--stages compress` 对已有 staged 交付物重新压缩,临时源 GLB 不会留在输出目录。
|
||||
- Base: 只传 `--input --output` 生成压缩 GLB,不生成伴生文件。
|
||||
- Bad: 使用 `--meshopt` 后没有做 Cesium 兼容性验证就当默认产物发布。
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- `node --check scripts/compress-glb.js`
|
||||
- `npm run test:compress-glb`:断言压缩 metadata 只替换主资产,不丢失语义资产。
|
||||
- `npm run test:compress-glb`:断言压缩 manifest 只替换主资产,不丢失语义 layer。
|
||||
- `node --check scripts/build-area.js`
|
||||
- 对目标区域跑一次 `npm run compress:glb -- ... --metadata --preview`
|
||||
- 对目标区域跑一次 `npm run build:area -- --stages compress`
|
||||
- `node scripts/glb-digest.js <compressed.glb>` 确认可解析结构和扩展
|
||||
- 浏览器/Cesium 预览压缩 HTML,确认 `EXT_texture_webp` 在目标环境可加载
|
||||
- 对目标区域跑一次 `npm run compress:glb -- ... --metadata`
|
||||
- 对目标区域跑一次默认 `npm run build:area`
|
||||
- `node scripts/glb-digest.js <area>.glb` 确认可解析结构和扩展
|
||||
- 浏览器/Cesium 预览标准 HTML,确认 `EXT_texture_webp` 在目标环境可加载
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
@@ -116,10 +111,110 @@ Wrong:
|
||||
npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a.glb
|
||||
```
|
||||
|
||||
Correct:
|
||||
Correct for the low-level script:
|
||||
|
||||
```bash
|
||||
npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a-compressed-webp768.glb
|
||||
npm run compress:glb -- --input outputs/a/a.glb --output /tmp/a-compressed.glb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 可复用资产包发布
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
`package` 将已验证的 static staging 提升为下游可消费的唯一交付边界:
|
||||
`<areaDir>/package/`。它不复制大文件到第二个发布目录;成功时将整个 staging 目录原子 rename
|
||||
为 package。车辆、路线和动态信号属于预览能力,必须留在 `<areaDir>/_preview/`,不能进入 package。
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
```bash
|
||||
npm run build:area -- --config config/areas/<area>.json --stages cesium,compress,package
|
||||
npm run build:area -- --config config/areas/<area>.json --stages preview
|
||||
node scripts/test-package-contract.js
|
||||
node scripts/test-package-examples.js
|
||||
```
|
||||
|
||||
下游入口永远是:
|
||||
|
||||
```text
|
||||
outputs/<area>/package/manifest.json
|
||||
```
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
发布 manifest 使用 `schema: "osm-asset-package/v1"`,至少包含:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "osm-asset-package/v1",
|
||||
"packageVersion": "1.0.0",
|
||||
"areaId": "example",
|
||||
"coordinateSystem": { "axes": "ENU", "units": "meters", "x": "east", "y": "north", "z": "up" },
|
||||
"placement": { "longitude": 114.3, "latitude": 30.5, "height": 0, "headingCorrectionDegrees": -90 },
|
||||
"bounds": { "minLon": 114.2, "minLat": 30.4, "maxLon": 114.4, "maxLat": 30.6 },
|
||||
"assets": [{
|
||||
"id": "main",
|
||||
"role": "scene",
|
||||
"category": "scene",
|
||||
"uri": "models/example.glb",
|
||||
"defaultLoad": true,
|
||||
"integrity": { "bytes": 123, "sha256": "..." }
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
- `coordinateSystem` 固定为 ENU meters,`placement` 是 WGS84 锚点与 heading correction。
|
||||
- 必须恰有一个 `role="scene"` / `category="scene"` / `defaultLoad=true` 主资产;roads、buildings、
|
||||
vegetation、water 是 `role="layer"` 且 `defaultLoad=false` 的可选语义层。
|
||||
- `assets[*].uri` 与 `runtime[*].uri` 只能是包内正向相对路径,禁止绝对路径、反斜杠和 `..`。
|
||||
- `runtime` 声明可编程运行时资产;当前至少包含 `traffic-signals` 锚点 JSON、
|
||||
`traffic-signals-dynamic` 灯珠 GLB,以及两个 `traffic-signals-countdown-*` 倒计时 GLB。
|
||||
它们是资产包的一部分,不属于 `_preview/`。
|
||||
- `package` 在晋升前调用 `validateManifest()`;再为每个模型写 bytes 与 SHA-256,然后复验。
|
||||
- `package` 是唯一拥有最终 package file records 的 stage。`cesium` / `compress` 只能拥有 staging
|
||||
或计算摘要,不能留下指向最终 package 文件的 outputs record。
|
||||
- 下游代码和 preview 都从 manifest URL 解析 `assets[*].uri`。参考
|
||||
`examples/cesium-asset-package.js` 与 `examples/three-asset-package.js`。
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| staging manifest 缺失或无效 JSON | `package` 失败,不替换上次成功 package |
|
||||
| URI 是绝对路径、含 `..` 或 `\\` | `validateManifest()` 失败 |
|
||||
| ENU / WGS84 字段缺失或越界 | `validateManifest()` 失败 |
|
||||
| 没有或多于一个 scene asset | `validateManifest()` 失败 |
|
||||
| manifest 声明的模型文件缺失 | `validateManifest()` 失败 |
|
||||
| rename 发布异常 | 尝试恢复 `<package>.previous`,保留原 package |
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: 下游只带走 `package/`,按 manifest 的 WGS84 placement 放置主模型,按需加载 layer。
|
||||
- Base: 只有 main scene 的区域仍是合法 package;没有几何的语义类别不写空 GLB。
|
||||
- Bad: 将 `_preview/` 的车辆模型或路线加进 package;它们是本项目的验证运行时,信号灯 runtime 则必须发布。
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- `npm run test:package-contract`:schema、URI、placement、唯一 scene 和模型存在性。
|
||||
- `npm run test:package-examples`:Cesium / Three.js resolver 都相对 manifest 解析 URI。
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium,compress,package,preview`
|
||||
- `npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
|
||||
- 将 `package/` 复制到其他目录后,重新运行 manifest validator;所有 `assets[*].uri` 必须仍能解析。
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
Wrong:
|
||||
|
||||
```js
|
||||
const modelUrl = asset.uri; // Relative to preview HTML by accident.
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```js
|
||||
const modelUrl = new URL(asset.uri, new URL(manifestUrl, window.location.href)).href;
|
||||
```
|
||||
|
||||
---
|
||||
@@ -272,6 +367,341 @@ if (!networkSaysIntersection && (roadCounts.get(endpoint.id) || 0) < 3) return n
|
||||
fs.writeFileSync(diagnosticsPath, `${JSON.stringify(diagnostics, null, 2)}\n`);
|
||||
```
|
||||
|
||||
## Native 道路标线
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
`node scripts/compile-native-roads.js --config <area>` 将原生道路标线写入
|
||||
`outputs/<area>/native-road/layers/`。它不读取 osm2streets 渲染几何;只复用
|
||||
`turn-lane-arrows.js` 中已测试的箭头模板。工作台 `GET /api/state` 原样服务这些
|
||||
GeoJSON,native Blender 构建通过 `catalog.NATIVE_ROAD_LAYERS` 消费它们。
|
||||
|
||||
### 2. 图层契约
|
||||
|
||||
| 文件 | 语义 | 必需 provenance | Blender material layer |
|
||||
|---|---|---|---|
|
||||
| `lane_separators.geojson` | 同向相邻车道的分隔线 | `native-road-lane-separator/v1` | `lane_separators` |
|
||||
| `direction_arrows.geojson` | 沿定向车道重复的直行方向箭头 | `native-road-direction-arrow/v1` | `lane_arrows_webscale` |
|
||||
| `turn_arrows.geojson` | 明确 `turn:lanes` 的路口动作箭头 | `native-road-turn-arrow/v1` | `lane_arrows_webscale` |
|
||||
|
||||
方向箭头必须带 `road_id`、`lane_id`、`osm_way_ids`、`direction`、`lane_index`、
|
||||
`sequence`、`distance_along_lane_meters` 和 `placement_interval_meters`。转向箭头
|
||||
必须带 `maneuver` 与 `placement_distance_meters`。两者不能共用 provenance 或假装为
|
||||
彼此:前者表达沿路行驶方向,后者表达路口处允许动作。
|
||||
|
||||
### 3. 放置与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 车道长度不足以容纳两端 14m 缓冲 | 不生成道路方向箭头 |
|
||||
| 可用车道长度 | 从 14m 起按 32m 间距生成 `through` 箭头 |
|
||||
| OSM 未提供 `turn:lanes` | 不生成路口转向箭头 |
|
||||
| `turn:lanes` 存在但动作不受已测试模板支持 | 记录诊断,不猜测动作 |
|
||||
| native Blender 构建缺任一图层文件 | 在 `ensureNativeRoadLayers()` 失败,不能静默漏画 |
|
||||
|
||||
### 4. 必需测试
|
||||
|
||||
- `npm run test:native-road`:方向箭头的 Polygon、provenance、间距,以及无标签道路
|
||||
不生成路口转向箭头。
|
||||
- `npm run test:road-workbench`:方向箭头开关、选择溯源和概览标签。
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview --road-provider native`:日志必须列出
|
||||
`direction_arrows` 与 `turn_arrows`,且不运行 `package`。
|
||||
|
||||
### 5. 错误与正确写法
|
||||
|
||||
错误:把默认 `through` 当作路口 `turn:lanes` 动作,统一写入 `turn_arrows.geojson`。
|
||||
|
||||
正确:道路方向箭头进入 `direction_arrows.geojson`;只有 OSM 明确标注的动作进入
|
||||
`turn_arrows.geojson`。工作台用两个开关呈现,Blender 复用同一现有箭头材质。
|
||||
|
||||
## Native 普通路口圆角
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
`compile-native-roads.js` 为普通 T / 十字路口生成 `intersection_surface.geojson`
|
||||
和 `sidewalk_surface.geojson` 的路口边界。路口道路面必须在同一 cutback 处结束,不能
|
||||
用未裁剪的道路矩形覆盖圆角边界。
|
||||
|
||||
### 2. 几何契约
|
||||
|
||||
- 相邻道路边缘使用两条支持切线的交点作为二次曲线控制点;采样段数由
|
||||
`JUNCTION_CURVE_SEGMENTS` 统一控制。
|
||||
- 机动车路口边界、人行道内侧路缘和人行道外侧边界都必须使用同一切线圆角规则;外侧
|
||||
不能只对内侧采样点做线性偏移,避免内外曲率不一致。
|
||||
- `boundary_mode` 使用 `rounded-approach-envelope`,无法安全构造的角保持确定性直线
|
||||
回退,并写入 `junction-rounded-corner-fallback` warning。
|
||||
- 已发布的 connector 必须包含在最终边界内,边界退化或 connector 越界时才允许使用
|
||||
`connector-convex-fallback`。
|
||||
|
||||
### 3. 校验与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 支持切线交点有限且曲线不过远 | 生成采样圆角 |
|
||||
| 切线近似平行或交点退化 | 保留该角直线并记录 `junction-rounded-corner-fallback` |
|
||||
| 边界自相交或 connector 越界 | 使用 connector 凸包兜底;仍自相交则不发布路口面 |
|
||||
|
||||
### 4. 必需测试
|
||||
|
||||
- `npm run test:native-road`:普通 T / 十字路口的圆角顶点数、内收方向、内外人行道
|
||||
曲线和 continuation 语义。
|
||||
- `npm run test:road-workbench`:工作台仍能加载 native 路口及人行道图层。
|
||||
- `npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json`。
|
||||
- `npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json`。
|
||||
|
||||
### 5. 错误与正确写法
|
||||
|
||||
错误:先对路缘生成圆角,再把外侧边界按每个采样点线性平移;这会导致内外曲率不同,
|
||||
在人行道角落留下不一致的折面。
|
||||
|
||||
正确:对内侧和外侧分别用相同的道路边缘支持切线规则生成曲线,仅在外侧切线退化时
|
||||
使用确定性的偏移回退。
|
||||
|
||||
## Native 道路中心虚线
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
`node scripts/compile-native-roads.js --config <area>` 为可确认的双向非 service
|
||||
道路段写入 `native-road/layers/center_lines.geojson`。这是原生几何:只能依据
|
||||
canonical OSM 中心线、native 双方向道路模型和 native 路口 cutback 生成,osm2streets
|
||||
的 `center_lines.geojson` 只可作为视觉基准,绝不能作为输入。
|
||||
|
||||
### 2. 调用形式
|
||||
|
||||
```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 build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview --road-provider native
|
||||
```
|
||||
|
||||
工作台 `GET /api/state` 通过 `layers.centerLines` 返回该 FeatureCollection;native
|
||||
Blender adapter 必须将 source `center_lines` 映射到既有 `center_lines` material layer。
|
||||
|
||||
### 3. 契约
|
||||
|
||||
- 每一 dash 均为 Polygon,长 `2m`、宽 `0.25m`、确定性间隔 `2m`,使用
|
||||
`native-road-center-line/v1` provenance。
|
||||
- 要素必须含有 `native_id`、`segment_id`、`road_id`、`directional_road_ids`、
|
||||
`osm_way_ids`、`dash_index`、`dash_length_m`、`dash_gap_m` 与 `placement_rule`,以便
|
||||
Workbench 用中文显示“道路中心虚线”并可回溯来源。
|
||||
- 只有同一 `segment_id` 恰有一条 forward 和一条 backward native road 时才生成;单向、
|
||||
`highway=service` 或退化中心线都不能伪造中心线。退化中心线须写
|
||||
`invalid-center-line` diagnostic。
|
||||
- 线段必须先经过 native junction cutback;再与 crosswalk、vehicle stop line 的控制面
|
||||
求冲突,冲突 dash 直接略去。控制标线优先于中心虚线。
|
||||
- `compiled.json.layers.centerLines`、`comparison.json.nativeCenterLineFeatures`、
|
||||
`build-area.js` 的 native input records 与 required-layer 验证必须全部使用
|
||||
`layers/center_lines.geojson`;缺失时在 Blender 启动前失败,不得静默漏画。
|
||||
|
||||
### 4. 校验与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 可确认的双向普通道路段 | 生成 2m / 0.25m 黄虚线,固定 2m gap |
|
||||
| 单向或 `highway=service` 道路 | 不生成中心虚线 |
|
||||
| 中心线不足两点、长度不可用 | `invalid-center-line` diagnostic,不写畸形 Polygon |
|
||||
| dash 进入 junction cutback | trim 后不生成该范围 dash |
|
||||
| dash 与斑马线或停止线相交 | 不生成冲突 dash |
|
||||
| native Blender 输入缺 `center_lines.geojson` | `ensureNativeRoadLayers()` 抛错 |
|
||||
|
||||
### 5. 正常、基础与错误示例
|
||||
|
||||
- 正常:一条有 forward/backward carriageway 的 residential 段在两个方向道路之间生成黄虚线。
|
||||
- 基础:没有双向证据的道路仍可有车道分隔线,但不产生道路中心虚线。
|
||||
- 错误:从 osm2streets 图层复制或裁剪中心线;这会将渲染器缺陷重新变成 native 数据依赖。
|
||||
|
||||
### 6. 必需测试
|
||||
|
||||
- `npm run test:native-road`:断言双向生成、provenance、2m 尺寸/间隔、单向和 service 跳过。
|
||||
- `npm run test:road-workbench`:断言 `centerLines` API、中文开关、选择溯源和概览计数。
|
||||
- `npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json`:输出合法图层,检查中心线与控制标线不重叠。
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview --road-provider native`:日志列出 `center_lines`,且不运行 `package`。
|
||||
|
||||
### 7. 错误与正确写法
|
||||
|
||||
错误:只按一条 directional road 生成中心线,或忽略控制标线。
|
||||
|
||||
```js
|
||||
const line = road.centerline;
|
||||
features.push(makeDash(line));
|
||||
```
|
||||
|
||||
正确:先确认成对的双方向道路、做路口裁剪,再排除控制标线冲突。
|
||||
|
||||
```js
|
||||
if (roads.length !== 2 || !forward || !backward || forward.highway === "service") continue;
|
||||
const line = trimLineAtJunctions(forward.centerline, forward.sourceNodeIds, junctionPlans);
|
||||
if (!ringsOverlapControl([ring], [...controls.crosswalks, ...controls.stopLines])) features.push(dash);
|
||||
```
|
||||
|
||||
## Native 道路中心线样式覆写
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
Road Workbench 选中 `native-road-center-line/v1` 要素后,可为其 `segment_id`
|
||||
保存样式覆写。覆写属于 `native-road-overrides.json`,不是对
|
||||
`center_lines.geojson` 的手工编辑;重新编译必须从覆写重建图层。
|
||||
|
||||
### 2. 调用形式
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "道路中心线:segment:way/123/1",
|
||||
"kind": "center-line-style",
|
||||
"segmentId": "segment:way/123/1",
|
||||
"color": "white",
|
||||
"pattern": "solid"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 契约
|
||||
|
||||
- `color` 只能是 `yellow` 或 `white`;`pattern` 只能是 `dashed` 或 `solid`。
|
||||
目标 `segmentId` 必须属于当前 native road model。
|
||||
- `double: true` 只表示双黄实线,因此只能与 `color: "yellow"`、
|
||||
`pattern: "solid"` 组合。编译器为每段的每个有效实线 mark 写两条相距 0.32m 的
|
||||
平行 Polygon,并记录 `double: true`、`effective_style:
|
||||
"double-yellow-solid"`;不得把它实现为可任意组合的双线开关。
|
||||
- 未覆写段保持黄色虚线(2m dash、2m gap);`solid` 为 0 gap,但相邻的 2m
|
||||
几何块须重叠 `0.04m`,避免投影精度造成可见裂缝。
|
||||
- 每个生成面记录 `color`、`pattern` 和 `effective_style`。Workbench 以这些属性
|
||||
着色;native Blender 将 white centre lines 分派至 `Native Center Line White`,
|
||||
yellow 则继续复用 `Center Line`。
|
||||
- 下拉框变化即暂存覆写,顶部“保存并重新生成”是唯一写盘/重编译动作;不要求用户
|
||||
再点击一个容易遗漏的暂存按钮。
|
||||
|
||||
### 4. 校验与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 合法颜色、图案和当前 segment | 保存后重新生成有效样式 |
|
||||
| 非法颜色/图案或不存在 segment | `validateOverrides()` 拒绝整个请求 |
|
||||
| 实线块触及控制标线 | 该块略去,不以连续性为由穿过控制标线 |
|
||||
| 白色中心线进入 native Blender | 使用白线材质,不改变 legacy 图层材质 |
|
||||
|
||||
### 5. 正常、基础与错误示例
|
||||
|
||||
- 正常:点选任意 dash,选择“白色实线”,保存重编译后整段显示连续白线。
|
||||
- 基础:选择“黄色虚线(默认)”仍是显式覆写,但几何与默认规则一致。
|
||||
- 错误:只在 Workbench 改填充色;Blender/Cesium 会继续显示旧黄色。
|
||||
|
||||
### 6. 必需测试
|
||||
|
||||
- `npm run test:native-road`:合法/非法样式覆写、solid 属性、控制标线避让。
|
||||
- `npm run test:road-workbench`:中文样式面板、下拉框自动暂存和 API payload。
|
||||
- `python3 -m unittest discover blender/tests`:catalog 仍是可导入的纯 Python。
|
||||
- Nantaizi native `blender,cesium,preview` 构建:既有 yellow centre lines 不回归。
|
||||
|
||||
### 7. 错误与正确写法
|
||||
|
||||
错误:实线块仅以零间隔精确相接,且每块使用高对比 outline。
|
||||
|
||||
正确:小幅重叠相邻块,并让 Workbench 实线 stroke 与 fill 同色。
|
||||
|
||||
## Native 道路外缘线样式覆写
|
||||
|
||||
`edge_lines.geojson` 是车行道最外侧边界标线,使用 `native-road-edge-line/v1`
|
||||
provenance。单向道路生成左右两条外缘线;双向道路每个方向只生成远离道路中心的外缘
|
||||
线,双向道路中间分界由 `center_lines.geojson` 负责。默认是白色实线,必须带
|
||||
`road_id`、`side`(`left` / `right`)、`osm_way_ids`、`color`、`pattern` 与
|
||||
`effective_style`。
|
||||
|
||||
区域配置中的 `nativeRoad.edgeLines` 默认是 `false`;因此 native Blender/Cesium 默认不
|
||||
生成或显示该层。只有明确设置为 `true` 才会发布 `edge_lines.geojson` 并交给下游消费。
|
||||
|
||||
Road Workbench 点选外缘线后以中文显示其方向侧边,可暂存以下 area-local 覆写:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "道路外缘线:road:way/123:forward:left",
|
||||
"kind": "edge-line-style",
|
||||
"roadId": "road:way/123:forward",
|
||||
"side": "left",
|
||||
"color": "white",
|
||||
"pattern": "solid"
|
||||
}
|
||||
```
|
||||
|
||||
`roadId` 必须是当前 directional native road 的精确 ID,`side` 必须为 `left` 或 `right`。
|
||||
实线使用连续 offset line;虚线使用确定性
|
||||
2m mark / 2m gap。工作台的暂存与“保存并重新生成”是唯一的写盘路径,不能只改浏览器填充色;
|
||||
native Blender 将该层映射到既有 `lane_separators` material layer。
|
||||
|
||||
## Native 控制标线
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
`node scripts/compile-native-roads.js --config <area>` 为 native road provider
|
||||
生成 `crosswalks.geojson` 和 `vehicle_stop_lines.geojson`。这是原生道路的独立
|
||||
产物,禁止读取 osm2streets 的渲染图层作为几何输入。
|
||||
|
||||
### 2. 调用形式
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
工作台 `GET /api/state` 通过 `layers.crosswalks` 和
|
||||
`layers.vehicleStopLines` 返回两个 FeatureCollection。
|
||||
|
||||
### 3. 契约
|
||||
|
||||
- 只有 `highway=crossing` 且 `crossing:markings` 不是 `no`、`none` 或
|
||||
`unmarked` 的 OSM 节点可以生成斑马线。每个安全匹配点生成六条 stripe,带
|
||||
`crossing_node_id`、`road_id`、`lane_id`、`osm_way_ids`、`direction`、
|
||||
`placement_method`、`junction_inset_m` 和 `native-road-crosswalk/v1` provenance。
|
||||
- 停止线还必须对应一个启用的 native arrival endpoint,且过街节点位于该进口到
|
||||
路口的安全距离内;其 provenance 是 `native-road-stop-line/v1`。不能确认进口
|
||||
时保留斑马线并写 `crossing-no-safe-stop-line` diagnostic,不得猜测一条线。停止线
|
||||
必须复用斑马线的 `junction_inset_m`,保持与斑马线的上游间距。
|
||||
- 有安全进口且存在普通路口 plan 时,斑马线中心推进到 cutback 内约 1.5 米;单次
|
||||
最大推进 4 米。`junction_inset_m` 记录实际推进量,避免远离路口的 crossing 被过度
|
||||
移动。
|
||||
- `catalog.NATIVE_ROAD_LAYERS` 将两个源层映射到现有的 `crosswalks` 和
|
||||
`vehicle_stop_lines` 材质层。不得把它们加入 legacy `SCENE_LAYERS`。
|
||||
- 控制标线优先于箭头:默认直行箭头与其相交时跳过;路口转向箭头依次尝试在距路口
|
||||
6、10、14、18、22 米处放置,均冲突时记录 `turn-arrow-control-conflict`。
|
||||
|
||||
### 4. 校验与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 标记过街没有可匹配 native lane | `crossing-no-native-lane`,不生成任何控制标线 |
|
||||
| 有横道但没有安全进口方向 | 生成斑马线,记录 `crossing-no-safe-stop-line`,不生成停止线 |
|
||||
| 箭头与任一控制标线相交 | 直行箭头跳过;转向箭头后移或记录冲突 diagnostic |
|
||||
| native Blender 输入缺任一控制图层 | `ensureNativeRoadLayers()` 在启动 Blender 前失败 |
|
||||
|
||||
### 5. 正常、基础与错误示例
|
||||
|
||||
- 正常:一个靠近路口的 marked crossing 生成 6 条斑马线和 1 条进口停止线。
|
||||
- 基础:一条孤立的 marked crossing 可以生成斑马线,但不能凭邻近道路方向臆造停止线。
|
||||
- 错误:先生成箭头再叠加停止线,导致两者重叠;控制标线是道路控制语义,必须优先。
|
||||
|
||||
### 6. 必需测试
|
||||
|
||||
- `npm run test:native-road`:断言 marked / unmarked / 无 native lane 的输出,停止线的
|
||||
provenance、斑马线与停止线共享 `junction_inset_m`,以及箭头遇控制标线时后移。
|
||||
- `npm run test:road-workbench`:断言 controls 开关、两条 API layer 和中文选中溯源。
|
||||
- `npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json`:核查两层
|
||||
feature count 及每条停止线都是 native arrival direction。
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview --road-provider native`:不运行 `package`。
|
||||
|
||||
### 7. 错误与正确写法
|
||||
|
||||
错误:以最近任意方向车道和固定正向偏移生成停止线。
|
||||
|
||||
```js
|
||||
const stopCenter = offsetByMeters(nearestLane.point, nearestLane.axis, 2.7);
|
||||
```
|
||||
|
||||
正确:先确认该方向的终点是一个已启用的路口 arrival,再在人行横道的上游生成停止线。
|
||||
|
||||
```js
|
||||
const approach = candidates.find((item) => arrivalEndpointIds.has(`endpoint:${item.road.id}:end`));
|
||||
const stopCenter = offsetByMeters(laneCenterAtCrossing, approach.placement.axis, -2.7);
|
||||
```
|
||||
|
||||
## 斑马线与停止线来源
|
||||
|
||||
### 1. 范围与触发条件
|
||||
@@ -385,6 +815,32 @@ area.outputs.trafficSignals
|
||||
- `layout.mastHeightMeters` 与 `layout.headCenterHeightMeters` 必须相等,表示横杆与灯壳的
|
||||
中心对齐;`lensVerticalOffsetsMeters` 以灯壳中心为基准,正值向上、负值向下。当前倒计时牌
|
||||
垂直偏移为 `0`,必须贴在横杆上而非悬挂。
|
||||
- 复合路口中,具有相同 `cluster_id` 的停止线按道路轴线匹配 OSM control arm;轴线的两个
|
||||
极性必须视为等价,但每条候选停止线仍只能消费一次。普通路口继续使用有向进口匹配,不能
|
||||
用复合路口的无向规则覆盖普通路口。
|
||||
- 复合路口的中心绿化带/中央分隔带不由 native 复合模板伪造;模板只生成 OSM 道路和路口
|
||||
内必要的安全岛。每条生成的复合进口车道须在停止线外侧上游约 `8m` 生成一支与车道拓扑
|
||||
一致的左转、直行或右转箭头;不得额外叠加一套固定直行箭头。
|
||||
- **控制要素避让顺序**:`buildComplexJunctionGeometry` 在 `compileLaneCenterlines` 之前生成,
|
||||
产出的 `complex-crosswalk` / `complex-corner-crosswalk` / `complex-stop-line` 必须以
|
||||
`{ crosswalks, stopLines }` 参数传入 `compileLaneCenterlines`。车道中心线、车道分隔线、
|
||||
中心虚线、导向箭头都在这一裁剪之后才落地。这里的裁剪对象包括两类车道几何:一是簇
|
||||
边界上的 OSM 车道,二是模板自造的 `cluster-approach-lane` 预览车道。裁剪必须先按到
|
||||
路口中心的距离把线定向为“外向内”,再复用 `trimLineBeforeFirstControl`:因为 OSM 车道
|
||||
由簇边缘向外指、预览车道由外圈向核心指,两种朝向都存在,直接裁会裁反。裁剪后的几何
|
||||
同时喂给车道标线源(`markingLanes[i].coordinates`),否则分隔线/箭头会画到斑马线之外
|
||||
继续侵入核心区。拓扑几何 `coordinates`(供 connector 使用)不得裁剪,否则 turn path
|
||||
会断。发布几何 vs 拓扑几何的双轨在 `compileLaneCenterlines` 内并存,别合并。
|
||||
- **复合路口人行道转角**:直步行带被 `trimLineAtComplexCluster` 裁在簇外圈,两条相邻的
|
||||
臂之间会留一个楔形无覆盖区。转角带由 `buildComplexJunctionGeometry` 与 `complex-corner-fillet`
|
||||
共用同一 `curve` / `tangents` / `edges` / `apex` 复用生成,不重新拟合曲线;宽度
|
||||
`SIDEWALK_WIDTH_METERS = 2` 必须与 `native-road.js:DEFAULT_SIDEWALK_WIDTH_METERS` 保持
|
||||
一致,法线方向按“离簇中心更远”选取(路缘相对中心是星形的)。判定两侧臂是否都带步行带
|
||||
时必须按臂的外向方向翻转 road 自身的 `sidewalkLeft/Right`,因为那是相对数字化方向的。
|
||||
端头以 `SIDEWALK_CORNER_OVERRUN_METERS = 6` 冗余越过簇边界,让转角带和直条带自然重叠,
|
||||
不追求精确缝合。自交(`ringSelfIntersects`)或任一坐标非有限时跳过并报
|
||||
`complex-junction-sidewalk-corner-fallback`;不允许硬塞坏几何。产物经 `islands` 通道
|
||||
与安全岛一同并入 `generatedComplexSidewalks`,最终落入 `sidewalk_surface.geojson`。
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
@@ -394,6 +850,9 @@ area.outputs.trafficSignals
|
||||
| 直接运行 `blender` / `preview` 但锚点不存在 | 在启动外部工具前报 `Traffic signal anchors not found` |
|
||||
| `signal_uid` 缺失/重复、非空 `display_id` 重复、字段或 Point 无效 | 重导入在替换任何输出前失败 |
|
||||
| 用户仅修改 QGIS 后运行 `reimport` | 重新生成锚点,不沿用旧坐标 |
|
||||
| 复合路口车道整体落在斑马线/停止线内 | 报 `lane-centerline-fully-inside-control`,回退为未裁剪发布几何,不写空 LineString |
|
||||
| 复合路口人行道转角自交或坐标非有限 | 报 `complex-junction-sidewalk-corner-fallback`,跳过该角,两侧步行带保持断开 |
|
||||
| 有 `referenceFile` 时同时配置 `coreRadiusMeters` | 校准值胜出,报 `complex-junction-configured-radius-ignored` 记录被忽略的配置值 |
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
@@ -607,7 +1066,7 @@ const gate = classifyAreaQuality(result);
|
||||
### 1. Scope / Trigger
|
||||
|
||||
Stage manifest 是区域构建阶段或独立验证通过后的机器可读产物契约。它覆盖预检记录与完整区域链:
|
||||
`preflight`、`intermediates`、`reimport`、`blender`、`cesium`、`preview` 和 `compress`。
|
||||
`preflight`、`intermediates`、`reimport`、`blender`、`cesium`、`compress`、`package` 和 `preview`。
|
||||
它用于诊断产物是否存在、是否 stale、体量是否超预算,以及后续 `check:area` /
|
||||
增量构建判断。
|
||||
|
||||
@@ -629,6 +1088,7 @@ Manifest 路径固定:
|
||||
<areaDir>/_pipeline/stages/cesium.manifest.json
|
||||
<areaDir>/_pipeline/stages/preview.manifest.json
|
||||
<areaDir>/_pipeline/stages/compress.manifest.json
|
||||
<areaDir>/_pipeline/stages/package.manifest.json
|
||||
```
|
||||
|
||||
代码入口:
|
||||
@@ -721,9 +1181,18 @@ File records use this shape:
|
||||
- `summary.glb.extensionsUsed`
|
||||
- `summary.budget`:effective limits、usage 和 violations;warnings 来自同一个预算评估
|
||||
|
||||
`cesium` 会调用 preview 生成函数,但 preview HTML / route / vehicle model 的 freshness
|
||||
所有权属于独立 `preview` manifest。否则单跑 `--stages preview` 会把 Cesium manifest
|
||||
错误判 stale。
|
||||
`cesium` 只拥有 staging 内的静态模型与 manifest;完成后必须依次由 `compress`、`package`
|
||||
交付。preview HTML / route / vehicle model 的 freshness 所有权属于独立 `preview` manifest。
|
||||
|
||||
`compress` manifest 不记录最终文件 records:它们位于可被 `package` rename 的 staging 目录。
|
||||
`package` manifest 是唯一记录 `package/manifest.json`、主 GLB 与 package 目录的 stage manifest,
|
||||
避免发布后 compress manifest 因路径迁移而立刻 stale。
|
||||
|
||||
`package` manifest:
|
||||
|
||||
- `inputs.stagingManifest`:发布后的同一 manifest 文件记录
|
||||
- `outputs.packageDir`、`outputs.manifest`、`outputs.primaryGlb`
|
||||
- `summary.assets` 与 `summary.packageDir`
|
||||
|
||||
### Preview Assembly Boundary
|
||||
|
||||
@@ -758,14 +1227,11 @@ runtime 与 HTML,并写 preview manifest。预览内容实现不得回流到
|
||||
|
||||
`compress` manifest:
|
||||
|
||||
- `inputs.glb`
|
||||
- `inputs.metadata`
|
||||
- `inputs.cesiumPreview`
|
||||
- `outputs.compressedGlb`
|
||||
- `outputs.compressedMetadata`
|
||||
- `outputs.compressedCesiumPreview`
|
||||
- `outputs.glb`
|
||||
- `outputs.metadata`
|
||||
- `outputs.cesiumPreview`
|
||||
- `summary.sourceGlb`
|
||||
- `summary.compressedGlb`
|
||||
- `summary.glb`
|
||||
- `summary.options`
|
||||
- `summary.compressionRatio`
|
||||
- `summary.savedBytes`
|
||||
@@ -791,8 +1257,8 @@ Manifest files are written atomically via `*.tmp` then `renameSync`.
|
||||
- Good: `--stages intermediates` 成功后写 `intermediates.manifest.json`,诊断显示
|
||||
`ok intermediates`。
|
||||
- Good: `--stages blender` 成功后写 `blender.manifest.json`,诊断显示 `ok blender`。
|
||||
- Good: `--stages cesium` 成功后写 `cesium.manifest.json` 和 `preview.manifest.json`。
|
||||
- Good: `--stages preview` 只更新 preview manifest,不让 cesium manifest stale。
|
||||
- Good: `--stages cesium,compress,package,preview` 成功后依序写四份各自拥有的 manifest。
|
||||
- Good: `--stages preview` 只更新 preview manifest,不让 package manifest stale。
|
||||
- Good: `--stages compress` 成功后写 `compress.manifest.json`,summary 记录压缩比和节省字节。
|
||||
- Base: 旧产物没有当前 ownership 路径的 manifest,诊断显示 expected manifest missing,
|
||||
提示重跑对应阶段。
|
||||
@@ -811,6 +1277,7 @@ Manifest files are written atomically via `*.tmp` then `renameSync`.
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages intermediates`
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender`
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium`
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages compress,package,preview`
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages preview`
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages compress`
|
||||
- `npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
|
||||
@@ -926,18 +1393,19 @@ gpkg: path.resolve(outputOverrides.gpkg || path.join(areaDir, `${fileStem}.gpkg`
|
||||
|
||||
| 阶段 | 做什么 | 读 | 写 |
|
||||
|---|---|---|---|
|
||||
| `intermediates` | OSM → osm2streets GeoJSON → GeoPackage → QGIS 工程 + 预览图 | `.osm` | `osm2streets_web_out/`、`.gpkg`、`.qgz`、`-preview.png` |
|
||||
| `intermediates` |(显式 legacy)OSM → osm2streets GeoJSON → GeoPackage → QGIS 工程 + 预览图 | `.osm` | `osm2streets_web_out/`、`.gpkg`、`.qgz`、`-preview.png` |
|
||||
| `reimport` | GeoPackage → GeoJSON(**反向**) | `.gpkg` | `osm2streets_web_out/` |
|
||||
| `blender` | OSM + GeoJSON → 场景 | `.osm`、`osm2streets_web_out/` | `.blend`、`.png` |
|
||||
| `cesium` | 场景 → GLB + 元数据 + 预览页 | `.blend` | `.glb`、`.json`、预览 HTML 及其静态资源 |
|
||||
| `preview` | 只补生成预览页 | `.glb`、`.json` | 预览 HTML 及其静态资源 |
|
||||
| `compress` | 生成并列压缩 Cesium 产物 | `.glb`、`.json`、默认预览 HTML | `-compressed-webp*.glb/json/html` |
|
||||
| `blender` | 默认 OSM → native road compiler → Blender 场景;legacy provider 时读取 GeoJSON | `.osm`、`native-road/`(legacy 为 `osm2streets_web_out/`) | `.blend`、`.png` |
|
||||
| `cesium` | 场景 → static staging GLB + manifest | `.blend` | `_pipeline/package-staging/models/*.glb`、staged manifest;动态 GLB 到 `_preview/` |
|
||||
| `compress` | 压缩 Cesium staging 主模型 | staged `.glb`、manifest | 压缩 staged `.glb`、manifest |
|
||||
| `package` | 校验并原子发布静态资产包 | staging manifest 与 models | `package/manifest.json`、`package/models/*.glb` |
|
||||
| `preview` | 用已发布 manifest 生成验证预览 | `package/manifest.json`、动态输入 | 预览 HTML、静态 runtime、`_preview/` 动态资源 |
|
||||
|
||||
调度是顶层的阶段 `if`(`build-area.js` 开头),顺序固定,**阶段之间不传内存状态,
|
||||
只通过磁盘产物耦合**。这就是单跑某个阶段能work 的原因。
|
||||
|
||||
`cesium` 阶段结束时会直接调 `writeCesiumPreview(area)`(`build-area.js:285`),所以
|
||||
`preview` 只在"已有 GLB、只想重生成 HTML"时才需要单独跑。
|
||||
完整构建中 `preview` 在 `package` 后运行。单跑 `preview` 用于已发布 package 但想重生成
|
||||
验证 UI 或 `_preview/` 动态资源的情况。
|
||||
|
||||
### 别名
|
||||
|
||||
@@ -947,16 +1415,15 @@ gpkg: path.resolve(outputOverrides.gpkg || path.join(areaDir, `${fileStem}.gpkg`
|
||||
|
||||
未知阶段名**抛错并列出合法值**(`:181`),不静默忽略。
|
||||
|
||||
### `all` 不含 `reimport`
|
||||
### `all` 是 native-only 完整构建
|
||||
|
||||
```js
|
||||
// 'reimport' is deliberately absent from 'all': it is a recovery step for
|
||||
// hand-edited GeoPackages, never part of a full build. build-area.js:159-160
|
||||
all: ["intermediates", "blender", "cesium"],
|
||||
// Legacy intermediates/reimport are deliberately absent from 'all'.
|
||||
all: ["blender", "cesium", "compress", "package", "preview"],
|
||||
```
|
||||
|
||||
`preview` 同样不在 `all` 里——`cesium` 已经包含它。`compress` 也不在 `all` 里——
|
||||
它生成的是替代压缩产物,不是 baseline GLB。
|
||||
`compress`、`package`、`preview` 均在 `all` 中,确保完整构建以压缩后的可复用静态 package
|
||||
以及可运行的验证预览交付。
|
||||
|
||||
### `intermediates` 与 `reimport` 互斥
|
||||
|
||||
@@ -966,9 +1433,8 @@ all: ["intermediates", "blender", "cesium"],
|
||||
|
||||
这是**显式拒绝而不是警告**——两者同时开,无论谁先跑,另一个的工作都白做。
|
||||
|
||||
`normalizeAreaConfig` 里 `stages.reimport`、`stages.preview` 和 `stages.compress` 硬编码为 `false`,
|
||||
**不能从配置文件打开**,只能靠 `--stages` 显式请求。
|
||||
恢复动作、补丁动作和替代产物动作都不该被一份配置文件变成默认行为。
|
||||
`normalizeAreaConfig` 里只有 `stages.reimport` 硬编码为 `false`,只能靠 `--stages` 显式请求;
|
||||
`stages.compress`、`stages.package` 和 `stages.preview` 为默认 `true`。
|
||||
|
||||
---
|
||||
|
||||
@@ -998,6 +1464,72 @@ parity 校验依赖 stage 的 stdout 标记来判断阶段是否跑到(如 `SC
|
||||
|
||||
---
|
||||
|
||||
## Native Road Parity
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
拆分 native road compiler、移动其模块,或声称 native-road JSON / GeoJSON 未变化时,
|
||||
用 `scripts/road-parity.js`。它独立于 build stage,只读现有 native-road 输出。
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
```bash
|
||||
node scripts/road-parity.js --config config/areas/<area>.json --snapshot <out.json>
|
||||
node scripts/road-parity.js --config config/areas/<area>.json --compare <baseline.json>
|
||||
```
|
||||
|
||||
`--config` 必填,且 `--snapshot` 与 `--compare` 必须二选一。
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- 快照覆盖 `outputs/<area>/native-road/` 下全部文件和同级
|
||||
`native-traffic-signals.json`。
|
||||
- 普通 JSON 递归按对象 key 排序后生成 `contentHash`。
|
||||
- GeoJSON 同时生成排序 feature 的 `contentHash` 与原序 `orderHash`;任一不同都算
|
||||
parity 失败。
|
||||
- 绝对路径归一化为 `<repo>/...` 或 `<external>/<basename>`,随机
|
||||
`native-road-*` staging 名归一化为 `<staging>`。坐标不做容差或舍入。
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 缺 `--config` 或同时/均未传 snapshot、compare | 打印 usage,退出非零 |
|
||||
| native-road 输出目录不存在 | 抛出 `Native road output not found`,退出非零 |
|
||||
| baseline 缺文件、出现额外文件或任一 hash/count/bytes 不同 | 每项打印 `ROAD_PARITY_DIFF`,退出非零 |
|
||||
| 完全一致 | 打印 `ROAD_PARITY_OK`,退出 0 |
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: 移动编译器前后,两个 hash 均一致。
|
||||
- Base: 绝对 OSM 输入路径变化但归一化后内容一致。
|
||||
- Bad: 只比较排序后的 GeoJSON 内容并忽略 `orderHash`,会掩盖输出顺序漂移。
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- `npm run test:road-parity`:相等基线、顺序 hash 变化与缺文件都应被断言。
|
||||
- `node --check scripts/road-parity.js`。
|
||||
- 对每个受支持区域运行 `--compare` 对已提交基线自比对。
|
||||
- 迁移前先连续编译两次;只有 control 实验全绿,基线才可作为 oracle。
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
Wrong:
|
||||
|
||||
```js
|
||||
// Sorting hides a changed feature order.
|
||||
assert.equal(sortedContentHash, baseline.contentHash);
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```js
|
||||
assert.equal(contentHash, baseline.contentHash);
|
||||
assert.equal(orderHash, baseline.orderHash);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 反模式
|
||||
|
||||
| 反模式 | 后果 |
|
||||
@@ -1005,7 +1537,7 @@ parity 校验依赖 stage 的 stdout 标记来判断阶段是否跑到(如 `SC
|
||||
| 在阶段函数里现拼输出路径 | 路径规则出现第二份定义 |
|
||||
| 低层脚本直接读 `config/areas/*.json` | 打破两层配置边界 |
|
||||
| 布尔配置用 `\|\|` 兜底 | `false` 被翻转成默认值 |
|
||||
| 让 `reimport` / `preview` 能从配置文件默认开启 | 恢复动作变成常规行为 |
|
||||
| 让 `reimport` 能从配置文件默认开启 | 恢复动作变成常规行为 |
|
||||
| 新阶段忘了 `ensureFile` 前置校验 | 单跑时报底层堆栈而非人话 |
|
||||
| 改 stage 的 stdout 标记 | 静默破坏 parity 契约 |
|
||||
| 顺手把多份 `parseArgs` 合并 | 扩大 diff,且独立入口的独立性是刻意的 |
|
||||
|
||||
@@ -120,6 +120,56 @@ 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
|
||||
```
|
||||
|
||||
### 两种调用姿势
|
||||
|
||||
| 阶段 | 参数 | 出处 |
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
| 加阶段、加 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-契约) |
|
||||
| 改复合路口几何(`buildComplexJunctionGeometry`、车道控制避让、人行道转角) | [CLI 与阶段](./cli-and-stages.md#可编辑信号设施与运行时锚点的跨阶段消费) 的复合路口小节 |
|
||||
| 改预览页生成 | [../preview/](../preview/index.md) |
|
||||
| 声称"纯重构,产物不变" | [产物一致性指南](../guides/artifact-parity-guide.md) |
|
||||
| Native road compiler CLI、输入或完成标记 | [编译器契约指针](./native-road-package.md) |
|
||||
|
||||
---
|
||||
|
||||
@@ -30,7 +32,7 @@ config/areas/<id>.json
|
||||
▼ build-area.js — 阶段调度
|
||||
_pipeline/osm2streets-qgis.config.json (派生配置)
|
||||
│
|
||||
├─[intermediates]─▶ build-osm2streets-qgis.js
|
||||
├─[intermediates]─▶ build-osm2streets-qgis.js(显式 legacy/reference)
|
||||
│ osm2streets-js-node 解析 .osm
|
||||
│ → splitLayers() 拆成九个图层
|
||||
│ → normalize-lane-arrows.py(QGIS Python)
|
||||
@@ -45,23 +47,27 @@ config/areas/<id>.json
|
||||
│ → 重建 scene.geojson + scene_style.json
|
||||
│ → _pipeline/stages/reimport.manifest.json
|
||||
│
|
||||
├─[blender]───────▶ Blender + blender/generate_scene.py
|
||||
│ 读 .osm + osm2streets_web_out/
|
||||
├─[blender]───────▶ compile-native-roads.js + Blender + blender/generate_scene.py
|
||||
│ 默认读 .osm + native-road/
|
||||
│ → <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
|
||||
│
|
||||
├─[preview]───────▶ 生成 <id>-cesium-preview.html
|
||||
│ + 拷贝 lib/cesium-preview.{js,css}
|
||||
│ + 车辆巡航路线与模型
|
||||
│ → _pipeline/stages/preview.manifest.json
|
||||
├─[compress]──────▶ 压缩 staging 内主 GLB 并更新 staged manifest
|
||||
│ → _pipeline/stages/compress.manifest.json
|
||||
│
|
||||
└─[compress]──────▶ 生成并列压缩 GLB / metadata / preview
|
||||
→ _pipeline/stages/compress.manifest.json
|
||||
├─[package]───────▶ 校验 manifest 与全部静态模型,原子发布 package/
|
||||
│ → package/manifest.json + package/models/*.glb
|
||||
│ → _pipeline/stages/package.manifest.json
|
||||
│
|
||||
└─[preview]───────▶ 生成 <id>-cesium-preview.html(native 可无车辆路线)
|
||||
│ + 拷贝 lib/cesium-preview.{js,css}
|
||||
+ _preview/ 车辆巡航路线、模型、动态信号
|
||||
│ → _pipeline/stages/preview.manifest.json
|
||||
```
|
||||
|
||||
**阶段之间只通过磁盘产物耦合**,不传内存状态。这是单跑任意阶段能work 的前提。
|
||||
@@ -87,7 +93,7 @@ config/areas/<id>.json
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|---|---|---|
|
||||
| `build-area.js` | 约 530 | 主入口:区域配置读取、阶段调度、preview 文件写入和 stage manifest ownership |
|
||||
| `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 和质量门分类 |
|
||||
@@ -99,6 +105,7 @@ config/areas/<id>.json
|
||||
| `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 | 产物一致性校验驱动 |
|
||||
|
||||
62
.trellis/spec/pipeline/native-road-package.md
Normal file
62
.trellis/spec/pipeline/native-road-package.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Native Road Compiler Contract
|
||||
|
||||
The native-road package is maintained in the private `road-compiler` repository:
|
||||
|
||||
`https://git.app.que01.top/que01/road-compiler`
|
||||
|
||||
This host consumes the exact git tag recorded in `package.json` and lockfile. The host owns area configuration normalization and writes `RoadCompilerInput`; the compiler owns the `native-road-package/v1` file contract and CLI. Production code must invoke the installed compiler CLI through `scripts/lib/road-compiler-cli.js`, not import compiler source files or read `packages/road-compiler`.
|
||||
|
||||
The completion marker is `NATIVE_ROAD_COMPILE_DONE <json>`. `build-area.js` validates the marker's count, JSON payload, area id, output path, process status, and signal before starting Blender.
|
||||
|
||||
## Scope / Trigger
|
||||
|
||||
This contract applies to native-road compilation from `scripts/build-area.js`, `scripts/compile-native-roads.js`, and the road workbench fresh-compile callback.
|
||||
|
||||
## Signatures
|
||||
|
||||
```text
|
||||
road-compiler --input <RoadCompilerInput.json>
|
||||
```
|
||||
|
||||
`RoadCompilerInput` contains `areaId`, absolute `osmFile`, `outDir`, `stagingDir`, `overridesFile`, `trafficSignalsFile`, optional `comparisonDir`, and `options`.
|
||||
|
||||
## Contracts
|
||||
|
||||
The compiler writes `compiled.json`, `diagnostics.json`, `comparison.json`, signal runtime files, `manifest.json`, and GeoJSON layers under `outDir`. Since `native-road-package/v1.1`, `manifest.json` is required: it declares every GeoJSON source, whether its role is renderable (`surface` / `marking`) or semantic, and the host material slot for renderable layers. The Blender adapter rejects a missing or invalid manifest, a declared/published GeoJSON mismatch, and an unknown material slot. On success stdout contains exactly one marker whose JSON includes `areaId`, `roads`, `endpoints`, `diagnostics`, and `output`.
|
||||
|
||||
## Validation & Error Matrix
|
||||
|
||||
| Condition | Required result |
|
||||
|---|---|
|
||||
| CLI cannot start | throw the spawn error |
|
||||
| non-zero exit or signal | fail with status/signal |
|
||||
| zero completion markers | fail |
|
||||
| multiple completion markers | fail |
|
||||
| malformed marker JSON | fail |
|
||||
| marker `areaId` or `output` mismatch | fail |
|
||||
|
||||
## Good / Base / Bad Cases
|
||||
|
||||
- Good: host writes an absolute input JSON, runs the installed exact-tag CLI, validates one marker, then starts Blender.
|
||||
- Base: compiler emits deterministic files and no optional comparison source exists.
|
||||
- Bad: host imports compiler source, reads `config/areas` inside the package, or accepts a missing/duplicate marker.
|
||||
|
||||
## Tests Required
|
||||
|
||||
- `scripts/test-road-compiler-cli.js` asserts marker success, missing, duplicate, malformed, area mismatch, and output mismatch cases.
|
||||
- `scripts/road-parity.js` compares `contentHash`, `orderHash`, feature counts, and byte counts for both supported areas.
|
||||
- `npm run test:road-workbench` asserts the workbench is loaded from the installed package.
|
||||
|
||||
## Wrong vs Correct
|
||||
|
||||
Wrong:
|
||||
|
||||
```js
|
||||
require("../packages/road-compiler/src/compile/compiler");
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```js
|
||||
spawnSync(process.execPath, [compilerCli(), "--input", inputFile]);
|
||||
```
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
## 定位
|
||||
|
||||
预览层是**验证性的,不是产物本身**。它加载 `cesium` 阶段导出的 `.glb` + `.json`,
|
||||
用来确认资产在真实 Cesium 里的样子。改这一层**不会**改变 Blender/GLB 主资产。
|
||||
预览层是**验证性的,不是产物本身**。它加载 `package/manifest.json`,用来确认已发布资产
|
||||
在真实 Cesium 里的样子。改这一层**不会**改变 package 内的 Blender/GLB 静态资产。
|
||||
|
||||
车辆巡航同理——README 里写明它是"用于验证高精度巡航可用性的预览层功能"。
|
||||
|
||||
@@ -53,9 +53,14 @@ const config = window.OSM_ASSET_PREVIEW_CONFIG || {}; // :4
|
||||
**加一个新的可配置项**:`cesiumPreviewHtml()` 里加进注入的 JSON,JS 侧从 `config` 读,
|
||||
两边都要动。
|
||||
|
||||
`build-area.js` 只保留 GLB / metadata 依赖检查、写入顺序和 preview manifest ownership;
|
||||
`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` 读取;预览只负责驱动其状态,不拥有这些文件。
|
||||
|
||||
---
|
||||
|
||||
@@ -185,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
|
||||
@@ -287,6 +307,105 @@ npm run build:area -- --config config/areas/<area>.json --stages cesium
|
||||
|
||||
---
|
||||
|
||||
## 实时 V2X 车辆仅数据源
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
适用于开启 `v2xPreview.enabled` 的 Cesium 运营预览。触发:修改
|
||||
`v2x-cesium-overlay.js`、preview descriptor 的 `routeName`、或实时车辆展示。
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
```text
|
||||
WS /network/ws/network/signal?authorization=<token>
|
||||
WS /network/ws/network/obuPosition?authorization=<token>
|
||||
WS /network/ws/network/targetPosition?authorization=<token>
|
||||
```
|
||||
|
||||
OBU 消息使用 `carCode|obuCode`、`lon`、`lat`、`angle`、`speed`;目标识别消息使用
|
||||
`data[deviceId][]` 内的 `id`、`longitude`、`latitude`、`type`、`subType`、`angle`、`speed`。
|
||||
两类车辆消息的根部还带 `interval`(推送间隔,`0` 视为 `500`)。
|
||||
信号灯消息使用 `lamps[]` 内的 `phaseNo`、`status`、`countDown`。
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- 两条车辆流均为 GCJ-02,必须在实体创建前恰好调用一次 `gcj02ToWgs84`。
|
||||
- 本预览的 `routeName` 必须为 `null`,且不得生成 `trafficSimulation` 描述符。
|
||||
真正的不变量是**不得有生成路线或交通仿真**,而不是「没有车模型」——
|
||||
实时 V2X 车辆需要打包的车模型才能渲染,故 `vehicleModelNames` 由
|
||||
`writeVehicleModel(area)` 正常写出(`build-area.js:writeCesiumPreview`),
|
||||
`vehicleModelName` 取其首项。早期版本靠清空模型列表来阻止仿真车辆,
|
||||
该机制已不适用,不要再用它作为约束手段。
|
||||
- 每辆实时车保留最多 24 个已转换的位置作为实际轨迹;轨迹不是推测路径。
|
||||
- **灯色码字典只有一份**,取自源看板 `HologramCross/components/utils.ts`:
|
||||
`11=灭 21=红 22=黄 23=绿 31=其他`,未知码归入「其他」,**不得**落到红色。
|
||||
overlay 归一化为 `{nodeKeys, color, countDown}` 后单向传给 `cesium-preview.js`;
|
||||
预览层不得自带第二份字典。
|
||||
- **三条 socket 必须发心跳** `{"heartBeat":"ping"}` / 30000ms,并在断线后退避重连,
|
||||
且**重连后重放订阅帧**(signal 发 `junctionId`,obu 发 bounds,target 发 `deviceId`)。
|
||||
缺心跳会被服务端断开,画面在一分钟后静默变空。
|
||||
- **相位到原生灯头靠几何绑定**:`traffic-signals.json` 无 `phaseNo`,用 link 末段停止线点与
|
||||
航向匹配原生 `stopLongitude/stopLatitude` 与行车方向;行车方向 =
|
||||
`faceHeadingDegrees + 180`(生成侧 `scripts/lib/traffic-signals.js` 定义
|
||||
`mast = travel - 90`、`face = travel + 180`)。
|
||||
一相位可点亮多条进口道,一条 link 可点亮同进口道多个灯头,两侧都取并集。
|
||||
未绑定相位须计数上报,可用 `v2xPreview.phaseSignalMap` 显式覆盖。
|
||||
- **车辆生命周期**:超过 `interval * 1.5` 未更新即隐藏(不删除),隐藏项作为同模型的可复用槽位;
|
||||
socket 断开清空车辆。OBU 数据 hook 标记为 `car_obu.glb`,但 dashboard 的最终
|
||||
`CrossCars` 模板实际渲染 `11.glb`,预览必须以 `11.glb` 为准;目标车为
|
||||
`${type}${subType}.glb`。`11/12/13/14/15/16/17/31/221/222.glb` 必须打入
|
||||
`_preview/v2x-vehicles/`。
|
||||
- **可见性是运行时契约**:有效推送创建的模型必须在路口概览下至少为 `28px`,并允许最大
|
||||
`8x` 放大;位置高度须高于静态道路表面。首次有效车辆推送应聚焦到车群附近,标签须不受
|
||||
静态 GLB 的深度遮挡。仅验证 WS 收包或 registry 条数不算通过。
|
||||
- 车辆推送体解析必须容忍非严格 JSON(源看板用 `saferEval`),失败须计数并在面板可见,
|
||||
**不得**静默丢弃;实现不得使用 `eval` / `new Function`。
|
||||
- 周流量比 `FlowTravelRatio/queryListWeek` **不属于**实时路口范围。
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 未登录、令牌失效、REST/WS 不可用 | 静态路口继续显示,车辆层为空,并显示实时数据不可用状态 |
|
||||
| 消息不是 JSON、心跳、坐标无效 | 忽略该消息,不创建车辆;非心跳的解析失败须计数 |
|
||||
| 收到有效车辆坐标 | 创建或更新真实车辆与实际轨迹 |
|
||||
| 车辆超过 `interval * 1.5` 未更新 | 隐藏该车并保留槽位,不得堆积幽灵车 |
|
||||
| 相机视野变化 | 节流后向 obu socket 发送 GCJ-02 四角 `bounds` |
|
||||
| 视野矩形不可用 | 发空帧(等于不过滤),不得跳过发送导致空场景 |
|
||||
| 相位一个都没绑定 | 面板显性提示并建议配置 `phaseSignalMap` |
|
||||
|
||||
### 5. Good / Base / Bad Cases
|
||||
|
||||
- Good:OBU 和感知目标连续推送,页面只显示对应车辆的实际行驶轨迹。
|
||||
- Base:服务无数据,页面没有车辆或线路。
|
||||
- Bad:将旧路线 JSON 或 `native-preview-traffic-simulation` 用作回退展示。
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- `npm run test:v2x-cesium-preview`:校验灯色字典(含 `2`/`3` 不再是黄/绿的回归断言)、
|
||||
宽松解析、几何相位绑定(对真实 `traffic-signals.json` 须达成精确一对一)、
|
||||
同相位多进口道并集、车辆超时与槽位复用、心跳间隔与重连重放订阅帧、bounds 报文。
|
||||
- `npm run test:preview-assets`:断言三条 WebSocket 存在,runtime 不调用
|
||||
`addVehicleCruises`、不显示 simulation 诊断,且预览层**不再自带** `lampColorName`
|
||||
或 `Number(status) === 3` 判断;断言实时车辆最小像素尺寸和诊断可见数量读数存在。
|
||||
- 对目标区域运行 `npm run build:area -- --config config/areas/<area>.json --stages preview`,
|
||||
检查 descriptor 中 `routeName` 为空、不存在 traffic-simulation 文件,
|
||||
且 `vehicleModelNames` 已写出(实时车辆渲染需要)。
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
错误:接口不可用时恢复构造路线。
|
||||
|
||||
```js
|
||||
const cruise = addVehicleCruises(viewer, routeData, signalData, start);
|
||||
```
|
||||
|
||||
正确:保持空车辆层,等待真实流。
|
||||
|
||||
```js
|
||||
const cruise = createLiveVehicleState();
|
||||
```
|
||||
|
||||
## 本地预览必须走 HTTP
|
||||
|
||||
```bash
|
||||
|
||||
@@ -31,7 +31,7 @@ classifyConnection(incomingEdge, outgoingEdge) =>
|
||||
`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` 作为强制输入;缺失或无效时在写产物前失败。
|
||||
- legacy osm2streets preview 必须将 `lane_polygons.geojson`、`network.json` 和 `intersection_surface.geojson` 作为强制输入;native preview 不读取这些文件,路线缺失时保留可用预览并省略车辆巡航。
|
||||
- route 经纬度由 Cesium 按 WGS84 直接放置;最终道路 GLB 必须由 WGS84 ECEF→ENU
|
||||
`Projector` 生成。禁止以固定米/度近似投影道路,否则即使 route 与 lane polygon
|
||||
完全一致,最终画面仍会随离锚点距离产生横向偏移。
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{"file": ".trellis/spec/preview/index.md", "reason": "校验改动未违反预览层约束:零依赖、无构建步骤、浏览器原生语法、IIFE。"}
|
||||
{"file": "docs/v2x-cesium-preview.md", "reason": "校验文档与实现一致:接口清单、坐标契约、配置项、回滚说明。"}
|
||||
{"file": ".trellis/tasks/08-24-v2x-realtime-cross-fidelity/prd.md", "reason": "AC1-AC10 是验收依据,逐条核对。"}
|
||||
{"file": ".trellis/tasks/08-24-v2x-realtime-cross-fidelity/design.md", "reason": "核对灯色字典、相位映射算法、心跳与订阅帧是否与源项目逐项对齐。"}
|
||||
235
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/design.md
Normal file
235
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/design.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Design — 还原 V2X 实时路口信号灯与车辆展示
|
||||
|
||||
## 1. 现状与根因
|
||||
|
||||
`scripts/lib/v2x-cesium-overlay.js`(418 行,单个 IIFE)承担了登录、REST、三个 WS、坐标转换、
|
||||
实体渲染的全部职责。REST 路径与登录契约与源项目一致,失真集中在数据处理与渲染层。
|
||||
|
||||
逐项根因(对应 PRD 的 R1–R10):
|
||||
|
||||
| # | 现象 | 根因位置 |
|
||||
| --- | --- | --- |
|
||||
| R1 | 连接几十秒后静默中断 | `openSocket()` 无心跳、无重连 |
|
||||
| R2 | 灯永远红 | `lampColor()` 判 `2`/`3`;真实码是 `21/22/23` |
|
||||
| R3 | 原生信号灯模型从不亮 | `signalByPhase` 回退 `signal.id`,与 `phaseNo` 永不相等 |
|
||||
| R4 | 同相位只亮一条进口道 | `state.linkPhases` 是 `Map<phase, entity>`,后写覆盖 |
|
||||
| R5 | 无倒计时 | `updateSignalPhases()` 丢弃 `countDown` |
|
||||
| R6 | OBU 可能一辆车都没有 | `connectObuSocket()` 的 `onOpen` 传 `null`,未发订阅帧 |
|
||||
| R7 | 幽灵车堆积 | `state.vehicles` 只增不删,未用推送 `interval` |
|
||||
| R8 | 车型错配 | 按数组下标取模型,非 `${type}${subType}` 语义 |
|
||||
| R9 | 推送可能整条被丢 | `JSON.parse` + 静默 `catch`,源项目用 `saferEval` |
|
||||
| R10 | 多余请求 | `FlowTravelRatio/queryListWeek` 不属实时路口 |
|
||||
|
||||
## 2. 边界与不变量
|
||||
|
||||
保持不变:
|
||||
- overlay 仍是**单文件零依赖 IIFE**,由 `build-area.js:561` 原样拷入产物。不新增文件,避免改动
|
||||
`area-preview.js` 的 HTML 装配与包契约。
|
||||
- 纯逻辑继续通过 `createV2xCesiumOverlay.utils` 导出,供 `scripts/test-v2x-cesium-overlay.js`
|
||||
的 `vm.runInNewContext` 沙箱直接测试(无 DOM、无 Cesium)。
|
||||
- 坐标契约不变:GCJ-02 → WGS84 只在建实体前转一次。
|
||||
- 令牌只进 `sessionStorage`,页面始终从登录门进入。
|
||||
|
||||
新增的可测试纯函数一律**不触碰 `window` / `Cesium` / `document`**,副作用留在薄薄的适配层。
|
||||
|
||||
## 3. 模块内部分层
|
||||
|
||||
文件内按四层组织,自下而上:
|
||||
|
||||
```
|
||||
L1 纯工具 gcj02ToWgs84 / joinUrl / md5 / toWebSocketUrl / getAngle / haversine
|
||||
L2 纯解析 lampColorName / parseLoosePayload / normalizeObuVehicle /
|
||||
normalizeTargetVehicles / normalizeSignalLamps
|
||||
L3 纯状态机 createVehicleRegistry / createPhaseBinding / buildPhaseSignalMap
|
||||
L4 副作用层 createSocket(心跳/重连) / 实体渲染 / UI 面板
|
||||
```
|
||||
|
||||
L1–L3 全部导出到 `.utils`,测试只打 L1–L3。
|
||||
|
||||
## 4. 关键设计决策
|
||||
|
||||
### 4.1 灯色状态字典(R2)
|
||||
|
||||
直接采用源项目 `HologramCross/components/utils.ts` 的字典,语义化返回而非直接返回颜色,
|
||||
使 overlay 与 `cesium-preview.js` 共用同一套判定:
|
||||
|
||||
```
|
||||
11 -> "off" 灭灯
|
||||
21 -> "red"
|
||||
22 -> "yellow"
|
||||
23 -> "green"
|
||||
31 -> "other"
|
||||
其他 -> "other" // 不再落到 red
|
||||
```
|
||||
|
||||
`lampColorName(status)` 放在 overlay 的 L2 并导出。`cesium-preview.js` 中重复的
|
||||
`lampColorName()` 删除,改为消费 overlay 通过 `setSignalState` 回调传入的**已归一化**灯态
|
||||
(`{nodeKeys, color, countDown}`),避免两处字典漂移。
|
||||
|
||||
> 取舍:也可以把字典抽到共享文件让两边 require,但 overlay 必须是浏览器端零依赖 IIFE,
|
||||
> `cesium-preview.js` 同样是浏览器端脚本,两者无模块系统。让数据流单向(overlay → preview)
|
||||
> 比共享常量更简单,也消除了双字典。
|
||||
|
||||
### 4.2 相位 → 原生信号灯映射(R3,本任务最难的一处)
|
||||
|
||||
`traffic-signals.json` 的信号灯项只有 `phaseGroup: 0`,**不含 V2X `phaseNo`**,两侧没有公共 ID。
|
||||
唯一可靠的连接是**几何**。
|
||||
|
||||
源项目的做法给了线索:`CrossTrafficLights3D.vue:96-104` 对每条 link 取 `geom.coordinates` 的
|
||||
最后两点,末点即停止线位置,两点连线的方位角即进口道航向。原生信号灯项恰好也有
|
||||
`stopLongitude/stopLatitude` 与 `headingDegrees`。
|
||||
|
||||
因此定义纯函数:
|
||||
|
||||
```
|
||||
buildPhaseSignalMap(links, nativeSignals, options) -> {
|
||||
byPhase: Map<phaseNo, string[]>, // nodeKey 列表
|
||||
bound: number,
|
||||
unbound: Array<{phaseNo, reason}>,
|
||||
diagnostics: Array<{linkId, matchedNodeKeys, distanceMeters, headingDeltaDegrees}>
|
||||
}
|
||||
```
|
||||
|
||||
算法:
|
||||
1. 对每条 link:解析 `geom`,取末两点,**先 GCJ-02 → WGS84**,再算 `stopPoint` 与
|
||||
`approachHeading = getAngle(prePoint, lastPoint)`。
|
||||
2. 对每个原生 signal:取 `(stopLongitude, stopLatitude)` 与**行车方向**。
|
||||
|
||||
行车方向的语义由生成侧 `scripts/lib/traffic-signals.js` 定死,不是猜的:
|
||||
- `:54` `headingDegrees = atan2(axis)`,而 `axis = roadAxis(stopLineCenter → intersectionCenter)`,
|
||||
即**停止线指向路口中心的行车方向**。
|
||||
- `:82` `mast_heading_deg = heading - 90`
|
||||
- `:83` `face_heading_deg = heading + 180`
|
||||
|
||||
运行时 `traffic-signals.json` 中 `headingDegrees === mastHeadingDegrees`(实测 7/7 相等,
|
||||
如 `150.658`),即存的是 **mast 值**;`faceHeadingDegrees` 比它小 90(`60.658`)。因此:
|
||||
|
||||
```
|
||||
travelHeading = (faceHeadingDegrees + 180) % 360 // 首选
|
||||
= (mastHeadingDegrees + 90) % 360 // face 缺失时的等价回退
|
||||
```
|
||||
|
||||
实测对全部 7 个信号自洽。
|
||||
3. 候选条件:`haversine(stopPoint, signalStop) <= maxDistanceMeters`
|
||||
且 `angleDelta(approachHeading, travelHeading) <= maxHeadingDeltaDegrees`。
|
||||
4. 一条 link **可匹配多个** signal(同一进口道的多个灯头),满足 R4 的一半。
|
||||
5. link 的 `phaseList` 中每个 `phase` → 该 link 匹配到的全部 nodeKey,**并集累加**
|
||||
(`Map<phaseNo, Set<nodeKey>>`),满足 R4 的另一半(同相位多进口道)。
|
||||
6. 默认容差:`maxDistanceMeters = 30`、`maxHeadingDeltaDegrees = 45`。
|
||||
7. 未匹配上的相位进入 `unbound`,面板显示「N 已绑定 / M 未绑定」。
|
||||
|
||||
**实现期修正**:设计初稿认为朝向语义不可判定,打算用「三字段 ± 180 的候选集,任一命中即可」。
|
||||
实测该方案在凤树二路路口产生**严重误匹配** —— 一条 link 同时命中 3 条不同 `approachId` 的进口道
|
||||
(距离 1.57 / 13.01 / 17.56 m,角差 3.8 / 7.8 / 5.4°),因为 ±180 × 三字段几乎放行任意 90° 朝向。
|
||||
改为上述单一确定式后,7 条 link 对 7 个信号达成 **7/7 精确一对一**,误匹配为 0。
|
||||
候选集方案已废弃。
|
||||
|
||||
|
||||
**配置覆盖**:`v2xPreview.phaseSignalMap = { "<phaseNo>": ["<nodeKey>", …] }` 存在时,
|
||||
该 phaseNo 直接用配置值,跳过几何匹配。这是几何匹配失败时的逃生舱,也让映射可人工固化。
|
||||
|
||||
> 取舍:不用 `approachId` / `sourceWayId` 匹配,因为那是 OSM 侧标识,V2X link 不携带;
|
||||
> 不用最近邻唯一指派(匈牙利算法),因为一进口道多灯头是正常情况,强制一对一会漏灯。
|
||||
> 容差可配是因为不同路口的停止线标注精度差异较大。
|
||||
|
||||
### 4.3 WebSocket 连接层(R1、R6)
|
||||
|
||||
新增 `createSocket({url, onOpen, onMessage, onClose, heartbeatMs, reconnect})` 副作用封装:
|
||||
|
||||
- 心跳:`setInterval(() => send('{"heartBeat":"ping"}'), 30000)`,与源项目一致。
|
||||
- 重连:指数退避 `1s, 2s, 4s, 8s`,上限 `8s`,**重连成功后重新执行 `onOpen`**,
|
||||
确保订阅帧(`junctionId` / `deviceId` / `bounds`)被重放。这是源项目 `onConnected` 的语义。
|
||||
- `close()` 主动关闭时不触发重连;`dispose()` 清心跳与退避计时器。
|
||||
- 计时器通过参数注入(`setIntervalFn` / `setTimeoutFn`),使重连与心跳逻辑可在测试中用假时钟驱动。
|
||||
|
||||
订阅帧内容严格对齐源项目:
|
||||
|
||||
| socket | 连接后首帧 | 来源 |
|
||||
| --- | --- | --- |
|
||||
| signal | `{"junctionId":"<crossCode>"}` | `CrossTrafficLights3D.vue:129` |
|
||||
| obu | `bounds \|\| ""`(dashboard 传 `null` → 空串) | `useObuCars.ts:31` |
|
||||
| target | `{"deviceId":"<ids>"}` 或 `{"deviceId":null}` | `useTargetCars.ts:50-53` |
|
||||
|
||||
OBU 的 bounds:源项目取 AMap 视野四角。本项目用 Cesium `camera.computeViewRectangle()`
|
||||
换算成同格式 `lng,lat;lng,lat;lng,lat;lng,lat`(**WGS84 → GCJ-02 反向转换**,因为服务端按 GCJ-02
|
||||
过滤),在 `camera.moveEnd` 时节流发送 `{"bounds": "<…>"}`。视野不可用时退回空串,等价于不过滤。
|
||||
|
||||
> 取舍:Cesium 是倾斜视角,`computeViewRectangle()` 在极端俯仰下可能返回 `undefined`;
|
||||
> 此时退回空串而非跳过发送,保证仍能收到全量推送——宁可多收也不要空场景。
|
||||
|
||||
### 4.4 车辆注册表(R7、R8)
|
||||
|
||||
纯状态机 `createVehicleRegistry({now})`:
|
||||
|
||||
```
|
||||
registry.ingest(vehicles, interval) -> {added, updated, hidden}
|
||||
registry.sweep(nowMs) -> hiddenKeys
|
||||
registry.list() -> VehicleRecord[]
|
||||
```
|
||||
|
||||
- 复刻源项目:`interval = Number(payload.interval) === 0 ? 500 : Number(payload.interval)`。
|
||||
- `sweep()`:`Math.abs(now - timeStamp) >= interval * 1.5` 时 `visible = false`。
|
||||
- 槽位复用:新车优先占用同模型的 `visible === false` 记录(源项目 `useTargetCars.ts:110`),
|
||||
减少 Cesium 实体的增删抖动。
|
||||
- `now` 注入,测试用假时钟推进。
|
||||
- socket `onClose` 时 `registry.clear()`,对齐源项目 `onDisconnected: cars.value = []`。
|
||||
|
||||
渲染适配层据 `visible` 切 `entity.show`,**不删实体**,与槽位复用配套。
|
||||
|
||||
模型选择(R8):OBU 的数据 hook 虽标记 `car_obu.glb`,但最终的 `CrossCars/index.vue` 模板固定
|
||||
传入 `11.glb`,因此 `modelNameFor(vehicle)` 对 OBU 返回 `11.glb`;目标车辆返回
|
||||
`${type}${subType}.glb`。预览阶段把 dashboard 的 `11/12/13/14/15/16/17/31/221/222.glb`
|
||||
复制到 `_preview/v2x-vehicles/`,并在 `vehicleModelNames` 中按文件名匹配;未知车型回退 `11.glb`
|
||||
并累加 `missingModels` 计数供面板展示。
|
||||
|
||||
平滑:用 `Cesium.SampledPositionProperty` 按 `duration` 插值,替代当前的直接赋值跳变。
|
||||
|
||||
### 4.5 宽松解析(R9)
|
||||
|
||||
源项目用 `saferEval`。浏览器端零依赖不能引入该库,也不应引入 `eval`。改为
|
||||
`parseLoosePayload(text)`:先 `JSON.parse`;失败则做一次受限规范化(单引号→双引号、
|
||||
裸键补引号、去尾逗号、`NaN`/`Infinity` → `null`)后重试;仍失败返回 `null` 并
|
||||
`state.parseFailures += 1`,面板显示失败计数(R9 要求可见)。
|
||||
|
||||
> 取舍:不用 `new Function` 还原 `saferEval` 的完整语义——那等于在预览页开一个任意代码执行面。
|
||||
> 受限规范化覆盖实际会遇到的非严格 JSON 形态,且失败可观测,比静默丢弃安全得多。
|
||||
|
||||
### 4.6 移除范围外请求(R10)
|
||||
|
||||
`loadLiveData()` 的 `Promise.allSettled` 去掉 `FlowTravelRatio/queryListWeek`,
|
||||
`state.metrics` 及面板中的 `flow metrics` 文案一并移除。
|
||||
|
||||
## 5. 数据流
|
||||
|
||||
```
|
||||
登录 → resolveCrossCode()
|
||||
→ queryCrossLinkInfo / queryPoles / findDeviceByCrossCode / crossDeviceConfig
|
||||
→ buildPhaseSignalMap(links, nativeSignals) [纯]
|
||||
→ connectLiveSockets()
|
||||
signal → normalizeSignalLamps → lampColorName → setSignalState({nodeKeys,color,countDown})
|
||||
→ cesium-preview 点亮原生灯头 + 倒计时
|
||||
obu → parseLoosePayload → normalizeObuVehicle ┐
|
||||
target → parseLoosePayload → normalizeTargetVehicles ┴→ registry.ingest → 渲染适配
|
||||
registry.sweep → entity.show
|
||||
```
|
||||
|
||||
## 6. 兼容性与回滚
|
||||
|
||||
- `v2xPreview.enabled=false` 时 `createV2xCesiumOverlay()` 仍返回 `null`,生成页不含面板,
|
||||
包产物字节不变(AC10)。
|
||||
- 原生信号灯在**无 V2X 灯态时**的行为保持现状(`hideUnconfirmedSignalAssets` 语义不变),
|
||||
只有收到实时灯态才点亮,不会退回模拟相位。
|
||||
- 回滚点:本任务对 `cesium-preview.js` 的改动限于删除重复的 `lampColorName` 与调整
|
||||
`setSignalState` 入参形状,可单独 revert 而不影响 overlay。
|
||||
- 新增 `phaseSignalMap`、`maxDistanceMeters`、`maxHeadingDeltaDegrees` 均为可选配置,
|
||||
缺省即当前默认行为。
|
||||
|
||||
## 7. 风险
|
||||
|
||||
| 风险 | 影响 | 缓解 |
|
||||
| --- | --- | --- |
|
||||
| 几何匹配在实际路口全部落空 | 灯仍不亮 | 面板显性报「0 已绑定」;`phaseSignalMap` 配置覆盖兜底 |
|
||||
| `computeViewRectangle()` 返回 undefined | OBU 过滤异常 | 退回空串=不过滤 |
|
||||
| 宽松解析未覆盖真实非法形态 | 丢消息 | `parseFailures` 计数可见,便于按真实样本补规则 |
|
||||
| 服务端心跳格式与源项目不符 | 仍被断开 | 心跳报文与间隔取自源项目源码,AC1 用模拟服务端验证 |
|
||||
| 真实推送 `interval` 缺失 | 车辆过早隐藏 | 缺失时回退 500ms,与源项目一致 |
|
||||
@@ -0,0 +1,4 @@
|
||||
{"file": ".trellis/spec/preview/index.md", "reason": "预览层契约:无构建步骤、零依赖 IIFE、参数经 window.OSM_ASSET_PREVIEW_CONFIG 注入。overlay 与 cesium-preview.js 的所有改动都受这些约束。"}
|
||||
{"file": ".trellis/spec/pipeline/index.md", "reason": "确认 build-area.js 如何把 v2x-cesium-overlay.js 拷入产物,避免误增文件破坏包契约。"}
|
||||
{"file": ".trellis/spec/config/index.md", "reason": "新增可选配置 v2xPreview.phaseSignalMap / maxDistanceMeters / maxHeadingDeltaDegrees 需符合区域配置约定。"}
|
||||
{"file": "docs/v2x-cesium-preview.md", "reason": "现有 V2X 预览文档:配置项、坐标契约、代理要求、回滚方式。移除 FlowTravelRatio 与新增配置后需同步更新。"}
|
||||
171
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/implement.md
Normal file
171
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/implement.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# Implement — 还原 V2X 实时路口信号灯与车辆展示
|
||||
|
||||
## 参照实现(只读)
|
||||
|
||||
`/Users/que01/Project/v2x_web/applications/dashboard/src/views/dashboard/components/Content/Map/HologramCross/`
|
||||
|
||||
改动前先读对应源文件,不要凭记忆写契约。
|
||||
|
||||
## 改动文件
|
||||
|
||||
| 文件 | 性质 |
|
||||
| --- | --- |
|
||||
| `scripts/lib/v2x-cesium-overlay.js` | 主要改动,L1–L4 分层重写 |
|
||||
| `scripts/lib/cesium-preview.js` | 删除重复 `lampColorName`,调整 `setSignalState` 入参 |
|
||||
| `scripts/test-v2x-cesium-overlay.js` | 扩充单测,覆盖 AC2–AC7 |
|
||||
| `.trellis/tasks/.../prd.md` `design.md` | 如实现中发现偏差需回写 |
|
||||
|
||||
不改:`build-area.js`、`area-preview.js`、包产物生成逻辑。
|
||||
|
||||
## 执行顺序
|
||||
|
||||
每步结束跑一次 `npm run test:v2x-cesium-preview`,保持绿色再进下一步。
|
||||
|
||||
### 步骤 1 — L2 灯色字典 + 宽松解析(R2、R9)
|
||||
|
||||
1. 在 overlay 加 `LAMP_STATUS`(`11/21/22/23/31`)与 `lampColorName(status)`,未知返回 `"other"`。
|
||||
2. 删除旧 `lampColor()` 中 `Number(status)===3 / ===2` 的判断。
|
||||
3. 加 `parseLoosePayload(text)`:`JSON.parse` → 受限规范化重试 → `null`。
|
||||
4. 心跳报文 `pong` 短路判定保留(与源项目一致)。
|
||||
5. 二者导出到 `.utils`。
|
||||
|
||||
验证:`node scripts/test-v2x-cesium-overlay.js`
|
||||
- `lampColorName(21)==="red"`, `(22)==="yellow"`, `(23)==="green"`, `(11)==="off"`, `(31)==="other"`
|
||||
- **`lampColorName(2)!=="yellow"` 且 `lampColorName(3)!=="green"`**(防回归,AC2)
|
||||
- `parseLoosePayload("{'a':1,}")` 解析成功;`parseLoosePayload("{bad")===null`(AC7)
|
||||
|
||||
**审查点 A**:灯色字典必须与 `HologramCross/components/utils.ts` 的
|
||||
`lightStatusColorDict` 逐项一致,不得增删码值。
|
||||
|
||||
### 步骤 2 — L1 几何工具(支撑 R3)
|
||||
|
||||
1. 加 `getAngle(start, end)`:直接对齐源项目 `utils.ts` 的实现(返回 0–360)。
|
||||
2. 加 `haversineMeters(a, b)`、`angleDeltaDegrees(a, b)`(后者处理 350° vs 10° 的环绕)。
|
||||
3. 导出到 `.utils`。
|
||||
|
||||
验证:正北 `getAngle([0,0],[0,1])≈0`;正东 `≈90`;
|
||||
`angleDeltaDegrees(350,10)===20`;`haversineMeters` 对已知两点误差 < 0.5m。
|
||||
|
||||
### 步骤 3 — 相位映射(R3、R4)
|
||||
|
||||
1. 实现 `buildPhaseSignalMap(links, nativeSignals, options)`,返回
|
||||
`{byPhase, bound, unbound, diagnostics}`,算法见 `design.md` §4.2。
|
||||
2. link 末两点**先 GCJ-02→WGS84 再算角度与距离**(原生信号是 WGS84)。
|
||||
3. 一 link 匹配多 signal;`Map<phaseNo, Set<nodeKey>>` 并集累加。
|
||||
4. 支持 `options.phaseSignalMap` 配置覆盖,命中则跳过几何匹配。
|
||||
5. 默认 `maxDistanceMeters=30`、`maxHeadingDeltaDegrees=45`。
|
||||
6. 导出到 `.utils`。
|
||||
|
||||
验证(AC3、AC4):
|
||||
- 用 `outputs/fengshu-er-road/package/runtime/traffic-signals.json` 的真实 7 条信号做夹具,
|
||||
配一组构造的 link(末点取自 `stopLongitude/stopLatitude` 反算 GCJ-02、
|
||||
航向取自 `headingDegrees`),断言 `bound > 0`。
|
||||
- 构造两条不同 link 共用同一 `phaseNo`,断言 `byPhase.get(phaseNo)` 含**两条 link 的全部 nodeKey**。
|
||||
- 构造一条远离所有信号的 link,断言其相位进入 `unbound` 且带 `reason`。
|
||||
- 配置覆盖生效:`phaseSignalMap` 指定的 phaseNo 取配置值。
|
||||
|
||||
**审查点 B**:这是全任务最易做错的一步。合入前必须用真实 `traffic-signals.json` 跑出
|
||||
非零绑定数,若为 0 则说明容差或坐标方向反了,**不得靠放大容差蒙混过关**——先打印
|
||||
`diagnostics` 的实际距离/角差,确认量级合理(距离应是米级而非千米级)。
|
||||
|
||||
### 步骤 4 — 车辆注册表(R7、R8)
|
||||
|
||||
1. 实现 `createVehicleRegistry({now})`,含 `ingest / sweep / list / clear`。
|
||||
2. `interval` 取自推送体,`0` 或缺失回退 `500`。
|
||||
3. `sweep`:`|now - timeStamp| >= interval*1.5` → `visible=false`。
|
||||
4. 槽位复用:优先占用同 `model` 的不可见记录。
|
||||
5. `modelNameFor(vehicle)`:OBU → dashboard 最终模板使用的 `11.glb`;target → `${type}${subType}.glb`。把 dashboard 车辆 GLB 复制到预览包。
|
||||
6. `normalizeTargetVehicles` 补上源项目的 `type==1 && isNil(subType) → subType=1`。
|
||||
7. 导出 `createVehicleRegistry`、`modelNameFor`。
|
||||
|
||||
验证(AC5):
|
||||
- 注入一次推送 → `list()` 一条 `visible=true`。
|
||||
- 假时钟推进 `interval*1.5` 后 `sweep()` → 该车 `visible=false`。
|
||||
- 再注入同 key → 复用原槽位,`list().length` 不变。
|
||||
- `clear()` 后为空。
|
||||
- `modelNameFor({kind:"obu"})==="11.glb"`;`{kind:"target",type:1,subType:2}` → `"12.glb"`。
|
||||
|
||||
### 步骤 5 — WebSocket 连接层(R1、R6)
|
||||
|
||||
1. 实现 `createSocket({url, onOpen, onMessage, onClose, heartbeatMs, reconnect, socketFactory, setIntervalFn, setTimeoutFn})`。
|
||||
2. 心跳 `{"heartBeat":"ping"}` / 30000ms。
|
||||
3. 退避重连 `1s→2s→4s→8s` 封顶,**重连成功重放 `onOpen`**。
|
||||
4. 主动 `close()` 不重连;`dispose()` 清所有计时器。
|
||||
5. 三个 socket 的订阅帧按 `design.md` §4.3 的表格实现。
|
||||
6. OBU bounds:`camera.computeViewRectangle()` → WGS84→GCJ-02 → `"lng,lat;…"` 四角,
|
||||
`camera.moveEnd` 节流发送;不可用时发空串。
|
||||
7. 导出 `createSocket`、`buildBoundsMessage`。
|
||||
|
||||
验证(AC1、AC6):
|
||||
- 假 socket + 假时钟:推进 30s 断言收到一帧 `{"heartBeat":"ping"}`;推进 90s 断言三帧。
|
||||
- 模拟 `onclose` 后推进退避时间,断言重新建连**且订阅帧被重放**。
|
||||
- signal 首帧 `{"junctionId":"<code>"}`;target 无配置首帧 `{"deviceId":null}`,
|
||||
有配置 `{"deviceId":"1,2"}`;obu 首帧为空串。
|
||||
- `dispose()` 后推进时钟不再产生任何发送。
|
||||
|
||||
**审查点 C**:心跳报文与间隔必须逐字节对齐源项目
|
||||
(`useObuCars.ts:28-31`、`useTargetCars.ts:27-30`、`CrossTrafficLights3D.vue:122-130`)。
|
||||
|
||||
### 步骤 6 — 接线与渲染适配(R4、R5、R10)
|
||||
|
||||
1. `loadLiveData()` 移除 `FlowTravelRatio/queryListWeek`,删 `state.metrics` 与面板 flow 文案。
|
||||
2. 用 `buildPhaseSignalMap` 结果替换 `state.linkPhases`(`Map<phase, entity>` 整体删除)。
|
||||
3. `updateSignalPhases` → 归一化为 `{nodeKeys, color, countDown}` 列表后调
|
||||
`context.setSignalState(...)`,同时给 link polyline 上色(同相位多 link 全部上色)。
|
||||
4. 车辆渲染改为消费 `registry`:`visible` 切 `entity.show`,位置用
|
||||
`SampledPositionProperty` 按 `duration` 插值。
|
||||
5. 面板状态行补:已绑定/未绑定相位数、缺失模型数、解析失败数。
|
||||
6. `dispose()` 清 socket、计时器、实体、registry。
|
||||
|
||||
### 步骤 7 — `cesium-preview.js` 对接(R2、R5)
|
||||
|
||||
1. 删除 `cesium-preview.js` 中的 `lampColorName()`(与 overlay 重复且映射错误)。
|
||||
2. `createLiveTrafficSignals().update(lamps)` 改签名为
|
||||
`update(entries)`,`entries = [{nodeKeys:string[], color:"red"|"yellow"|"green"|"off"|"other", countDown:number}]`。
|
||||
3. 按 `nodeKeys` 直接点亮 `TrafficSignalDynamic_<nodeKey>_<color>` 节点,
|
||||
删除失效的 `signalByPhase` 回退逻辑。
|
||||
4. `countDown` 驱动 `category === "countdown"` 资产显示。
|
||||
5. `color === "off" | "other"` 时三色节点全灭。
|
||||
|
||||
验证:`npm run test:preview-assets`
|
||||
|
||||
### 步骤 8 — 全量校验
|
||||
|
||||
```bash
|
||||
npm run test:v2x-cesium-preview
|
||||
npm run test:v2x-preview-server
|
||||
npm run test:preview-assets
|
||||
npm run test:traffic-signals
|
||||
npm run test:native-preview-traffic
|
||||
npm run test:package-contract
|
||||
```
|
||||
|
||||
AC10 校验:将区域配置 `v2xPreview.enabled` 置 `false` 重新生成,
|
||||
确认生成页无 V2X 面板且 `package/` 下产物字节不变。
|
||||
|
||||
## 人工验收(AC9,需真实上游)
|
||||
|
||||
```bash
|
||||
V2X_UPSTREAM=http://<host>:<port> \
|
||||
npm run serve:v2x-preview -- --root outputs/fengshu-er-road --host 0.0.0.0 --port 7862
|
||||
```
|
||||
|
||||
登录后逐项确认:
|
||||
- [ ] 信号灯随相位变色,颜色与 dashboard 实时路口一致
|
||||
- [ ] 倒计时数字走动
|
||||
- [ ] 车辆出现、移动平滑、离开视野后消失(无幽灵车堆积)
|
||||
- [ ] 连接持续 5 分钟以上不掉线
|
||||
- [ ] 车辆与路口位置无明显偏移(坐标未二次转换)
|
||||
- [ ] 面板绑定数、失败数符合预期
|
||||
|
||||
## 回滚点
|
||||
|
||||
- 步骤 7 独立可 revert(只动 `cesium-preview.js`)。
|
||||
- 步骤 1–6 全在 overlay 单文件内,`git checkout scripts/lib/v2x-cesium-overlay.js` 即回到当前状态。
|
||||
- `v2xPreview.enabled=false` 是运行时总开关。
|
||||
|
||||
## 不做(留待后续任务)
|
||||
|
||||
- AMap 底图 / 矢量瓦片图层
|
||||
- `CrossDevices/Model3D.vue` 3D 杆件模型(设备维持点位)
|
||||
- `useV2XEvents` 事件气泡
|
||||
99
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/prd.md
Normal file
99
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/prd.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# 还原 V2X 实时路口信号灯与车辆展示
|
||||
|
||||
## Goal
|
||||
|
||||
把 `v2x_web` dashboard「实时路口」(`HologramCross`) 的实时数据行为完整搬到本项目的 Cesium 预览中,使
|
||||
`outputs/<area>/…-cesium-preview.html` 在登录后能持续、正确地显示 **信号灯相位灯色与倒计时** 和
|
||||
**OBU / 目标识别车辆**。
|
||||
|
||||
当前实现(Codex 移植)接口地址是对的,但数据处理与渲染层基本失真,实际效果是:灯永远红、车可能一辆都不出现、
|
||||
出现后也不消失、连接几十秒后被服务端断开。本任务修复这条链路。
|
||||
|
||||
## Source of Truth
|
||||
|
||||
参照实现(只读,不修改):
|
||||
`/Users/que01/Project/v2x_web/applications/dashboard/src/views/dashboard/components/Content/Map/HologramCross/`
|
||||
|
||||
- `components/CrossTrafficLights3D.vue` — 信号灯:link 相位、WS 订阅、灯色/倒计时
|
||||
- `components/utils.ts` — 灯色状态字典、航向角计算
|
||||
- `components/CrossCars/hooks/useObuCars.ts` — OBU 车辆
|
||||
- `components/CrossCars/hooks/useTargetCars.ts` — 目标识别车辆
|
||||
- `components/CrossCars/type.ts` — 车辆数据结构
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1 WebSocket 连接层对齐
|
||||
- 三个 socket(`signal` / `obuPosition` / `targetPosition`)均按源项目发送心跳
|
||||
`{"heartBeat":"ping"}`,间隔 30000ms。
|
||||
- 断线自动重连,重连后重新发送各自的订阅消息(signal 发 `junctionId`,target 发 `deviceId`,obu 发 bounds)。
|
||||
- 页面 `visibilitychange` 回到可见时行为不得导致重复连接堆积;`dispose()` 必须清掉心跳与重连计时器。
|
||||
|
||||
### R2 信号灯灯色映射修正
|
||||
- 采用源项目 `utils.ts` 的状态字典:`11=灭灯 21=红 22=黄 23=绿 31=其他`。
|
||||
- 现有 `lampColor()`(overlay)与 `lampColorName()`(`cesium-preview.js`)中基于 `2`/`3` 的判断必须移除。
|
||||
- 未知状态码按「其他/灭灯」处理,不得静默落到红色。
|
||||
|
||||
### R3 相位到原生信号灯的映射
|
||||
- `traffic-signals.json` 中不存在 V2X `phaseNo`(只有 `phaseGroup: 0`),当前回退到 `signal.id` 导致永不匹配。
|
||||
必须建立一条真实可用的 V2X `phaseNo` → 原生信号灯头的映射。
|
||||
- 映射结果必须可诊断:面板要能显示「N 个相位已绑定 / M 个未绑定」。
|
||||
- 允许区域配置提供显式覆盖映射。
|
||||
|
||||
### R4 一个相位可点亮多条进口道
|
||||
- 同一 `phaseNo` 对应的所有 link / 信号灯头都要被点亮,当前 `Map<phase, entity>` 的覆盖行为必须消除。
|
||||
|
||||
### R5 倒计时
|
||||
- 使用推送中的 `countDown` 字段(源项目 `phaseValue['time'] = signal.countDown`),驱动预览中已有的
|
||||
countdown 资产或等效显示。
|
||||
|
||||
### R6 车辆订阅正确建立
|
||||
- OBU socket 连接后必须发送订阅消息(源项目 `send(bounds || '')`),相机视野变化时发送
|
||||
`{"bounds": "<lng,lat;…>"}`。
|
||||
- target socket 连接后发送 `{"deviceId": "<ids joined by comma>"}`,无配置时发送 `{"deviceId": null}`。
|
||||
|
||||
### R7 车辆生命周期
|
||||
- 按源项目实现超时隐藏:超过 `interval * 1.5` 未更新的车辆隐藏并可复用槽位;`interval` 取自推送体,
|
||||
`0` 时回退 `500`。
|
||||
- socket 断开时清空车辆列表。
|
||||
- 车辆位置更新使用推送间隔做平滑插值,避免逐帧跳变。
|
||||
|
||||
### R8 车辆模型命名与资产对齐
|
||||
- OBU 数据层标记为 `car_obu.glb`,但 dashboard 的实际 `CrossCars` 模板固定渲染 `11.glb`;预览必须遵从该最终渲染契约。目标识别车辆使用 `${type}${subType}.glb`。
|
||||
- 预览包必须携带 dashboard 实际使用的车辆模型;包内缺少未知车型时回退到 `11.glb`,并在面板提示,不得静默显示错误车型。
|
||||
|
||||
### R9 推送体解析健壮性
|
||||
- 源项目对两个车辆 socket 使用 `saferEval` 而非 `JSON.parse`,说明载荷不保证严格 JSON。
|
||||
- 解析必须容忍非严格 JSON,且解析失败要计数并在面板可见,不得整条静默丢弃。
|
||||
|
||||
### R10 范围纠正
|
||||
- `/facilities/api/FlowTravelRatio/queryListWeek`(周流量比)不属于「实时路口」,从实时加载链路移除。
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- 不移植 AMap 底图 / 矢量瓦片图层(`linkVectorHdMapTileUrl`)。
|
||||
- 不移植 `CrossDevices/Model3D.vue` 的 3D 杆件模型;设备维持点位显示(可后续独立任务)。
|
||||
- 不移植 `useV2XEvents` 的 V2X 事件气泡。
|
||||
- 不改变原生包产物(GLB / metadata / traffic-signals.json 的生成逻辑)。
|
||||
|
||||
## Constraints
|
||||
|
||||
- 不得提交上游地址、账号、令牌、AMap key;`v2xPreview` 配置保持非敏感。
|
||||
- 令牌只存 `sessionStorage`,页面始终从登录门进入。
|
||||
- 坐标契约不变:V2X 数据 GCJ-02,进 Cesium 前转一次 WGS84;原生数据不得二次转换。
|
||||
- `v2xPreview.enabled` 默认 `false`,关闭时生成页不含 V2X 面板。
|
||||
- 生成的预览为静态页,无构建步骤,overlay 保持零依赖 IIFE。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] AC1 三个 socket 均可见 30s 心跳;模拟服务端在 60s+ 后连接仍存活。
|
||||
- [ ] AC2 单测覆盖灯色映射:`21→红 22→黄 23→绿 11→灭 31→其他`,且 `2`/`3` 不再被当作黄/绿。
|
||||
- [ ] AC3 单测覆盖相位映射:给定 V2X link 相位与 `traffic-signals.json`,输出的绑定数 > 0,
|
||||
未绑定项被明确列出。
|
||||
- [ ] AC4 单测覆盖同相位多进口道:一个 `phaseNo` 对应 2 条 link 时,两条都被点亮。
|
||||
- [ ] AC5 单测覆盖车辆超时:注入两次推送并推进时间超过 `interval*1.5`,车辆转为不可见。
|
||||
- [ ] AC6 单测覆盖 OBU/target 订阅消息:连接后发出的首帧内容与源项目一致。
|
||||
- [ ] AC7 单测覆盖非严格 JSON 推送体能被解析,且失败计数可读。
|
||||
- [ ] AC8 `npm run test:v2x-cesium-preview`、`test:v2x-preview-server`、`test:preview-assets` 全绿。
|
||||
- [ ] AC9 真机(`npm run serve:v2x-preview` 接真实上游)登录后可见:信号灯随相位变色并显示倒计时、
|
||||
车辆出现并移动、离开后消失。此项由开发者人工确认。
|
||||
- [ ] AC10 `v2xPreview.enabled=false` 重新生成后,页面不含 V2X 面板,包产物字节不变。
|
||||
26
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/task.json
Normal file
26
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "v2x-realtime-cross-fidelity",
|
||||
"name": "v2x-realtime-cross-fidelity",
|
||||
"title": "还原 V2X 实时路口信号灯与车辆展示",
|
||||
"description": "",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-24",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
1
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/check.jsonl
Normal file
1
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/check.jsonl
Normal file
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -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."}
|
||||
96
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/prd.md
Normal file
96
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/prd.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# 后续计划:drawtonomy 扩展 PoC
|
||||
|
||||
前置任务:`08-25-road-compiler-extraction` 的 Phase 0-3 完成并完成验收。
|
||||
评估依据:父任务归档前的 `design.md` §4 决策记录 D1 / D2 / D3。
|
||||
|
||||
## Goal
|
||||
|
||||
在**编译器仓库内**实现一个 drawtonomy 扩展:把编译产物注入其浏览器编辑器,
|
||||
回读快照后走其开源导出器产出 OpenDRIVE / Lanelet2。
|
||||
|
||||
定位是 **PoC,不进关键路径**。目的是验证「编译器当 scene generator +
|
||||
drawtonomy 当编辑器与工业格式后端」这条链路是否值得投入。
|
||||
|
||||
## 前提认知(已核实)
|
||||
|
||||
必须先明确,否则会做错方向:
|
||||
|
||||
- **drawtonomy 编辑器闭源**,克隆仓库只有 SDK / dev-server / mcp-server。
|
||||
README 卖点里的 topology-aware lanes、lane tool、intersection templates、
|
||||
Map→lanes 全部不在开源代码内。
|
||||
- **它不做 raw OSM 推导**。`exporter/osmParser.ts` 是 Lanelet2 解析器
|
||||
(车道左右边界已显式)。整个 exporter 目录 grep `highway` 命中 1 次且是
|
||||
车道类型字符串。→ **不能替代本编译器**,只能做下游。
|
||||
- **junctionTools 不能做成扩展**:无 canvas/overlay 能力、无工具注册、
|
||||
**无任何 change 事件推送**(`ExtensionClient.handleMessage` 入站只有
|
||||
`ext:init` / 5 个 `*-response` / `ext:error`,读取全靠轮询)。
|
||||
→ junction 编辑留在自有 workbench。
|
||||
- **`drawtonomy-dev-server` 是 drawtonomy.com 的缓存代理**(TTL 1 小时),
|
||||
非自托管。宿主协议会漂(manifest 有 `minHostVersion`)。→ 不进关键路径。
|
||||
|
||||
## Requirements
|
||||
|
||||
### R4.1 扩展:编译产物 → 编辑器
|
||||
|
||||
- manifest capabilities:`shapes:write`、`ui:panel`、`snapshot:read`、`ui:notify`
|
||||
- 面板调编译器的本地 HTTP 服务(`road-workbench` 已经是一个 HTTP 服务)取产物
|
||||
- 车道边界 → `createLaneWithBoundaries(leftPoints, rightPoints, opts)` → `addShapes()`
|
||||
- 坐标转换:本项目 WGS84/ENU(米)→ drawtonomy 画布像素。
|
||||
沿用其 `drawtonomy_origin_lat/lon` + `latLonToCanvas` 约定
|
||||
- ⚠️ **借它的结构,不借它的坐标系**:其 `BaseShape.x/y` 是画布像素
|
||||
(`z` 注释明确写 "world units — NOT canvas pixels like x/y")。
|
||||
编译器内部坐标系不得因此改变
|
||||
|
||||
### R4.2 回读 → 工业格式导出
|
||||
|
||||
- `requestSnapshot()` 取回快照
|
||||
- 本地跑其开源 `exportToOpenDrive` / `lanelet2`(Apache-2.0)
|
||||
- 产出 `.xodr` / `.osm`(Lanelet2),用其 `validateOpenDrive` 自校验
|
||||
- Lanelet2 是 Autoware 的输入格式 —— 对本项目 V2X 方向有实际价值
|
||||
|
||||
### R4.3 许可与依赖合规
|
||||
|
||||
- drawtonomy SDK 是 Apache-2.0:借用代码需保留 NOTICE / 署名
|
||||
- 若依赖 drawtonomy.com 托管服务,需查其服务条款(app 非 Apache-2.0)
|
||||
- ESM/CJS 互操作:SDK 是 ESM+TS,编译器是 CJS。
|
||||
本阶段可**局部**引入构建步骤,但仅限扩展目录,不得污染编译器核心
|
||||
(父任务 C2 的边界)
|
||||
|
||||
### R4.4 归属
|
||||
|
||||
扩展代码放**编译器仓库**,不放宿主 —— 符合父任务「独立维护」目标。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] AC4.1 扩展能把一个区域的编译产物注入 drawtonomy 编辑器并正确显示车道
|
||||
- [ ] AC4.2 编辑器内手改后回读快照,能导出通过 `validateOpenDrive` 的 `.xodr`
|
||||
- [ ] AC4.3 能导出 Lanelet2 `.osm`
|
||||
- [ ] AC4.4 编译器核心未引入构建步骤(构建仅限扩展目录)
|
||||
- [ ] AC4.5 编译器内部坐标系未因适配画布像素而改变
|
||||
- [ ] AC4.6 Apache-2.0 署名 / NOTICE 已按要求保留
|
||||
- [ ] AC4.7 结论记录:这条链路是否值得继续投入,写入任务 `research/`
|
||||
|
||||
## 启动条件
|
||||
|
||||
- 道路编译器独立化父任务(Phase 0-3)已完成验收。
|
||||
- 编译器仓库、稳定 CLI 与版本化输出契约已可用。
|
||||
- 此任务不阻塞道路编译器抽离或渲染分离的交付。
|
||||
|
||||
## 明确不做
|
||||
|
||||
- junctionTools 做成扩展(API 不支持,见前提认知)
|
||||
- 把 drawtonomy 放进生产构建链路
|
||||
- 用 drawtonomy 替代编译器(D1 已否决)
|
||||
- 自建通用白板编辑器
|
||||
|
||||
## 后续可能(不属本阶段)
|
||||
|
||||
父任务 design §4 的 D3 列了三项值得从 drawtonomy 借用的东西,
|
||||
它们与本阶段独立,应各自开任务:
|
||||
|
||||
- `odrGeometryFit.ts` 的拟合器 → 替掉手调的
|
||||
`approachWidthMultiplier=1.45` / `coreRadiusMeters=28`,改为对 `referenceFile`
|
||||
拟合、残差作质量指标
|
||||
- validator 的 mutation-proven 方法 → 给现有 31 条诊断规则建触发证明
|
||||
- validator 的分层命名空间(`xml.*` / `ref.*` / `junction.*` / `geom.*`)
|
||||
→ 替代当前平铺的 31 条规则
|
||||
28
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/task.json
Normal file
28
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/task.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"id": "rc-p4-drawtonomy-ext",
|
||||
"name": "rc-p4-drawtonomy-ext",
|
||||
"title": "后续计划:drawtonomy 扩展 PoC",
|
||||
"description": "道路编译器独立化完成后,评估在编译器仓库内实现 drawtonomy 扩展:编译产物注入编辑器,快照回读后走开源 OpenDRIVE/Lanelet2 导出",
|
||||
"status": "planning",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P3",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-25",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "不属于道路编译器独立化父任务的完成条件。父任务验收完成后,将此任务作为下一项候选任务提示。",
|
||||
"meta": {
|
||||
"follow_up_after": "08-25-road-compiler-extraction"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Native Road Parity Baselines
|
||||
|
||||
Generated from commit `25cf82e7c75ea8d881639c4a80648121efbd39f6` on 2026-08-25.
|
||||
|
||||
```bash
|
||||
npm run road:compile -- --config config/areas/fengshu-er-road.json
|
||||
node scripts/road-parity.js --config config/areas/fengshu-er-road.json \
|
||||
--snapshot .trellis/tasks/08-25-road-compiler-extraction/baseline/fengshu-er-road.json
|
||||
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
node scripts/road-parity.js --config config/areas/nantaizi-lake-innovation-valley.json \
|
||||
--snapshot .trellis/tasks/08-25-road-compiler-extraction/baseline/nantaizi-lake-innovation-valley.json
|
||||
```
|
||||
|
||||
Both samples emit all twelve native-road layers. `fengshu-er-road` enables its
|
||||
configured complex-junction template; `nantaizi-lake-innovation-valley` uses
|
||||
the default, template-free compiler path. `hanyang-block` is an abandoned area
|
||||
and deliberately excluded from this corpus.
|
||||
|
||||
The snapshot normalizes absolute source paths and random `native-road-*`
|
||||
staging names. It does not round coordinates or ignore feature ordering.
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"contract": "native-road-package/v1",
|
||||
"areaId": "fengshu-er-road",
|
||||
"files": {
|
||||
"../native-traffic-signals.json": {
|
||||
"contentHash": "88e7f0ef4fc7bdb9ef202af018b3fb3179035ffc8ed5eeba03276c263e1f0476",
|
||||
"bytes": 5886
|
||||
},
|
||||
"comparison.json": {
|
||||
"contentHash": "651eb4000a44a7521a79c3e1429795b83d20f8899e0c67c4bb25b56cf4a6f75d",
|
||||
"bytes": 1295
|
||||
},
|
||||
"compiled.json": {
|
||||
"contentHash": "2ab82eafcdab70155078fae3559692ef1fde0645a52c6decbaea762a9ffeb24a",
|
||||
"bytes": 159230
|
||||
},
|
||||
"diagnostics.json": {
|
||||
"contentHash": "1d9fdae06ddcbc19b5262b797a4a35c494fc30a98a66dbfa111533b12c5fdfc7",
|
||||
"bytes": 6862
|
||||
},
|
||||
"layers/center_lines.geojson": {
|
||||
"contentHash": "04db66df5821b319d59297b8515502fabcad95faf083cae19edb338be2f6024b",
|
||||
"orderHash": "04db66df5821b319d59297b8515502fabcad95faf083cae19edb338be2f6024b",
|
||||
"features": 5,
|
||||
"bytes": 6909
|
||||
},
|
||||
"layers/connectors.geojson": {
|
||||
"contentHash": "e7979d4642c7c10f33fb23e92721f22533a851ae1a2b4d35f224d50927bf2fe9",
|
||||
"orderHash": "c327b7b154d4d9ca04cd895523eb0d8ad67c37051ef1d2135b2b46c51e12df58",
|
||||
"features": 90,
|
||||
"bytes": 197885
|
||||
},
|
||||
"layers/crosswalks.geojson": {
|
||||
"contentHash": "354cd4d7ed105a11b95904b44848dce567b3a3383f76755936d8c00691fe29af",
|
||||
"orderHash": "d24376438ac956bde52fcbfa955dd72c2ba31a97995608aaed1c79f6fd3633ab",
|
||||
"features": 152,
|
||||
"bytes": 219286
|
||||
},
|
||||
"layers/direction_arrows.geojson": {
|
||||
"contentHash": "7b8362ad4912c997a7661befde55ddfe58757c3c8d139c15867e1938ea8f6918",
|
||||
"orderHash": "5274e8b433946410a7cdffdaa452c1750e04f53e73a86bf81d034cfb26d4c254",
|
||||
"features": 686,
|
||||
"bytes": 1053308
|
||||
},
|
||||
"layers/edge_lines.geojson": {
|
||||
"contentHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"orderHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"features": 0,
|
||||
"bytes": 52
|
||||
},
|
||||
"layers/intersection_surface.geojson": {
|
||||
"contentHash": "b2022ca70339aa470f3be451bf3ba2f55b59c3900645c864538372173811309f",
|
||||
"orderHash": "b2022ca70339aa470f3be451bf3ba2f55b59c3900645c864538372173811309f",
|
||||
"features": 5,
|
||||
"bytes": 13048
|
||||
},
|
||||
"layers/lane_centerlines.geojson": {
|
||||
"contentHash": "19c550c794810c1071de55dee158295235106a56e3877a2ba10da920ee214593",
|
||||
"orderHash": "67f8e7d33ec578644416e4837dd4aafef62a55aaeb4d438d07ab07eddfbb1df1",
|
||||
"features": 84,
|
||||
"bytes": 73294
|
||||
},
|
||||
"layers/lane_separators.geojson": {
|
||||
"contentHash": "02fdf2c7b6e89c2850a60ea2ac72b9f62910259bdb2949681fcaa7e67d2b51f0",
|
||||
"orderHash": "81782a059c4ff9f69fdf7de9107d20a8d6cf55d34bb0620ec28a9a8c5106c982",
|
||||
"features": 3596,
|
||||
"bytes": 4164106
|
||||
},
|
||||
"layers/road_surface.geojson": {
|
||||
"contentHash": "5a2936a705e64338d0d79b395a13d761f1893dd72330a9b9ee4696ae75eab295",
|
||||
"orderHash": "d8402ae6e772e0778b862b190abfa291de1b02a15537d455f7dcd8596245dfd8",
|
||||
"features": 30,
|
||||
"bytes": 49554
|
||||
},
|
||||
"layers/sidewalk_surface.geojson": {
|
||||
"contentHash": "b6f8f876e9f18fd2ef6749125a09d654c73d986e916bd262ceb9967bca1a54c6",
|
||||
"orderHash": "3fd42a67e18e3b8743fa538428e039ef66a1f44150a6881a7aaa18326607b5cb",
|
||||
"features": 26,
|
||||
"bytes": 54997
|
||||
},
|
||||
"layers/turn_arrows.geojson": {
|
||||
"contentHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"orderHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"features": 0,
|
||||
"bytes": 52
|
||||
},
|
||||
"layers/vehicle_stop_lines.geojson": {
|
||||
"contentHash": "cb904550ab8eec011cf33e5edfa81044de6dfca0280dbfe488e77229055ed590",
|
||||
"orderHash": "8a4b877a17a5af60adc8656465afad6756944736291664139e878d46c5a4f363",
|
||||
"features": 4,
|
||||
"bytes": 4191
|
||||
},
|
||||
"traffic-signal-assemblies.json": {
|
||||
"contentHash": "1788026a0c8c531b2fc7dd49628f7fbadd809c7b2f7f8819cffb3ac7d30689ca",
|
||||
"orderHash": "b409ea6b77b6c8639f63c33a156581f9b184a84fde34d265b5deb05705828251",
|
||||
"features": 7,
|
||||
"bytes": 5411
|
||||
},
|
||||
"traffic-signals.json": {
|
||||
"contentHash": "625b79b6eb82b41f2099ba51960717c7f794fdfbd87310eb12e99da966540d89",
|
||||
"bytes": 15491
|
||||
}
|
||||
},
|
||||
"volatileExcluded": [
|
||||
"absolute paths -> <repo> or <external>",
|
||||
"native-road-* staging directory -> <staging>"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"contract": "native-road-package/v1",
|
||||
"areaId": "nantaizi-lake-innovation-valley",
|
||||
"files": {
|
||||
"../native-traffic-signals.json": {
|
||||
"contentHash": "36cb9f069fb44a6e24f0af0d130ef8cb5b4a93250dc9fb0891191d1ba039a5b2",
|
||||
"bytes": 28329
|
||||
},
|
||||
"comparison.json": {
|
||||
"contentHash": "5aa11da443221a48288641ac6950c6e4d75d834713a93110bbf265859202a265",
|
||||
"bytes": 1153
|
||||
},
|
||||
"compiled.json": {
|
||||
"contentHash": "ea5ae4a04f1259b1ccec9fc1cca7ef8ba81426a77584a447480b6047b37f5e01",
|
||||
"bytes": 184985
|
||||
},
|
||||
"diagnostics.json": {
|
||||
"contentHash": "7e9327f0424dd612f389c560c114a4cafee1349bbfa36f49b2b4eab846e5ca91",
|
||||
"bytes": 9755
|
||||
},
|
||||
"layers/center_lines.geojson": {
|
||||
"contentHash": "2b2710fce06e75b0a3e4941c2da6119cea7b8bb7834345607bbee384de20ee6b",
|
||||
"orderHash": "ea71a8841370689d6fc2b75d0b30ec7104638fb2904da6d707dc82ff3131e065",
|
||||
"features": 511,
|
||||
"bytes": 694102
|
||||
},
|
||||
"layers/connectors.geojson": {
|
||||
"contentHash": "901a86bc6c536d38bb73ae4654ec1ce7d1c0790c2d6eba92543c48f584cd15ac",
|
||||
"orderHash": "4df882ad59b65a97d892df64de0156b35beaf071b04eed671ef7b535e9e2a7b9",
|
||||
"features": 76,
|
||||
"bytes": 160866
|
||||
},
|
||||
"layers/crosswalks.geojson": {
|
||||
"contentHash": "53fda76182c0d1db5f9e7ac284337ece62655d7fee8bb446fe170ea26abe8bbc",
|
||||
"orderHash": "53fda76182c0d1db5f9e7ac284337ece62655d7fee8bb446fe170ea26abe8bbc",
|
||||
"features": 48,
|
||||
"bytes": 50536
|
||||
},
|
||||
"layers/direction_arrows.geojson": {
|
||||
"contentHash": "38e80bbfe02d4f12ce40e7d0c8342e3bef6b602c64f81dd320b366b6a6b3d205",
|
||||
"orderHash": "579aa6c37585bf4edf9d6b6b1e120f976b93ec2d0c303d82a154b75c114a45f5",
|
||||
"features": 324,
|
||||
"bytes": 493431
|
||||
},
|
||||
"layers/edge_lines.geojson": {
|
||||
"contentHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"orderHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"features": 0,
|
||||
"bytes": 52
|
||||
},
|
||||
"layers/intersection_surface.geojson": {
|
||||
"contentHash": "7f79e57215e3fd07c5c0f703c71e516873db770cb869efdf18925a3184059ca3",
|
||||
"orderHash": "d69df8d92d0fce8f7ee172959b978d941fd011e094857e9ab7304b3d1e98bf92",
|
||||
"features": 12,
|
||||
"bytes": 38528
|
||||
},
|
||||
"layers/lane_centerlines.geojson": {
|
||||
"contentHash": "3fea4ebfe62e0d720c6977e2289f4e6517ed7b3c5ab361270d03378971aec780",
|
||||
"orderHash": "69a8ebaa42a74da4f7d5940c5a81bd82ceb104efd3f858723dccfadbae12121e",
|
||||
"features": 50,
|
||||
"bytes": 43582
|
||||
},
|
||||
"layers/lane_separators.geojson": {
|
||||
"contentHash": "dc0b50b53da191e65f3dba01162acb7561966d1796a9c0789527cebfd6ed06a4",
|
||||
"orderHash": "0bfd873e5698869fb4855783fe30bb6db29456958e4d154b7264374a5fdb4a10",
|
||||
"features": 731,
|
||||
"bytes": 835933
|
||||
},
|
||||
"layers/road_surface.geojson": {
|
||||
"contentHash": "81af19c71cf6fc8a3884488c00cfb590e1a7fe9ed9e4351fce65825ceadd50fa",
|
||||
"orderHash": "f2ddec5258411fbdbc050b7ecefbb157b59e00b8226afcc30d2b772e1859e357",
|
||||
"features": 25,
|
||||
"bytes": 38275
|
||||
},
|
||||
"layers/sidewalk_surface.geojson": {
|
||||
"contentHash": "bb8f4516e27b16238d753b112d18b8acfa5cfad2cea522a65b7972a609be9563",
|
||||
"orderHash": "4fa773a6f39c5b6d23551e1338c6d99ef8a5ecd9ebfcafa16154c9765fef8c6e",
|
||||
"features": 73,
|
||||
"bytes": 122432
|
||||
},
|
||||
"layers/turn_arrows.geojson": {
|
||||
"contentHash": "a65bb8fe079f27e00bf2e6208148fc6b73d2d07e76d48b1cd1bf8a610d7e2d66",
|
||||
"orderHash": "66966ac2c65464f9c6715ca9cda5a3620520bf1d066cba2dee36114dc46af936",
|
||||
"features": 108,
|
||||
"bytes": 132536
|
||||
},
|
||||
"layers/vehicle_stop_lines.geojson": {
|
||||
"contentHash": "4f45caf3c0bf13bbf10d9cac15fa80b1a9be3969ed84803ba48e56c4a0080992",
|
||||
"orderHash": "4f45caf3c0bf13bbf10d9cac15fa80b1a9be3969ed84803ba48e56c4a0080992",
|
||||
"features": 8,
|
||||
"bytes": 8475
|
||||
},
|
||||
"traffic-signal-assemblies.json": {
|
||||
"contentHash": "9f8d032c00784bb72bb1322238cd7b0a80e621d435b43c728da86f1e77ca352a",
|
||||
"orderHash": "0b9cae13094dba1994faca4bf1d4d80e8bc5497daa46df5899bf5e6fc7f26d61",
|
||||
"features": 35,
|
||||
"bytes": 26398
|
||||
},
|
||||
"traffic-signals.json": {
|
||||
"contentHash": "c4b68cc1cefeb0d393a18eef6c7eb79933fba28630826745165ed433c25779d0",
|
||||
"bytes": 74151
|
||||
}
|
||||
},
|
||||
"volatileExcluded": [
|
||||
"absolute paths -> <repo> or <external>",
|
||||
"native-road-* staging directory -> <staging>"
|
||||
]
|
||||
}
|
||||
@@ -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."}
|
||||
294
.trellis/tasks/08-25-road-compiler-extraction/design.md
Normal file
294
.trellis/tasks/08-25-road-compiler-extraction/design.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# 道路编译器独立化 — 技术设计(parent)
|
||||
|
||||
本文件是 **契约的权威定义**,供所有子任务引用。
|
||||
Phase 0 的交付物是把本节内容落成编译器仓库内的正式文档 + 校验脚本,
|
||||
而不是重新发明契约。
|
||||
|
||||
---
|
||||
|
||||
## 1. 契约:`native-road-package/v1`
|
||||
|
||||
### 1.1 输入契约
|
||||
|
||||
现状:`compile-native-roads.js` 接收整个 normalized areaConfig,但**实际只用 9 个字段**。
|
||||
|
||||
```
|
||||
area.id → areaId
|
||||
area.input → osmFile (OSM XML 路径)
|
||||
area.nativeRoad.edgeLines → options.edgeLines
|
||||
area.nativeRoad.junctionTemplates → options.junctionTemplates
|
||||
area.outputs.nativeRoadOverrides → overridesFile
|
||||
area.outputs.nativeTrafficSignals → trafficSignalsFile
|
||||
area.outputs.nativeRoadDir → outDir
|
||||
area.outputs.pipelineDir → stagingDir
|
||||
area.outputs.geojsonDir → comparisonDir (见 K2,倾向移除)
|
||||
```
|
||||
|
||||
目标形状:
|
||||
|
||||
```js
|
||||
// RoadCompilerInput —— 编译器唯一入口参数
|
||||
{
|
||||
areaId: string,
|
||||
osmFile: string, // 绝对路径
|
||||
outDir: string, // native-road/ 的目标位置
|
||||
stagingDir: string, // 原子提升用的临时目录父级
|
||||
overridesFile: string,
|
||||
trafficSignalsFile: string,
|
||||
options: {
|
||||
edgeLines: boolean,
|
||||
junctionTemplates: {
|
||||
enabled: boolean,
|
||||
references: [],
|
||||
clusters: [{ id, template, referenceFile, nodeIds, ...几何参数 }]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**宿主侧责任**:`scripts/lib/area-config.js` 把 areaConfig 映射成 `RoadCompilerInput`。
|
||||
**编译器侧责任**:不认识 areaConfig,不读 `config/areas/*.json`,不推导任何路径。
|
||||
|
||||
### 1.2 输出契约
|
||||
|
||||
`compiled.json` 顶层键(已核实):
|
||||
|
||||
```
|
||||
schema, areaId, source, model, movements, trafficSignals, diagnostics, layers
|
||||
```
|
||||
|
||||
`source` 含 `{ osm, overrides, trafficSignals }` 三个路径 —— 是 parity 归一化的重点对象。
|
||||
|
||||
```
|
||||
<outDir>/ # 原子提升:先写 staging 再 rename
|
||||
manifest.json # ← Phase 3 新增,图层自声明
|
||||
compiled.json # 单一读模型,含 model + movements
|
||||
diagnostics.json # severity / subject / sourceIds / rule / message / geometry?
|
||||
comparison.json # ← 见 K2,可能移除
|
||||
layers/
|
||||
road_surface.geojson
|
||||
intersection_surface.geojson
|
||||
sidewalk_surface.geojson
|
||||
edge_lines.geojson
|
||||
lane_separators.geojson
|
||||
center_lines.geojson
|
||||
crosswalks.geojson
|
||||
vehicle_stop_lines.geojson
|
||||
direction_arrows.geojson
|
||||
turn_arrows.geojson
|
||||
lane_centerlines.geojson # 语义层,不渲染
|
||||
connectors.geojson # 语义层,不渲染
|
||||
|
||||
<trafficSignalsFile> # 兄弟文件,不在 outDir 内
|
||||
```
|
||||
|
||||
**stdout 完成标记**(与宿主 `SCENE_DONE` / `CESIUM_EXPORT_DONE` 同约定):
|
||||
|
||||
```
|
||||
NATIVE_ROAD_COMPILE_DONE {"areaId":…,"roads":N,"endpoints":N,"diagnostics":N,"output":…,"comparison":…}
|
||||
```
|
||||
|
||||
### 1.3 消费方式:子进程为主契约
|
||||
|
||||
| 方式 | 定位 |
|
||||
|---|---|
|
||||
| CLI 子进程 + 读 `outDir` + 解析 stdout 标记 | **主契约**。宿主 pipeline 层本就是唯一能启动外部进程的层 |
|
||||
| `require()` in-process | 仅作性能优化,不得成为唯一路径 |
|
||||
|
||||
选子进程的理由:
|
||||
1. 语言无关 —— 编译器将来若换 TS/Rust,宿主零改动。
|
||||
2. 强制文件契约成为真契约,无法偷传对象绕过边界。
|
||||
3. 与既有 QGIS / GDAL / Blender 调用方式一致。
|
||||
|
||||
`build-area.js:7` 现在是 in-process `require`。Phase 2 改为子进程调用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 模块清单:什么搬、什么留
|
||||
|
||||
### 2.1 搬(约 3400 行核心 + workbench)
|
||||
|
||||
| 文件 | 行数 | 依赖 |
|
||||
|---|---|---|
|
||||
| `scripts/lib/native-road.js` | 1695 | fs, path, turn-lane-arrows, complex-junction |
|
||||
| `scripts/lib/complex-junction.js` | 474 | fs, gaode-junction-reference |
|
||||
| `scripts/lib/turn-lane-arrows.js` | 502 | fs, path, lane-geometry |
|
||||
| `scripts/lib/gaode-junction-reference.js` | 231 | fs |
|
||||
| `scripts/lib/lane-geometry.js` | 161 | 无(纯函数) |
|
||||
| `scripts/lib/native-traffic-signals.js` | 49 | osm, traffic-signals ← **见 K1** |
|
||||
| `scripts/lib/osm.js` | 102 | 无 |
|
||||
| `scripts/compile-native-roads.js` | — | area-config ← **要换成窄契约** |
|
||||
| `scripts/check-native-roads.js` | — | area-config ← 同上 |
|
||||
| `scripts/road-workbench.js` + `scripts/workbench/app.js` | 173 + — | area-config ← 同上 |
|
||||
| `scripts/test-native-road.js`、`scripts/test-road-workbench.js` | — | fixture ← **见 K5** |
|
||||
|
||||
依赖图(已核实,`native-road.js` 对宿主零耦合):
|
||||
|
||||
```
|
||||
native-road.js ──→ turn-lane-arrows ──→ lane-geometry (纯)
|
||||
└──────────→ complex-junction ──→ gaode-junction-reference
|
||||
native-traffic-signals ──→ osm.js
|
||||
└──→ traffic-signals.js ← 共用,需拆
|
||||
```
|
||||
|
||||
### 2.2 留
|
||||
|
||||
- `scripts/lib/area-config.js` —— 宿主拥有,新增 `toRoadCompilerInput()` 映射
|
||||
- `scripts/build-area.js` —— 改为子进程调用编译器
|
||||
- `scripts/lib/traffic-signals.js` 的 legacy 读取器部分(见 K1)
|
||||
- `blender/` 全部 —— Phase 3 内部重组,但不搬出仓库
|
||||
- `scripts/lib/scene-layers.js`、osm2streets / QGIS legacy 链路 —— 完全不动
|
||||
|
||||
### 2.3 K1 的拆分建议(Phase 1 需细读确认)
|
||||
|
||||
`lib/traffic-signals.js` 当前混了两类东西:
|
||||
|
||||
| 类别 | 使用方 | 归属 |
|
||||
|---|---|---|
|
||||
| OSM 信号节点提取 + 信号文档 schema | `native-traffic-signals.js` | **随编译器走** —— 编译器生成该文档,就该拥有其契约 |
|
||||
| `readTrafficSignalFeatures` | `build-osm2streets-qgis.js` | 留宿主 |
|
||||
| `readTrafficSignals` | `build-area.js` | 留宿主 |
|
||||
| `buildTrafficSignals` | `test-preview-assets.js` | 待判定 |
|
||||
|
||||
拆完后宿主从编译器包 import 信号文档 schema,反向依赖为 0 不受影响
|
||||
(宿主依赖编译器是允许的方向)。
|
||||
|
||||
---
|
||||
|
||||
## 3. Phase 3:渲染分离的设计
|
||||
|
||||
### 3.1 现状问题
|
||||
|
||||
`blender/osmassets/catalog.py:53` 的 `NATIVE_ROAD_LAYERS` 是一张跨仓库重复表:
|
||||
|
||||
```python
|
||||
# 注释自陈:"It is a provider adapter, not a second scene-layer registry."
|
||||
NATIVE_ROAD_LAYERS = (
|
||||
{"source": "road_surface", "material_layer": "road_surface"},
|
||||
{"source": "edge_lines", "material_layer": "lane_separators"},
|
||||
... 共 10 项
|
||||
)
|
||||
```
|
||||
|
||||
编译器写 12 个 geojson,这张表只列 10 项 —— `lane_centerlines` / `connectors`
|
||||
是语义层不参与渲染。**但这个事实只存在于这张表的省略里,编译器侧没有任何声明。**
|
||||
|
||||
后果:编译器新增图层 → 必须有人记得去另一个仓库改 `catalog.py` → 忘了就静默少渲染一层。
|
||||
这正是 `.trellis/spec/pipeline/index.md` 首页警告的「最容易出静默错误」。
|
||||
|
||||
### 3.2 目标:编译器自声明图层
|
||||
|
||||
`<outDir>/manifest.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"contract": "native-road-package/v1",
|
||||
"areaId": "fengshu-er-road",
|
||||
"layers": [
|
||||
{ "source": "road_surface", "role": "surface",
|
||||
"materialLayer": "road_surface" },
|
||||
|
||||
{ "source": "lane_centerlines", "role": "semantic" },
|
||||
{ "source": "connectors", "role": "semantic" },
|
||||
|
||||
{ "source": "center_lines", "role": "marking",
|
||||
"materialLayer": "center_lines",
|
||||
"splitBy": { "prop": "color",
|
||||
"cases": [ { "match": "white", "material": "native_center_line_white" },
|
||||
{ "default": true, "material": "center_lines" } ] } },
|
||||
|
||||
{ "source": "lane_separators", "role": "marking",
|
||||
"materialLayer": "lane_separators",
|
||||
"splitBy": { "prop": "color",
|
||||
"cases": [ { "match": "yellow", "material": "native_lane_separator_yellow" },
|
||||
{ "default": true, "material": "lane_separators" } ] } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `role: "semantic"` 的图层 Blender 直接跳过 —— 把"不渲染"从省略变成显式声明。
|
||||
- `splitBy` 表达当前 `generate_scene.py:815-819` 里硬编码的
|
||||
`color != "white"` / `color == "yellow"` 分流逻辑。
|
||||
- 材质本体(颜色、z 高度、贴图)仍归宿主 `catalog.py::MATERIALS` —— 编译器不懂渲染。
|
||||
编译器只声明"我有这些图层、它们是什么角色、该用哪个材质槽"。
|
||||
|
||||
### 3.3 Blender 侧改造
|
||||
|
||||
- `generate_scene.py` 内道路分支抽为 `blender/osmassets/native_roads.py`,读 manifest 遍历。
|
||||
- 建筑(`handle_building` / `dispatch_ways`)、植被、水体保持原位不动。
|
||||
- `catalog.NATIVE_ROAD_LAYERS` 删除。
|
||||
|
||||
### 3.4 AC5 的验证方法
|
||||
|
||||
向编译器加一个 throwaway 图层(例如 `debug_probe.geojson` + manifest 声明),
|
||||
不改宿主任何代码,跑 blender 阶段,确认它被渲染出来。验证完回滚该图层。
|
||||
|
||||
---
|
||||
|
||||
## 4. 决策记录
|
||||
|
||||
### D1 不用 drawtonomy 替代编译器 ❌
|
||||
|
||||
评估结论(2026-08-25):
|
||||
|
||||
- 克隆仓库内**没有编辑器本体**。`packages/` 只有 SDK / dev-server / mcp-server;
|
||||
白板闭源,托管在 drawtonomy.com。README 卖点里的 topology-aware lanes、
|
||||
lane tool、intersection/roundabout templates、Map→lanes **全部不在开源代码内**。
|
||||
- **它完全不做 raw OSM 推导**。`exporter/osmParser.ts` 首行自陈是
|
||||
"Lanelet2 OSM (.osm XML) parser" —— 读的是 Lanelet2(车道左右边界已显式)。
|
||||
整个 `exporter/` 目录 grep `highway` 命中 1 次,是 `opendrive.ts:685` 的
|
||||
车道类型字符串,不是 OSM 标签解析。从不读 `highway=*` / `lanes` / `turn:lanes`。
|
||||
- 本编译器的核心能力恰是"从稀疏有歧义的中国 OSM 标签推导车道几何",方向垂直。
|
||||
- 语义成熟度对比:本编译器有逐值 provenance
|
||||
(`tag:lanes:forward` / `inferred:highway-default` / `override:<id>`)+ 31 条语义诊断规则;
|
||||
drawtonomy SDK 两者皆无,其 25 条校验规则全是 OpenDRIVE 结构/XML 完整性检查。
|
||||
|
||||
### D2 drawtonomy 作为**下游后端 + 编辑器**(后续独立 PoC)✅
|
||||
|
||||
可用能力(扩展 API,`types.ts:257` 8 个 capability):
|
||||
`shapes:write` + `ui:panel` 足以把编译产物注入编辑器;
|
||||
`snapshot:read` 回读后由本地跑其开源 `exportToOpenDrive` / `lanelet2`。
|
||||
|
||||
不可用能力(决定 junctionTools 不能做成扩展):
|
||||
- 无 canvas / overlay 能力,UI 只能是侧栏 iframe
|
||||
- 无工具注册,画布指针事件完全归宿主
|
||||
- **无任何 change 事件推送** —— `ExtensionClient.handleMessage` 入站只有
|
||||
`ext:init` / 按 requestId 匹配的 5 个 `*-response` / `ext:error`,所有读取靠轮询
|
||||
|
||||
结论:junctionTools 留在自有 workbench(画布自己的,可任意绘制)。
|
||||
drawtonomy 承担场景编排 + 工业格式导出。二者是不同的活,不强行合并 UI。
|
||||
|
||||
依赖风险记录:`drawtonomy-dev-server` 是 `https://www.drawtonomy.com` 的
|
||||
**缓存代理**(TTL 1 小时),非自托管。manifest 有 `minHostVersion` 字段,
|
||||
说明宿主协议会漂。后续 PoC 不把它放进关键路径。
|
||||
|
||||
### D3 值得从 drawtonomy 借用的(Apache-2.0,需保留 NOTICE/署名)
|
||||
|
||||
| 来源 | 行数 | 用途 | 建议阶段 |
|
||||
|---|---|---|---|
|
||||
| `exporter/odrGeometryFit.ts` | 610 | 折线→解析曲线拟合(中位数去噪 + 贪心生长 + 最简原语优先 + 逐拟合回验 + G1 硬不变量)。可替掉手调的 `approachWidthMultiplier=1.45` / `coreRadiusMeters=28`,改为对 `referenceFile` 拟合、残差作质量指标 | 拆分后独立任务 |
|
||||
| validator 的 mutation-proven 方法 | — | 故意破坏合法输入、断言校验器必须抓到。本编译器 31 条诊断规则目前无任何触发证明 | 拆分后独立任务,成本低 |
|
||||
| validator 的分层 + 命名空间(`xml.*` → `ref.*` → `junction.*` → `geom.*`) | — | 替代当前 31 条平铺规则 | 同上 |
|
||||
| OpenDRIVE + Lanelet2 导出器 | 3059 + 869 | 补齐工业格式输出(Lanelet2 是 Autoware 的输入格式) | 后续独立 PoC |
|
||||
|
||||
### D4 为什么 Phase 1 与 Phase 2 必须分开
|
||||
|
||||
Phase 1 只改"包边界与入口契约",仓库不变 → 若产物变化,成因必在代码改动。
|
||||
Phase 2 只改"仓库位置与消费方式",代码不变 → 若产物变化,成因必在搬迁。
|
||||
合并执行则两者混淆,parity oracle 失去诊断价值。
|
||||
|
||||
### D5 IR 重构推迟到拆分之后
|
||||
|
||||
见 prd.md C1。拆分的正确性完全建立在"产物逐字节不变"上,
|
||||
同期改 IR 会同时摧毁 oracle 与归因能力。
|
||||
|
||||
---
|
||||
|
||||
## 5. 回滚形状
|
||||
|
||||
| Phase | 回滚方式 |
|
||||
|---|---|
|
||||
| 0 | 无代码改动,仅新增文档与基线,无需回滚 |
|
||||
| 1 | `git revert`;`packages/road-compiler/` 与旧 `scripts/lib/*` 并存过渡期内可切回旧路径 |
|
||||
| 2 | 宿主依赖回指本仓库内路径(`file:packages/road-compiler`),编译器仓库保留不动 |
|
||||
| 3 | Blender 侧恢复 `catalog.NATIVE_ROAD_LAYERS`,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."}
|
||||
139
.trellis/tasks/08-25-road-compiler-extraction/prd.md
Normal file
139
.trellis/tasks/08-25-road-compiler-extraction/prd.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 道路编译器独立化(parent)
|
||||
|
||||
## Goal
|
||||
|
||||
把 native road compiler 从本仓库拆成可独立维护的项目,与本项目通过**版本化文件契约**相辅相成;
|
||||
同时把道路渲染与 OSM 建筑渲染分离,使二者可各自演进。
|
||||
|
||||
本任务是 parent:它拥有源需求、契约定义、子任务地图、跨子任务验收标准和最终集成评审。
|
||||
**它自身不承担实现工作**,所有可交付物在子任务中完成。
|
||||
|
||||
## 背景:为什么现在拆
|
||||
|
||||
- `scripts/lib/native-road.js` 已 1695 行,`compileGeometry()` 单函数编排 15+ 个 pass,
|
||||
顺序依赖只由行号编码,每加一个特性就往既有函数尾部挂位置参数
|
||||
(`compileLaneMarkings` 已 7 个位置参数)。
|
||||
- 道路与建筑渲染纠缠在 `blender/generate_scene.py` 一个脚本内,
|
||||
道路图层表 `NATIVE_ROAD_LAYERS` 硬编码在 `blender/osmassets/catalog.py:53`。
|
||||
- 道路编译的迭代节奏(几何、路口、标线、V2X 语义)与建筑/植被/水体渲染完全不同,
|
||||
放在一个仓库里互相牵制。
|
||||
|
||||
拆分的可行性前提(已核实):
|
||||
- `native-road.js` 只 require `fs` / `path` / 两个同族道路模块,**对宿主项目零耦合**。
|
||||
- 对宿主的唯一耦合是三个 CLI 入口里的 `readAreaConfig`。
|
||||
- 输出侧 `native-road/` **已经是文件契约**,`blender/generate_scene.py:801` 按图层名读取。
|
||||
- stdout 标记 `NATIVE_ROAD_COMPILE_DONE` 已存在,与 `SCENE_DONE` / `CESIUM_EXPORT_DONE`
|
||||
同一约定 —— 子进程边界事实上已预留。
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1 契约先行
|
||||
|
||||
- R1.1 输入输出契约必须在任何代码移动之前写死并版本化为 `native-road-package/v1`,
|
||||
命名对齐既有 `osm-asset-package/v1` 约定。
|
||||
- R1.2 契约的权威定义见本任务 `design.md`;Phase 0 负责把它落成仓库内文档 + 校验脚本。
|
||||
|
||||
### R2 可证明的等价性
|
||||
|
||||
- R2.1 拆分过程中每一步都必须对两个有效区域
|
||||
(`fengshu-er-road` / `nantaizi-lake-innovation-valley`)
|
||||
验证产物与基线一致。
|
||||
- R2.2 任何差异必须逐条书面解释后才可接受,禁止"看起来差不多"。
|
||||
- R2.3 验证方法沿用既有 `.trellis/spec/guides/artifact-parity-guide.md`。
|
||||
|
||||
### R3 独立可维护
|
||||
|
||||
- R3.1 拆出的项目必须能脱离宿主自测(自带 fixture,不读宿主 `inputs/` 或 `outputs/`)。
|
||||
- R3.2 保留 git 历史(`git subtree split` 或 `git filter-repo`)——
|
||||
1695 行几何逻辑的 blame 是踩坑记录,丢失后无人敢改。
|
||||
- R3.3 编译器不得反向依赖宿主任何模块、配置或目录布局。
|
||||
|
||||
### R4 相辅相成(防漂移)
|
||||
|
||||
四个机制缺一不可:
|
||||
|
||||
| 机制 | 作用 | 落地于 |
|
||||
|---|---|---|
|
||||
| `native-road-package/v1` 版本号 | 破坏性变更必须升版本,宿主主动 opt-in | Phase 0 |
|
||||
| 编译器自声明图层(layer manifest) | 加图层不需要改宿主代码 | Phase 3 |
|
||||
| 两区域 parity 基线留在编译器仓库当测试语料 | 编译器无法静默弄坏宿主 | Phase 0 → Phase 2 |
|
||||
| 宿主锁版本依赖,不用 `latest` | 升级是决定,不是意外 | Phase 2 |
|
||||
|
||||
### R5 渲染分离
|
||||
|
||||
- R5.1 道路图层表必须从宿主 `catalog.py` 的硬编码变为编译器输出的 manifest。
|
||||
- R5.2 `blender/generate_scene.py` 内道路渲染分支抽离为独立模块;
|
||||
建筑 / 植被 / 水体保持原位。
|
||||
- R5.3 分离后,编译器新增图层不需要修改宿主任何代码即可被渲染。
|
||||
|
||||
## Constraints(硬约束)
|
||||
|
||||
### C1 不得在拆分过程中重构 IR 🔴
|
||||
|
||||
lane graph 提为一等 IR 是正确方向,但**必须在 Phase 2 完成之后作为独立任务**。
|
||||
|
||||
理由:拆分的正确性完全依赖"产物逐字节不变"这一 oracle。
|
||||
一旦同时改 IR,oracle 失效,且无法判断产物变化来自搬迁还是重设计。
|
||||
|
||||
未来若做,形式是**新增输出**而非替换 `compiled.json`,
|
||||
靠"解锁 drawtonomy / OpenDRIVE / Lanelet2 导出"赚取存在理由。
|
||||
|
||||
### C2 拆分期不引入构建步骤
|
||||
|
||||
编译器保持 CommonJS、无构建步骤,与宿主 pipeline 层运行时一致
|
||||
(见 `.trellis/spec/pipeline/index.md`)。
|
||||
TypeScript / ESM 是独立决策;若后续 drawtonomy PoC 需要 SDK 互操作,只能在其独立任务的扩展目录内处理。
|
||||
|
||||
### C3 Phase 1 必须先于 Phase 2
|
||||
|
||||
"包边界"与"换仓库"分两步做。合并执行时若产物变化,无法区分成因。
|
||||
|
||||
## 子任务地图
|
||||
|
||||
| Phase | 子任务 | 交付物 | 阻塞后续 |
|
||||
|---|---|---|---|
|
||||
| 0 | `08-25-rc-p0-contract-baseline` | 契约文档 + 两区域 checksum 基线 + 校验脚本 | 是 |
|
||||
| 1 | `08-25-rc-p1-package-boundary` | 本仓库内 `packages/road-compiler/`,窄输入契约 | 是 |
|
||||
| 2 | `08-25-rc-p2-repo-split` | 独立仓库 + 宿主锁版本消费 | 是 |
|
||||
| 3 | `08-25-rc-p3-render-separation` | layer manifest + Blender 道路模块抽离 | 否 |
|
||||
|
||||
顺序约束:**0 → 1 → 2 必须串行**。3 依赖 2 完成。
|
||||
|
||||
## 跨子任务验收标准
|
||||
|
||||
- [ ] AC1 Phase 0 基线建立后,两区域每个输出文件的 checksum 已提交进版本控制
|
||||
- [ ] AC2 Phase 1 结束时,`npm run build:area` 两区域产物对 AC1 基线逐字节一致
|
||||
- [ ] AC3 Phase 2 结束时,宿主从打包依赖构建,两区域 parity 仍成立
|
||||
- [ ] AC4 Phase 2 结束时,编译器仓库 `npm test` 在不访问宿主仓库的情况下通过
|
||||
- [ ] AC5 Phase 3 结束时,向编译器新增一个图层,宿主**零代码改动**即可渲染出来(实测验证)
|
||||
- [ ] AC6 Phase 3 结束时,`.blend` 结构摘要对基线一致(走 artifact-parity-guide)
|
||||
- [ ] AC7 全程未修改 `compiled.json` 的结构(C1 未被违反)
|
||||
- [ ] AC8 编译器仓库对宿主的反向依赖数为 0(grep 验证)
|
||||
|
||||
## 已识别风险(跨子任务,逐个必须有归属)
|
||||
|
||||
| # | 风险 | 归属 Phase | 处置 |
|
||||
|---|---|---|---|
|
||||
| K1 | `lib/traffic-signals.js` 是真共用模块:`reimport-gpkg.js` / `build-osm2streets-qgis.js` / `build-area.js` / `test-preview-assets.js` 都在用,而 `native-traffic-signals.js` 也依赖它 | 1 | 需细读后拆分:信号文档 schema + OSM 信号节点提取随编译器走(编译器生成它就该拥有契约),legacy QGIS/预览读取器留宿主。**全案唯一需要细读再动的地方** |
|
||||
| K2 | `comparison.json` 依赖宿主产物 —— `compile-native-roads.js:87` 读 `area.outputs.geojsonDir` 里 osm2streets 输出做对比,拆出后摸不到 | 0(决策)/ 1(执行) | osm2streets 已 legacy,倾向直接砍掉,用高德参考 + 规范校验取代。决策需在 Phase 0 定档 |
|
||||
| K3 | config 内绝对路径指向宿主仓库:`config/areas/fengshu-er-road.json` 的 `referenceFile: "/Users/que01/osm2streets-qgis-workflow/inputs/osm/珠山湖大道(枫树二路)口.geojson"` | 1 | 需定参考文件解析约定(相对 config 目录 / 显式 basePath) |
|
||||
| K4 | workbench 的 OpenLayers 来自宿主 `node_modules`(design.md 的 import map 方案) | 2 | 新仓库自带依赖 |
|
||||
| K5 | 测试 fixture 依赖宿主:`test-native-road.js:167` 读 `inputs/osm/枫树二路.osm`,其余为内联合成 OSM | 1 | 该文件(或裁剪版)作为测试数据提交进编译器仓库 |
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- lane graph IR 重构(见 C1,未来独立任务)
|
||||
- pass manager / 显式依赖声明重构(同上,属编译器内部演进,不属本次拆分)
|
||||
- 替换编译器为 drawtonomy 或 osm2streets —— 已评估否决:
|
||||
drawtonomy 开源部分只读 Lanelet2(显式车道边界),不做 OSM `highway=*` 推导,
|
||||
与本编译器的核心能力方向垂直
|
||||
- osm2streets / QGIS legacy 链路的任何改动
|
||||
- 宿主侧建筑 / 植被 / 水体渲染逻辑的改动(Phase 3 只抽离道路部分)
|
||||
|
||||
## Notes
|
||||
|
||||
- 评估结论与选型依据见 `design.md` 的「决策记录」一节。
|
||||
- 时间估计:Phase 0-2 约 3 天(搬家 + 证明没搬坏),Phase 3 约 1-2 天。
|
||||
拿到"道路与建筑渲染分离、可独立维护"是在 Phase 3 结束。
|
||||
- 父任务验收完成后,下一项候选任务是独立的
|
||||
`08-25-rc-p4-drawtonomy-ext`(drawtonomy 扩展 PoC);它不属于本任务完成条件。
|
||||
33
.trellis/tasks/08-25-road-compiler-extraction/task.json
Normal file
33
.trellis/tasks/08-25-road-compiler-extraction/task.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"id": "road-compiler-extraction",
|
||||
"name": "road-compiler-extraction",
|
||||
"title": "道路编译器独立化(parent)",
|
||||
"description": "把 native road compiler 拆成可独立维护的项目,并与本项目通过版本化契约相辅相成;同时分离道路与 OSM 建筑渲染",
|
||||
"status": "planning",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P1",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-25",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [
|
||||
"08-25-rc-p0-contract-baseline",
|
||||
"08-25-rc-p1-package-boundary",
|
||||
"08-25-rc-p2-repo-split",
|
||||
"08-25-rc-p3-render-separation"
|
||||
],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {
|
||||
"next_task": "08-25-rc-p4-drawtonomy-ext"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Verify package stage ordering, manifest freshness, and external-tool boundaries."}
|
||||
{"file":".trellis/spec/config/index.md","reason":"Verify all new output paths remain centralized in area-config."}
|
||||
{"file":".trellis/spec/preview/index.md","reason":"Verify preview remains a consumer of package assets, not part of the published package."}
|
||||
{"file":".trellis/spec/guides/artifact-parity-guide.md","reason":"Assess intentional output-contract changes and asset verification scope."}
|
||||
@@ -0,0 +1,135 @@
|
||||
# Asset Package Contract Design
|
||||
|
||||
## Product Boundary
|
||||
|
||||
`outputs/<area-id>/` remains the build workspace. Its `package/` child is the only
|
||||
publishable subtree and may be copied unchanged to another project. The package has
|
||||
no dependency on the parent directory, repository checkout, absolute local paths,
|
||||
QGIS, Blender, or the Cesium preview.
|
||||
|
||||
```
|
||||
outputs/<area-id>/
|
||||
package/ # publishable boundary
|
||||
manifest.json # asset-package/v1
|
||||
models/
|
||||
<area-id>.glb # compressed complete static scene
|
||||
roads.glb
|
||||
buildings.glb
|
||||
vegetation.glb
|
||||
water.glb
|
||||
<area-id>-cesium-preview.html # local verification only
|
||||
_preview/ # vehicles, routes, dynamic signal runtime
|
||||
_pipeline/ # staging, manifests and build diagnostics
|
||||
osm2streets_web_out/, *.gpkg, *.qgz, *.blend, *.png
|
||||
```
|
||||
|
||||
The package does not duplicate the GLBs: Cesium export and compression target the
|
||||
package model paths. Build staging lives under `_pipeline/` and is removed before a
|
||||
successful package is published.
|
||||
|
||||
## Manifest v1
|
||||
|
||||
The JSON root uses `schema: "osm-asset-package/v1"`. Required top-level fields are:
|
||||
|
||||
| Field | Contract |
|
||||
|---|---|
|
||||
| `schema`, `packageVersion`, `areaId` | Stable identity and schema evolution point |
|
||||
| `coordinateSystem` | Literal local ENU contract: X east, Y north, Z up, meters |
|
||||
| `placement` | WGS84 anchor longitude/latitude/height plus heading correction degrees |
|
||||
| `bounds` | WGS84 `minLon`, `minLat`, `maxLon`, `maxLat` |
|
||||
| `assets` | Deterministic list of declared publishable assets |
|
||||
| `sceneStats` | Optional descriptive OSM-derived counts, not loading-critical |
|
||||
|
||||
Each asset has `id`, `role`, `category`, `uri`, and `defaultLoad`. `uri` is a
|
||||
forward-slash relative path inside `package/`; it must not be absolute, start with
|
||||
`/`, contain `..`, or resolve outside the package. Roles are `scene` for the complete
|
||||
static scene and `layer` for separately loadable semantic subsets. Categories are
|
||||
fixed in v1: `scene`, `roads`, `buildings`, `vegetation`, `water`.
|
||||
|
||||
`models/<area-id>.glb` is the only `scene` entry and defaults to load. Layer GLBs are
|
||||
optional alternatives for selective loading and default off; consumers must not load
|
||||
both the complete scene and overlapping layers unless intentionally composing them.
|
||||
|
||||
The manifest deliberately excludes `source_osm`, `source_geojson`, arbitrary Cesium
|
||||
JavaScript, vehicle routes, vehicle models, dynamic signal states, countdown models,
|
||||
preview runtime paths, build timestamps and desktop paths. File digests and sizes may
|
||||
be added under an optional `integrity` object only after they are computed from final
|
||||
compressed files.
|
||||
|
||||
## Pipeline Design
|
||||
|
||||
### 1. Static export seed
|
||||
|
||||
`blender/export_cesium.py` continues to own static GLB generation, WGS84 bounds,
|
||||
anchor and semantic collection selection. It must emit data suitable for a manifest
|
||||
seed, not a preview metadata document. Static traffic-signal geometry already in the
|
||||
main scene remains part of the static scene; animated traffic signal and countdown
|
||||
collections are not package assets.
|
||||
|
||||
### 2. Package staging and compression
|
||||
|
||||
The build orchestrator owns output paths through `normalizeAreaConfig()`. It adds
|
||||
explicit package paths and exports static GLBs to a package staging directory beneath
|
||||
`_pipeline/`. Compression works only on staged package files and rewrites the staged
|
||||
manifest's main GLB URI after successful compression. It never needs to copy or alter
|
||||
preview HTML.
|
||||
|
||||
After path validation, file existence checks, and manifest validation, the package
|
||||
stage promotes the whole staging directory to `outputs/<area-id>/package/`. Promotion
|
||||
is directory-level and replaces a previous package only after the new one is valid.
|
||||
On failure, the previous published package remains usable and staging is retained or
|
||||
reported for diagnosis according to existing pipeline failure conventions.
|
||||
|
||||
### 3. Preview adapter
|
||||
|
||||
The existing Cesium preview remains a verification tool outside the package. It reads
|
||||
the package manifest via its relative path, then loads preview-only route, vehicle and
|
||||
dynamic-signal descriptors from `_preview/`. Dynamic traffic assets are declared in a
|
||||
preview-specific descriptor, never appended to package `assets`. This preserves
|
||||
existing high-precision cruise validation without making it a downstream requirement.
|
||||
|
||||
### 4. Stage and compatibility behavior
|
||||
|
||||
Canonical order becomes `intermediates/reimport -> blender -> cesium -> compress ->
|
||||
package -> preview`. `package` is included in the full default build and is callable
|
||||
explicitly to validate/publish existing staged static exports. `preview` stays an
|
||||
optional/verification stage, not a package dependency.
|
||||
|
||||
Existing `npm run build:area -- --config ... --stages ...` remains supported. Root
|
||||
level legacy GLB/JSON/HTML files are not deleted by the migration; they are not read
|
||||
as a fallback by the new package contract. A clean full build produces the package as
|
||||
the canonical downstream asset source.
|
||||
|
||||
## Consumer Examples
|
||||
|
||||
Examples live in repository source, outside individual packages:
|
||||
|
||||
- Cesium: fetch `manifest.json`, derive the ENU frame from `placement`, apply heading
|
||||
correction, then load `assets` by `uri`.
|
||||
- Three.js: load the selected model URL relative to the manifest URL; expose the same
|
||||
ENU placement object to the host application's georeferencing adapter. It must not
|
||||
imply that Three.js alone converts ENU to WGS84.
|
||||
|
||||
Both examples load the complete scene and demonstrate selecting a single `layer`.
|
||||
They validate that URLs are resolved relative to the manifest rather than the current
|
||||
page or repository root.
|
||||
|
||||
## Validation and Rollback
|
||||
|
||||
Unit tests validate schema fields, category/role legality, package-relative URI rules,
|
||||
and rejection of omitted/extra files. An integration fixture constructs a minimal
|
||||
package and verifies that Cesium and Three.js example resolvers choose identical
|
||||
relative URLs and placement values. A target-area package test checks final compressed
|
||||
GLB parsing and manifest references.
|
||||
|
||||
The package stage writes a stage manifest containing only final published records and
|
||||
their integrity. Diagnostic checks distinguish missing package, invalid package, and
|
||||
stale package. Rollback is to the prior `package/` directory; legacy preview outputs
|
||||
remain untouched throughout rollout.
|
||||
|
||||
## Deferred Decisions
|
||||
|
||||
- Rich per-feature semantics, routing graph delivery and simulation inputs are not in
|
||||
v1; they should be introduced through a later schema version only when a downstream
|
||||
consumer requires them.
|
||||
- Asset coverage improvements are downstream of this contract work.
|
||||
@@ -0,0 +1,4 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Stage ordering, manifests, external-command boundary, and compression contracts."}
|
||||
{"file":".trellis/spec/config/index.md","reason":"Area output-path normalization and configuration contract."}
|
||||
{"file":".trellis/spec/preview/index.md","reason":"Preview config injection and browser runtime compatibility."}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"Blender-to-Node-to-browser coordinate and artifact contract risks."}
|
||||
@@ -0,0 +1,108 @@
|
||||
# Asset Package Contract Implementation Plan
|
||||
|
||||
## Phase 0: Contract Lock-In
|
||||
|
||||
- [ ] Add a package-contract module that owns v1 schema constants, allowed roles and
|
||||
categories, URI containment validation, manifest construction and validation.
|
||||
- [ ] Add focused unit fixtures for valid manifests and each invalid condition:
|
||||
missing schema/placement/bounds, malformed coordinates, invalid role/category,
|
||||
absolute or traversing URI, duplicate asset ID, missing primary scene, and referenced
|
||||
file outside the package.
|
||||
- [ ] Document `package/` as the sole downstream boundary in README and pipeline/config
|
||||
specs; retain a clear distinction between release assets and preview artifacts.
|
||||
|
||||
Gate: manifest v1 can be created and structurally validated without invoking Blender,
|
||||
Cesium or QGIS.
|
||||
|
||||
## Phase 1: Package Paths and Static Export
|
||||
|
||||
- [ ] Extend `scripts/lib/area-config.js` with paths for `packageDir`, package manifest,
|
||||
package model directory, primary GLB and semantic layer GLBs; keep all path derivation
|
||||
in this module.
|
||||
- [ ] Change `blender/export_cesium.py` metadata output to a static manifest seed:
|
||||
bounds, ENU placement and static scene/layer declarations only. Remove local source
|
||||
paths and inline Cesium source code from the published data.
|
||||
- [ ] Direct static GLB output to package staging paths and retain dynamic signal/countdown
|
||||
output as preview-only paths outside package staging.
|
||||
- [ ] Update `build-area.js`, semantic asset checks, stage manifests and diagnostics to
|
||||
consume the new path contract rather than constructing paths locally.
|
||||
|
||||
Gate: a Cesium export produces a valid static manifest seed and every declared static
|
||||
asset exists under package staging; no preview-only asset appears in it.
|
||||
|
||||
## Phase 2: Compression and Atomic Publication
|
||||
|
||||
- [ ] Refactor the default compression stage to operate on staged package files,
|
||||
preserving the final manifest shape while replacing only the primary GLB bytes and
|
||||
integrity data.
|
||||
- [ ] Add a `package` stage after `compress`; validate every asset and atomically promote
|
||||
staging to `outputs/<area-id>/package/`.
|
||||
- [ ] Update canonical stage ordering, `all`, interactive CLI labels and defaults.
|
||||
- [ ] Ensure a failed compression/package validation never replaces an existing published
|
||||
package; record diagnostics sufficient to identify the failed source/staging path.
|
||||
|
||||
Gate: a full build creates one publishable package with compressed primary GLB and no
|
||||
duplicate static GLBs at the area root.
|
||||
|
||||
## Phase 3: Preview Adapter and Compatibility
|
||||
|
||||
- [ ] Change preview HTML generation to reference `package/manifest.json` relative to
|
||||
the area output root.
|
||||
- [ ] Move route/vehicle/dynamic-signal discovery into a preview-only descriptor beneath
|
||||
`_preview/`, and make `cesium-preview.js` merge it after loading the package manifest.
|
||||
- [ ] Preserve existing preview controls and graceful behavior when preview-only data is
|
||||
absent; preview must still render the package static scene.
|
||||
- [ ] Update `diagnose:area`, `check:area` and stage manifest freshness to distinguish
|
||||
package artifacts from preview-only files.
|
||||
|
||||
Gate: the local Cesium preview loads the published package and continues to show
|
||||
optional cruise/dynamic preview content without adding either to package manifest.
|
||||
|
||||
## Phase 4: Downstream Consumption Proof
|
||||
|
||||
- [ ] Add Cesium example code that resolves the manifest URL, validates placement and
|
||||
loads the full scene or selected layers.
|
||||
- [ ] Add Three.js example code with a manifest-relative resolver and an explicit ENU
|
||||
placement handoff to the host's georeferencing integration.
|
||||
- [ ] Add automated resolver tests so neither example can regress to page-relative,
|
||||
desktop-absolute or repository-relative URLs.
|
||||
- [ ] Publish consumer documentation including overlap rules for complete scene vs layers.
|
||||
|
||||
Gate: examples run from a copied `package/` directory and require no files outside it.
|
||||
|
||||
## Phase 5: Full Verification and Migration
|
||||
|
||||
- [ ] Run focused Node tests for package schema, stage resolution, compression metadata,
|
||||
preview behavior, diagnostics and asset budgets.
|
||||
- [ ] Run the full default build for the primary area, then `check:area` and package
|
||||
integrity validation.
|
||||
- [ ] Inspect the packaged GLB structure and validate the Cesium example in browser;
|
||||
verify Three.js resolver output with the same manifest.
|
||||
- [ ] Capture expected intentional output-contract changes in docs/changelog and update
|
||||
relevant Trellis pipeline/config/preview specs.
|
||||
- [ ] Preserve legacy root-level artifacts during rollout; document that downstream
|
||||
consumers must migrate to `package/manifest.json` rather than treating legacy files
|
||||
as fallback.
|
||||
|
||||
## High-Risk Boundaries
|
||||
|
||||
- `blender/export_cesium.py` to Node: static metadata fields and semantic asset file
|
||||
names are a cross-runtime contract.
|
||||
- Node packaging to preview runtime: preview-only dynamic data must never silently enter
|
||||
the package manifest.
|
||||
- Compression to publication: paths must remain package-relative and a partial write must
|
||||
not replace the last valid package.
|
||||
- Consumers: ENU axes and heading correction are load-bearing; examples must not create
|
||||
a second, incompatible georeferencing convention.
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```bash
|
||||
npm run test:build-stages
|
||||
npm run test:compress-glb
|
||||
npm run test:preview-assets
|
||||
npm run test:budgets
|
||||
npm run test:preflight
|
||||
npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
node scripts/glb-digest.js outputs/nantaizi-lake-innovation-valley/package/models/nantaizi-lake-innovation-valley.glb
|
||||
```
|
||||
@@ -0,0 +1,61 @@
|
||||
# Reusable asset package contract
|
||||
|
||||
## Goal
|
||||
|
||||
将当前面向 Cesium 预览的区域输出升级为可由多个下游项目稳定消费的静态资产包。资产包
|
||||
必须有版本化 manifest、仅使用包内相对引用,并明确发布资产与本项目调试/预览产物的边界。
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- `blender/export_cesium.py` 已生成 `<area>.json`,包含主 GLB、roads/buildings/
|
||||
vegetation/water 分层 GLB、WGS84 anchor、ENU 坐标约定、heading correction 和 OSM bounds。
|
||||
- 该 JSON 同时包含桌面绝对 `source_osm` / `source_geojson` 路径、Cesium 代码片段,以及
|
||||
动态信号灯和倒计时预览资产;因此不是可发布的下游契约。
|
||||
- `outputs/<area>/` 还包含 QGIS、GeoJSON、Blender、Cesium HTML/runtime、车辆巡航和自检文件;
|
||||
它是构建工作目录,不是干净的发布目录。
|
||||
- 当前默认 Cesium GLB 已在构建末端压缩,且 metadata 中的资产 URL 使用同目录相对文件名。
|
||||
- 项目目标是生成可复用资产;交通仿真、跟车和信号调度属于下游运行时能力,不属于资产包首版。
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1:定义一个版本化 `manifest.json` v1,描述区域 ID、WGS84 anchor、ENU 轴向与 heading
|
||||
correction、WGS84 bounds、发布资产、语义类别和包内相对路径。
|
||||
- R2:资产包只含可复用的静态交付资产;主场景和道路、建筑、植被、水体等分层模型必须有
|
||||
明确的角色、加载语义和默认行为。
|
||||
- R3:manifest 和包内文件不得包含桌面绝对路径、构建临时目录、Cesium HTML/runtime、
|
||||
QGIS、GeoJSON、`.blend`、车辆巡航或动态交通信号调度依赖。
|
||||
- R4:在不破坏现有区域预览、诊断和构建中间产物的前提下,增加明确的资产包发布阶段。
|
||||
- R5:提供 Cesium 与 Three.js 的最小加载示例,均从 manifest 读取包内相对 URL,并按相同的
|
||||
WGS84/ENU 契约放置主场景或分层资产。
|
||||
- R6:定义发布资产、可选静态资产和本项目仅检查产物的分类规则,并由测试验证。
|
||||
- R7:完整构建后的资产包应可独立复制到其他项目,而无需本仓库的 `outputs` 目录结构或本地路径。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 目标区域生成一个可独立分发的资产包目录,其中仅有 manifest 和 manifest 引用的发布资产。
|
||||
- [ ] manifest 的 schema version、坐标契约、bounds、所有资产类别和 URL 可由程序校验。
|
||||
- [ ] 所有 manifest URL 都是安全的包内相对路径;不得含绝对路径、`..` 或未声明文件。
|
||||
- [ ] 主 GLB 与每个发布分层资产都能在 Cesium 和 Three.js 示例中按 manifest 正确放置与加载。
|
||||
- [ ] 车辆、巡航路线、Cesium preview HTML/runtime、QGIS/GeoJSON/`.blend` 和动态信号调度资产
|
||||
不会进入发布包。
|
||||
- [ ] 既有 `outputs/<area>/` 预览工作流继续可用,现有非交互 build 命令保持兼容。
|
||||
- [ ] 发布包缺文件、manifest 路径越界、坐标字段无效或资产类别不合法时,构建/校验非零退出。
|
||||
|
||||
## Proposed Delivery Phases
|
||||
|
||||
1. 契约与目录边界:冻结 manifest v1 schema、发布目录结构、资产角色和坐标定义。
|
||||
2. 资产包发布阶段:从现有 Cesium 导出与压缩结果收集、校验并写入独立包目录。
|
||||
3. 下游消费证明:Cesium / Three.js 示例仅依赖 manifest 与包内容,并覆盖主场景和按类别加载。
|
||||
4. 质量门与迁移:增加结构、路径隔离、坐标和加载验证;保留旧预览输出并记录迁移规则。
|
||||
|
||||
## Key Decision
|
||||
|
||||
- 发布包根目录固定为 `outputs/<area-id>/package/`。该目录是可整体复制给下游项目的唯一
|
||||
发布边界;GLB 和 manifest 直接生成或移动到这里,避免与工作目录再保留一套大模型副本。
|
||||
本项目的预览页可通过包内相对路径读取发布资产,但 HTML/runtime 本身不属于发布包。
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- 交通流、车辆行为、信号相位控制、路口调度和其他运行时仿真。
|
||||
- 在本任务中扩展 OSM 到建筑、路灯、标志、植被等资产覆盖率或生成质量。
|
||||
- 删除既有 `outputs/<area>/` 中由用户保留的历史调试产物。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "asset-package-contract",
|
||||
"name": "asset-package-contract",
|
||||
"title": "Reusable asset package contract",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-11",
|
||||
"completedAt": "2026-08-12",
|
||||
"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 @@
|
||||
# Interactive Area Build CLI Design
|
||||
|
||||
## Scope
|
||||
|
||||
Add a zero-dependency terminal interface for choosing an area configuration and one or more existing
|
||||
pipeline stages. It delegates execution to the existing `build-area.js` entry point and does not change
|
||||
the pipeline's stage implementations or generated artifacts.
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
npm run build
|
||||
-> scripts/interactive-build.js
|
||||
-> terminal menus (TTY only)
|
||||
-> node scripts/build-area.js --config <area> --stages <canonical-list>
|
||||
-> existing build pipeline
|
||||
```
|
||||
|
||||
Extract the stage metadata, aliases, canonical execution order, and mutual-exclusion validation from
|
||||
`build-area.js` into a CommonJS module under `scripts/lib/`. Both the interactive script and
|
||||
`build-area.js` consume this module, making the menu's options the same source of truth as execution.
|
||||
|
||||
## Interaction
|
||||
|
||||
1. Discover and sort `config/areas/*.json`; present a single-select area menu.
|
||||
2. Present a multi-select stage menu with descriptions and selection markers.
|
||||
3. Arrow keys move focus, space toggles, enter confirms, and Ctrl-C/Escape cancels.
|
||||
4. The menu prevents or reports the `intermediates`/`reimport` conflict before spawning a build.
|
||||
5. The selected stages are normalized to canonical pipeline order and passed to `build-area.js`.
|
||||
|
||||
The interactive entry requires both stdin and stdout to be TTYs. Otherwise it exits non-zero with an
|
||||
instruction to use `npm run build:area -- --config ... --stages ...`; it never falls back to defaults.
|
||||
|
||||
## Compatibility
|
||||
|
||||
- `build-area.js` remains the execution owner and keeps its CLI flags and defaults.
|
||||
- `npm run build:area` continues to invoke it directly.
|
||||
- `npm run build` changes only from the former default build alias to the interactive wrapper.
|
||||
- A successful `cesium` selection continues to generate preview output through the existing behavior;
|
||||
selecting `preview` explicitly remains valid for a standalone preview refresh.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit-test stage normalization, alias expansion, ordering, and mutual exclusion.
|
||||
- Unit-test config discovery and non-TTY refusal without launching a build.
|
||||
- Syntax-check both CLI entry scripts.
|
||||
- Manually exercise the menu in a TTY: select an area, select unordered stages, confirm the canonical
|
||||
command preview, and cancel before execution.
|
||||
|
||||
## Risks And Rollback
|
||||
|
||||
Raw terminal input must always restore raw mode and cursor state on confirmation, cancellation, and
|
||||
errors. The wrapper only spawns the unchanged existing build script, so rollback is deleting the new
|
||||
interactive entry and restoring the `build` package script.
|
||||
@@ -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,13 @@
|
||||
# Interactive Area Build CLI Implementation Plan
|
||||
|
||||
1. Extract stage definitions, aliases, canonical ordering, and conflict validation from
|
||||
`scripts/build-area.js` into a shared CommonJS module.
|
||||
2. Update `build-area.js` to consume the shared resolver without changing non-interactive semantics.
|
||||
3. Add `scripts/interactive-build.js` with TTY detection, area discovery, raw-mode selection menus,
|
||||
cancellation handling, and child-process delegation to `build-area.js`.
|
||||
4. Change `package.json` so `npm run build` calls the new interactive script while `build:area` remains
|
||||
unchanged.
|
||||
5. Add focused Node tests for stage planning and non-TTY behavior; do not invoke Blender, QGIS, or a
|
||||
real area build in tests.
|
||||
6. Run syntax checks, the focused tests, existing preflight/preview tests, and manually exercise the
|
||||
interactive flow in a TTY.
|
||||
@@ -0,0 +1,53 @@
|
||||
# Interactive area build CLI
|
||||
|
||||
## Goal
|
||||
|
||||
提供一个交互式区域构建入口,减少手写 `npm run build:area -- --config ... --stages ...`
|
||||
的频率;用户可选择区域和一个或多个构建阶段,CLI 按既有阶段约束执行。
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- 当前 `scripts/build-area.js` 支持 `intermediates`、`reimport`、`blender`、`cesium`、
|
||||
`preview` 与 `compress` 六个阶段,并接受逗号分隔的 `--stages`。
|
||||
- `intermediates` 与 `reimport` 互斥:前者会从 OSM 重建 GeoPackage,后者回导 QGIS 编辑。
|
||||
- 执行顺序固定为 `intermediates/reimport -> blender -> cesium -> preview -> compress`;
|
||||
`cesium` 当前会自动写入 preview 产物。
|
||||
- 项目没有终端交互依赖;现有命令行调用必须继续可用于脚本和自动化。
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1:提供交互式入口,列出可用区域配置并允许用户选择目标区域。
|
||||
- R2:交互式入口允许一次选择多个阶段,并清晰展示阶段名称与用途。
|
||||
- R3:选择互斥阶段时必须阻止执行并解释原因;不得静默选择其中之一。
|
||||
- R4:执行多个阶段时必须使用既有依赖顺序,而非用户勾选顺序。
|
||||
- R5:保留 `npm run build:area -- --config ... --stages ...` 的现有非交互行为与语义。
|
||||
- R6:无 TTY、取消输入或无效输入时必须安全退出,不启动任何构建。
|
||||
- R7:TTY 交互使用方向键移动、空格多选、回车确认;菜单应显示当前选择状态。
|
||||
- R8:`npm run build` 启动交互式入口;`npm run build:area` 继续保留为非交互入口。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 用户可从终端选择区域和多项阶段,无需手写配置路径或逗号分隔阶段名。
|
||||
- [ ] 选择 `intermediates` 与 `reimport` 时,CLI 明确报互斥错误且不运行构建。
|
||||
- [ ] 选择乱序的多个阶段时,实际执行顺序仍遵循既有管线顺序。
|
||||
- [ ] 既有非交互 `build:area` 调用保持兼容。
|
||||
- [ ] 取消或在非交互环境调用不会触发构建。
|
||||
- [ ] 用户可用方向键、空格和回车完成区域及阶段选择。
|
||||
- [ ] `npm run build` 启动交互菜单,`npm run build:area` 的现有调用保持不变。
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- 采用终端原生多选菜单,而非输入逗号分隔编号;避免引入运行时依赖。
|
||||
- `npm run build` 作为日常交互入口;`npm run build:area` 继续服务自动化和精确调用。
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- 修改各 stage 的构建业务逻辑、依赖关系或产物格式。
|
||||
- 将巡航路线、QGIS 图层编辑或区域配置编辑纳入本次交互流程。
|
||||
- GUI 或浏览器界面的构建控制台。
|
||||
|
||||
## 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": "interactive-area-cli",
|
||||
"name": "interactive-area-cli",
|
||||
"title": "Interactive area build CLI",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-11",
|
||||
"completedAt": "2026-08-11",
|
||||
"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,48 @@
|
||||
# Design: Vehicle Incident And Information Preview
|
||||
|
||||
## Boundary
|
||||
|
||||
This is browser-only Cesium preview state. It changes neither generated OSM data nor `package/` assets.
|
||||
Vehicle routes and models remain `_preview/` resources; refresh resets all vehicle event state.
|
||||
|
||||
## Data Model
|
||||
|
||||
Each object returned by `addCruiseVehicle()` gains:
|
||||
|
||||
```js
|
||||
{
|
||||
id: "vehicle-1",
|
||||
modelName: "car_a01_002",
|
||||
status: "normal" | "breakdown" | "accident",
|
||||
incidentNote: "",
|
||||
motion: { state },
|
||||
markerEntity
|
||||
}
|
||||
```
|
||||
|
||||
The current route distance remains the single motion source. The clock tick leaves it unchanged while
|
||||
status is not `normal`; returning to normal resumes from that position.
|
||||
|
||||
## Interaction Flow
|
||||
|
||||
1. Vehicle Cesium Entities carry a `vehicleId` property.
|
||||
2. A `ScreenSpaceEventHandler` picks a clicked entity, resolves its vehicle, synchronizes the existing
|
||||
vehicle selector, and opens a positioned information card.
|
||||
3. The card displays vehicle number, model family, route label/length, configured speed, state, and note.
|
||||
4. Card controls set normal/breakdown/accident and persist only in the in-memory vehicle object.
|
||||
5. State changes update marker visibility, the route material, card content, and Cesium render request.
|
||||
|
||||
## Visual Contract
|
||||
|
||||
- Normal: no marker; original route color; motion active.
|
||||
- Breakdown: yellow wrench label above the vehicle; route retains its original color; motion frozen.
|
||||
- Accident: red warning-triangle label above the vehicle; route material becomes red; motion frozen.
|
||||
- Marker positions use the same vehicle `CallbackProperty`, so they move with the vehicle before an event
|
||||
and remain at the incident location while stopped.
|
||||
|
||||
## Compatibility And Risks
|
||||
|
||||
- Picking must ignore routes, static models, and signal entities.
|
||||
- No route must retain the existing disabled cruise controls and never show a card.
|
||||
- The card must be HTML/CSS UI, not Cesium InfoBox, because the viewer deliberately disables InfoBox.
|
||||
- The feature must not alter signal runtime behavior or vehicle asset URLs.
|
||||
@@ -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,17 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Add an initially hidden vehicle information card to `area-preview.js` and responsive card styles to
|
||||
`cesium-preview.css`.
|
||||
2. Extend the browser runtime vehicle records with stable identity, parsed model metadata, incident status,
|
||||
note, and a marker entity.
|
||||
3. Gate route-distance advancement on normal state; add status transition helper for marker, route material,
|
||||
and card refresh.
|
||||
4. Add Cesium click picking and card controls. Keep the existing selector synchronized for follow/route use.
|
||||
5. Add focused assertions to `test-preview-assets.js` for card wiring, status behavior, and marker/route
|
||||
contracts.
|
||||
6. Run syntax checks, preview test, stage test, target-area preview regeneration, and `check:area`.
|
||||
|
||||
## Rollback
|
||||
|
||||
Revert only the preview HTML/CSS/runtime/test files. No package, Blender, route, or OSM product files should
|
||||
need rollback.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Vehicle incident and information preview
|
||||
|
||||
## Goal
|
||||
|
||||
在 Cesium 验证预览中,让使用者能够查看每辆巡航车辆的基础信息,并人为标记车辆事故/故障状态,直观看到该状态对车辆和路线展示的影响。
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- 车辆是 `scripts/lib/cesium-preview.js` 创建的 Cesium Entity;当前已有车型、路线、巡航速度、
|
||||
跟随、显隐与路线选择。
|
||||
- 车辆模型和路线都属于 `_preview/` 验证资源,不是 area package 的下游资产契约。
|
||||
- 当前不存在车辆业务资料、事故状态、碰撞检测或自动事件推演。
|
||||
- 项目定位是资产生成与验证,不能将交通流/事故仿真作为本任务的范围。
|
||||
|
||||
## Requirements
|
||||
|
||||
- 每辆预览车辆必须有稳定的显示编号、车型、当前路线、设定速度和行驶状态。
|
||||
- 点击场景中的车辆后,必须弹出该车辆的信息卡,展示基础信息与当前事件状态;信息卡内完成状态与说明编辑。
|
||||
- 使用者必须能将选中车辆在正常、故障和事故状态之间切换,并能提供简短事件说明。
|
||||
- 故障和事故是不同状态:二者均停止巡航;故障使用黄色标记,事故使用红色标记并提示路线中断。
|
||||
- 故障使用车辆顶部黄色扳手标记;事故使用红色警示三角标记,并将该车辆当前路线改为红色。
|
||||
- 非正常车辆必须有可见的场景标记;恢复正常后继续按既有路线巡航。
|
||||
- 事故/故障仅在当前浏览器预览会话中存在;刷新或重建预览后不保留。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 自动碰撞检测、车辆间物理碰撞、自动事故生成。
|
||||
- 信号配时、交通流、调度策略或持久化事件记录。
|
||||
- 向 `package/` 加入车辆、路线或事故数据。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 用户可点击任一可见车辆,弹出其基础信息、状态和事件说明;现有车辆选择控件仍可用于路线和跟随。
|
||||
- [ ] 用户可标记故障或事故,并看到车辆停止、状态文本和明显的场景标记。
|
||||
- [ ] 故障显示黄色扳手;事故显示红色警示三角且当前路线变红。
|
||||
- [ ] 用户可恢复正常,车辆继续其现有路线;其他车辆不受影响。
|
||||
- [ ] 无可用路线时,车辆信息和事故控件按既有禁用语义处理。
|
||||
- [ ] 预览资产、路线与现有信号灯 runtime 继续可加载;不新增下游 package 内容。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "vehicle-incident-info",
|
||||
"name": "vehicle-incident-info",
|
||||
"title": "Vehicle incident and information preview",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-12",
|
||||
"completedAt": "2026-08-12",
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -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."}
|
||||
@@ -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."}
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"id": "native-road-compiler",
|
||||
"name": "native-road-compiler",
|
||||
"title": "Native road compiler workbench",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-13",
|
||||
"completedAt": "2026-08-18",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [
|
||||
"08-14-native-road-lane-markings",
|
||||
"08-17-native-road-control-markings",
|
||||
"08-17-native-road-center-lines",
|
||||
"08-17-native-rounded-junctions",
|
||||
"08-18-native-traffic-signal-parity"
|
||||
],
|
||||
"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 @@
|
||||
# Native Lane Markings Design
|
||||
|
||||
## Architecture
|
||||
|
||||
The native compiler remains the source of truth. It derives two new polygon
|
||||
layers alongside its existing surface, sidewalk, lane-centerline, and
|
||||
connector outputs:
|
||||
|
||||
```text
|
||||
canonical directed roads + lane centerlines + junction cutbacks
|
||||
|
|
||||
+-- lane separators: paint polygons between adjacent same-direction lanes
|
||||
|
|
||||
+-- direction arrows: repeated through-arrow template on directed lanes,
|
||||
| outside the reserved junction marking zone
|
||||
|
|
||||
+-- turn arrows: tested existing template, anchored to an incoming lane
|
||||
only when that lane has an explicit supported turn:lanes value
|
||||
```
|
||||
|
||||
No OSM2streets rendered geometry is consumed by this path. The existing arrow
|
||||
template library is reused only as a geometry/style asset, so the native lane
|
||||
ID and OSM tags remain the evidence for placement.
|
||||
|
||||
## Contracts
|
||||
|
||||
- `layers/lane_separators.geojson`: polygon FeatureCollection. Each feature
|
||||
records `native_id`, directed `road_id`, adjacent lane indices, source OSM
|
||||
ways, and `native-road-lane-separator/v1` provenance.
|
||||
- `layers/turn_arrows.geojson`: polygon FeatureCollection. Each feature records
|
||||
its `native_id`, `road_id`, `lane_id`, OSM way IDs, direction, lane index,
|
||||
maneuver, template asset, placement distance, and placement provenance.
|
||||
- Unsupported turn values, a missing usable incoming-lane segment, or an
|
||||
insufficient pre-junction placement distance create a diagnostic and no
|
||||
arrow geometry.
|
||||
- `layers/direction_arrows.geojson`: polygon FeatureCollection. Each feature
|
||||
records the native lane and directed road, OSM way IDs, a stable sequence
|
||||
index, its distance along the lane, and `native-road-direction-arrow/v1`
|
||||
provenance. It uses the tested `through` template but is not a turn claim.
|
||||
- The workbench serves both layers, draws them separately from its current
|
||||
centerline/connector debug layer, and selects them by `native_id`.
|
||||
- `catalog.NATIVE_ROAD_LAYERS` maps the two native sources to the existing
|
||||
`lane_separators` and `lane_arrows_webscale` Blender materials. The native
|
||||
adapter does not extend the osm2streets scene-layer registry.
|
||||
|
||||
## Placement
|
||||
|
||||
An incoming lane is oriented in driving direction. A turn arrow is sampled
|
||||
from that lane's endpoint backwards by the configured safe distance, staying
|
||||
outside the junction cutback. Its template basis uses the sampled lane tangent;
|
||||
therefore it is on and aligned with the lane rather than the OSM centerline or
|
||||
a screen-space direction. Multiple template rings remain separate polygons.
|
||||
|
||||
Lane separators are narrow polygons centered between adjacent lane centerlines
|
||||
on a single directional carriageway. They stop at the same junction cutbacks as
|
||||
the lane centerlines. A one-lane direction produces none.
|
||||
|
||||
Direction arrows are sampled at a fixed road-scale interval along the same
|
||||
directed lane centerline. Their candidates exclude both endpoint buffers and
|
||||
the turn-arrow reserve at the incoming end. This preserves a readable repeated
|
||||
direction cue without overlapping a turn instruction at a junction.
|
||||
|
||||
## Compatibility And Rollback
|
||||
|
||||
All new files are additive under `native-road/layers/`. The existing
|
||||
osm2streets/QGIS layer contract and `package/` are unchanged. Selecting
|
||||
`--road-provider osm2streets` remains rollback. Missing native marking files
|
||||
are a native Blender build error rather than a silent omission.
|
||||
|
||||
## Risks
|
||||
|
||||
- OSM turn tags can be incomplete or incompatible with the inferred lane
|
||||
count. These are diagnostics, not guessed arrows.
|
||||
- Very short approaches can have no safe position before the cutback. They are
|
||||
skipped with a source-traceable diagnostic.
|
||||
- Blender and Cesium need a real native build to verify the mesh/material
|
||||
contract, not only GeoJSON unit tests.
|
||||
@@ -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 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Add reusable template-placement helper to the existing turn-arrow module,
|
||||
preserving its supported-asset gate and output ring shape.
|
||||
2. Extend the native compiler with lane-separator, repeated road-direction
|
||||
arrow, and explicit-turn-arrow polygon generation plus source diagnostics
|
||||
and layer persistence.
|
||||
3. Extend native output records/counts and Blender adapter mappings. Reuse
|
||||
existing `lane_separators` and `lane_arrows_webscale` materials only.
|
||||
4. Add Workbench layers, toggles, selection/provenance inspector entries, and
|
||||
preserve the existing Workbench-only direction triangle behavior.
|
||||
5. Add focused fixtures for supported turn placement, unsupported maneuver,
|
||||
short approach skip, and separator geometry; run native/workbench/build
|
||||
stage tests.
|
||||
6. Build Nantaizi with `blender,cesium,preview --road-provider native`, inspect
|
||||
the final preview, and confirm no `package/` publication occurred.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
npm run test:native-road
|
||||
npm run test:road-workbench
|
||||
npm run test:turn-lane-arrows
|
||||
npm run test:build-stages
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json \
|
||||
--stages blender,cesium,preview --road-provider native
|
||||
```
|
||||
|
||||
Rollback is selecting `--road-provider osm2streets`; no existing output path
|
||||
is replaced.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Native road lane markings and turn arrows
|
||||
|
||||
## Goal
|
||||
|
||||
Complete the native-road visual language for Nantaizi before any cross-area
|
||||
migration: lane separators, travel-direction markers, and OSM-backed turn
|
||||
arrows must be inspectable in the Road Workbench and visible in Blender and
|
||||
Cesium output.
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- Native output currently contains directed lane centerlines and connector
|
||||
curves, but it does not emit paintable lane-separator or turn-arrow polygons.
|
||||
- The existing osm2streets path has tested arrow templates in
|
||||
`scripts/lib/turn-lane-arrows.js`, the `lane_arrows_webscale` material layer,
|
||||
and a matching Blender material. Reuse these instead of introducing a second
|
||||
arrow style.
|
||||
- Earlier reviews established that direction markers must sit on the OSM / lane
|
||||
centerline, use a clearly directional sharp triangle, and never be treated as
|
||||
a road-surface decoration that drifts sideways.
|
||||
- Turn arrows must follow the actual incoming lane and be placed before its
|
||||
junction, with OSM source way, direction, lane index, and maneuver retained
|
||||
as provenance.
|
||||
- Scope remains Nantaizi only. Existing osm2streets output remains untouched.
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1: Native compilation emits polygonal lane-separator markings derived from
|
||||
its own directed lane geometry.
|
||||
- R2: Native compilation emits turn-arrow polygons for supported OSM
|
||||
`turn:lanes` maneuvers, using the existing tested arrow templates and the
|
||||
exact native incoming-lane centerline for placement.
|
||||
- R2a: Native compilation emits repeated straight-ahead direction-arrow
|
||||
polygons along directed lanes, matching the visual role of osm2streets'
|
||||
ordinary road arrows. These are a separate layer from turn arrows, retain
|
||||
their own provenance, and leave a clear buffer around junction turn arrows.
|
||||
- R3: Native output preserves provenance for every marking: native road/lane
|
||||
ID, OSM way IDs, direction, lane number, maneuver, and placement method.
|
||||
- R4: The Workbench renders markings in a separately controllable layer and
|
||||
exposes those provenance fields on selection.
|
||||
- R5: The native Blender adapter consumes native marking layers through the
|
||||
existing lane-separator and lane-arrow material layers; Cesium must receive
|
||||
the same geometry through the exported GLB.
|
||||
- R6: Unsupported, unplaceable, or ambiguous arrow inputs become diagnostics;
|
||||
the compiler must not invent a maneuver.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Nantaizi native output contains valid polygon GeoJSON for generated lane
|
||||
separators, repeated road direction arrows, and every supported, explicitly
|
||||
tagged turn arrow.
|
||||
- [ ] A Workbench user can toggle, select, and inspect a generated marker and
|
||||
see its lane, OSM, maneuver, and placement provenance.
|
||||
- [ ] A selected direction marker is geometrically aligned to its directed lane
|
||||
centerline; a selected turn arrow is on its incoming lane before the junction.
|
||||
- [ ] Blender scene output and Cesium GLB contain native lane markings and
|
||||
arrows with the existing visual material language.
|
||||
- [ ] Unit tests cover a normal supported arrow, an unsupported maneuver, and
|
||||
an unsafe/too-short placement; native compile and final Nantaizi visual build
|
||||
pass without publishing `package/`.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Inventing turn arrows for untagged lanes, traffic-control semantics, changing
|
||||
QGIS/osm2streets layers, or processing another area.
|
||||
|
||||
## Key Decision
|
||||
|
||||
- Sharp travel-direction triangles remain a Workbench-only inspection aid.
|
||||
They explain raw OSM node order after a road is selected.
|
||||
- Repeated `through` direction arrows are final road markings, distinct from
|
||||
both those debug triangles and OSM-backed junction turn arrows. They are
|
||||
placed on native lane centerlines at a fixed interval and enter the same
|
||||
Blender/Cesium material layer as turn arrows.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-road-lane-markings",
|
||||
"name": "native-road-lane-markings",
|
||||
"title": "Native road lane markings and turn arrows",
|
||||
"description": "Complete Nantaizi native lane separators, travel direction and turn-arrow geometry through Road Workbench, Blender and Cesium before cross-area migration.",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P1",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-14",
|
||||
"completedAt": "2026-08-14",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-13-native-road-compiler",
|
||||
"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,35 @@
|
||||
# Native Lane Separator Style Overrides Design
|
||||
|
||||
## Contract
|
||||
|
||||
The override is identified by the stable `roadId` plus the adjacent lane pair:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "车道分隔线:road:way/123:forward:1-2",
|
||||
"kind": "lane-separator-style",
|
||||
"roadId": "road:way/123:forward",
|
||||
"leftLaneIndex": 1,
|
||||
"rightLaneIndex": 2,
|
||||
"color": "white",
|
||||
"pattern": "dashed"
|
||||
}
|
||||
```
|
||||
|
||||
The same `yellow|white` and `dashed|solid` enums are reused. A default is
|
||||
white dashed. The generated Polygon stores `color`, `pattern` and
|
||||
`effective_style`; source IDs remain unchanged.
|
||||
|
||||
## Rendering
|
||||
|
||||
The existing lane separator polygon represents the whole lane-pair path.
|
||||
`solid` retains it as one continuous feature. `dashed` samples regular dash
|
||||
polygons along the shared centreline with deterministic spacing and preserves
|
||||
junction cutback. White uses the existing `lane_separators` material; yellow
|
||||
uses a native-only yellow material route. Workbench separates lane separators
|
||||
from turn arrows so their styles remain selectable and visible.
|
||||
|
||||
## Compatibility
|
||||
|
||||
No override means current visual default. Road edges, curbs and centre lines
|
||||
are untouched. Existing `native-road-overrides/v1` files remain valid.
|
||||
@@ -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,7 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Validate and resolve per-lane-pair style overrides.
|
||||
2. Generate default/dashed/solid style geometry and effective properties.
|
||||
3. Add the native Blender yellow lane-separator material route.
|
||||
4. Add Workbench selection, Chinese editor and automatic staging.
|
||||
5. Test compiler, Workbench, Blender catalog and Nantaizi native build.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Native lane separator style overrides
|
||||
|
||||
## Goal
|
||||
|
||||
Make native same-direction lane separators selectable and persistently editable
|
||||
in Road Workbench, using the style override pattern proven for centre lines.
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- `compileLaneMarkings()` writes one continuous `0.12m` Polygon separator
|
||||
between each adjacent lane pair, identified by `road_id`, `left_lane_index`
|
||||
and `right_lane_index`.
|
||||
- Lane separators currently have fixed light marking material and are displayed
|
||||
together with turn arrows in Workbench.
|
||||
- `native-road-overrides.json` already supports validated, persistent centre
|
||||
line styles by logical native segment. The new type must not alter road-edge,
|
||||
curb or sidewalk geometry.
|
||||
|
||||
## Initial Requirements
|
||||
|
||||
- Selecting a lane separator must show its lane-pair source and a Chinese
|
||||
style editor.
|
||||
- The first style catalog should mirror centre lines: white/yellow and
|
||||
dashed/solid.
|
||||
- The effective style must be generated into GeoJSON, visible in Workbench,
|
||||
and consumed by native Blender/Cesium.
|
||||
- Default output must remain the current white dashed-style separator.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Road-edge lines, curbs, sidewalks, double lines, partial ranges, lane-specific
|
||||
legal restrictions and osm2streets layers.
|
||||
|
||||
## Key Decision
|
||||
|
||||
Each override applies only to the selected adjacent lane pair. This preserves
|
||||
separate marking semantics on roads with three or more lanes.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Selecting a separator exposes a Chinese style editor for its adjacent
|
||||
lane pair; choosing a style stages it automatically.
|
||||
- [ ] Save and regeneration preserve the per-pair style across reloads while
|
||||
unedited separators retain the default.
|
||||
- [ ] GeoJSON, Workbench, Blender and Cesium show the same effective style.
|
||||
- [ ] Tests cover validation, defaults, per-pair isolation, automatic staging
|
||||
and native scene consumption.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-lane-separator-styles",
|
||||
"name": "native-lane-separator-styles",
|
||||
"title": "Native lane separator style overrides",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"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,64 @@
|
||||
# Native Road Centre-Line Style Overrides Design
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
Workbench selects a generated centre-line dash
|
||||
-> segment_id identifies the logical road segment
|
||||
-> staged center-line-style override
|
||||
-> native-road-overrides.json
|
||||
-> compileCenterLines resolves default or effective style
|
||||
-> center_lines.geojson polygons with style fields
|
||||
-> Workbench / Blender / Cesium
|
||||
```
|
||||
|
||||
The authoritative edit is a new override kind, not a mutation of
|
||||
`center_lines.geojson`. It has a deterministic ID based on `segmentId`, so a
|
||||
later recompilation replaces the segment's style rather than accumulating
|
||||
records.
|
||||
|
||||
## Override Contract
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "道路中心线:segment:way/123/1",
|
||||
"kind": "center-line-style",
|
||||
"segmentId": "segment:way/123/1",
|
||||
"color": "yellow",
|
||||
"pattern": "dashed"
|
||||
}
|
||||
```
|
||||
|
||||
`validateOverrides()` accepts only known model segment IDs and the finite
|
||||
string enums `yellow|white` and `dashed|solid`. A style override applies once
|
||||
to the paired forward/backward native roads for that segment. The existing
|
||||
schema version remains `native-road-overrides/v1` because this is an additive
|
||||
kind and old files remain valid.
|
||||
|
||||
## Geometry and Material
|
||||
|
||||
- Default remains yellow dashed: 2m dash, 2m gap, 0.25m width.
|
||||
- `solid` generates deterministic adjacent 2m pieces with no gap. Pieces still
|
||||
undergo the same junction cutback and control-marking exclusion as dashed
|
||||
lines; this avoids creating a solid polygon across an excluded crossing.
|
||||
- Style fields `color`, `pattern`, `dash_length_m`, `dash_gap_m`, and
|
||||
`effective_style` are stored on every generated polygon.
|
||||
- Blender currently maps all `center_lines` to one yellow material, so the
|
||||
native adapter must support a white centre-line material route without
|
||||
changing legacy osm2streets layers. The route must preserve the existing
|
||||
yellow material for default and yellow overrides.
|
||||
|
||||
## Workbench UX
|
||||
|
||||
Selecting a centre-line dash shows a compact Chinese style panel in the
|
||||
existing form area. A select control presents the four named choices. Choosing
|
||||
one stages an override; the existing 保存 / 保存并重新生成 actions remain the
|
||||
only persistence and generation actions. The panel also shows whether the
|
||||
style is default or overridden and identifies the native road segment.
|
||||
|
||||
## Compatibility and Rollback
|
||||
|
||||
Unedited segments generate byte-compatible geometry style defaults apart from
|
||||
the added style properties. Oneway/service filtering and control priority are
|
||||
unchanged. Selecting `--road-provider osm2streets` remains a full rollback.
|
||||
|
||||
@@ -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 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Add validated `center-line-style` overrides and a helper which resolves the
|
||||
effective default/override style by segment ID.
|
||||
2. Generate yellow/white and dashed/solid centre-line geometry while retaining
|
||||
cutback and control-marking exclusion behaviour; publish effective style
|
||||
properties.
|
||||
3. Extend the native Blender layer adapter/material handling to distinguish
|
||||
white from yellow centre-line features without affecting legacy layers.
|
||||
4. Add Workbench style controls, staging, Chinese selection evidence, and
|
||||
immediate regenerated-layer display.
|
||||
5. Add focused compiler, override, Workbench, Blender catalog, and build-stage
|
||||
tests; compile/check Nantaizi and build Blender/Cesium/preview without the
|
||||
package stage.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
npm run test:native-road
|
||||
npm run test:road-workbench
|
||||
python3 -m unittest discover blender/tests
|
||||
npm run test:build-stages
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview --road-provider native
|
||||
```
|
||||
|
||||
## Rollback
|
||||
|
||||
Remove the override records or choose `--road-provider osm2streets`; no legacy
|
||||
output is modified.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Native road center line style overrides
|
||||
|
||||
## Goal
|
||||
|
||||
Allow a Road Workbench user to select a generated native road centre line and
|
||||
persistently override its marking style, without turning the generated GeoJSON
|
||||
into the source of truth.
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- Native `center_lines.geojson` currently contains automatic yellow dashed
|
||||
polygons (`2m` dash, `2m` gap, `0.25m` width), each tied to a `segment_id`.
|
||||
- A displayed dash can already be selected and exposes its segment provenance,
|
||||
but has no editing controls.
|
||||
- `native-road-overrides.json` is the persistent authority for existing road
|
||||
and connection edits. The Workbench already stages, validates, saves, then
|
||||
recompiles those overrides.
|
||||
- The generated layer is consumed by both Workbench and native Blender/Cesium;
|
||||
styling must therefore be compiled geometry and use the existing
|
||||
`center_lines` material path, not a Workbench-only display tint.
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1: Selecting a centre-line dash must expose a Chinese style editor for its
|
||||
logical target and show the current effective style.
|
||||
- R2: Supported styles must include at least solid/dashed and white/yellow
|
||||
marking colours.
|
||||
- R3: Style choices must be stored in `native-road-overrides.json`, validated,
|
||||
reapplied during compilation, and survive a future Workbench launch.
|
||||
- R4: The regenerated GeoJSON must carry source traceability and effective
|
||||
style fields so Workbench, Blender and Cesium show the same marking.
|
||||
- R5: Default automatic centre lines remain unchanged for segments without an
|
||||
override; one-way/service exclusions and control-marking avoidance remain
|
||||
authoritative.
|
||||
|
||||
## Initial Scope Boundary
|
||||
|
||||
- Editing individual dash polygons is out of scope: they are derived pieces,
|
||||
not user-owned objects.
|
||||
- Per-segment control is recommended for the first version because current
|
||||
native segments already stop at junctions and have stable IDs.
|
||||
- Double-line semantics, legal `overtaking` inference, hand-drawn partial
|
||||
ranges, and changes to osm2streets output are out of scope unless explicitly
|
||||
accepted during planning.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] A user can select a centre line, choose a supported style in Chinese,
|
||||
save it, regenerate, and see the result immediately in the Workbench.
|
||||
- [ ] After reload and recompilation, the selected segment retains its style
|
||||
while unedited segments retain the automatic yellow dashed default.
|
||||
- [ ] Native Blender/Cesium uses the same effective style and does not require
|
||||
osm2streets geometry.
|
||||
- [ ] Tests cover schema validation, style geometry, default fallback,
|
||||
persistence/API wiring, and Workbench selection/edit controls.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- The first version applies one override to the complete native segment between
|
||||
junctions. Individual dash and partial-range editing are deferred.
|
||||
- The initial style catalog is four explicit choices: yellow dashed, white
|
||||
dashed, yellow solid, and white solid. Double-line semantics are deferred.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-road-center-line-styles",
|
||||
"name": "native-road-center-line-styles",
|
||||
"title": "Native road center line style overrides",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"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,50 @@
|
||||
# Native Road Centre Lines Design
|
||||
|
||||
## Architecture
|
||||
|
||||
The native compiler adds one independent polygon layer:
|
||||
|
||||
```text
|
||||
canonical bidirectional segment + trimmed OSM centreline
|
||||
-> repeated 2m x 0.25m dash polygons
|
||||
-> native-road/layers/center_lines.geojson
|
||||
-> Workbench / native Blender adapter / Cesium
|
||||
```
|
||||
|
||||
It does not read osm2streets `center_lines.geojson`. The legacy layer is a
|
||||
visual baseline only.
|
||||
|
||||
## Placement
|
||||
|
||||
- A candidate must have exactly forward and backward native roads for the same
|
||||
`segmentId`, neither be `highway=service`, and have a valid trimmed OSM
|
||||
centreline.
|
||||
- Dashes use the legacy observed dimensions: 2m length, 0.25m width, with a
|
||||
deterministic 2m gap. The first dash starts at a fixed segment-local offset
|
||||
so rebuilds do not drift.
|
||||
- The line is trimmed with the same `junctionPlans` cutback used by native lane
|
||||
centreline generation. Any dash intersecting a crosswalk or stop line is
|
||||
omitted, preserving the control-marking priority already established.
|
||||
- One-way and service segments have no generated feature. A degenerate source
|
||||
line produces a diagnostic rather than malformed geometry.
|
||||
|
||||
## Contracts
|
||||
|
||||
`layers/center_lines.geojson` is a Polygon FeatureCollection. Every feature
|
||||
has `native_id`, `segment_id`, `directional_road_ids`, `osm_way_ids`,
|
||||
`dash_index`, `dash_length_m`, `dash_gap_m`, `placement_rule`, and
|
||||
`provenance="native-road-center-line/v1"`.
|
||||
|
||||
`compiled.json.layers.centerLines`, comparison count
|
||||
`nativeCenterLineFeatures`, and `build-area.js` required native records use the
|
||||
same filename. `catalog.NATIVE_ROAD_LAYERS` maps source `center_lines` to the
|
||||
existing `center_lines` material layer.
|
||||
|
||||
The Workbench loads this source into the marking overlay, exposes a Chinese
|
||||
toggle, and identifies it as `道路中心虚线` on selection.
|
||||
|
||||
## Compatibility
|
||||
|
||||
The change is additive within `native-road/`; `--road-provider osm2streets`
|
||||
remains unaffected and is rollback. A native Blender build treats a missing
|
||||
layer as an input error rather than silently omitting it.
|
||||
@@ -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,19 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Add centre-dash constants and a pure native compiler routine based on the
|
||||
shared bidirectional segment model and junction-trimmed centreline.
|
||||
2. Filter invalid, one-way, service, and control-marking-conflicting dashes;
|
||||
record actionable diagnostics for invalid geometry only.
|
||||
3. Write `center_lines.geojson`, compilation/comparison counts, native build
|
||||
records, required-layer checks, and Blender material adapter mapping.
|
||||
4. Add Workbench API state, Chinese toggle, summary count, rendering, and
|
||||
source evidence for a selected centre dash.
|
||||
5. Add focused native and Workbench tests for generation, skips, control
|
||||
avoidance, provenance, and file/API contracts.
|
||||
6. Run native/unit/workbench/build-stage tests, compile/check Nantaizi, then
|
||||
validate `blender,cesium,preview --road-provider native` without `package`.
|
||||
|
||||
## Rollback
|
||||
|
||||
Choose `--road-provider osm2streets`; no legacy layer or published package is
|
||||
modified.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Native road center lines
|
||||
|
||||
## Goal
|
||||
|
||||
Restore the existing osm2streets-style road-centre dashed markings in the
|
||||
native-road provider before treating native output as ready for broader quality
|
||||
gates or new-area validation.
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- Nantaizi's existing `osm2streets_web_out/center_lines.geojson` has 885
|
||||
`type="center line"` polygons. Its sampled dash geometry is approximately
|
||||
2.00m long by 0.25m wide.
|
||||
- The legacy extraction excludes centre lines that overlap crosswalk zones and
|
||||
that fall on service driving polygons (`build-osm2streets-qgis.js:474-480`).
|
||||
- Native output currently has only same-direction `lane_separators`; it has no
|
||||
`center_lines.geojson`, so a bidirectional road's directional carriageways
|
||||
lack their visual and semantic divider.
|
||||
- Existing Blender already owns a `center_lines` / `Center Line` material
|
||||
layer. Native must adapt to it rather than creating a second scene registry.
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1: For supported non-service, bidirectional native road segments, emit a
|
||||
`center_lines.geojson` dashed centre divider derived from the canonical OSM
|
||||
centreline and the segment's two directional carriageways.
|
||||
- R2: Match the established visual baseline: yellow 0.25m-wide, 2m-long
|
||||
dashes, clipped away from ordinary junction cutbacks and control markings.
|
||||
- R3: Preserve source traceability: each dash records its native segment,
|
||||
source OSM way, involved directional roads, placement interval/rule, and a
|
||||
dedicated provenance value.
|
||||
- R4: Add the layer to compilation records, Road Workbench display/selection
|
||||
in Chinese, and the existing Blender/Cesium `center_lines` material path.
|
||||
- R5: Do not render a centre divider on one-way or `highway=service` segments;
|
||||
report invalid geometry instead of fabricating a divider.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Native Nantaizi output contains a valid `layers/center_lines.geojson`
|
||||
with source-traceable 2m x 0.25m dashed polygons.
|
||||
- [ ] No native centre dash intersects a generated crosswalk or vehicle stop
|
||||
line, and no dash reaches into the supported junction surface cutback.
|
||||
- [ ] The Workbench can toggle and select centre lines, presenting their
|
||||
source and rule in Chinese rather than calling them lane separators.
|
||||
- [ ] Native `blender,cesium,preview` consumes the layer using the existing
|
||||
`center_lines` material and does not run `package`.
|
||||
- [ ] Tests cover two-way generation, one-way/service skips, control-marking
|
||||
avoidance, provenance, output contract, and Workbench wiring.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Centre-line editing overrides, solid/double-centre-line semantics, arbitrary
|
||||
OSM `overtaking` interpretation, lane colouring changes, and other areas.
|
||||
|
||||
## Key Decision
|
||||
|
||||
This task deliberately matches the existing stable visual layer first. It does
|
||||
not claim to infer a country-wide legal marking taxonomy from sparse OSM tags.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-road-center-lines",
|
||||
"name": "native-road-center-lines",
|
||||
"title": "Native road center lines",
|
||||
"description": "Align native-road output with the existing center_lines layer before broader quality work.",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-13-native-road-compiler",
|
||||
"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,66 @@
|
||||
# Native Road Control Markings Design
|
||||
|
||||
## Architecture
|
||||
|
||||
The native compiler remains the source of truth. It extends its canonical OSM
|
||||
parse with marked crossing nodes, then derives two additive polygon layers from
|
||||
the crossing evidence, native directed lanes, and native junction plans:
|
||||
|
||||
```text
|
||||
OSM crossing node + native directed lane centerlines + junction plans
|
||||
|
|
||||
+-- crosswalks.geojson: six zebra stripe polygons per safe crossing
|
||||
|
|
||||
+-- vehicle_stop_lines.geojson: one safe approach stop-line polygon
|
||||
```
|
||||
|
||||
No osm2streets GeoJSON is read. Existing crossing geometry helpers may be
|
||||
extracted or adapted only when they operate on native lane data and retain
|
||||
native provenance.
|
||||
|
||||
## Source And Placement
|
||||
|
||||
- A source node is eligible only when `highway=crossing` and
|
||||
`crossing:markings` is not `no`, `none`, or `unmarked`.
|
||||
- The compiler finds native roads containing the crossing's OSM node and uses
|
||||
the nearest compatible directed lane centerline to obtain the road tangent.
|
||||
- Crosswalk stripes are perpendicular to that tangent and constrained to the
|
||||
native road width. Existing fixed zebra dimensions are retained initially:
|
||||
six 0.45m stripes with 0.45m gaps, 0.45m stripe width, and a highway-based
|
||||
stripe length.
|
||||
- A stop line is generated only when the crossing can be associated with a
|
||||
supported junction approach and a safe outside-of-junction side. Otherwise
|
||||
the crosswalk may remain valid but the missing stop line is diagnostic.
|
||||
- Duplicate nearby crossing nodes use a stable cluster representative so one
|
||||
physical crosswalk does not produce duplicate stripes.
|
||||
|
||||
## Contracts
|
||||
|
||||
`layers/crosswalks.geojson` and `layers/vehicle_stop_lines.geojson` are Polygon
|
||||
FeatureCollections. Each feature records its crossing OSM node, source OSM way,
|
||||
native directed road/lane when available, direction, placement method, and
|
||||
`native-road-crosswalk/v1` or `native-road-stop-line/v1` provenance.
|
||||
|
||||
The Workbench API returns both layers as `state.layers.crosswalks` and
|
||||
`state.layers.vehicleStopLines`. Its browser map uses separate toggleable
|
||||
layers and selection evidence; the scene-preview toggle leaves real control
|
||||
markings visible.
|
||||
|
||||
`catalog.NATIVE_ROAD_LAYERS` maps the sources to existing `crosswalks` and
|
||||
`vehicle_stop_lines` material layers. This native adapter must not add them to
|
||||
the osm2streets `ROAD_LAYERS` / `SCENE_LAYERS` registry.
|
||||
|
||||
## Compatibility And Rollback
|
||||
|
||||
The new files are additive under `native-road/layers/`. Existing osm2streets
|
||||
output and QGIS input are unchanged. Selecting `--road-provider osm2streets`
|
||||
remains rollback. A native Blender build treats a missing new layer as an error
|
||||
rather than silently omitting a visible marking.
|
||||
|
||||
## Risks
|
||||
|
||||
- Crossing nodes can be detached from a routable road or lie on an ambiguous
|
||||
multi-road segment. These become diagnostics rather than guessed geometry.
|
||||
- A physical crossing split into several OSM nodes must deduplicate stably.
|
||||
- A crosswalk near a non-supported junction may get stripes but no valid stop
|
||||
line; this difference must be exposed in workbench provenance.
|
||||
@@ -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 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Extend the native OSM parse/model with marked crossing-node evidence while
|
||||
preserving existing road IDs and parser behavior.
|
||||
2. Add native crossing clustering, tangent resolution from directed lane
|
||||
centerlines, stripe geometry, safe stop-line placement, and source
|
||||
diagnostics.
|
||||
3. Persist the two new layers in `compile-native-roads.js`, comparison counts,
|
||||
native build records, required native layer checks, and existing Blender
|
||||
material mappings.
|
||||
4. Add Workbench API/state fields, Chinese layer toggles, selection evidence,
|
||||
and summary counts while retaining scene-preview behavior.
|
||||
5. Add focused fixtures for marked and unmarked crossings, duplicate cluster
|
||||
handling, a missing native-lane diagnostic, and output layer contracts.
|
||||
6. Run native/unit/workbench/build-stage tests, compile/check Nantaizi, then
|
||||
build `blender,cesium,preview --road-provider native` without `package/`.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
npm run test:native-road
|
||||
npm run test:road-workbench
|
||||
npm run test:build-stages
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json \
|
||||
--stages blender,cesium,preview --road-provider native
|
||||
```
|
||||
|
||||
Rollback is selecting `--road-provider osm2streets`; no legacy output path is
|
||||
changed.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Native road control markings
|
||||
|
||||
## Goal
|
||||
|
||||
Give Nantaizi's native-road provider inspectable, source-traceable crosswalk
|
||||
and vehicle stop-line geometry, so intersection control markings do not depend
|
||||
on the osm2streets render output.
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- Native road output already owns road surfaces, sidewalks, lane separators,
|
||||
repeated direction arrows, and explicit junction-turn arrows.
|
||||
- Nantaizi's OSM input contains explicit marked crossings, including zebra and
|
||||
traffic-signal crossings. The existing osm2streets output currently has 48
|
||||
crosswalk-stripe polygons and 8 stop-line polygons.
|
||||
- `build-osm2streets-qgis.js:1067` derives these markings from crossing nodes
|
||||
plus osm2streets driving lanes. Native must not read that rendered geometry;
|
||||
it can reuse only the tested geometry rules after adapting them to native
|
||||
directed lanes and junction plans.
|
||||
- Existing Blender materials already provide `crosswalks` and
|
||||
`vehicle_stop_lines`; native may map into them without changing the legacy
|
||||
osm2streets layer registry.
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1: Native compilation uses explicit marked OSM crossing nodes only, and
|
||||
emits crosswalk-stripe polygons plus approach stop lines only where safe
|
||||
native directed-road placement exists.
|
||||
- R2: Each generated feature preserves crossing node, OSM road, native road or
|
||||
lane, direction, placement method, and relevant junction provenance.
|
||||
- R3: The Road Workbench independently toggles, selects, and describes native
|
||||
crosswalks and stop lines in Chinese.
|
||||
- R4: Native Blender and Cesium builds consume both layers with the existing
|
||||
control-marking materials; no `package/` publication is part of validation.
|
||||
- R5: Missing compatible road context, ambiguous geometry, and unsupported
|
||||
crossing inputs are diagnostics; the compiler must not invent a crossing.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Nantaizi native output contains valid `crosswalks.geojson` and
|
||||
`vehicle_stop_lines.geojson` features with source-traceable properties,
|
||||
without reading osm2streets rendered layers.
|
||||
- [ ] A Workbench user can toggle and select either marking type and see the
|
||||
crossing node, associated road/direction, and placement evidence.
|
||||
- [ ] Native Blender/Cesium output contains both marking types using existing
|
||||
materials, after `blender,cesium,preview --road-provider native` and without
|
||||
publishing `package/`.
|
||||
- [ ] Tests cover a marked crossing, an unmarked crossing skip, a missing or
|
||||
ambiguous native-road placement skip, and output-layer contract checks.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Hand-placed control-marking overrides, traffic-signal state-machine changes,
|
||||
freehand polygon editing, processing another region, and importing
|
||||
osm2streets-rendered crosswalk geometry.
|
||||
|
||||
## Key Decision
|
||||
|
||||
The first version is explicit-OSM-only. This is feasible for Nantaizi and
|
||||
keeps control markings evidence-backed; missing data remains a diagnostic for
|
||||
OSM improvement rather than a silent geometric guess.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-road-control-markings",
|
||||
"name": "native-road-control-markings",
|
||||
"title": "Native road control markings",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-13-native-road-compiler",
|
||||
"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,19 @@
|
||||
# Native road edge markings
|
||||
|
||||
## Goal
|
||||
|
||||
TBD.
|
||||
|
||||
## Requirements
|
||||
|
||||
- TBD
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] TBD
|
||||
|
||||
## 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": "native-road-edge-markings",
|
||||
"name": "native-road-edge-markings",
|
||||
"title": "Native road edge markings",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Check compiler and workbench marking contracts."}
|
||||
{"file":".trellis/spec/blender/testing.md","reason":"Run required Blender test suite."}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"Verify browser, compiler, and Blender consume the same output."}
|
||||
@@ -0,0 +1,22 @@
|
||||
# Design
|
||||
|
||||
The compiler owns marking semantics. `center-line-style` remains segment
|
||||
scoped because a bidirectional road segment shares one centre-line decision.
|
||||
`edge-line-style` is scoped to `{ roadId, side }`, since a directional
|
||||
carriageway has independently editable left and right outside edges.
|
||||
|
||||
`double: true` is valid only with yellow solid centre lines. Generation emits
|
||||
two 0.32m-separated parallel polygons for each normal solid mark, preserving
|
||||
junction cutback and crosswalk/stop-line clearance. This keeps the existing
|
||||
solid line implementation and avoids a separate geometry pipeline.
|
||||
|
||||
Road edge lines use the existing road-edge offset geometry. A solid style is
|
||||
one continuous buffered offset line; a dashed style uses deterministic 2m
|
||||
marks at 4m spacing. Both carry `effective_style`, source IDs, side, and
|
||||
native provenance.
|
||||
|
||||
The Workbench uses one marking form with an explicit selected target type.
|
||||
Only a centre-line target exposes the double-yellow option; lane separators
|
||||
and edge lines retain the ordinary colour/pattern choices. Staged changes pass
|
||||
through the existing save and regenerate flow, so browser, compiler and
|
||||
Blender read the same persisted override.
|
||||
@@ -0,0 +1,3 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Native-road output, override, and stage contracts."}
|
||||
{"file":".trellis/spec/blender/asset-generation.md","reason":"Native provider adapter and Blender output requirements."}
|
||||
{"file":".trellis/spec/guides/artifact-parity-guide.md","reason":"Intentional render-output change validation."}
|
||||
@@ -0,0 +1,10 @@
|
||||
# Implementation
|
||||
|
||||
1. Extend override validation and native marking generation for explicit
|
||||
double-yellow centre lines and per-side edge-line styles.
|
||||
2. Extend the Chinese Workbench selection/form flow without adding another
|
||||
editor or output format.
|
||||
3. Add compiler and Workbench regression coverage for valid and invalid
|
||||
payloads, generated geometry, and layer adapter completeness.
|
||||
4. Compile/check Nantaizi, run native/Workbench/build-stage/Blender tests,
|
||||
then run the native Blender/Cesium/preview chain.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Native road marking semantics
|
||||
|
||||
## Goal
|
||||
|
||||
Finish the native road marking model so the Nantaizi Road Workbench can
|
||||
persist meaningful centre-line and road-edge-line styles, regenerate native
|
||||
geometry deterministically, and deliver the same result to Blender/Cesium.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Add an explicit double-yellow-solid centre-line semantic. It must not be a
|
||||
generic double-line switch that permits invalid colour/pattern combinations.
|
||||
- Keep all marking edits area-local in `native-road-overrides.json`; generated
|
||||
GeoJSON remains derived output.
|
||||
- Make road edge lines selectable and style-editable in the Chinese Road
|
||||
Workbench, alongside existing centre lines and lane separators.
|
||||
- Preserve the default Nantaizi output unless a user supplies an override.
|
||||
- Native geometry must remain independent of osm2streets render geometry.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] A `center-line-style` override with `double: true`, `yellow`, and
|
||||
`solid` emits two offset centre-line polygons with an explicit effective
|
||||
style, while invalid double combinations are rejected.
|
||||
- [ ] An `edge-line-style` override targets one directional road side and
|
||||
supports white/yellow plus solid/dashed styles.
|
||||
- [ ] The Workbench exposes Chinese selection feedback, stages the correct
|
||||
override payload, and only shows the double-yellow choice for centre lines.
|
||||
- [ ] Nantaizi native compile/check, Workbench/native tests, Blender pure
|
||||
tests, and native Blender/Cesium/preview build pass.
|
||||
|
||||
## 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": "native-road-marking-semantics",
|
||||
"name": "native-road-marking-semantics",
|
||||
"title": "Native road marking semantics",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Native geometry correctness and output contracts."}
|
||||
{"file":".trellis/spec/blender/testing.md","reason":"Blender validation requirements."}
|
||||
@@ -0,0 +1,18 @@
|
||||
# Design
|
||||
|
||||
Each approach contributes its two carriageway-edge points at the common
|
||||
cutback distance. Points are ordered around the junction node. For each pair
|
||||
from adjacent approaches, the compiler samples a deterministic quadratic
|
||||
Bezier whose control point follows the pedestrian-side curb arc toward the
|
||||
junction. Rounded plans use a larger cutback than the legacy straight envelope
|
||||
so this visible curb shape still contains all turning connectors. Approach road
|
||||
surfaces terminate at the same cutback, so they cannot cover the junction
|
||||
outline in 3D output.
|
||||
|
||||
The curve is accepted only when the support intersection is finite, the pair
|
||||
belongs to different approaches, and the resulting ring remains valid and
|
||||
contains all published connector coordinates. Otherwise the original straight
|
||||
chord remains for that corner and the plan reports a mixed/fallback boundary.
|
||||
|
||||
Lane connectors remain a separate vehicle-path layer. This task changes only
|
||||
the road/intersection outline and sidewalk-corner shape.
|
||||
@@ -0,0 +1,2 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Native geometry and build-stage contracts."}
|
||||
{"file":".trellis/spec/guides/artifact-parity-guide.md","reason":"Intentional geometry output change validation."}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Implementation
|
||||
|
||||
1. Build a rounded junction boundary from ordered approach-edge records with
|
||||
tangent support-line intersections and deterministic curve samples.
|
||||
2. Expose boundary mode/provenance and preserve containment fallback.
|
||||
3. Extend focused native-road tests for curved ordinary intersections.
|
||||
4. Validate Nantaizi compile/check, workbench tests, and native 3D build.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Rounded native road junctions
|
||||
|
||||
## Goal
|
||||
|
||||
Replace the octagonal native junction outline with smooth, tangentially joined
|
||||
road-edge corners for ordinary Nantaizi T and cross junctions.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Junction surface boundaries must connect adjacent approach carriageway edges
|
||||
with a smooth outward curve rather than a straight octagonal chord.
|
||||
- Sidewalk corner surfaces must use the same rounded boundary concept so road
|
||||
and pedestrian geometry do not disagree visually.
|
||||
- Preserve a deterministic straight-edge fallback and an explicit diagnostic
|
||||
when a corner cannot be safely constructed.
|
||||
- Do not change lane connector semantics or derive geometry from osm2streets.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] Ordinary cross/T fixtures generate rounded junction polygons with more
|
||||
than the prior eight straight boundary vertices and `boundary_mode` records
|
||||
the chosen style.
|
||||
- [x] Connector containment remains valid and degenerate geometry falls back
|
||||
without publishing self-intersecting polygons.
|
||||
- [x] Nantaizi compile/check and native Blender/Cesium/preview succeed.
|
||||
|
||||
## Verification Record
|
||||
|
||||
- Native geometry and Workbench tests passed.
|
||||
- Nantaizi native compile/check passed with `ok=true`.
|
||||
- Native Blender/Cesium/preview verification was completed manually in a
|
||||
working Blender environment.
|
||||
|
||||
## 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": "native-rounded-junctions",
|
||||
"name": "native-rounded-junctions",
|
||||
"title": "Rounded native road junctions",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-18",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-13-native-road-compiler",
|
||||
"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,51 @@
|
||||
# Design
|
||||
|
||||
## Provider Boundary
|
||||
|
||||
`build-area.js` will resolve a native provider by default. Native stages consume only OSM, native
|
||||
road overrides, and `native-road/` outputs. Legacy `intermediates`, `reimport`, and osm2streets
|
||||
Blender input remain behind explicit legacy selection and keep their existing output contract.
|
||||
|
||||
## Native Data Flow
|
||||
|
||||
```text
|
||||
OSM + native-road-overrides.json
|
||||
-> compile-native-roads.js
|
||||
-> native-road/layers/*.geojson + compiled.json + traffic-signals.json
|
||||
-> Blender generate_scene.py --native-road --traffic-signals
|
||||
-> .blend
|
||||
-> Cesium/package GLB + manifest + runtime/traffic-signals.json
|
||||
-> preview HTML + optional native vehicle route
|
||||
```
|
||||
|
||||
The native compiler is the sole authority for road geometry, lane semantics, intersection surfaces,
|
||||
stop lines, and signal runtime. No native stage reads `geojsonDir` or legacy assembly files.
|
||||
|
||||
## Preview Route
|
||||
|
||||
Add a native route adapter that derives route segments from `compiled.json` model roads/endpoints and
|
||||
native lane/connector geometry. If the native route cannot be built for a valid area, preview remains
|
||||
usable without vehicles and records a warning; missing legacy osm2streets lane polygons is never a
|
||||
hard failure on the native path.
|
||||
|
||||
## Package Runtime
|
||||
|
||||
The package stage will copy the native-road signal runtime into its staging runtime directory and
|
||||
declare it in the package manifest. Preview receives the package-relative runtime URI. Legacy signal
|
||||
runtime publication remains conditional on the legacy provider.
|
||||
|
||||
## Configuration And Compatibility
|
||||
|
||||
`roadProvider` defaults to `native`; native stages are the default stage set. QGIS config fields and
|
||||
legacy stage aliases remain accepted for explicit migration/debug commands, but native manifests and
|
||||
docs do not claim them as inputs.
|
||||
|
||||
The Workbench may continue to read an existing `geojsonDir` only for an explicitly labeled reference
|
||||
layer and comparison counters. Those reads are optional, isolated from native compile state, and must
|
||||
never become required inputs for `/api/state`, native editing, package publication, or preview.
|
||||
|
||||
## Rollback
|
||||
|
||||
The original QGIS/osm2streets branch baseline is tagged
|
||||
`qgis-original-baseline-20260818` on `main`. Reverting the native-only work can therefore use the tag
|
||||
or switch to `main`; no destructive cleanup of legacy scripts 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."}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user