
很多搞GIS的朋友应该都有过这种经历手上拿到一块DEM数据想在Cesium里看实际地形起伏效果结果要么用在线商用服务担心数据安全要么用付费工具折腾半天还得花钱买授权。前阵子我正好要做一个区域性地形展示项目预算为零还得保证能直接在浏览器里流畅渲染最终用CesiumLab2配合nginx搭了一套完整的三维地形服务整个过程从数据准备到线上访问大概花了一个下午。这套方案全程零成本、可离线运行流程也不复杂完全适合个人开发者、测绘相关从业者或高校研究场景使用。这篇文章会把整个流程完整拆开DEM数据从哪来、怎么用CesiumLab2做地形切片、nginx怎么配才能让Cesium正确加载地形、前端又该怎么设置地形Provider包括我在实际部署中踩过的坑和排查思路。只要你跟着走一遍完全可以复现这套三维地形服务。1. 整体方案设计与思路拆解1.1 为什么选择CesiumLab2nginx这个组合很多人在做离线地形服务时第一反应是找现成的地形服务端框架比如GeoServer发布地形或者花精力折腾各种瓦片服务中间件。但我实测下来对付三维地形数据这类较为单一的瓦片场景这些重型工具反而显得累赘。CesiumLab2本身就是围绕Cesium生态做数据处理的工具集对地形切片做了专门优化一键转换就能输出Cesium能直接识别的Terrain格式省去自己拼接多软件流程的麻烦。nginx在这里面担任的角色是纯静态文件托管加轻量级反向代理。CesiumLab2切完片之后产生的是一大堆JSON和图像文件本质上是静态资源nginx处理静态文件的性能和稳定性非常成熟占用的系统资源也很低。相比直接挂IIS或Apachenginx的配置路径更直观我这种经常在不同机器间切换部署的人copy一份配置文件就能跑起来简直不要太顺手。整个链路里没有数据库、没有后端服务、没有商业组件数据切片和Web服务两端都是免费开源的。工程成本几乎为零但效果和商业方案没有明显差别这也是这套组合最大的价值所在。尤其适合预算有限但需要在地理信息领域快速搭建原型系统的开发者。1.2 核心链路到底是怎么跑的理解这套方案前先把整条数据链路看明白。原始的DEM数据是一个覆盖某块区域的TIFF或IMG栅格文件里面记录了每个像素点对应的高程值这种通用格式无法被Cesium前端直接渲染因为Cesium需要的是按层级划分的、带地形误差控制的瓦片数据。CesiumLab2做的工作就是把这一个大文件按照四叉树结构切分成多级LOD瓦片。每一级瓦片代表了不同缩放层级下的地形覆盖范围级别越低覆盖面越广但精度粗糙级别越高精度精细但覆盖范围变小。切片后还会生成一个layer.json配置文件里面记录了这个地形数据集的基本信息比如坐标系、瓦片方案、采样精度等。nginx把切片后的数据目录作为静态站点发布出来Cesium前端通过地形Provider请求layer.json再按当前视角动态请求对应层级的瓦片数据。整个链路下来从数据处理到前端渲染每一环各司其职没有太多多余的中间层。理解了这个链路后面做参数配置时就不会一头雾水。1.3 适用场景与条件限制这套方案最适合的应用场景是单一区域或多块区域内连续地形的展示需求比如某个省市的数字孪生项目、山区道路规划的三维复核、水利工程的流域地形可视化。数据量一般在几百M到几十G这个区间如果上升到整个省或全国范围、TB级别的数据量那CesiumLab2本身的切片效率和nginx静态托管的加载优化可能就会遇到瓶颈需要考虑分布式存储加更高阶的切片策略。另外这方案是单向的地形数据发布如果还要支持用户交互编辑地形、动态生成地形模型等实时需求它就不太够用了。搞清楚边界条件再选型能少走很多弯路。2. DEM数据获取与预处理2.1 地形数据源怎么选做地形切片的第一步是拿到合适的原始DEM数据这一步决定了后续整个链路能展现出什么效果。如果只是做演示或者开发测试推荐先去国内外几个免费数据源看看。SRTM 90米分辨率数据和ALOS 30米分辨率数据目前都能免费下载覆盖范围基本做到全球无死角很多区域性的地形展示需求直接下载ALOS的数据就够用了。如果需要更高精度的数据国内部分省市的基础测绘成果可以通过当地自然资源主管部门申请使用但这种流程周期较长且数据文件往往涉及保密协议不太适合拿来写技术博客做演示。日常做技术预研和原型演示免费的公开数据集完全足够重点在于熟悉整个工具链的用法等真正有了关键数据再替换进去就行。我建议下载数据时一次性把整个研究区域包下来避免只下载局部小方块导致后面切片时边缘出现空洞。毕竟很多DEM下载工具都是按经纬度网格裁剪如果目标区域跨了多个图幅一定要把涉及到的所有图幅都拉下来后面在CesiumLab2里可以多文件一起合并处理。2.2 数据格式与坐标系检查拿到DEM数据后别急着直接丢进CesiumLab2先确认两个关键要素文件格式和坐标系信息。CesiumLab2对常见栅格格式支持得比较全面GeoTIFF、IMG这些都没问题但前提是投影坐标系已经被正确写入文件元数据。有一个很隐蔽的坑我提醒大家注意部分下载工具默认给的是WGS84经纬度坐标系而有些区域数据可能是先行定义的投影坐标系比如UTM或高斯-克吕格投影。CesiumLab2在处理时对坐标系有自动识别能力但偶尔会识别不准导致切片后地形位置偏移甚至完全显示不出来。稳妥起见下载完数据后先用QGIS或ArcGIS打开看一眼在图层属性里确认坐标系统再动手。另外小Tip如果数据是多块分幅文件可以在QGIS里先做镶嵌合并成一个完整的大文件也可以保留多个文件直接拖进CesiumLab2的输入列表里工具会自动拼合处理任选一种方式就行。但需要注意多个文件的坐标系和分辨率尽量保持一致混用不同投影的数据容易在镶嵌时出现错位和拉伸变形。2.3 数据裁剪与无效值清理很多公开DEM数据源会在海洋、湖泊或数据缺失区域记录一个特殊的无效值通常为负数或-9999CesiumLab2在切片时如果不对无效值做处理这些区域会被强制赋予一个异常高程值最终渲染效果就是地形表面出现莫名其妙的尖刺或凹陷。处理无效值我通常在切片前用QGIS做一次栅格计算器操作把无效值区域统一设为一个接近周边地形的值或者干脆裁剪掉不需要覆盖的空白区域。如果只是在CesiumLab2中简单设置也可以利用其内置的无效值处理参数来规避具体的值需要根据源数据中的元数据去填不要凭空猜测。预处理这一步看着不显眼但往往决定最终渲染效果是否“干净”。我见过很多人在切片完成后才抱怨地形有破损或异常凸起排查到最后基本都是无效值没有清理干净导致的所以建议大家在数据这端多花十分钟后面能省好几个小时的排查功夫。3. CesiumLab2地形切片实操3.1 数据导入与参数配置打开CesiumLab2之后第一个功能区就是数据处理选择地形切片类型。将准备好的DEM文件拖入文件列表工具会自动读取数据的基础信息并显示出来。这里需要特别关注输出坐标系设置默认情况下使用数据的原始坐标系即可如果前端Cesium的全球场景与你本地区域坐标系混用建议统一输出为EPSG:4326坐标系这样和Cesium默认的WGS84跨度完全匹配。切片参数里面最核心的是地形细节层次设置。CesiumLab2提供了不同的细节等级选项现实中我一般选择中等质量档起步先确保流程能跑通再根据渲染效果调整更高质量的设置。输出格式选择散列文件目录的形式方便后续用nginx直接托管。采样间隔和误差阈值这两个参数容易被人忽略但它们决定了瓦片生成数量和数据体积。采样间隔设得越小保留的地形细节越多但切片耗时和文件数量会呈指数级增长。误差阈值则控制着LOD切换的敏感度值越小地形切换越精细但会加重渲染负担建议先用默认值跑完之后再看实际效果决定要不要调。3.2 切片过程与输出结果校验点击开始处理之后CesiumLab2会显示一个实时进度条整个耗时取决于数据范围大小和计算机性能。我拿一块约2GB的高精度DEM测试数据覆盖大概1万平方公里在中端工作站上跑了大概15分钟。如果你的数据范围特别大等的时候建议不要频繁拖动窗口或做其他高负载操作工具偶尔会因为资源竞争导致进度卡死。切片完成后在输出目录里你会看到两类内容一个layer.json文件和数十个层级目录每级目录下是排列规则的瓦片文件夹。打开layer.json看一眼里面记录了tileInfo、投影信息、层级范围等关键参数如果能看到这些内容说明切片基本成功了。这时用浏览器的静态文件方式直接访问layer.json路径能访问到说明数据文件本身没问题后面就剩nginx和前端配置了。有一个最容易踩的坑是CesiumLab2默认输出的文件路径可能包含中文而Cesium在加载地形时对中文路径的兼容性不太好会出现莫名其妙的404。我一般会在输出路径设置时直接改成纯英文目录省得后面排查时怀疑人生。3.3 多块数据合并与其他输出格式某些场景下你手里会有多块不同区域的地形数据希望合并成一个地形服务整体发布。在CesiumLab2的输入列表里把多个DEM文件一次性添加进去工具会按照空间位置自动拼接处理输出结果中相邻区域的瓦片边界会做一定融合处理。另外CesiumLab2也支持输出其他格式如3D Tiles或点云但那是针对倾斜模型的数据处理方式与本次的地形切片流程不同。做地形服务时认准“地形切片”这个功能入口选错入口会导致后续数据格式完全不对路。如果你不确定自己数据适合哪种输出方式先看看数据的语义是连续的表面高程还是离散的模型点前者走地形切片后者走3D Tiles流程。4. 基于nginx的地形服务部署4.1 nginx安装与基础配置方法地形切片完成后需要用Web服务器把它暴露出来供前端访问。我选择nginx的原因很简单免费、轻量、配置灵活而且无论Windows还是Linux都能跑得很稳。Windows环境下直接去nginx官网下载Windows编译版压缩包解压后运行nginx.exe即可默认监听80端口打开浏览器访问本机IP就能看到欢迎页面。Linux环境下的安装方式根据发行版有所不同在Ubuntu和Debian系可以直接用包管理器安装在CentOS或RHEL系则可能需要先配置EPEL源再安装。如果服务器在内网环境且无法连接外部软件仓库也可以下载编译好的二进制包或源码包自行编译安装。部署完成后用nginx -t命令检查配置文件语法输出syntax is ok就说明没问题了。nginx本身默认的配置就具备静态文件服务能力本质上是把磁盘上的文件映射为HTTP访问路径。以前很少接触nginx的开发者可能会把它想得复杂但实际上我们只需要修改server块中的root和location配置就能把地形数据目录发布成一个网站。4.2 地形服务的静态目录与URL规则在nginx配置中新增一个server块监听一个独立端口比如8088然后将root指向CesiumLab2的输出目录这样通过http://服务器IP:8088/layer.json就能直接访问到地形数据的入口配置文件。Cesium前端会基于这个入口URL自动拼接出类似{level}/{x}/{y}.terrain的瓦片请求路径所以root路径和磁盘目录的真实结构必须严格对应。我第一次部署时犯过一个错误把root指向了切片输出目录的上一级目录结果layer.json能访问但瓦片请求全部404。排查才发现Cesium请求瓦片的路径是http://ip:port/terrain/{level}/{x}/{y}.terrain其中terrain是根目录下的一个实际子目录root必须指到terrain所在的这一层而不是包含terrain的上一层。这个逻辑想通以后nginx的配置其实就是在做磁盘目录和URL路径之间的一个映射关系。如果你服务器上还有其他Web服务占用着80端口也不用非得跟别人抢换个端口就行。但需要注意修改端口后防火墙也要放行对应端口否则外部浏览器还是访问不到。云服务器用户记得在安全组里同时放开这一条入站规则很多人本地调试得好好的部署到服务器就白屏多半是没放端口。4.3 反向代理与额外优化配置除了静态托管nginx的另一个常见用途是反向代理在三维地形服务中也能派上用场。比如你希望对外只暴露一个统一端口把地形请求代理到其他被隐藏的内部端口服务上这时就可以用location块配置proxy_pass指向内部服务地址。还有一类常见场景是前后端分离部署Cesium前端页面放在一个端口地形服务放在另一个独立端口为了规避跨域问题可以为前端页面所在的服务添加一个location规则把地形相关的请求路径代理到地形服务端口。这种玩法虽然多了一层转发但能避免前端页面上写死多个IP端口导致的繁琐跨域配置后期做容器化部署时也更方便统一入口。值得注意的是Cesium在WebGL渲染时对网络请求数量有较大压力尤其是地形数据量大的场景。nginx中开启gzip压缩并在location里添加合适的缓存过期响应头可以让已访问过的瓦片在本地浏览器缓存中直接读取大幅减少重复请求量。实测中开启缓存后再次进入同一区域的加载速度能提升一半以上非常值得配置。4.4 nginx常见配置陷阱排查nginx在使用中会遇到一些典型报错我先列几个最常见的。第一个是403 Forbidden多半是nginx进程对地形数据目录没有读权限Linux环境下通过chmod命令给目录添加可读执行权限即可解决。第二个是404 Not Found排查思路先看浏览器实际访问的URL路径再用nginx -t确认配置无误然后再检查root路径映射大概率是路径层级对不上。第三个是502 Bad Gateway这通常只在配置了反向代理时出现意思是nginx没法连通上游服务先确认被代理的服务启动正常再说。开头提到的在Linux发行版上部署nginx有些发行版的nginx默认站点配置用了include方式加载了多个配置文件如果你修改的配置没生效八成是改错了文件。确认当前生效的配置文件可以通过nginx -T输出完整配置内容在输出里搜索server_name或listen端口就能定位到实际加载的是哪个配置块。另外我遇到过一种比较隐蔽的问题nginx配置了HTTPS证书但没配好证书链导致浏览器访问时提示证书无效。如果你为地形服务配置了SSL记得证书文件要包含完整的证书链同时将HTTP请求重定向至HTTPS否则Cesium在混合内容安全策略下会用HTTP请求瓦片资源浏览器默认拦截导致地形无法加载。这种情况在正式生产部署时经常出现排查时第一时间看控制台的混合内容警告会少走很多弯路。5. 浏览器渲染与前端联调5.1 Cesium Viewer初始化与地形Provider挂载服务端全部就绪后前端接入相对简单。使用Cesium时核心操作是创建一个Viewer实例把地形Provider传入这个实例使其生效。地形Provider的URL直接指向nginx发布出来的layer.json地址比如http://127.0.0.1:8088/terrain/layer.json设置完成后还需要调用viewer.terrainProvider替换原有地形数据源。下面给出一段常用的代码示例我保留了最核心的配置项大家根据自己的服务地址替换即可。const viewer new Cesium.Viewer(cesiumContainer, { baseLayerPicker: false, geocoder: false, animation: false, timeline: false, infoBox: false, }); const terrainProvider await Cesium.createWorldTerrainAsync({ url: http://127.0.0.1:8088/terrain/layer.json, }); viewer.scene.setTerrain(new Cesium.Terrain(terrainProvider)); viewer.scene.globe.depthTestAgainstTerrain true;注意我在代码里把Cesium的默认在线底图关闭了因为纯内网环境无法加载在线卫星影像关闭后依然能看到地形起伏的灰模效果。如果你有自己发布好的影像瓦片服务也可以通过imageryProvider参数挂上地形和影像叠加后整体效果会真实非常多。另外代码里我特意开启了depthTestAgainstTerrain这个选项开启后地球表面和地形之间的遮挡关系会得到正确测试放置模型或进行量测时才不会出现模型漂在地底下或陷入山体内的现象。首次接入地形服务的开发者经常忽略这个参数导致卫星影像与地形高程不匹配大部分情况下这个设置是最终的解决方案。5.2 地形请求是否正常的调试方法浏览器环境下接入完成后可以用开发者工具的网络面板查看请求情况。打开F12面板后切换至Network选项卡刷新页面并拖动视角到地形覆盖的区域正常可以看到大量类似terrain/{level}/{x}/{y}.terrain的请求发出状态码为200且响应内容里能看到JSON格式的TiledTerrainData。如果你发现网络面板里根本没有地形相关请求先检查Cesium初始化时代码有没有报错很多情况下是因为地形Provider的URL构造错误或者传入的URL没有被Cesium正确识别。另一种可能是瓦片请求发出但返回404这时网络面板会显示红色请求记录排查方向直接锁定nginx的root路径配置。在Network面板启用Preserve log选项然后操作视角到目标区域通过查看请求URL中level、x、y的变化规律还能快速验证瓦片服务是否支持多层级缩放。当你把层级从低放大到高时如果请求数量急速上升说明LOD机制正常工作地形细节会逐步呈现出来。如果长时间只有同一个层级的请求可能是Cesium最小级别限制或数据本身没切成多级回到CesiumLab2参数设置中检查输出层级范围。5.3 常见渲染效果异常与处理地形接入后很多人会遇到地形加载出来是一片完全扁平的平面这种情况基本可以肯定是高程数值没有被正确读取。检查一下CesiumLab2切片时是否勾选了合适的输出格式或者数据源本身的高程范围是否过小比如一个平坦城区范围内高差只有几米地形起伏效果从宏观上看确实和平面没有区别这属于正常现象。另一种常见情况是地形数据比周边地形高了或低了整整一大截看起来数据像被平移了一样。这一般是数据源在坐标转换时基准面对齐出了问题需要回到数据预处理阶段检查原始DEM的垂直基准信息和Cesium默认的高度系统是否一致。不同国家或地区的DEM高程基准可能存在差异必要时需要在切片前对高程数据做整体偏移校正。还有一种场景是地形表面出现了明显的锯齿纹理或破碎条带这多半是切片参数里的误差阈值设置过小或过大导致LOD切换过于频繁。调整CesiumLab2中的网格简化参数和误差阈值同时为Cesium设置一个合适的地形细节高度范围通过viewer.scene.screenSpaceCameraController上的参数控制最大地形级别可以有效缓解纹理撕裂和加载闪烁现象让整个体验接近顺滑的在线地图浏览效果。6. 常见问题与排查技巧实录6.1 从零开始的排查路线图做完整套流程后如果遇到地形加载不出来别慌按照我总结的排查路网逐级验证通常能在十分钟内定位问题。第一步先确认数据切片是否成功用浏览器直接访问nginx给出的layer.json地址能够正常返回JSON说明数据服务正常否则问题出在服务端或文件路径上。第二步确认前端请求链路打开F12控制台看有没有报错信息。如果控制台弹出CesiumTerrainProvider相关的加载失败错误就说明请求已经发出但服务端响应异常回到nginx配置和防火墙设置这项查。如果控制台没有任何报错但地形就是不显示试着在viewer初始化时添加官方推荐的地形调试选项以代码方式输出地形加载状态的变化日志。最后一步确认WebGL和硬件加速情况。部分低配设备或远程云桌面环境默认关闭了显卡硬件加速WebGL渲染会非常缓慢甚至白屏这种情况下不管配置再对都会有问题。可以在浏览器中访问WebGL支持检测页面确认当前环境支持WebGL2后再检查Cesium的渲染设置。6.2 性能优化建议分享在局域网内部署时带宽和延迟都不是瓶颈反而是浏览器端的请求数量和WebGL渲染开销容易拖慢体验。如果你的数据范围特别大建议在nginx层和Cesium前端同时做缓存机制的优化。nginx侧配置expires指令对瓦片类静态资源设置较长的缓存时间Cesium端通过requestScheduler参数限制并发瓦片请求数避免瞬间请求风暴把浏览器网络链接打满。同时建议在数据切片的参数选择上优先保证中低层级的瓦片质量让视角快速定位时有足够的地形轮廓呈现而较高层级的精致细节可以稍微放松精度参数减少切片生产时间和瓦片总数量。地理信息系统的数据生产是个持续迭代的过程期望一步到位把所有细节塞到最高层级往往得不偿失。我自己在实际部署中还发现一个有趣的点把地形数据放在固态硬盘上能让瓦片请求的平均响应时间下降一个数量级。因为nginx本身是高频IO密集型的静态服务磁盘寻址能力直接影响并发请求的吞吐表现条件允许的情况下尽量把切片目录放到SSD上效果比调优更多配置项都来得明显。6.3 从单机到多环境扩展这套方案在单台服务器的内网环境中表现良好但当访问用户数量变多或需要跨地域访问时单机nginx的带宽和CPU会成为瓶颈。后续如果遇到这种增长需求可以考虑在nginx前加一层CDN或使用LVS等负载均衡产品将瓦片内容分发到边缘节点。不过对多数场景来说单机静态服务配合合理的缓存策略已经完全够用不用过度设计系统架构。如果把这套环境从Windows机器迁移到Linux服务器上主要步骤如下将地形数据目录完整拷贝到Linux服务器在Linux上重新安装nginx复用相同的server配置内容检查目录权限和防火墙规则重新加载配置。整个过程几乎不需要修改前端代码只要服务地址保持一致部署的迁移成本非常低这也是nginx加静态文件的典型优势。跑通这套流程之后我强烈建议你在此基础上继续研究Cesium的影像服务对接和3D Tiles模型接入功能。地形、影像、模型三者在Cesium场景中是三个独立的图层维度把地基打牢之后往上叠加任何业务数据都会顺畅许多。别有等一切都完美了再上手的念头先让地形数据在浏览器里转起来哪怕只是一个灰模板块也比纸上谈兵更有实际收获。