docs(assets): document reusable asset package contract
This commit is contained in:
@@ -25,9 +25,10 @@
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
`compress` 是完整构建和 `all` 的默认末端阶段。它在已有 Cesium GLB 上执行 texture
|
||||
resize + WebP transcode,并以标准 `<area-id>.glb`、`.json` 和预览 HTML 替换未压缩导出。
|
||||
未压缩版本只在 `<areaDir>/_pipeline/compress-*` 临时目录中存在,成功或失败后都会清理。
|
||||
`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` 是该阶段调用的低层脚本,也可单独运行做实验。
|
||||
|
||||
@@ -63,18 +64,16 @@ npm run compress:glb -- --input in.glb --output out.glb [options]
|
||||
|
||||
- 输入必须是现有 `.glb` 文件;`--output` 必须不同于 `--input`。
|
||||
- 低层脚本仍输出到与输入不同的路径;`build-area.js` 使用临时输入/输出路径,避免原地压缩。
|
||||
- `compress` 阶段依赖标准 `glb`、`metadata` 和 `cesiumPreview` 已存在,并在全部临时交付文件
|
||||
生成和校验后以 `renameSync` 替换它们。
|
||||
- `compress` 阶段依赖 staged `glb` 与 `metadata` 已存在,并在全部临时交付文件生成和校验后
|
||||
以 `renameSync` 替换它们。
|
||||
- 默认链固定为 `gltf-transform resize -> gltf-transform webp`。
|
||||
- `compress` 阶段默认 `textureSize=768`;低层脚本单独运行时默认 `--texture-size 1024`。
|
||||
- `build-area.js` 最终只保留 `<fileStem>.glb`、`<fileStem>.json` 和
|
||||
`<fileStem>-cesium-preview.html`;不会写并列 `-compressed-*` 交付物。
|
||||
- 伴生 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
|
||||
|
||||
@@ -85,23 +84,21 @@ 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: `all` 或默认构建先导出 Cesium,再压缩成标准交付物。
|
||||
- Good: `--stages compress` 对已有标准交付物重新压缩,临时源 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 compress:glb -- ... --metadata`
|
||||
- 对目标区域跑一次默认 `npm run build:area`
|
||||
- `node scripts/glb-digest.js <area>.glb` 确认可解析结构和扩展
|
||||
- 浏览器/Cesium 预览标准 HTML,确认 `EXT_texture_webp` 在目标环境可加载
|
||||
@@ -122,6 +119,103 @@ 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` 只能是包内正向相对路径,禁止绝对路径、反斜杠和 `..`。
|
||||
- `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/` 的车辆模型或动态交通灯加进 `assets`,使资产包绑定本项目的验证运行时。
|
||||
|
||||
### 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;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## OSM 预检命令
|
||||
|
||||
### 1. Scope / Trigger
|
||||
@@ -605,7 +699,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` /
|
||||
增量构建判断。
|
||||
|
||||
@@ -627,6 +721,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
|
||||
```
|
||||
|
||||
代码入口:
|
||||
@@ -719,9 +814,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
|
||||
|
||||
@@ -786,8 +890,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,
|
||||
提示重跑对应阶段。
|
||||
@@ -806,6 +910,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`
|
||||
@@ -924,15 +1029,16 @@ gpkg: path.resolve(outputOverrides.gpkg || path.join(areaDir, `${fileStem}.gpkg`
|
||||
| `intermediates` | 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 | 标准 `.glb/json/html` |
|
||||
| `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,11 +1053,11 @@ gpkg: path.resolve(outputOverrides.gpkg || path.join(areaDir, `${fileStem}.gpkg`
|
||||
```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", "compress"],
|
||||
all: ["intermediates", "blender", "cesium", "compress", "package", "preview"],
|
||||
```
|
||||
|
||||
`preview` 不在 `all` 里——`cesium` 已经包含它。`compress` 在 `all` 里,确保完整构建
|
||||
以压缩后的标准路径交付。
|
||||
`compress`、`package`、`preview` 均在 `all` 中,确保完整构建以压缩后的可复用静态 package
|
||||
以及可运行的验证预览交付。
|
||||
|
||||
### `intermediates` 与 `reimport` 互斥
|
||||
|
||||
@@ -961,9 +1067,8 @@ all: ["intermediates", "blender", "cesium", "compress"],
|
||||
|
||||
这是**显式拒绝而不是警告**——两者同时开,无论谁先跑,另一个的工作都白做。
|
||||
|
||||
`normalizeAreaConfig` 里 `stages.reimport` 和 `stages.preview` 硬编码为 `false`,
|
||||
只能靠 `--stages` 显式请求;`stages.compress` 为默认 `true`。
|
||||
恢复动作和补丁动作不该被一份配置文件变成默认行为,压缩则属于标准交付链。
|
||||
`normalizeAreaConfig` 里只有 `stages.reimport` 硬编码为 `false`,只能靠 `--stages` 显式请求;
|
||||
`stages.compress`、`stages.package` 和 `stages.preview` 为默认 `true`。
|
||||
|
||||
---
|
||||
|
||||
@@ -1000,7 +1105,7 @@ parity 校验依赖 stage 的 stdout 标记来判断阶段是否跑到(如 `SC
|
||||
| 在阶段函数里现拼输出路径 | 路径规则出现第二份定义 |
|
||||
| 低层脚本直接读 `config/areas/*.json` | 打破两层配置边界 |
|
||||
| 布尔配置用 `\|\|` 兜底 | `false` 被翻转成默认值 |
|
||||
| 让 `reimport` / `preview` 能从配置文件默认开启 | 恢复动作变成常规行为 |
|
||||
| 让 `reimport` 能从配置文件默认开启 | 恢复动作变成常规行为 |
|
||||
| 新阶段忘了 `ensureFile` 前置校验 | 单跑时报底层堆栈而非人话 |
|
||||
| 改 stage 的 stdout 标记 | 静默破坏 parity 契约 |
|
||||
| 顺手把多份 `parseArgs` 合并 | 扩大 diff,且独立入口的独立性是刻意的 |
|
||||
|
||||
@@ -51,17 +51,21 @@ config/areas/<id>.json
|
||||
│ → _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
|
||||
│ + 拷贝 lib/cesium-preview.{js,css}
|
||||
+ _preview/ 车辆巡航路线、模型、动态信号
|
||||
│ → _pipeline/stages/preview.manifest.json
|
||||
```
|
||||
|
||||
**阶段之间只通过磁盘产物耦合**,不传内存状态。这是单跑任意阶段能work 的前提。
|
||||
@@ -87,7 +91,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 +103,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 | 产物一致性校验驱动 |
|
||||
|
||||
Reference in New Issue
Block a user