本项目以 C++ 版 cesium-terrain-builder(CTB)及其使用的 GDAL 行为作为 oracle,
对齐 Rust 版 ctb-rs 的 Terrain 输出。比较对象是 ctb-tile 生成的 .terrain
文件路径集合,以及 gzip 解压后的 heightmap payload;压缩容器字节差异不作为行为差异。
本次对齐覆盖 Terrain + Average 路径,包含:
- 真实 Copernicus DSM COG 的 geodetic 全量输出。
world3857/source.tif的 Mercator Terrain VRT block pooled 输出。
- 输入:
tests/Copernicus_DSM_COG_10_N22_00_E108_00_DEM.tif(Git LFS;原始 路径/Users/sander/coding/demo/download-data/Copernicus_DSM_COG_10_N22_00_E108_00_DEM.tif) - 命令范围:
ctb-tile -q -c 4 -s 14 -e 0 - C++ oracle 路径数:11391
- Rust 修复后路径数:11391
- 输入:
/private/tmp/ctb-mercator-pooled-check/world3857/source.tif - 数据:720×720、EPSG:3857、Int32
- C++ oracle 路径数:38
- Rust 修复后路径数:38
路径集合一致,但解压后 payload 差异较大:
| 项 | 修复前 |
|---|---|
| Terrain 文件总数 | 11391 |
| payload 相同 | 32 |
| payload 不同 | 11359 |
| 最大 u16 高度差 | 1919 |
| 最大高度差 | 383.8 m |
路径集合一致,38 个文件中有 10 个 payload 不同:
0/0/0、2/0/0、2/0/1、2/0/2、2/1/0、2/1/1、
2/1/2、2/1/3、2/2/0、2/2/2
C++ 的 transformer 使用 GDAL 选出的 overview GeoTransform 做坐标数学,但
psWarpOptions->hSrcDS 仍指向主数据集,因此最终 source 窗口按 base 数据尺寸
夹取并读取。Rust 不能只按 base 数据尺寸或只按 overview 尺寸计算,需要保留
level: 0 + overview metadata 的采样语义,并用 overview GT 计算 pooled
source window。
GDAL 的 average_margin 由实际 pooled source window 宽高推导,而不是由固定
HEIGHTMAP_TILE_SIZE=65 或原始 transform ratio 推导。真实 COG 在低 zoom
会出现很大的 pooled margin;修正后才与 C++ 的 WarpRegion 行为一致。
C++ 的 Mercator Terrain 流程:
TerrainTiler用mGrid.tileSize()创建 Mercator VRT,尺寸为 256×256。- 只
RasterIO读取左上 65×65 高度样本。 VRTWarpedDataset默认 block 尺寸为min(nXSize,512) × min(nYSize,128),因此实际 warp destination 是 256×128。
Rust 之前按 65×65 计算 pooled source window 和 margin,导致 Mercator 首 block
与 C++ 不一致。修复后 TerrainSamplePlan 保存:
- geodetic:65×65
- mercator:256×128
输出仍固定为 65×65,只影响 pooled window、margin 和行坐标计算。
C++ 的 GWKAverageOrModeComputeLineCoords 对整行 warp block 像素调用
GDALApproxTransform,Mercator 每次变换 256 个点,而不是逐像素调用精确
GDALGenImgProjTransform。
Rust 之前使用逐像素精确变换,递归近似分支产生的坐标与 C++ 有约 1e-14
级误差;当 source 坐标位于 0.5 边界附近时,会表现为 ±1 的整数舍入差。
修复后移植了 GDALApproxTransformInternal 的等价逻辑。
本机 C++ GDAL 构建会把以下表达式收缩为 FMA:
origin + pixel * pixel_sizeGDALApproxTransformInternal的插值和误差表达式
Rust 使用普通 +/* 时末位浮点结果不同。为逐位匹配 C++ oracle,坐标计算
改为 f64::mul_add。
GDALApproxTransformInternal 的 half-2 和 fallback 分支只对
nPoints - nMiddle - 2 个点执行 base transform;最后一个点会被
Start/Middle/End(SME)结果覆盖。
Rust 修正为:
dst_x[n_middle + 1..n_points - 1]
dst_y[n_middle + 1..n_points - 1]
避免把 C++ 随后覆盖的点当作独立精确变换输入。
| 场景 | 路径一致 | 解压后 payload 差异 |
|---|---|---|
Mercator world3857/source.tif |
38/38 | 0 |
| Copernicus geodetic 全量 | 11391/11391 | 0 |
验证门禁:
cargo fmt --all -- --checkcargo clippy --all-targets -- -D warningscargo test- 88 个 lib 单元测试
- 9 个
ctb-tile参数测试 - 12 个 CLI 测试
- 1 个
ctb-info测试
cargo build --release- Mercator 38 个 Terrain payload 与 C++ oracle 逐文件比较
- Copernicus 11391 个 Terrain payload 与 C++ oracle 逐文件比较
src/terrain_sampling.rs:Terrain pooled Average、source window、margin、GDALApproxTransform行坐标和 FMA 对齐。TECHNICAL_PLAN.md:P14/P15 的根因、实施范围和验证记录。TEST_STRATEGY.md:C++/Rust oracle 测试策略和实测结果。TODO.md:P14/P15 任务状态。
本次总结针对 Terrain + Average 路径,重点修复真实 COG geodetic 和 Mercator Terrain VRT block pooled 行为。RasterTiler、非 Average resampling 的完整 C++ 差分不属于本次 P15 范围。