简介:这份资源面向需要在无网络环境下展示三维地形的Cesium开发者与GIS技术人员,提供将DEM地形tif文件转换为Cesium可加载离线terrain瓦片的完整工具链与操作说明。压缩包共11个文件,约45.9MB,包含msi与exe安装程序、Python脚本、pdf使用说明及json、terrain等配置与数据文件,覆盖Python 2.7、GDAL、PIL、NumPy等地理处理依赖的安装包,以及坐标转换与瓦片生成脚本,另附示例DEM数据与生成结果目录,便于对照验证。已有3092人学习下载。通过这份资料,读者可搭建起从DEM数据检查、SRTM格式瓦片生成到坐标系转换的完整流程,理解各工具在转换链路中的作用,并借助使用说明排查路径设置、坐标匹配、GDAL报错等常见问题,最终把本地高程数据接入Cesium地形提供者,实现离线地形渲染。
1. 从一次地形加载失败说起:Cesium 离线 terrain 到底该怎么装
去年帮某实验室做一套内网环境的三维地理系统,Cesium 底图跑得好好的,一切到地形就白茫茫一片,控制台报Failed to obtain terrain tile。排查了一下午才发现,问题不在 Cesium 本身,而是离线 terrain 数据压根没生成对——切片层级、瓦片格式、layer.json里的available数组,任何一处对不上,地形就加载不出来。这套离线地形方案在涉密内网、离线演示、教学环境里非常常见,但它的安装链路比装一个 npm 包长得多:要准备 DEM 原始数据、要装切片工具、要生成瓦片金字塔、要配layer.json、最后还要在 Cesium 里改terrainProvider。装不好不是报错就是地形塌陷,新手很容易卡在“工具装完了但数据出不来”这一步。这篇笔记就把我踩过的坑按顺序捋一遍,从环境准备到最终验证,照着走能跑通。
2. 环境准备与工具选型:DEM 数据、切片程序和运行依赖
2.1 先搞清楚离线 terrain 的数据链路
Cesium 的离线地形不是一张图片,而是一套按层级切好的瓦片集合,通常采用heightmap或quantized-mesh两种格式。heightmap格式老一些,兼容性好,瓦片是 PNG,Cesium 直接能读;quantized-mesh是现在的主流,瓦片是.terrain二进制文件,体积小、精度高,但生成工具链要求更严。我一般推荐quantized-mesh,因为 Cesium 官方从 1.40 之后对它的支持最完整,加载性能也明显更好。
整条链路是这样的:原始 DEM(通常是 GeoTIFF)→ 切片工具生成瓦片目录 → 生成layer.json描述文件 → Cesium 通过CesiumTerrainProvider指向这个目录。任何一环版本不匹配,都会导致地形加载失败。所以环境准备阶段,重点不是装多少东西,而是把版本对齐。
2.2 工具版本对照表
下面这张表是我在多个环境里验证过的组合,Windows 和 Linux 都适用。注意 Python 版本很关键,很多切片工具对 Python 3.9 以上支持不好。
| 组件 | 推荐版本 | 作用 | 备注 |
|---|---|---|---|
| Python | 3.8.x | 运行切片脚本 | 3.9+ 部分库编译失败 |
| GDAL | 3.4.x | 读取 GeoTIFF DEM | 需与 Python 绑定 |
| cesium-terrain-builder | 0.4.1 | 生成 quantized-mesh 瓦片 | 需编译 |
| Cesium | 1.95+ | 前端加载 | 低版本不支持 quantized-mesh |
| Node.js | 16.x | 本地起静态服务 | 仅验证用 |
提示:如果你只是做
heightmap格式,可以用cesium-terrain-builder的简化分支,但quantized-mesh必须用完整版,编译时依赖zlib和libpng。
2.3 安装 Python 与 GDAL 绑定
先确认 Python 版本,别用系统自带的。我习惯用pyenv或直接装 3.8 的独立包。装完 Python 后,GDAL 的 Python 绑定是第一个坑,直接pip install gdal大概率失败,因为版本要和系统 GDAL 库对应。
# 以 Ubuntu 为例,先装系统 GDAL 库 sudo apt-get install gdal-bin libgdal-dev # 查看系统 GDAL 版本 gdal-config --version # 假设输出 3.4.1,则安装对应 Python 绑定 pip install GDAL==3.4.1这段命令的逻辑是:系统库提供底层读写能力,Python 绑定只是接口层,版本必须一致。gdal-config --version输出的版本号就是你要pip install的版本。如果报numpy相关错误,先pip install numpy再重试。Windows 下建议用OSGeo4W安装包,勾选 GDAL 和 Python 绑定,省去编译麻烦。
2.4 编译安装 cesium-terrain-builder
这个工具是 C++ 写的,需要cmake和make。源码编译是绕不过去的,但步骤不复杂。
git clone https://github.com/geo-data/cesium-terrain-builder.git cd cesium-terrain-builder mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j4 sudo make install-DCMAKE_BUILD_TYPE=Release开启优化,切片速度会快不少;-j4按 CPU 核数调整。编译完成后,ctb-tile命令应该能在终端直接调用。如果提示找不到libgdal,检查cmake输出里 GDAL 的路径是否正确,必要时用-DGDAL_INCLUDE_DIR手动指定。
3. 生成离线地形瓦片:从 DEM 到 layer.json 的完整操作
3.1 DEM 数据预处理:投影与裁剪
原始 DEM 如果是地理坐标系(WGS84),切片工具通常能直接处理,但如果是投影坐标系(如 UTM),必须先转回 WGS84,否则瓦片层级会错乱。我一般用gdalwarp做这一步。
# 将 UTM 投影的 DEM 转为 WGS84 gdalwarp -t_srs EPSG:4326 input_utm.tif dem_wgs84.tif # 裁剪到目标区域,减少切片量 gdalwarp -te 100.0 20.0 120.0 40.0 dem_wgs84.tif dem_clip.tif-t_srs EPSG:4326指定输出为 WGS84 经纬度;-te是裁剪范围,按你的实际区域改。裁剪这步很多人跳过,结果切了几十万块瓦片,磁盘直接爆掉。先裁再切,能省一半以上时间。
3.2 执行切片:ctb-tile 参数详解
切片命令本身不长,但参数决定成败。
# 生成 quantized-mesh 瓦片,层级 0 到 12 ctb-tile -f Mesh -C -N -o ./terrain_tiles dem_clip.tif # 生成 layer.json ctb-tile -f Mesh -C -N -l -o ./terrain_tiles dem_clip.tif-f Mesh指定输出 quantized-mesh 格式;-C表示生成layer.json;-N表示不生成法线(法线会增大体积,除非需要光照);-l是只生成layer.json不重复切片。-o是输出目录。执行完你会看到terrain_tiles下出现0/、1/等层级目录,每个目录里是.terrain文件。
注意:层级范围默认根据 DEM 分辨率自动算,但你可以用
-z指定最大层级。层级越高瓦片越多,12 层大概对应 10 米精度,够用就别往上升。
3.3 layer.json 的关键字段检查
layer.json是 Cesium 加载地形的入口,里面几个字段必须对。
{ "tilejson": "2.1.0", "format": "quantized-mesh-1.0", "version": "1.0.0", "scheme": "tms", "tiles": ["{z}/{x}/{y}.terrain"], "projection": "EPSG:4326", "bounds": [100.0, 20.0, 120.0, 40.0], "available": [[0,0,0], [1,0,0], [1,1,0], ...] }format必须是quantized-mesh-1.0,写错 Cesium 直接不认;scheme是tms,不是xyz;available数组列出所有存在的瓦片坐标,如果这个数组和实际文件对不上,Cesium 会请求不存在的瓦片然后报 404。我遇到过available只写了前几层的情况,地形只加载了一部分,排查了半天。
3.4 本地验证瓦片是否可访问
切片完别急着往 Cesium 里塞,先用本地服务验证一下。
# Python 起一个静态服务 cd terrain_tiles python -m http.server 8080 # 浏览器访问 # http://localhost:8080/layer.json # http://localhost:8080/0/0/0.terrain如果layer.json能打开,.terrain文件能下载(浏览器可能提示下载而不是显示),说明瓦片本身没问题。这一步能排除掉一半的“Cesium 加载失败”问题——很多时候是文件路径或服务配置错了,不是数据错了。
4. Cesium 端配置与避坑:terrainProvider 接入和常见报错
4.1 在 Cesium 中指向离线地形
Cesium 加载离线地形只需要改一个地方:terrainProvider。
// 创建 CesiumTerrainProvider,指向本地服务 const terrainProvider = new Cesium.CesiumTerrainProvider({ url: 'http://localhost:8080', requestVertexNormals: false, // 与切片时的 -N 对应 requestWaterMask: false // 没有水面掩膜就关掉 }); // 应用到 viewer const viewer = new Cesium.Viewer('cesiumContainer', { terrainProvider: terrainProvider });url指向layer.json所在的根目录,不要带layer.json本身;requestVertexNormals如果切片时用了-N不生成法线,这里必须false,否则 Cesium 会请求法线数据然后失败;requestWaterMask同理,没有水面数据就关掉。这两个参数是新手最容易忽略的,报错信息又不直观,往往表现为地形加载一半就停。
4.2 跨域与路径问题
如果 Cesium 页面和地形服务不在同一个端口,浏览器会拦跨域请求。本地验证时最简单的办法是用同一个服务托管 Cesium 页面和瓦片,或者给静态服务加 CORS 头。
# 用 Python 起服务时加 CORS 支持 python -c " import http.server, socketserver class CORSHandler(http.server.SimpleHTTPRequestHandler): def end_headers(self): self.send_header('Access-Control-Allow-Origin', '*') super().end_headers() socketserver.TCPServer(('', 8080), CORSHandler).serve_forever() "这段脚本在响应头里加了Access-Control-Allow-Origin: *,本地调试够用。生产环境别用*,按实际域名配。
4.3 避坑记录:五个让我加班的地形加载问题
现象一:控制台报Failed to obtain terrain tile,地形完全不显示。原因:layer.json里的format字段写成了heightmap-1.0,但实际瓦片是.terrain二进制。 解决:确认切片格式和format字段一致,quantized-mesh对应quantized-mesh-1.0。
现象二:地形只加载了低层级,放大后变空白。原因:available数组只包含了低层级瓦片坐标,高层级没写进去。 解决:重新生成layer.json,确保available覆盖所有实际存在的瓦片。可以用脚本遍历目录自动生成。
现象三:地形显示但高度明显不对,像被压扁了。原因:DEM 的高程单位是英尺,但 Cesium 按米解析。 解决:切片前用gdalwarp -s_srs或gdal_translate -scale把高程转成米,或者在layer.json里加"heightOffset"校正。
现象四:Cesium 报Request has failed,但浏览器直接访问瓦片 URL 能下载。原因:Cesium 请求时带了Accept: application/vnd.quantized-mesh头,静态服务返回的 MIME 类型不对。 解决:给.terrain文件配置正确的 MIME 类型application/vnd.quantized-mesh,或者用支持自定义 MIME 的服务。
现象五:切片过程内存溢出,机器卡死。原因:ctb-tile默认一次性读入整个 DEM,大文件直接撑爆内存。 解决:先用gdalwarp裁剪到最小必要范围,或者用-z限制最大层级减少瓦片数量。
5. 装完怎么验证跑通:三层检查法和一个习惯
装完不代表跑通,我一般走三层检查,从数据到前端逐层确认。
第一层,文件层。进terrain_tiles目录,确认layer.json存在且available数组长度和实际瓦片数量对得上。用find . -name "*.terrain" | wc -l数一下瓦片总数,再和layer.json里available的条目数对比,差太多就是生成有问题。
第二层,服务层。起本地服务后,用curl模拟 Cesium 的请求头访问一个瓦片。
curl -H "Accept: application/vnd.quantized-mesh" -I http://localhost:8080/0/0/0.terrain看返回状态是不是200,Content-Type是不是application/vnd.quantized-mesh。如果是404,说明路径或available有问题;如果是200但类型不对,Cesium 可能解析失败。
第三层,渲染层。打开 Cesium 页面,把相机飞到地形区域,打开浏览器开发者工具的 Network 面板,过滤.terrain请求。正常情况应该看到一系列200响应,层级随缩放递增。如果看到大量404或请求中断,回到第二层查。
最后说一个习惯:每次生成完瓦片,我都会先用ctb-tile自带的ctb-info工具检查一下瓦片元数据,确认层级范围和投影信息。这个命令很多人不知道,但它能在切片阶段就暴露问题,比等到 Cesium 里报错再回头查省事得多。从那以后我每次部署离线地形,都强制走一遍“文件数 → curl 头 → Network 面板”这三步,再也没出现过地形加载到一半崩掉的情况。希望帮到你。
本文还有配套的精品资源,点击获取