Cesium离线地形生成全流程:DEM预处理到Quantized-Mesh瓦片部署 简介面向需要在离线环境下使用Cesium展示三维地形的GIS开发者这份资源提供了一套从数字高程模型DEM的tif文件转换为Cesium量化网格地形terrain的完整工具链与说明文档。压缩包共11个文件涵盖Python 2.7、GDAL、PIL、NumPy等运行环境安装包以及坐标系转换和地形瓦片生成脚本另附使用说明PDF便于按步骤操作。已有3081人学习下载适合正在搭建离线地形服务、或需将本地高程数据接入Cesium项目的技术人员。通过这套程序用户能够将原始DEM数据依次转换为SRTM格式的分块瓦片并完成坐标系统一与目录配置最终在Cesium中直接加载本地地形。资源包大小45.9MB文件类型包括msi/exe安装程序、py脚本、json配置、terrain瓦片和txt说明等结构清晰能有效降低离线地形数据生产的门槛尤其适合内网环境下的三维地球项目。 搞Cesium三维可视化的人基本都会走到“离线地形”这一步内网环境、数据保密、在线服务不稳定或者干脆就是要用自己手里的高精度DEM而不是第三方公开数据。Cesium离线terrain地形数据的生成核心就是用DEM高程数据切出Cesium能直接识别的地形瓦片。这篇我把整个链路需要的程序、参数、坑都理一遍照着做基本能跑通适合GIS开发、三维可视化工程师参考。先说结论我用到现在最稳的组合是“DEMGeoTIFF GDAL预处理 Cesium Terrain BuilderCTB生成瓦片 Nginx静态托管”前端用CesiumTerrainProvider加载。整套流程完全离线、免费、可控生成的quantized-mesh格式瓦片能被Cesium原生识别不用改任何协议。后面会按这个链路一步步拆开讲。1. 离线terrain是什么为什么要自己生成1.1 在线地形服务的局限很多新手一开始直接用Cesium官方地形比如Cesium World Terrain拖进来确实有效果地球表面立刻有山脉起伏。但这种做法的前提是网络通畅、数据源可用、不涉及业务限制。实际项目里我遇到的情况通常是这几种部署在政务内网或私有云物理上不通外网在线地形根本加载不出来项目数据本身是涉密测绘成果不允许上传到第三方云服务需要的地形范围很偏在线服务那块的精度和现势性不达标需要高频交互或预烘焙场景依赖在线请求不稳定延迟不可控。只要命中上面任意一条就必须本地生成terrain地形数据。离线地形的好处不只是“不依赖网络”更重要的是数据和业务系统完全解耦瓦片生成一次后可以反复部署加载速度也更快。1.2 离线地形文件格式与原理Cesium能使用的地形数据主要有两种形式一种是旧的Heightmap高度图基于规则格网存储高度值简单但精度和压缩率一般另一种是Quantized-Mesh量化网格常简写为QM这是目前的事实标准也是生成离线terrain时最该选的格式。Quantized-Mesh的核心思想很简单把地形表面划分成四叉树瓦片每个瓦片是一个三角形网格顶点高程值经过量化压缩降低传输体积。Cesium在运行时根据相机位置和视角动态加载不同层级的瓦片所以你会看到场景越拉近地面细节越丰富。CTB生成的就是这种格式目录结构为{z}/{x}/{y}.terrain根目录还有layer.json描述瓦片元数据。提示只要看到layer.json和一堆.terrain文件基本就能确定是Quantized-Mesh瓦片。这个目录结构后面会直接挂到静态服务器上路径不能随便改。2. 工具选型生成离线terrain需要哪些程序2.1 数据源准备地形数据的源头是DEM数字高程模型。免费公开数据里SRTM和ASTER GDEM最常见SRTM 30米分辨率在城市和山区基本够用ASTER GDEM覆盖范围更广但噪声略大。精度要求更高的话可以用ALOS AW3D30或者商业高精度DEM。无论数据来自哪里到最后都要统一成GeoTIFF格式坐标系最好是WGS84经纬度EPSG:4326。很多人的习惯是拿到数据就直接丢给切图工具结果生成的地形要么偏位要么高度乱飘。原因基本都出在数据源没整理坐标系不统一、nodata区域没处理、数据范围超出目标区域。所以在工具链里GDAL是绕不开的第一步。2.2 核心工具对比离线terrain的完整工具链我整理了一个表方便按项目情况选程序作用适用场景备注GDALDEM格式转换、重投影、裁剪、nodata处理所有项目必须建议用QGIS自带版本减少编译麻烦Cesium Terrain BuilderCTB生成Quantized-Mesh瓦片和layer.json开源免费首选需要编译或Docker参数不多Nginx / Caddy静态托管terrain瓦片所有部署场景注意跨域和缓存配置QGIS数据预览、裁剪、快速检查图形化处理也可以完成部分GDAL操作Global Mapper商业切片工具大量商业项目导出的地形格式比较多需确认是否Cesium兼容Cesium ion在线托管与切片服务非离线场景离线项目不要依赖它2.3 为什么不推荐直接用Cesium ion很多教程上来就让你注册Cesium ion然后把DEM拖进去生成地形拿一个access token在前端加载。这套流程做原型很快但和“离线”两个字天然冲突。ion的地形瓦片托管在云上即使你自己上传数据运行时的瓦片请求仍然要走他们的服务token过期或者服务调整都会影响项目。另一个问题是涉密项目里数据根本不能出网所以ion这条路可以直接排除。3. 实操过程从原始DEM到离线terrain瓦片3.1 环境准备CTB的官方仓库提供编译源码但直接编译会踩不少依赖坑。我建议优先用Docker镜像省掉很多环境问题。如果团队没有Docker也可以用Linux环境依赖libgdal-dev、libssl-dev、cmake、g编译流程在README里写得很清楚。Windows下的朋友建议先用WSL不要直接在原生Windows上折腾CTB对Windows的原生支持一般我曾经在Windows环境下折腾编译花了一下午最后还是回到WSL一次通过。环境准备好后先用gdalinfo验证GDAL可用再确认CTB的命令能执行gdalinfo --version ctb-tile --help3.2 DEM预处理这一步决定地形最终质量也是最容易被跳过的一步。我通常分三步处理原始DEM第一步重投影到EPSG:4326。有些商业DEM或区域测绘数据用的是当地高斯投影或CGCS2000坐标Cesium默认按WGS84经纬度处理瓦片范围所以必须转换。命令示例gdalwarp -t_srs EPSG:4326 -r bilinear -of GTiff input.tif dem_4326.tif第二步裁剪到目标范围并统一分辨率。范围过大会导致瓦片数量爆炸先裁剪到项目实际区域。注意保留一定缓冲区否则地形边缘容易出裂缝。第三步处理nodata空洞。原始DEM经常有水域、云遮挡等无效值直接用nodata位置生成的地形会出现塌陷坑。先用gdalinfo -stats查看nodata值范围再用gdal_fillnodata插值填补gdal_fillnodata.py dem_4326.tif dem_filled.tif注意DEM的高程基准需要关注。Cesium地形默认按WGS84椭球高处理如果DEM是EGM96/EGM2008正常高生成的地形整体会有一个系统偏差可能浮空或陷地。高精度项目需要先把正高改正到椭球高或者通过大地水准面模型处理。如果只是展示效果几十米的偏差肉眼不易察觉但做工程测量级的项目必须处理。3.3 使用CTB生成瓦片预处理完的DEM就可以交给CTB了。我的常用命令是ctb-tile -f -C -N -o ./terrain_tiles ./dem_filled.tif其中-f表示强制覆盖输出目录-C用于裁剪瓦片边缘以避免黑边-N是关闭默认的概览生成如果生成了额外的低分辨率概览反而会加大目录体积。具体参数在不同版本里略有差异拿到工具后先执行ctb-tile --help确认。CTB没有自动并发功能对大范围DEM会很慢我通常提前把大DEM按范围切块再分别运行CTB最后把多个地形目录合并部署。生成结束后检查输出目录find ./terrain_tiles -maxdepth 2 -type f | head -20正常情况下能看到layer.json和按层级分好的.terrain文件。用cat layer.json确认包含tileFormat、bounds等字段如果缺少layer.jsonCesium是无法加载的。4. 在Cesium中加载离线地形4.1 静态服务部署与跨域terrain瓦片本质就是静态文件Nginx或Caddy都能托管。Nginx里配置一个静态站点映射到terrain目录server { listen 8080; server_name localhost; root /data/terrain_tiles; autoindex off; add_header Access-Control-Allow-Origin *; expires 1d; }跨域头是必须加的Cesium前端页面和瓦片服务经常不在同一个域下少了Access-Control-Allow-Origin会直接报跨域错。另外加expires可以充分利用浏览器缓存重复加载同一区域时能少请求很多瓦片。4.2 CesiumJS对接代码前端加载离线terrain的代码很简单。新版本的Cesium推荐用fromUrl方式const terrainProvider await Cesium.CesiumTerrainProvider.fromUrl( http://localhost:8080/, { requestVertexNormals: true, requestWaterMask: false } ); viewer.terrainProvider terrainProvider;requestVertexNormals建议开启虽然会增大瓦片体积但地形着色、坡度分析、压平效果都会依赖法线。requestWaterMask默认关闭CTB生成的地形也没有水面数据开启只会增加无效请求。如果用的是老版Cesium1.100之前可能没有fromUrl那就用new Cesium.CesiumTerrainProvider({ url: ... })。4.3 层级与加载性能调整离线地形生成后层级是固定的但Cesium加载时的策略可以调节。maximumScreenSpaceError控制地形细分程度默认值是2数值越小地形越精细但请求量也越大。内网部署带宽充足可以设为1.5左右如果机器性能一般建议保持2并限制最大层级viewer.scene.terrainProvider terrainProvider; viewer.scene.globe.maximumScreenSpaceError 1.8;CTB在生成时layer.json里会写明availableLevel没有的层级请求会404因此前端不用特殊处理但要确保瓦片服务完整覆盖相机可能到达的范围。相机范围大而瓦片缺失时地形会出现明显的“洞”排查起来很麻烦。5. 常见问题与排查技巧实录5.1 地表高程整体偏移或地形漂浮最典型的现象地形块加载了但山明显比真实位置高或大范围区域整体凹陷。原因基本是高程基准不匹配。Cesium默认把瓦片高度当作WGS84椭球高而大部分测绘DEM给的是海拔高。简单验证方法找一个已知高程点对比Cesium里的三维高程值。如果偏差是常量可以在DEM预处理时手动做改正如果偏差随区域变化就得引入大地水准面格网做转换。5.2 瓦片404或地形显示不全瓦片404通常有三类原因一是路径大小写不对CTB生成的小写z/x/y结构很固定Nginx上注意别开autoindex导致路径被解析错二是layer.json缺失三是跨域问题看起来是404实际上请求被浏览器拦截。先开Chrome开发者工具看Network确认请求状态码再逐项排查。这里要特别提醒root指令在Nginx里指向的目录必须直接包含0/1/2这些层级目录不能多套一层文件夹否则路径全错。5.3 加载卡顿、内存占用过高离线瓦片通常比在线瓦片大因为本地不受带宽限制CTB默认也没有做极致的网格压缩。项目里如果持续卡顿优先检查是不是加载了太多高分辨率层级。我处理过一次性能问题最后发现是DEM范围切太大CTB生成了十几层级瓦片体积几十GB浏览器加载根本吃不消。解决办法是重新裁剪DEM范围控制生成层级上限把layer.json的availableLevel范围缩小只保留实际会用到的层级。5.4 问题速查表现象常见原因解决思路地形完全无起伏terrainProvider没赋值成功检查控制台报错确认layer.json可访问部分区域凹陷或空洞DEM nodata未处理使用gdal_fillnodata填补空洞地形与影像错位坐标系不一致统一用EPSG:4326重投影高度整体偏差正高与椭球高混用做高程基准改正瓦片请求404路径层级错误或layer.json缺失检查Nginx root与CTB输出结构加载后明显卡顿瓦片层级过深或范围过大裁剪范围、限制最大层级6. 离线terrain的进一步扩展地形瓦片稳定跑起来后很多好效果就能往上叠加了。Cesium里常见的地形压平就是基于terrain数据的几何处理离线地形因为高度数据可控压平效果反而更容易调准。还有动态光照、自定义地形材质、河流材质、热力图叠加这些本质上都需要一个稳定的地形基础否则特效做得再好地形一跳就全露馅。最近不少人在问Cesium实现高德箭头效果、绘制矩形、画圆柱体这些几何操作它们不直接依赖离线地形但只要有terrain数据这些几何体就能贴到地形表面而不是浮在半空。我自己的经验是先把地形链路跑通再慢慢加符号化和特效调试思路会清晰很多。7. 最后再分享一个实用技巧CTB不是万能的它在处理超大范围DEM时内存占用高、速度慢。我的经验是尽量“小步快跑”先切一小块DEM比如5公里乘5公里生成瓦片并验证效果再逐步扩大范围。这样即使参数有问题排查成本也低不会等跑了一晚上数据后才发现方向错了。另外工具版本一定要记录。GDAL和CTB不一样生成的瓦片在Cesium新版里的表现也不一致。我吃过一次亏升级GDAL后输出的GeoTIFF仍能生成瓦片但Cesium加载时地形出现少量接缝最后回退GDAL版本才解决。离线地形生成没有那么多运气成分关键就是把数据和工具链路稳在一个可复现的版本组合上然后长期复用。本文还有配套的精品资源点击获取