Mac 下使用 Docker 制作 Cesium Terrain 地形切片完整流程
在 Mac 环境下使用 Docker Desktop 和 `ctb-quantized-mesh` 工具,将 DEM 数据制作成 Cesium 可直接加载的 quantized-mesh 地形切片。
1. 文档说明#
本文重点解决以下问题:
- 如何在 Mac 上安装 Docker Desktop;
- 如何拉取适合 Apple Silicon 的 quantized-mesh 镜像;
- 如何将单个 DEM 文件转换为 Cesium terrain;
- 如何将多个 DEM 文件合并为 VRT 后统一切片;
- 如何指定 terrain 地形切片层级;
- 如何处理大 DEM 切片时报错或性能问题;
- 如何将生成结果与 Nginx 静态发布目录衔接;
- 如何在 Cesium 前端中加载生成的地形切片。
本文统一使用以下工作目录:
bash/Volumes/Lenovo/xlgl/ 建议最终目录结构为: ```bash /Volumes/Lenovo/xlgl/ ├── dem/ │ ├── xlgl_dem.tif │ └── ... │ ├── work/ │ ├── xlgl_dem.vrt │ └── xm_30m_dem_lifted_tiled.tif │ ├── terrain/ │ ├── layer.json │ ├── 0/ │ ├── 1/ │ ├── 2/ │ └── ... │ └── imagery/ └── ...
其中:
| 目录 | 说明 |
|---|---|
/Volumes/Lenovo/xlgl/dem/ |
存放原始 DEM 数据 |
/Volumes/Lenovo/xlgl/work/ |
存放中间文件,如 VRT、重采样或 tiled DEM |
/Volumes/Lenovo/xlgl/terrain/ |
存放最终生成的 Cesium terrain 地形切片 |
/Volumes/Lenovo/xlgl/imagery/ |
存放影像瓦片,可选 |
2. 总体技术思路#
Cesium 的地形服务不是直接加载普通 GeoTIFF DEM,而是需要加载符合 Cesium terrain 规范的切片数据。
本流程采用:
textDEM GeoTIFF ↓ 必要时预处理为 tiled GeoTIFF / VRT ↓ ctb-tile 生成 quantized-mesh terrain tiles ↓ ctb-tile 生成 layer.json ↓ Nginx 静态发布 ↓ CesiumTerrainProvider 加载
核心工具为:
textDocker Desktop for Mac ctb-quantized-mesh GDAL Nginx CesiumJS
其中 ctb-quantized-mesh 用于生成 Cesium quantized-mesh 格式地形切片。
3. 安装 Docker Desktop for Mac#
3.1 下载 Docker Desktop#
到 Docker 官方下载页面下载 Docker Desktop for Mac。
根据你的 Mac 芯片选择对应版本:
- Apple Silicon:适用于 M1 / M2 / M3 / M4 系列芯片;
- Intel:适用于 Intel 芯片 Mac。
安装完成后,首次启动 Docker Desktop,按照向导完成初始化。
3.2 验证 Docker 是否安装成功#
打开终端执行:
bashdocker --version
如果能看到 Docker 版本号,说明 Docker 命令可用。
继续执行:
bashdocker run hello-world
如果输出 Hello from Docker!,说明 Docker Desktop 可以正常拉取并运行容器。
4. 配置 Docker 文件共享权限#
Mac 上的 Docker Desktop 运行的是 Linux 容器。容器默认不能随意访问 Mac 本地磁盘目录,需要在 Docker Desktop 中显式授权。
如果后续执行容器命令时挂载:
bash-v "/Volumes/Lenovo/xlgl:/data"
失败,或者容器中看不到 /data 下的文件,需要检查 Docker Desktop 文件共享配置。
操作路径:
textDocker Desktop → Settings → Resources → File Sharing
将以下目录加入共享:
bash/Volumes/Lenovo
或者只加入项目目录:
bash/Volumes/Lenovo/xlgl
建议加入:
bash/Volumes/Lenovo
这样后续多个项目目录都可以被 Docker 容器访问。
5. 拉取 quantized-mesh 镜像#
Apple Silicon Mac 推荐直接使用 GHCR 镜像:
bashdocker pull ghcr.io/tum-gis/ctb-quantized-mesh:latest
该镜像中已经包含:
ctb-tilegdalbuildvrt- 常用 GDAL 工具
拉取完成后可以测试镜像是否可运行:
bashdocker run --rm ghcr.io/tum-gis/ctb-quantized-mesh:latest ctb-tile --help
如果能看到 ctb-tile 的参数说明,说明镜像可用。
6. 准备 DEM 数据#
6.1 单个 DEM 文件#
假设你的 DEM 文件为:
bash/Volumes/Lenovo/xlgl/dem/xlgl_dem.tif
检查文件是否存在:
bashls /Volumes/Lenovo/xlgl/dem/xlgl_dem.tif
查看 DEM 基本信息:
bashgdalinfo /Volumes/Lenovo/xlgl/dem/xlgl_dem.tif
重点检查:
- 坐标系是否正确;
- 是否有 NoData;
- 像元大小是否合理;
- 范围是否覆盖目标区域;
- DEM 是否为单波段高程数据。
6.2 多个 DEM 文件#
如果 DEM 是多个 .tif 文件,例如:
bash/Volumes/Lenovo/xlgl/dem/ ├── dem_01.tif ├── dem_02.tif ├── dem_03.tif └── ...
建议先构建 VRT 虚拟拼接文件,再统一切片。
VRT 不会真正生成一个超大的 GeoTIFF,而是建立一个虚拟索引,适合多个 DEM 拼接后参与后续处理。
7. 对 DEM 做必要预处理#
7.1 为什么需要预处理#
如果 DEM 文件较大,或者切片过程中出现异常,可以先将 DEM 转换为 tiled GeoTIFF,并建立金字塔概览。
这样可以提高 GDAL 读取效率,也可以减少 ctb-tile 处理大数据时的报错概率。
常见适用情况:
- DEM 文件很大;
- 切片过程中内存占用过高;
- 切片速度很慢;
ctb-tile报错;- 需要生成较高层级 terrain;
- DEM 原始文件不是 tiled 结构。
7.2 转换为 tiled GeoTIFF#
示例:将原始 DEM 转换为 tiled GeoTIFF。
bashmkdir -p /Volumes/Lenovo/xlgl/work gdal_translate /Volumes/Lenovo/xlgl/xm_30m_dem_lifted.tif /Volumes/Lenovo/xlgl/work/xm_30m_dem_lifted_tiled.tif -of GTiff -co TILED=YES -co BIGTIFF=YES -co COMPRESS=LZW
参数说明:
| 参数 | 说明 |
|---|---|
-of GTiff |
输出为 GeoTIFF |
-co TILED=YES |
使用 tiled GeoTIFF 组织方式 |
-co BIGTIFF=YES |
支持超过 4GB 的大文件 |
-co COMPRESS=LZW |
使用 LZW 压缩,减小文件体积 |
7.3 建立金字塔概览#
对 tiled GeoTIFF 建立 overview:
bashgdaladdo -r average /Volumes/Lenovo/xlgl/work/xm_30m_dem_lifted_tiled.tif 2 4 8 16 32
参数说明:
| 参数 | 说明 |
|---|---|
-r average |
使用平均值重采样,适合 DEM |
2 4 8 16 32 |
建立多级概览 |
完成后,可以再次查看文件信息:
bashgdalinfo /Volumes/Lenovo/xlgl/work/xm_30m_dem_lifted_tiled.tif
如果能看到 Overviews 信息,说明金字塔概览已建立。
8. 单个 DEM 文件生成 terrain 地形切片#
8.1 创建输出目录#
本文使用不带重复 xlgl 的输出目录:
bashmkdir -p /Volumes/Lenovo/xlgl/terrain
8.2 使用原始 DEM 直接切片#
假设 DEM 为:
bash/Volumes/Lenovo/xlgl/dem/xlgl_dem.tif
执行:
bashdocker run --rm -v "/Volumes/Lenovo/xlgl:/data" ghcr.io/tum-gis/ctb-quantized-mesh:latest sh -lc ' ctb-tile -f Mesh -C -N -o /data/terrain /data/dem/xlgl_dem.tif && ctb-tile -f Mesh -C -N -l -o /data/terrain /data/dem/xlgl_dem.tif '
执行完成后,输出目录类似:
bash/Volumes/Lenovo/xlgl/terrain/ ├── layer.json ├── 0/ ├── 1/ ├── 2/ └── ...
8.3 使用预处理后的 tiled DEM 切片#
如果已经生成了:
bash/Volumes/Lenovo/xlgl/work/xm_30m_dem_lifted_tiled.tif
则建议使用该文件切片:
bashmkdir -p /Volumes/Lenovo/xlgl/terrain docker run --rm -v "/Volumes/Lenovo/xlgl:/data" ghcr.io/tum-gis/ctb-quantized-mesh:latest sh -lc ' ctb-tile -f Mesh -C -N -o /data/terrain /data/work/xm_30m_dem_lifted_tiled.tif && ctb-tile -f Mesh -C -N -l -o /data/terrain /data/work/xm_30m_dem_lifted_tiled.tif '
9. 多个 DEM 文件生成 terrain 地形切片#
9.1 构建 VRT 并切片#
如果 DEM 目录中有多个 .tif 文件:
bash/Volumes/Lenovo/xlgl/dem/*.tif
可以在容器中先构建 VRT,再执行 terrain 切片:
bashmkdir -p /Volumes/Lenovo/xlgl/work mkdir -p /Volumes/Lenovo/xlgl/terrain docker run --rm -v "/Volumes/Lenovo/xlgl:/data" ghcr.io/tum-gis/ctb-quantized-mesh:latest sh -lc ' cd /data/dem && gdalbuildvrt /data/work/xlgl_dem.vrt *.tif && ctb-tile -f Mesh -C -N -o /data/terrain /data/work/xlgl_dem.vrt && ctb-tile -f Mesh -C -N -l -o /data/terrain /data/work/xlgl_dem.vrt '
9.2 多 DEM 流程说明#
上述命令分为三步:
bashcd /data/dem
进入容器内 DEM 目录。
bashgdalbuildvrt /data/work/xlgl_dem.vrt *.tif
将多个 DEM TIFF 构建为一个 VRT。
bashctb-tile -f Mesh -C -N -o /data/terrain /data/work/xlgl_dem.vrt
生成 quantized-mesh 地形切片。
bashctb-tile -f Mesh -C -N -l -o /data/terrain /data/work/xlgl_dem.vrt
生成 layer.json。
10. 指定 terrain 生成层级#
10.1 参数说明#
ctb-tile 可以通过以下参数控制生成层级:
| 参数 | 说明 |
|---|---|
-s, --start-zoom |
起始层级 |
-e, --end-zoom |
结束层级 |
常用写法:
bash-s 13 -e 0
表示生成从 13 到 0 的层级。
注意:ctb-tile 的层级参数写法和一般 XYZ 瓦片工具习惯可能不同,通常可以使用:
bash-s 最大层级 -e 最小层级
例如:
bash-s 13 -e 0
10.2 生成 0 到 13 级 terrain#
bashmkdir -p /Volumes/Lenovo/xlgl/terrain docker run --rm -v "/Volumes/Lenovo/xlgl:/data" ghcr.io/tum-gis/ctb-quantized-mesh:latest sh -lc ' ctb-tile -f Mesh -C -N -s 13 -e 0 -o /data/terrain /data/dem/xlgl_dem.tif && ctb-tile -f Mesh -C -N -s 13 -e 0 -l -o /data/terrain /data/dem/xlgl_dem.tif '
10.3 生成高层级时的注意事项#
强行生成更高层级是可以的,但不一定有实际意义。
需要根据 DEM 分辨率判断合理最大层级。
例如:
| DEM 分辨率 | 建议最大层级 |
|---|---|
| 90m DEM | 8-10 级左右 |
| 30m DEM | 10-13 级左右 |
| 10m DEM | 13-15 级左右 |
| 更高精度 DEM | 可继续提高,但需测试性能 |
如果 DEM 分辨率较低,却强行生成到很高层级,可能出现:
- 视觉效果没有明显提升;
- 文件数量暴增;
- 生成时间大幅增加;
- 前端加载数据量变大;
- 局部瓦片实际精度不足。
11. Nginx 发布目录衔接#
生成 terrain 后,推荐使用 Nginx 静态发布。
如果 Nginx 配置为:
nginxserver { listen 8080; server_name localhost; root /Volumes/Lenovo/xlgl; location /terrain/ { autoindex off; add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods "GET, OPTIONS" always; add_header Access-Control-Allow-Headers "*" always; } location ~ ^/terrain/.*\.terrain$ { default_type application/octet-stream; add_header Content-Encoding gzip always; add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods "GET, OPTIONS" always; add_header Access-Control-Allow-Headers "*" always; } location ~ ^/terrain/.*layer\.json$ { default_type application/json; add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods "GET, OPTIONS" always; add_header Access-Control-Allow-Headers "*" always; } location /imagery/ { autoindex off; add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods "GET, OPTIONS" always; add_header Access-Control-Allow-Headers "*" always; } if ($request_method = OPTIONS) { return 204; } }
则 terrain 访问地址为:
texthttp://localhost:8080/terrain/
layer.json 地址为:
texthttp://localhost:8080/terrain/layer.json
实际文件路径为:
bash/Volumes/Lenovo/xlgl/terrain/layer.json
12. 测试 terrain 是否发布成功#
12.1 检查输出文件#
bashls /Volumes/Lenovo/xlgl/terrain
应能看到:
bashlayer.json 0 1 2 ...
12.2 测试 layer.json#
bashcurl -I http://localhost:8080/terrain/layer.json
正常情况下应返回:
textHTTP/1.1 200 OK Content-Type: application/json Access-Control-Allow-Origin: *
12.3 测试 terrain 瓦片#
bashcurl -I http://localhost:8080/terrain/0/0/0.terrain
正常情况下应包含:
textHTTP/1.1 200 OK Content-Type: application/octet-stream Content-Encoding: gzip Access-Control-Allow-Origin: *
如果实际不存在 0/0/0.terrain,可以通过以下命令查找真实瓦片:
bashfind /Volumes/Lenovo/xlgl/terrain -name "*.terrain" | head
然后用真实路径替换测试地址。
13. Cesium 前端加载 terrain#
13.1 新版本 Cesium 推荐写法#
javascriptconst terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl( "http://localhost:8080/terrain/" ); const viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider });
13.2 旧版本 Cesium 写法#
javascriptconst terrainProvider = new Cesium.CesiumTerrainProvider({ url: "http://localhost:8080/terrain/" }); const viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider });
13.3 加载局部 focus terrain#
如果生成了局部 terrain:
bash/Volumes/Lenovo/xlgl/terrain/focus/152502/
则前端可加载:
javascriptconst terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl( "http://localhost:8080/terrain/focus/152502/" );
14. 常见问题与处理方法#
14.1 Docker 容器中找不到 DEM 文件#
现象:
textNo such file or directory
原因通常是 Docker Desktop 未授权 /Volumes/Lenovo 目录。
解决:
- 打开 Docker Desktop;
- 进入
Settings > Resources > File Sharing; - 添加:
bash/Volumes/Lenovo
- Apply & Restart;
- 重新执行 Docker 命令。
也可以检查容器内是否能看到文件:
bashdocker run --rm -v "/Volumes/Lenovo/xlgl:/data" ghcr.io/tum-gis/ctb-quantized-mesh:latest sh -lc 'ls -lah /data && ls -lah /data/dem'
14.2 生成 terrain 时速度很慢#
可优先执行以下处理:
bashgdal_translate /Volumes/Lenovo/xlgl/dem/xlgl_dem.tif /Volumes/Lenovo/xlgl/work/xlgl_dem_tiled.tif -of GTiff -co TILED=YES -co BIGTIFF=YES -co COMPRESS=LZW gdaladdo -r average /Volumes/Lenovo/xlgl/work/xlgl_dem_tiled.tif 2 4 8 16 32
然后使用:
bash/data/work/xlgl_dem_tiled.tif
作为 ctb-tile 输入。
14.3 生成高层级时文件数量过多#
高层级会显著增加瓦片数量。
例如从 10 级提高到 13 级,文件数量可能大幅增加。
建议:
- 30m DEM 通常不必超过 13 级;
- 调试阶段先生成到 10 或 11 级;
- 确认效果后再生成到 13 级;
- 局部区域可单独生成高层级;
- 全域 terrain 不建议盲目生成过高层级。
14.4 Cesium 加载 terrain 后没有起伏#
可能原因:
- 前端没有正确启用 terrain;
- 请求地址没有指向
layer.json所在目录; - DEM 高程值本身不明显;
- 生成的是未抬升 DEM,视觉起伏不强;
- terrain 瓦片请求失败;
.terrain响应头缺少Content-Encoding: gzip。
检查浏览器 Network 面板,确认是否成功请求:
texthttp://localhost:8080/terrain/layer.json http://localhost:8080/terrain/.../*.terrain
14.5 Cesium 控制台报 terrain 解析错误#
重点检查 .terrain 响应头:
bashcurl -I http://localhost:8080/terrain/0/0/0.terrain
应包含:
textContent-Encoding: gzip
如果缺失,需要检查 Nginx 配置:
nginxlocation ~ ^/terrain/.*\.terrain$ { default_type application/octet-stream; add_header Content-Encoding gzip always; }
如果你的 .terrain 文件不是 gzip 编码,则不能强制加该响应头。此时需要确认 ctb-tile 输出文件的真实编码方式。
14.6 layer.json 能访问,但 terrain 瓦片 404#
先查找真实文件路径:
bashfind /Volumes/Lenovo/xlgl/terrain -name "*.terrain" | head -20
如果真实路径是:
bash/Volumes/Lenovo/xlgl/terrain/5/25/12.terrain
则对应 URL 为:
texthttp://localhost:8080/terrain/5/25/12.terrain
如果 Nginx 返回 404,说明 URL 和磁盘目录映射不一致,需要检查:
nginxroot /Volumes/Lenovo/xlgl;
15. 推荐完整命令:单 DEM 标准流程#
下面是一组从预处理到生成 terrain 的完整命令,可作为标准流程使用。
bash# 1. 创建目录 mkdir -p /Volumes/Lenovo/xlgl/dem mkdir -p /Volumes/Lenovo/xlgl/work mkdir -p /Volumes/Lenovo/xlgl/terrain # 2. 检查 DEM gdalinfo /Volumes/Lenovo/xlgl/dem/xlgl_dem.tif # 3. 转换为 tiled GeoTIFF gdal_translate /Volumes/Lenovo/xlgl/dem/xlgl_dem.tif /Volumes/Lenovo/xlgl/work/xlgl_dem_tiled.tif -of GTiff -co TILED=YES -co BIGTIFF=YES -co COMPRESS=LZW # 4. 建立 overview gdaladdo -r average /Volumes/Lenovo/xlgl/work/xlgl_dem_tiled.tif 2 4 8 16 32 # 5. 生成 Cesium terrain docker run --rm -v "/Volumes/Lenovo/xlgl:/data" ghcr.io/tum-gis/ctb-quantized-mesh:latest sh -lc ' ctb-tile -f Mesh -C -N -s 13 -e 0 -o /data/terrain /data/work/xlgl_dem_tiled.tif && ctb-tile -f Mesh -C -N -s 13 -e 0 -l -o /data/terrain /data/work/xlgl_dem_tiled.tif ' # 6. 检查输出 ls /Volumes/Lenovo/xlgl/terrain find /Volumes/Lenovo/xlgl/terrain -name "*.terrain" | head # 7. 测试 Nginx 发布 curl -I http://localhost:8080/terrain/layer.json
16. 推荐完整命令:多 DEM 标准流程#
bash# 1. 创建目录 mkdir -p /Volumes/Lenovo/xlgl/dem mkdir -p /Volumes/Lenovo/xlgl/work mkdir -p /Volumes/Lenovo/xlgl/terrain # 2. 在 Docker 容器中构建 VRT 并生成 terrain docker run --rm -v "/Volumes/Lenovo/xlgl:/data" ghcr.io/tum-gis/ctb-quantized-mesh:latest sh -lc ' cd /data/dem && gdalbuildvrt /data/work/xlgl_dem.vrt *.tif && ctb-tile -f Mesh -C -N -s 13 -e 0 -o /data/terrain /data/work/xlgl_dem.vrt && ctb-tile -f Mesh -C -N -s 13 -e 0 -l -o /data/terrain /data/work/xlgl_dem.vrt ' # 3. 检查输出 ls /Volumes/Lenovo/xlgl/terrain find /Volumes/Lenovo/xlgl/terrain -name "*.terrain" | head # 4. 测试 Nginx 发布 curl -I http://localhost:8080/terrain/layer.json
17. 最终成果检查清单#
完成后依次检查:
17.1 磁盘文件#
bashls /Volumes/Lenovo/xlgl/terrain/layer.json find /Volumes/Lenovo/xlgl/terrain -name "*.terrain" | head
17.2 Nginx 访问#
bashcurl -I http://localhost:8080/terrain/layer.json
17.3 terrain 文件响应头#
bashcurl -I http://localhost:8080/terrain/0/0/0.terrain
如该瓦片不存在,先用:
bashfind /Volumes/Lenovo/xlgl/terrain -name "*.terrain" | head
找一个真实瓦片测试。
17.4 Cesium 前端#
javascriptconst terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl( "http://localhost:8080/terrain/" );
确认浏览器 Network 面板中:
layer.json返回 200;.terrain文件返回 200;.terrain响应头包含Content-Encoding: gzip;- 没有 CORS 报错;
- 场景中地形起伏正常。
18. 总结#
Mac 下制作 Cesium terrain 地形切片的推荐流程是:
text安装 Docker Desktop ↓ 授权 /Volumes/Lenovo 文件共享 ↓ 拉取 ghcr.io/tum-gis/ctb-quantized-mesh:latest ↓ 准备 DEM ↓ 必要时转换 tiled GeoTIFF 并建立 overview ↓ 使用 ctb-tile 生成 quantized-mesh tiles ↓ 使用 ctb-tile -l 生成 layer.json ↓ 通过 Nginx 发布 /Volumes/Lenovo/xlgl/terrain ↓ Cesium 使用 CesiumTerrainProvider.fromUrl 加载
最终推荐访问地址为:
texthttp://localhost:8080/terrain/
最终推荐磁盘目录为:
bash/Volumes/Lenovo/xlgl/terrain/