踐)
簡介面向需要將本地DEM高程tif轉(zhuǎn)換為Cesium離線terrain數(shù)據(jù)的WebGIS開發(fā)者這份資源打通了從環(huán)境準(zhǔn)備到瓦片生成的關(guān)鍵鏈路。包內(nèi)共11個文件含msi/exe安裝程序、Python腳本、PDF說明文檔、JSON配置與terrain瓦片數(shù)據(jù)壓縮包約45.9MB其中msi/exe組件對應(yīng)Python 2.7、GDAL、PIL、NumPy等依賴用于tif解析與地理計算而兩個核心Python腳本可完成坐標(biāo)轉(zhuǎn)換和DEM到SRTM格式地形瓦片的批量生成滿足離線切片需求。另含示例DEM數(shù)據(jù)及已生成的瓦片目錄配合PDF使用說明可解決路徑設(shè)置、坐標(biāo)系匹配、GDAL報錯、內(nèi)存不足等常見問題幫助快速在Cesium中配置離線地形提供者并將瓦片對接到三維場景。附帶的使用說明PDF從安裝配置到參數(shù)調(diào)整均有圖文指引能有效降低上手門檻。目前已有3081人學(xué)習(xí)下載適合內(nèi)網(wǎng)環(huán)境、應(yīng)急演示或?qū)?shù)據(jù)安全性要求較高的三維地形展示項目。 做WebGIS的同學(xué)一定沒少被地形數(shù)據(jù)折騰過。項目一旦要部署到內(nèi)網(wǎng)或?qū)>W(wǎng)Cesium默認(rèn)那一套在線地形服務(wù)基本就廢了要么連不上要么跨境請求慢得讓人抓狂更別提數(shù)據(jù)還不受自己控制。所以“Cesium生成離線terrain地形數(shù)據(jù)”就成了很多三維GIS項目繞不開的硬需求。這篇文章我把這套流程里需要的程序清單、完整做法和一些踩過的坑全部整理出來適合做內(nèi)網(wǎng)三維GIS、Cesium二次開發(fā)以及想搞清楚地形瓦片底層原理的開發(fā)者參考。1. Cesium為什么要專門的離線terrain數(shù)據(jù)1.1 在線地形與離線地形的本質(zhì)區(qū)別很多人第一次接觸Cesium時直接用viewer.terrainProvider await Cesium.createWorldTerrainAsync()一把梭感覺地形是“白送”的。但生產(chǎn)環(huán)境不是這么回事。Cesium自帶的全球地形數(shù)據(jù)服務(wù)部署在公網(wǎng)上走的是國外CDN項目一旦遷移到單位的內(nèi)網(wǎng)、政務(wù)網(wǎng)、私有云這些在線服務(wù)全部失效。即使能通遇到帶寬瓶頸或者服務(wù)商調(diào)整策略地形加載也會時好時壞。離線terrain數(shù)據(jù)本質(zhì)上就是把Cesium在線地形服務(wù)那一套瓦片文件搬到自己的服務(wù)器上。生成完以后地形瓦片就是一堆本地靜態(tài)文件配一個nginx或者任意靜態(tài)資源服務(wù)就能被Cesium讀取。它的核心優(yōu)點(diǎn)有三個第一是數(shù)據(jù)完全可控想用哪個區(qū)域、想做到多少級、想換高程源都可以第二是加載穩(wěn)定不依賴外網(wǎng)第三是符合很多項目的數(shù)據(jù)安全要求地形高程數(shù)據(jù)不必經(jīng)過第三方服務(wù)器。1.2 地形瓦片的核心格式Quantized-MeshCesium加載離線terrain時最常用的格式是Quantized-Mesh中文通常叫量化網(wǎng)格。它跟普通三維模型格式最大的區(qū)別在于它是專門為流式加載設(shè)計的。同一個區(qū)域內(nèi)一個地形瓦片往往只有幾十KB到幾百KB頂點(diǎn)坐標(biāo)通過量化算法壓縮到很小的字節(jié)數(shù)只存儲必要的高程和三角形網(wǎng)格信息。Cesium拿到這些瓦片后會根據(jù)相機(jī)位置按四叉樹規(guī)則動態(tài)請求不同層級的瓦片遠(yuǎn)處加載低層級近處加載高層級。很多初學(xué)者會問為什么不能直接把一張高程柵格圖DEM丟給Cesium因?yàn)橐粡堅糄EM可能幾百M(fèi)B甚至幾個GB瀏覽器根本加載不動也沒法按視錐裁剪。地形切片相當(dāng)于把DEM拆成了一張張帶金字塔層級的三角形小網(wǎng)片瀏覽器按需加載內(nèi)存和帶寬壓力都小很多。打個比方原始DEM像一整塊超大地毯terrain瓦片就是把地毯裁成小塊并編好號你想看哪塊就取哪塊不用整屋鋪開。1.3 離線地形生成的核心鏈路離線地形生成的完整鏈路可以理解為“數(shù)據(jù)準(zhǔn)備—切片—部署”三步走先準(zhǔn)備高程源數(shù)據(jù)SRTM、ASTER、ALOS這些公開DEM都可以再用GDAL工具做格式轉(zhuǎn)換、投影轉(zhuǎn)換、裁剪、合并和空洞修補(bǔ)然后用地形切片工具生成Quantized-Mesh瓦片并自動產(chǎn)出layer.json文件最后把瓦片目錄部署到靜態(tài)服務(wù)器前端用CesiumTerrainProvider加載。鏈路看起來不復(fù)雜但每一步都有不少細(xì)節(jié)。尤其是GDAL命令那一堆參數(shù)新手看到容易頭大。實(shí)際上這些都是固定套路做完一個樣例項目之后就能形成肌肉記憶。2. 程序準(zhǔn)備生成離線terrain要用哪些工具2.1 主流水地形成工具的橫向?qū)Ρ饶壳氨容^主流的地形切片工具有以下幾類我直接做了個對比表方便大家根據(jù)項目情況選型。工具語言輸入格式輸出格式維護(hù)狀態(tài)適用場景Cesium Terrain BuilderCGDAL支持的柵格terrain/quantized-mesh已停止維護(hù)老項目、學(xué)習(xí)原理ctb-tileCGDAL支持的柵格terrain/quantized-mesh社區(qū)持續(xù)維護(hù)大多數(shù)新項目自研Node工具Node.jsGeoTIFF等quantized-mesh看自己有特殊定制需求在線轉(zhuǎn)換服務(wù)商業(yè)上傳DEM服務(wù)化地形持續(xù)數(shù)據(jù)量小、可聯(lián)網(wǎng)我的建議是優(yōu)先使用ctb-tile。它算是Cesium Terrain Builder的延續(xù)版修復(fù)了不少舊工具在GDAL新版本下的編譯問題和瓦片生成bug而且一直在跟隨Cesium官方的格式更新。網(wǎng)上很多老教程還在教Cesium Terrain Builder但用到新系統(tǒng)上大概率會栽在編譯環(huán)節(jié)建議直接跳過。2.2 最小環(huán)境與GDAL安裝生成terrain數(shù)據(jù)需要Linux環(huán)境推薦用Ubuntu 22.04或者Debian 12這類長期支持版本。如果平時用Windows開發(fā)建議裝WSL既不影響日常使用又能跑完整的命令行工具鏈。先安裝基礎(chǔ)依賴和GDALsudo apt update sudo apt install build-essential cmake git sqlite3 sudo apt install gdal-bin libgdal-dev gdalinfo --versionGDAL裝好后可以用gdalinfo查看柵格信息、gdal_translate做格式轉(zhuǎn)換、gdalwarp做投影變換、gdal_merge.py做鑲嵌合并、gdal_fillnodata.py填補(bǔ)空洞后面全都會用到。2.3 編譯ctb-tile時容易踩的依賴坑ctb-tile需要自己編譯依賴主要有GDAL開發(fā)庫、curl、sqlite3等。具體步驟git clone https://github.com/ahuarte47/ctb-tile.git cd ctb-tile mkdir build cd build cmake .. make -j4 sudo make install如果系統(tǒng)里GDAL版本比較新編譯時經(jīng)常報找不到GDAL相關(guān)頭文件的錯誤。這時候需要指定GDAL的安裝路徑常見做法是加幾個cmake參數(shù)cmake -DGDAL_INCLUDE_DIR/usr/include/gdal -DGDAL_LIBRARY/usr/lib/x86_64-linux-gnu/libgdal.so ..另外libcurl4-openssl-dev如果沒裝編譯時也會報錯。建議先把依賴一次裝齊sudo apt install libcurl4-openssl-dev libsqlite3-dev libtiff-dev這些坑我在第一次編譯時全踩過后來發(fā)現(xiàn)只要按照這個順序裝依賴基本一次就能過。3. 實(shí)操從DEM到可加載的terrain地形3.1 高程源數(shù)據(jù)怎么挑離線地形生成的地基是DEM數(shù)據(jù)。常見公開高程源有三種SRTM V330米分辨率覆蓋最穩(wěn)全球大多數(shù)區(qū)域都能用、ALOS AW3D3030米分辨率精度和現(xiàn)勢性更好尤其城市區(qū)域、ASTER GDEM V330米分辨率但云影和空洞較多需要額外處理。工程上我一般首選ALOS AW3D30如果沒有該區(qū)域數(shù)據(jù)再退回SRTM。下載時注意確認(rèn)坐標(biāo)系是否為WGS84或者EPSG:4326高程單位是否為米。很多DEM原始文件是IMG、DTED、HGT格式這些都沒關(guān)系GDAL都能讀后面統(tǒng)一轉(zhuǎn)成GeoTIFF就行。3.2 DEM預(yù)處理三步走拿到DEM后不能直接丟給切片工具必須做三步預(yù)處理否則生成出來的地形可能有空洞、錯位甚至沒法用。第一步檢查并轉(zhuǎn)換格式。用gdalinfo查看原始文件信息gdalinfo srtm_01.tif重點(diǎn)看Driver是否為GTiffCoordinate System是否為EPSG:4326Band 1是否為浮點(diǎn)型以及NoData Value是否正常。如果不是TIF格式先轉(zhuǎn)換gdal_translate -of GTiff input.dt2 output.tif第二步合并和裁剪。如果研究區(qū)跨了多幅DEM用gdal_merge.py先合并gdal_merge.py -o mosaic.tif srtm_01.tif srtm_02.tif srtm_03.tif范圍太大時建議先用矢量邊界裁剪這樣切片速度會快很多也能減少磁盤占用gdalwarp -t_srs EPSG:4326 -cutline study_area.geojson -crop_to_cutline mosaic.tif clip.tif第三步處理空洞和異常值。SRTM在山地和云覆蓋區(qū)域經(jīng)常有NoData空洞這些洞不處理生成的地形上就會出現(xiàn)一個個“天坑”。先用gdalinfo -stats看有沒有NoData值然后用gdal_fillnodata.py填充gdal_fillnodata.py -md 20 clip.tif filled.tif-md 20表示最多向外搜索20個像素去插值填補(bǔ)基本能解決大部分小空洞。3.3 用ctb-tile生成Quantized-Mesh瓦片預(yù)處理完的高程TIFF就可以切片了。ctb-tile最常用的命令如下ctb-tile -o ./terrain -p geographic -c default -e terr filled.tif幾個關(guān)鍵參數(shù)的含義-o ./terrain指定輸出目錄-p geographic使用經(jīng)緯度網(wǎng)格切分地形沒投影變形Cesium官方在線地形就是這么做的-c default輸出Cesium默認(rèn)的壓縮格式也就是Quantized-Mesh-e terr地形瓦片文件擴(kuò)展名通常命名成.terrain。生成完成后目錄結(jié)構(gòu)大概是下面這樣terrain/ ├── layer.json ├── 0/ │ └── 0/ │ └── 0.terrain ├── 1/ │ ├── 0/ │ │ └── 0.terrain │ └── 1/ │ └── 0.terrain └── ...其中l(wèi)ayer.json是整個地形的元數(shù)據(jù)描述文件包含坐標(biāo)系、瓦片范圍、層級信息Cesium加載時首先要請求的就是它強(qiáng)烈建議生成后打開看一眼內(nèi)容是不是符合預(yù)期。3.4 nginx部署與Cesium加載地形瓦片本質(zhì)上是一堆靜態(tài)文件部署方式很靈活我習(xí)慣用nginx。配置一個簡單靜態(tài)站點(diǎn)server { listen 80; server_name localhost; root /data/terrain; index index.html; location / { add_header Access-Control-Allow-Origin *; try_files $uri $uri/ 404; } }注意add_header Access-Control-Allow-Origin *這一句很關(guān)鍵如果前端頁面和地形服務(wù)的域名不一致不加會直接觸發(fā)跨域問題。前端加載離線terrain的代碼如下const terrainProvider await Cesium.CesiumTerrainProvider.fromUrl( http://localhost/terrain, { requestVertexNormals: true } ); const viewer new Cesium.Viewer(cesiumContainer, { terrainProvider: terrainProvider });如果還要做地形陰影和動態(tài)光照效果把viewer.scene.globe.enableLighting true打開Cesium會根據(jù)太陽位置自動模擬光照地形起伏會非常明顯。這里有個細(xì)節(jié)開啟光照需要頂點(diǎn)法線數(shù)據(jù)所以fromUrl里務(wù)必設(shè)置requestVertexNormals: true不然陰影可能出不來。4. 常見問題與排查技巧4.1 瓦片請求404或者layer.json加載失敗這個問題出現(xiàn)頻率最高。瀏覽器F12打開控制臺如果看到terrain/0/0/0.terrain等請求返回404多半是路徑?jīng)]配對。先手動在瀏覽器訪問一下http://localhost/terrain/layer.json確認(rèn)能正常返回JSON。如果代理到nginx后404重點(diǎn)檢查root路徑是不是指向了包含layer.json的那個目錄??缬騿栴}也是重災(zāi)區(qū)。前端頁面在8080端口地形服務(wù)在80端口瀏覽器會認(rèn)為跨域所以nginx必須加Access-Control-Allow-Origin *。如果用的是開發(fā)服務(wù)器還要確認(rèn)代理是否把地形路徑也轉(zhuǎn)發(fā)過去了。4.2 高程整體偏高或出現(xiàn)深坑出現(xiàn)過好幾次地形加載成功后有的區(qū)域高程明顯不正常要么整體抬高要么出現(xiàn)極深的天坑。罪魁禍?zhǔn)谆径际荖oData值沒有處理干凈。SRTM的空洞值有時候是-32768如果不填充Cesium會把這個值當(dāng)成真實(shí)高程地形直接往下捅穿。排查方法很簡單生成瓦片前用gdalinfo -stats查看NoData設(shè)置如果NoData值異常先通過gdal_fillnodata.py處理。另外如果DEM原始像素深度是16位整型且高程單位不是米需要先轉(zhuǎn)成浮點(diǎn)型并統(tǒng)一單位否則做出來的地形比例不對。4.3 只有低層級放大后模糊沒細(xì)節(jié)這種情況通常有兩個原因一是ctb-tile默認(rèn)生成的最大層級不夠二是源DEM分辨率不夠到高層級已經(jīng)沒有新信息可以表示。如果源數(shù)據(jù)是30米分辨率一般來說到第15到17級性價比最高再往上強(qiáng)行生成更多層級只是把同一個像素拉伸放大沒有實(shí)際細(xì)節(jié)。如果確實(shí)需要更高級別可以用-l參數(shù)指定最大層級ctb-tile -l 18 -o ./terrain -p geographic -c default -e terr filled.tif同時也要考慮到層級越高瓦片數(shù)量是成4的指數(shù)倍增長的磁盤占用和生成時間都會爆炸式上漲小范圍測試沒問題全區(qū)域跑之前先算一下容量。4.4 攝像機(jī)靠近地形時加載卡頓有的項目要求相機(jī)貼近地形飛行瓦片請求量會非常大如果機(jī)器性能一般很容易卡。我常用的優(yōu)化策略是在保證核心區(qū)域高層級的同時對周邊大范圍只生成較低層級瓦片并配合CesiumTerrainProvider的自動LOD機(jī)制讓相機(jī)拉遠(yuǎn)時自動切換粗粒度瓦片拉近時才請求精細(xì)瓦片。另外如果地形加載完還有白塊閃爍可以試試在初始化Viewer時降低scene.globe.maximumScreenSpaceError這個值默認(rèn)是2調(diào)到1.5左右能看到更精細(xì)的網(wǎng)格但渲染開銷也會增加。實(shí)測下來配合Cesium當(dāng)前版本的渲染優(yōu)化一般項目里1.5到2之間效果都不錯。4.5 貼地對象與地形不貼合的處理在地形上疊加高德箭頭效果、矩形、圓柱體、雷達(dá)光波這些元素時最容易出問題的是要么穿模要么浮空。Entity對象需要顯式設(shè)置貼地屬性entity.heightReference Cesium.HeightReference.CLAMP_TO_GROUND;對于GroundPrimitive類型的對象如果更換地形數(shù)據(jù)后沒有自動更新可以手動觸發(fā)一次刷新或者重建primitive的幾何狀態(tài)。這種情況多發(fā)生在地形瓦片異步加載完成之后用scene.requestRender()強(qiáng)制渲染一幀基本能解決。4.6 Vue3項目集成離線terrain的注意點(diǎn)在Vue3單頁應(yīng)用里集成Cesium離線地形最關(guān)鍵的問題有兩個異步加載和組件銷毀。terrainProvider創(chuàng)建是異步的必須放在onMounted里await組件切換時如果只銷毀DOM不銷毀ViewerGPU資源會持續(xù)泄漏。推薦寫法import * as Cesium from cesium; let viewer null; onMounted(async () { viewer new Cesium.Viewer(map, { terrainProvider: await Cesium.CesiumTerrainProvider.fromUrl(/terrain) }); }); onUnmounted(() { viewer?.destroy(); viewer null; });Vue3項目里還要注意Cesium的靜態(tài)資源路徑是不是被Webpack或Vite正確處理不然會出現(xiàn)模型貼圖或地形加載失敗的問題。5. 進(jìn)階擴(kuò)展與個人體會5.1 區(qū)域級地形的容量評估30米分辨率的DEM做好一個3000平方公里的小城市量化網(wǎng)格地形大約在幾百M(fèi)B到1GB之間具體看地形起伏程度。如果是全國或者全球級別數(shù)據(jù)量會到幾百GB甚至TB級別本地搭建性價比太低。我的建議是先裁剪研究區(qū)按行政邊界或者項目范圍分塊生成再通過nginx的try_files把這些塊作為同一套瓦片服務(wù)暴露給Cesium。多臺機(jī)器并行切片時可以用-j參數(shù)控制線程數(shù)或者用GNU Parallel把不同區(qū)域分給不同進(jìn)程跑效率提升明顯。5.2 離地地形也可以給Unity/Unreal項目復(fù)用生成出來的terrain瓦片不只能用在Web端Cesium for Unity和Cesium for Unreal同樣支持離線terrain加載。在數(shù)字孿生項目里用同一套離線地形同時支撐Web大屏和桌面端的做法很常見。這樣只需要一次切片、一次部署多個端共享省掉了反復(fù)下載和轉(zhuǎn)換的麻煩。5.3 先做小范圍驗(yàn)證再全量跑最后分享一點(diǎn)個人經(jīng)驗(yàn)。我最早做離線地形時直接找了一個大范圍DEM開始切片結(jié)果跑了兩個多小時加載才發(fā)現(xiàn)坐標(biāo)系沒轉(zhuǎn)成WGS84整個地形位置偏了還浪費(fèi)了一下午。后來我養(yǎng)成了一個習(xí)慣先切一小塊區(qū)域驗(yàn)證瓦片目錄結(jié)構(gòu)、layer.json內(nèi)容和Cesium加載效果都正常再全量生成。全量跑完以后再用Cesium里自帶的坐標(biāo)拾取工具隨機(jī)抽幾個點(diǎn)和原始DEM對比一下高程偏差在1到2米內(nèi)基本正常。還有一個很容易被忽略的小細(xì)節(jié)生成完的terrain目錄里如果有多余的.tmp文件或者隱藏文件nginx會因?yàn)檎也坏秸_文件而返回404部署前最好清理一遍。離線terrain這個東西原理并不復(fù)雜工具鏈也就那么幾個但只要數(shù)據(jù)和路徑上哪個環(huán)節(jié)稍有疏忽前端就是一片白屏所以按流程走、逐項驗(yàn)證比盲目求快靠譜得多。本文還有配套的精品資源點(diǎn)擊獲取