简介:这份资源是面向三维 GIS 与 Cesium 开发者的地形数据处理工具,用于将 TIF 高程影像切割为 Cesium 可直接加载的 .terrain 瓦片,解决地形切片流程繁琐、格式不兼容的问题,适合具备一定 GIS 基础、需要自建地形服务的开发与测绘人员。压缩包共 108 个文件,约 20.53MB,以 40 个 dll 动态库和 4 个 exe 可执行程序为核心,配合 29 个 csv 投影与基准参数表、6 个 wkt 坐标系定义、8 个 gfs 配置及 xsd、xml 等元数据文件,另有 dgn、dxf 等中间数据与示例 terrain 成果,构成完整的切片运行环境。目前已有 3498 人学习下载,说明该工具在 Cesium 地形实践中具备一定认可度。读者可据此搭建本地切片环境,理解投影参数与瓦片组织方式,快速产出可用的地形数据,减少自行摸索与排错成本。
1. CTB 地形切片生成器:把 TIF 高程数据变成 Cesium 能直接吃的地形瓦片
手里有一份 30 米分辨率的 DEM 高程 TIF,想在 Cesium 里叠出真实起伏的三维地形,结果卡在第一步——Cesium 不认 TIF,它只认自己那套 quantized-mesh 格式的地形瓦片。CTB 地形切片生成器就是干这件事的:把 GeoTIFF 高程数据切成 Cesium 可直接加载的地形切片包,输出标准目录结构,丢到静态服务器上配个CesiumTerrainProvider就能用。它适合做三维 GIS 可视化、地形分析 Demo、数字孪生底图搭建的从业者,尤其是手上只有原始 DEM、不想依赖在线地形服务的场景。这篇按「它到底切了什么 → 怎么跑起来 → 参数怎么调 → 哪里会翻车」的顺序拆,新手能照着复现,熟手能看清边界。
2. 切片原理与数据准备:quantized-mesh 到底存了什么
2.1 从 TIF 到 quantized-mesh 的转换链路
Cesium 的地形服务不是简单把 TIF 切图片,它用的是一种叫 quantized-mesh 的网格编码格式。理解这条链路,后面调参数才不会瞎猜。整个流程大致是:读取 GeoTIFF 的栅格高程值 → 按经纬度构建 TMS 瓦片金字塔(第 0 层 2 块、第 1 层 8 块,逐层四叉细分)→ 每块瓦片内按地形复杂度自适应三角化 → 顶点坐标量化压缩 → 输出.terrain二进制瓦片加layer.json元数据。
关键点在于「自适应三角化」:平坦区域用少量大三角形,起伏剧烈区域加密顶点,这样在保证视觉精度的同时把瓦片体积压下来。这也是为什么同一份 DEM,切出来的包可能几百 MB 也可能几个 GB,取决于地形复杂度和最大层级设置。CTB 生成器封装的就是这条链路,输入 TIF、输出瓦片目录,中间不需要你手写三角化算法。
数据准备上有几个硬性前提,先自查再动手:
| 检查项 | 要求 | 不满足的后果 |
|---|---|---|
| 坐标系 | 必须是地理坐标(WGS84,EPSG:4326) | 切片错位、地形飘到海里 |
| 数据类型 | 16 位或 32 位整型/浮点高程 | 8 位数据精度不够,地形台阶感明显 |
| NoData 值 | 明确且统一 | 空洞区域出现异常尖刺或深坑 |
| 高程单位 | 米 | 英尺数据不换算,山体高度差 3 倍多 |
坐标系这一条踩坑率最高。很多公开 DEM 是投影坐标(比如 UTM),直接喂进去切出来的瓦片经纬度全错。常见做法是先用 GDAL 转成 EPSG:4326 再切:
# 把投影坐标 DEM 重投影到 WGS84 地理坐标,并统一 NoData 为 -9999 gdalwarp -t_srs EPSG:4326 -dstnodata -9999 input_utm.tif output_wgs84.tif # 确认输出坐标系和范围,重点看 Coordinate System 和 Corner 坐标 gdalinfo output_wgs84.tif-t_srs EPSG:4326指定目标坐标系,-dstnodata -9999把无数据区统一成一个明确值,避免切片时把 NoData 当成 0 米海拔处理导致地形出现平台或尖刺。gdalinfo输出里重点核对Coordinate System is后面是不是 WGS 84,以及四个角点经纬度是否落在合理范围(经度 -180~180,纬度 -90~90)。如果角点经纬度是几万几十万的数值,说明还是投影坐标,得继续转。
2.2 瓦片金字塔层级怎么定
最大层级(max zoom)直接决定切片耗时和包体积,也决定地形细节上限。经验上按数据分辨率和目标显示尺度反推:30 米 DEM 切到 12~14 层基本够用,再往上切只是把同样的高程插值得更密,视觉提升有限但体积翻倍。
粗略估算公式:某层级瓦片的地面分辨率 ≈ 156543 / 2^level 米/像素(赤道处)。30 米数据对应 level 约 12~13 就接近原始精度,切到 15 层以上属于过度切片。我一般先切到 12 层看效果,确认加载正常、地形形态对,再决定要不要补到 14 层。层级每加 1,瓦片数量约翻 4 倍,切片时间和磁盘占用同步上涨,这一步别一上来就拉满。
3. 跑通切片:命令行参数与输出结构
3.1 最小可运行命令
生成器通常是命令行工具,核心就三个参数:输入 TIF、输出目录、最大层级。最小跑通命令长这样:
# 最小切片:输入 DEM,输出到 terrain_out,最大 12 层 ctb-terrain --input ./dem_wgs84.tif \ --output ./terrain_out \ --max-zoom 12 \ --format quantized-mesh--input指向已转好坐标系的 GeoTIFF;--output是瓦片根目录,工具会自动建层级子目录;--max-zoom控制金字塔深度;--format指定输出格式,Cesium 地形用quantized-mesh。跑完输出目录结构大致是:
terrain_out/ ├── layer.json ├── 0/ │ ├── 0/ │ │ └── 0.terrain │ └── 1/ │ └── 0.terrain ├── 1/ │ └── ... └── 12/ └── ...layer.json是元数据入口,里面记录了可用层级范围、瓦片格式、边界范围。Cesium 加载时先请求这个文件,再按需拉取各层级瓦片。目录里的数字是 TMS 的{z}/{x}/{y}结构,注意 Cesium 的 y 轴方向和标准 TMS 是反的,生成器一般已经处理好,但如果你自己拼 URL 模板,y 要按2^z - 1 - y翻转,这是手写服务端时最容易错的地方。
3.2 参数逐项说明与调优
除了三个核心参数,实际使用中还有几个值得关注的开关:
# 带调优参数的完整命令 ctb-terrain --input ./dem_wgs84.tif \ --output ./terrain_out \ --max-zoom 14 \ --min-zoom 0 \ --format quantized-mesh \ --vertex-density 1.0 \ --threads 8 \ --nodata -9999--min-zoom一般保持 0,让低层级也有瓦片兜底,否则相机拉远时地形会消失。--vertex-density控制三角化密度,默认 1.0,调大顶点更密、地形更细腻但体积涨,调小则相反,做性能敏感的场景可以降到 0.6~0.8 试。--threads按 CPU 核数给,切片是 CPU 密集型,8 核机器给 8 能明显缩短时间。--nodata要和前面 GDAL 处理时设的值一致,否则空洞区会被当成有效高程。
参数之间有个隐性约束:max-zoom和vertex-density同时调高,体积是乘法级增长。我见过把 14 层加密度 1.5 一起上的,单省范围切出好几个 GB,浏览器加载卡到没法用。稳妥做法是先固定密度 1.0,只调层级,效果满意后再微调密度。
3.3 本地验证切片结果
切完别急着往 Cesium 里塞,先在本地起个静态服务验证瓦片能正常请求:
# 在输出目录起静态服务,模拟 Cesium 的请求路径 cd terrain_out python -m http.server 8080浏览器访问http://localhost:8080/layer.json,能看到 JSON 内容说明元数据正常;再随便访问一个http://localhost:8080/0/0/0.terrain,返回二进制而不是 404,说明瓦片路径结构对。这一步能提前排掉「目录层级错位」「layer.json 缺失」这类低级问题,比在 Cesium 里对着黑屏猜要快得多。
4. 接入 Cesium:TerrainProvider 配置与常见错位
4.1 加载自定义地形服务
切片包放到静态服务器后,Cesium 端配置很直接:
// 初始化 Viewer 时挂上自定义地形 const viewer = new Cesium.Viewer('cesiumContainer', { terrainProvider: new Cesium.CesiumTerrainProvider({ url: 'http://your-host/terrain_out/', // 指向 layer.json 所在目录 requestVertexNormals: true, // 请求法线,光照更真实 requestWaterMask: false // 无水体数据时关掉,省请求 }) }); // 相机飞到目标区域,确认地形起伏 viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(116.0, 40.0, 50000) });url必须指向layer.json所在的那一级目录,末尾带不带斜杠要看服务端行为,本地http.server建议带上。requestVertexNormals: true让 Cesium 请求顶点法线,配合光照能看出山体明暗,但会增加瓦片体积和解析开销,纯展示可以关掉。requestWaterMask只有切片时生成了水体掩膜才有意义,普通 DEM 切片没有,开着只会多一轮 404 请求。
4.2 地形错位与高度异常的定位
接入后最常见两类问题:地形整体偏移,或者高度明显不对。偏移九成是坐标系问题——切片用的不是 WGS84,或者 Cesium 端相机坐标和地形坐标系不匹配。高度异常则多是单位或 NoData 处理问题:英尺当米用会整体偏高约 3.28 倍,NoData 没处理好会在空洞区出现异常尖刺。
排查顺序建议固定下来:先看layer.json里的bounds是否覆盖目标区域,再看单个瓦片用工具解析出的顶点范围是否落在预期经纬度内,最后才怀疑 Cesium 端配置。这个顺序能把问题范围从「整条链路」缩到「某一环」,比东改一处西改一处高效得多。
5. 避坑清单:切片翻车的五个高频现场
5.1 切片跑完但 Cesium 一片空白
现象:目录里瓦片文件都在,layer.json也能访问,Cesium 里地形却是平的或者直接报错。原因通常是layer.json里的format字段和实际瓦片格式不一致,或者tiles数组里的层级范围和目录实际层级对不上。解决:打开layer.json核对format是否为quantized-mesh-1.0,available数组的起止层级和磁盘上的数字目录是否吻合,不一致就重新生成或手动修正元数据。
5.2 地形出现规则网格状台阶
现象:山体表面有明显方格状台阶,像马赛克。原因多为源数据是 8 位整型,高程量化精度只有 256 级,起伏被压成阶梯。解决:换 16 位或 32 位源数据重切;实在只有 8 位数据,切片前用 GDAL 做一次平滑或重采样到更高位深,但精度损失无法完全挽回,属于源头问题。
5.3 切片时间长得离谱
现象:一个省的范围切了几个小时还没完。原因通常是max-zoom设太高叠加vertex-density偏大,或者threads没给够。解决:先降层级到 12 试切,确认流程通再逐步加;threads按物理核数给;切片前用gdalinfo看数据范围,范围过大先裁切到目标区域再切,别拿全国数据切一个市。
5.4 空洞区域出现尖刺或深坑
现象:DEM 边缘或云遮挡区出现异常尖峰、深坑。原因是 NoData 值没统一,切片时被当成有效高程参与三角化。解决:切片前用gdalwarp -dstnodata统一 NoData,切片命令的--nodata参数与之保持一致;必要时先用gdal_fillnodata填补小空洞再切。
5.5 浏览器加载卡顿、内存飙升
现象:地形能显示但拖动卡顿,标签页内存持续上涨。原因是瓦片体积过大或层级过深,单瓦片解析开销高。解决:降max-zoom或vertex-density,开启服务端 gzip 压缩(.terrain是二进制,压缩率不错),并确认 Cesium 的maximumScreenSpaceError没设得过小导致过度请求。
6. 进阶技巧:用裁切和分层策略控制包体积
切片包体积是这类资源落地时最容易被低估的成本。一份全国 30 米 DEM 切到 14 层,轻松几十 GB,静态托管和加载都吃不消。我一般用两个策略压体积:按需裁切和分层精度。
按需裁切是先确定业务实际覆盖范围,用 GDAL 裁出目标区域再切:
# 按经纬度范围裁切,只保留目标区域,减少无效瓦片 gdal_translate -projwin 115.0 41.0 117.0 39.0 \ -projwin_srs EPSG:4326 \ dem_wgs84.tif dem_clip.tif-projwin四个值依次是左上经度、左上纬度、右下经度、右下纬度,-projwin_srs声明这个范围的坐标系。裁完再切,瓦片数量能降一个数量级,这是最直接有效的体积控制手段。
分层精度则是核心区域切深、外围切浅。做法是把 DEM 按区域拆成两份,核心区切到 14 层,外围只切到 11 层,输出到同一个目录的不同子路径,Cesium 端用多个TerrainProvider叠加或者按相机距离切换。这个方案实现上稍复杂,但对「城市核心区精细、周边大范围概览」的场景很划算。
验证体积是否合理,我有个习惯:切完后统计各层级瓦片数量和总大小,看层级间增长是否符合四叉树规律。如果某一层瓦片数突然暴涨,往往是该层触发了大量空洞瓦片或边界瓦片,回头检查 NoData 和裁切边界。从那以后我每次切片前都强制走一遍「转坐标系 → 统一 NoData → 裁切 → 试切低层级 → 全量切」的流程,跳过任何一步都可能在后面某个环节翻车。希望帮到你。
本文还有配套的精品资源,点击获取