博客

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 规范的切片数据。

本流程采用:

text
DEM GeoTIFF ↓ 必要时预处理为 tiled GeoTIFF / VRT ↓ ctb-tile 生成 quantized-mesh terrain tiles ↓ ctb-tile 生成 layer.json ↓ Nginx 静态发布 ↓ CesiumTerrainProvider 加载

核心工具为:

text
Docker 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 是否安装成功#

打开终端执行:

bash
docker --version

如果能看到 Docker 版本号,说明 Docker 命令可用。

继续执行:

bash
docker 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 文件共享配置。

操作路径:

text
Docker Desktop → Settings → Resources → File Sharing

将以下目录加入共享:

bash
/Volumes/Lenovo

或者只加入项目目录:

bash
/Volumes/Lenovo/xlgl

建议加入:

bash
/Volumes/Lenovo

这样后续多个项目目录都可以被 Docker 容器访问。


5. 拉取 quantized-mesh 镜像#

Apple Silicon Mac 推荐直接使用 GHCR 镜像:

bash
docker pull ghcr.io/tum-gis/ctb-quantized-mesh:latest

该镜像中已经包含:

  • ctb-tile
  • gdalbuildvrt
  • 常用 GDAL 工具

拉取完成后可以测试镜像是否可运行:

bash
docker 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

检查文件是否存在:

bash
ls /Volumes/Lenovo/xlgl/dem/xlgl_dem.tif

查看 DEM 基本信息:

bash
gdalinfo /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。

bash
mkdir -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:

bash
gdaladdo -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 建立多级概览

完成后,可以再次查看文件信息:

bash
gdalinfo /Volumes/Lenovo/xlgl/work/xm_30m_dem_lifted_tiled.tif

如果能看到 Overviews 信息,说明金字塔概览已建立。


8. 单个 DEM 文件生成 terrain 地形切片#

8.1 创建输出目录#

本文使用不带重复 xlgl 的输出目录:

bash
mkdir -p /Volumes/Lenovo/xlgl/terrain

8.2 使用原始 DEM 直接切片#

假设 DEM 为:

bash
/Volumes/Lenovo/xlgl/dem/xlgl_dem.tif

执行:

bash
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/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

则建议使用该文件切片:

bash
mkdir -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 切片:

bash
mkdir -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 流程说明#

上述命令分为三步:

bash
cd /data/dem

进入容器内 DEM 目录。

bash
gdalbuildvrt /data/work/xlgl_dem.vrt *.tif

将多个 DEM TIFF 构建为一个 VRT。

bash
ctb-tile -f Mesh -C -N -o /data/terrain /data/work/xlgl_dem.vrt

生成 quantized-mesh 地形切片。

bash
ctb-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#

bash
mkdir -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 配置为:

nginx
server { 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 访问地址为:

text
http://localhost:8080/terrain/

layer.json 地址为:

text
http://localhost:8080/terrain/layer.json

实际文件路径为:

bash
/Volumes/Lenovo/xlgl/terrain/layer.json

12. 测试 terrain 是否发布成功#

12.1 检查输出文件#

bash
ls /Volumes/Lenovo/xlgl/terrain

应能看到:

bash
layer.json 0 1 2 ...

12.2 测试 layer.json#

bash
curl -I http://localhost:8080/terrain/layer.json

正常情况下应返回:

text
HTTP/1.1 200 OK Content-Type: application/json Access-Control-Allow-Origin: *

12.3 测试 terrain 瓦片#

bash
curl -I http://localhost:8080/terrain/0/0/0.terrain

正常情况下应包含:

text
HTTP/1.1 200 OK Content-Type: application/octet-stream Content-Encoding: gzip Access-Control-Allow-Origin: *

如果实际不存在 0/0/0.terrain,可以通过以下命令查找真实瓦片:

bash
find /Volumes/Lenovo/xlgl/terrain -name "*.terrain" | head

然后用真实路径替换测试地址。


13. Cesium 前端加载 terrain#

13.1 新版本 Cesium 推荐写法#

javascript
const terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl( "http://localhost:8080/terrain/" ); const viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider });

13.2 旧版本 Cesium 写法#

javascript
const 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/

则前端可加载:

javascript
const terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl( "http://localhost:8080/terrain/focus/152502/" );

14. 常见问题与处理方法#

14.1 Docker 容器中找不到 DEM 文件#

现象:

text
No such file or directory

原因通常是 Docker Desktop 未授权 /Volumes/Lenovo 目录。

解决:

  1. 打开 Docker Desktop;
  2. 进入 Settings > Resources > File Sharing;
  3. 添加:
bash
/Volumes/Lenovo
  1. Apply & Restart;
  2. 重新执行 Docker 命令。

也可以检查容器内是否能看到文件:

bash
docker 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 时速度很慢#

可优先执行以下处理:

bash
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 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 后没有起伏#

可能原因:

  1. 前端没有正确启用 terrain;
  2. 请求地址没有指向 layer.json 所在目录;
  3. DEM 高程值本身不明显;
  4. 生成的是未抬升 DEM,视觉起伏不强;
  5. terrain 瓦片请求失败;
  6. .terrain 响应头缺少 Content-Encoding: gzip。

检查浏览器 Network 面板,确认是否成功请求:

text
http://localhost:8080/terrain/layer.json http://localhost:8080/terrain/.../*.terrain

14.5 Cesium 控制台报 terrain 解析错误#

重点检查 .terrain 响应头:

bash
curl -I http://localhost:8080/terrain/0/0/0.terrain

应包含:

text
Content-Encoding: gzip

如果缺失,需要检查 Nginx 配置:

nginx
location ~ ^/terrain/.*\.terrain$ { default_type application/octet-stream; add_header Content-Encoding gzip always; }

如果你的 .terrain 文件不是 gzip 编码,则不能强制加该响应头。此时需要确认 ctb-tile 输出文件的真实编码方式。


14.6 layer.json 能访问,但 terrain 瓦片 404#

先查找真实文件路径:

bash
find /Volumes/Lenovo/xlgl/terrain -name "*.terrain" | head -20

如果真实路径是:

bash
/Volumes/Lenovo/xlgl/terrain/5/25/12.terrain

则对应 URL 为:

text
http://localhost:8080/terrain/5/25/12.terrain

如果 Nginx 返回 404,说明 URL 和磁盘目录映射不一致,需要检查:

nginx
root /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 磁盘文件#

bash
ls /Volumes/Lenovo/xlgl/terrain/layer.json find /Volumes/Lenovo/xlgl/terrain -name "*.terrain" | head

17.2 Nginx 访问#

bash
curl -I http://localhost:8080/terrain/layer.json

17.3 terrain 文件响应头#

bash
curl -I http://localhost:8080/terrain/0/0/0.terrain

如该瓦片不存在,先用:

bash
find /Volumes/Lenovo/xlgl/terrain -name "*.terrain" | head

找一个真实瓦片测试。

17.4 Cesium 前端#

javascript
const 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 加载

最终推荐访问地址为:

text
http://localhost:8080/terrain/

最终推荐磁盘目录为:

bash
/Volumes/Lenovo/xlgl/terrain/