@@ -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, 且独立入口的独立性是刻意的 |