资讯动态

Cesium离线地形实战:DEM切瓦片到quantized-mesh验收

发布时间:2026/10/26 4:01:54 来源:尧图企业网站定制
简介面向需要在无网络环境下展示三维地形的 Cesium 开发者与地理信息学习者这套资料围绕将 DEM 高程 tif 数据转换为 Cesium 可加载的离线 terrain 瓦片提供从依赖安装、脚本调用到示例数据与结果目录的完整流程帮助降低环境配置与脚本调试门槛。压缩包共 11 个文件约 45.9MB包含 3 个 msi 与 2 个 exe 安装程序、2 个 Python 脚本、1 份 PDF 使用说明以及 json、terrain、txt 等配置和结果文件覆盖 Python、PIL、GDAL、NumPy 等运行依赖与地形生成环节。已有 3095 人学习说明其在离线地形制作场景中具备较高参考价值。读者可据此理解 DEM 数据检查、瓦片生成、坐标处理、地形提供者配置等关键环节并借助说明文档与示例结果排查路径、投影和脚本运行问题适合希望搭建私有离线地形底图或研究地形瓦片生产流程的中高级开发者。1. Cesium 离线地形为什么自有 DEM 必须自己切瓦片公有地形服务在内网环境基本不可达而自采或采购的高精度 DEM 精度往往又高于公开数据一个量级这两类场景最后都会落到同一件事上把一张 GeoTIFF 切成 Cesium 能按需请求的地形瓦片。Cesium 的三维球体不会直接读 DEM它只认两样东西——沿经纬度分级的瓦片目录和一份描述可用范围的 layer.json。中间这层转换就是离线 terrain 的全部工作量。说穿了不复杂但每一步都有能让人卡半天的细节NoData 没处理干净海面会冒出一圈尖刺layer.json 的 available 写死远处地形直接空白切完不验证上线才发现整体下沉几十米。这篇按我实际跑过多次的顺序从 DEM 预处理、切片参数到前端加载和精度验收一条线走完把踩过的坑一并摊开。2. DEM 预处理从散装栅格到一块能切的 GeoTIFF2.1 先算清楚需要切到第几级拿到 DEM 第一件事不是打开 GDAL而是算一下切片层级。层级开高了瓦片数量指数爆炸磁盘和切片时间都扛不住开低了地形细节又糊成一片。Cesium 的地形瓦片沿用地理坐标分幅0 级是横向 2 块、纵向 1 块之后每级在经纬两个方向各翻一倍。单块瓦片的地面宽度可以这样估# 估算某一级地形瓦片的实际采样间距 EARTH_CIRCUMFERENCE 40075017.0 # 赤道周长单位米 def tile_sample_spacing(zoom, tile_size65): # 每级横向瓦片数 2 * 2^zoom tile_width EARTH_CIRCUMFERENCE / (2 * (2 ** zoom)) # 瓦片内顶点数 tile_size相邻顶点间距是 tile_size-1 段 return tile_width / (tile_size - 1) for z in range(10, 16): print(fzoom{z}, 瓦片宽度约 {EARTH_CIRCUMFERENCE/(2*(2**z)):.0f} m, f采样间距约 {tile_sample_spacing(z):.1f} m)这段的输出大致是zoom 12 采样间距约 76 米zoom 13 约 38 米zoom 14 约 19 米zoom 15 约 9.5 米。对照手里的数据源就能定层级——1 角秒量级约 30 米的 DEM 切到 14 级已经足够再往上只是把同一批高程值重复插值文件体积翻四倍画面看不出差别。90 米量级的公开数据切 13 级就到底了。这一步多花五分钟能省掉后面几小时的无效计算。2.2 坐标系对齐ctb 只认 EPSG:4326地形切片工具有个硬性前提输入的栅格必须是地理坐标经纬度投影坐标的 DEM 直接喂进去输出要么报错要么位置整体偏移。常见的 UTM 分带数据、地方坐标系的测绘成果都得先过一遍gdalwarpgdalwarp \ -t_srs EPSG:4326 \ # 目标坐标系地形切片必须用经纬度 -r bilinear \ # DEM 用双线性别用 nearest -s_srs EPSG:32650 \ # 源坐标系按实际填省略则读文件头 -srcnodata -32768 \ # 源数据的无效值 -dstnodata -32768 \ # 目标保留同一个无效值方便后续统一处理 -multi \ # 多进程 -wo NUM_THREADSALL_CPUS \ # 单进程内再开多线程 -co TILEDYES -co COMPRESSDEFLATE \ dem_utm.tif dem_wgs84.tif重采样方式这里值得停一下。nearest会保留原始像元值但地形是连续曲面最近邻会在坡度变化处产生阶梯状台阶切片之后放大看能明显看到一块块的平面。bilinear是常规选择平滑但不至于过度模糊cubic更锐利代价是可能产生超出原始范围的极值山脊处偶尔出现不自然的尖角。我一般用bilinear除非数据本身噪声很大需要cubic压一下。另外注意-srcnodata和-dstnodata要写成一致的值。有些数据源标的是-9999有些是-32768如果源和目标不一致重投影后的边界会混进一堆异常高程后面切片时在数据边缘形成一圈悬崖。2.3 镶嵌把几百个分幅拼成一块实际拿到的 DEM 通常是一堆按图幅切好的小块。切片的输入最好是一整块 GeoTIFF所以需要先镶嵌。直接用gdal_merge.py拼大文件会很慢且吃内存标准做法是先建虚拟栅格再转实体# 第一步生成 VRT 虚拟镶嵌几乎不占磁盘只记录各分幅的位置关系 gdalbuildvrt \ -srcnodata -32768 \ -vrtnodata -32768 \ -resolution highest \ # 分幅分辨率不一致时取最高避免被拉低 -allow_projection_difference \ # 分幅间坐标系有微小差异时容错 mosaic.vrt \ dem_tiles/*.tif # 第二步VRT 转实体 GeoTIFF顺便做分块和压缩 gdal_translate \ -of GTiff \ -co TILEDYES \ -co BLOCKXSIZE512 -co BLOCKYSIZE512 \ -co COMPRESSDEFLATE -co PREDICTOR2 \ -co BIGTIFFIF_SAFER \ # 超过 4GB 自动切 BigTIFF mosaic.vrt mosaic.tif-resolution highest这条容易被忽略。如果分幅里混了不同分辨率的数据不加这个参数gdalbuildvrt会按最低分辨率统一结果就是整块数据被降采样白瞎了高精度那部分。PREDICTOR2是针对高程这类连续整数数据的预测器压缩率比默认高不少几 GB 的文件能省下几百 MB。镶嵌完记得用gdalinfo看一眼范围和高程分布gdalinfo -stats mosaic.tif | head -40重点看Min/Max两行。如果最小值是-32768或者某个异常大的负数说明 NoData 没设对如果最大值突然飙到八九千米而目标区域最高峰只有两三千米那多半是某块分幅混进了异常值得单独揪出来处理。这一步的检查成本极低但能挡掉后面七八成的诡异问题。2.4 NoData 的处理边界NoData 在地形切片里是个绕不开的东西。海洋、湖泊、数据覆盖不到的区域原始 DEM 都会填一个标记值。如果这个值原样进入切片流程工具会把它当成真实高程在海平面附近造出一片深坑或者高台。两种处理方式一是把 NoData 改成一个合理的填充值比如统一填 0海平面适合沿海区域二是用gdal_fillnodata.py做邻域插值填补适合内陆的数据空洞代价是填补区域的地形是编出来的精度要求高的项目要慎用。# 方案一NoData 统一填 0适用于海域 gdal_calc.py -A mosaic.tif --outfilemosaic_filled.tif \ --calcA*(A-1000) --NoDataValue0 # 方案二邻域插值填补小面积空洞注意 -md 是搜索距离像素 gdal_fillnodata.py -md 20 -si 0 -of GTiff mosaic.tif mosaic_filled.tifgdal_calc.py那一行的逻辑是把小于 -1000 的值全部置零相当于认为深海区域按海平面处理。阈值 -1000 是经验值死海这类低于海平面的区域要相应放宽否则真实洼地会被误伤。gdal_fillnodata.py的-md 20表示在 20 像素半径内找有效值插值半径越大填补越平滑但也越容易把真实的地形起伏抹平一般控制在 10 到 30 之间。3. 切片ctb-tile 出 quantized-mesh 的参数与验证3.1 两种地形格式的选择Cesium 支持两种地形瓦片格式选错了后面全是返工。核心差异如下维度heightmap-1.0quantized-mesh-1.0数据结构65×65 规则高程网格不规则三角网 顶点量化同等精度下体积较大约为前者的 50%70%顶点法线不支持支持octvertexnormals 扩展水面效果不支持支持 water mask 扩展Cesium 版本要求早期版本即可需要较新的版本生成工具参数-f Terrain-f Mesh新项目一律选quantized-mesh。它用三角网描述地表平坦区域自动减少三角形数量陡峭区域加密同样的视觉精度下数据量更小而且只有这个格式能带顶点法线光照效果才正常否则山体看上去是一片死平的色块。heightmap只在对接极老版本的客户端时才需要。3.2 工具链的编译与依赖切片工具依赖 GDAL 做栅格读写依赖 zlib 做瓦片压缩编译前先把开发包装齐# Debian/Ubuntu 系 sudo apt-get update sudo apt-get install -y build-essential cmake \ libgdal-dev libproj-dev libz-dev libboost-dev # 确认 GDAL 版本工具对 GDAL 版本有最低要求 gdal-config --version从源码仓库拉到本地后走标准的 CMake 流程mkdir -p build cd build cmake .. \ -DCMAKE_BUILD_TYPERelease \ # 切片是计算密集型必须开优化 -DCMAKE_INSTALL_PREFIX/usr/local make -j$(nproc) sudo make installCMAKE_BUILD_TYPERelease这条别省。默认的构建类型没有开编译器优化切片速度能差三到五倍一次切十几级的数据时间差距是几小时。编译完成后用ctb-tile --help确认命令可用同时看下输出的版本信息不同版本对参数的支持有差异尤其是-z的写法和--resume的行为。3.3 切片命令与参数逐项拆解先探一下数据适合切到哪一级工具自带一个探测子命令ctb-info -f Mesh mosaic_filled.tif它会打印出栅格的范围、分辨率以及建议的层级区间。这个建议值可以直接作为下一步-z的参考比手算更贴合实际数据。正式切片ctb-tile \ -f Mesh \ # 输出 quantized-mesh 格式 -C \ # 自动生成 layer.json -N \ # 不生成法线先用它快速出一版看效果 -l my-terrain \ # layer.json 里的图层名前端按此识别 -z 0-14 \ # 层级范围0 到 14 级全切 -c 8 \ # 并发线程数按 CPU 核数给 -t 65 \ # 瓦片内顶点数quantized-mesh 固定用 65 -o ./terrain_output \ # 输出根目录 mosaic_filled.tif参数里最需要想清楚的是三个。-z 0-14的层级范围。0 级是必须的Cesium 从全球视角开始加载没有 0 级瓦片相机拉远时地形直接消失。上限按 2.1 节算出来的采样间距定不要贪高。-c 8的并发数。切片是 CPU 密集型的线程数给到物理核数比较合适超线程核心收益有限反而争抢内存带宽。给太多还会导致同时打开的文件句柄超限中途报Too many open files。-N的含义是不生成顶点法线。第一次跑建议加上速度快一倍左右先把整体效果和瓦片完整性确认了再关掉它重切一遍带法线的正式版本。法线存在的意义是让光照在山体上有明暗过渡没有法线的地形在倾斜视角下看起来像纸片。如果是大数据量、切到一半中断了重跑时加-R走断点续传ctb-tile -f Mesh -C -R -l my-terrain -z 0-14 -c 8 -o ./terrain_output mosaic_filled.tif-R会跳过已存在的瓦片文件。这里有个前提上一次中断时输出的文件必须是完整写完的。如果进程被强杀在写文件的中途会留下半截瓦片续传时被当成已完成跳过前端加载到那一块就报解析错误。稳妥做法是中断后先检查最后修改时间最新的几个文件大小是否为 0有异常就删掉对应层级目录再续。3.4 layer.json 的字段校验工具自动生成的layer.json是整个地形服务的地图前端完全靠它决定请求哪些瓦片、按什么格式解析。切完必须打开看一眼{ tilejson: 2.1.0, format: quantized-mesh-1.0, version: 1.0.0, scheme: tms, tiles: [{z}/{x}/{y}.terrain?v{version}], projection: EPSG:4326, bounds: [-180, -90, 180, 90], available: [ [0, 0, 0, 0], [1, 0, 0, 1], [2, 0, 0, 3] ], extensions: [octvertexnormals] }几个字段容易出问题。format必须和实际文件内容一致。切的是 Mesh这里写成heightmap-1.0前端会按错误的结构解析症状是地形全平或者直接抛异常。available是可用瓦片范围数组每个元素是[起始X, 起始Y, 结束X, 结束Y]对应一个层级。这个数组由工具根据数据范围自动算出来一般不用改。但如果手工合并过多次切片结果或者用-R续传过这个数组可能和磁盘上的实际文件对不上——数组里说某块有瓦片实际文件不存在前端请求就是一片 404。bounds是数据的地理范围。它影响的是前端对这块地形的整体定位写错了地形会被拉伸到错误的位置。工具生成的一般是对的除非输入的 DEM 范围信息本身有问题。最后一步验证起个静态服务用浏览器直接请求一个已知存在的瓦片python3 -m http.server 8080 --directory ./terrain_output # 另开终端按 layer.json 里的 available 挑一个实际存在的瓦片 curl -I http://localhost:8080/my-terrain/5/10/12.terrain返回 200 且Content-Length大于 0说明目录结构和文件命名没问题。返回 404 就按路径逐段对通常是图层目录名和-l参数不一致或者层级目录少了一层。4. Cesium 端接入TerrainProvider 配置与贴地验证4.1 静态服务的三个配置要点把切片输出目录挂到 Web 服务器上就行但有几个点必须配否则前端会报一堆看起来莫名其妙的错。server { listen 80; server_name localhost; location /terrain/ { alias /data/terrain_output/; autoindex off; # 关键一跨域头前端和后端不同端口时必需 add_header Access-Control-Allow-Origin *; # 关键二.terrain 的 MIME 类型 types { application/vnd.quantized-mesh terrain; } default_type application/octet-stream; # 关键三不要再对 .terrain 开 gzip gzip off; # 瓦片文件小且多缓存给足 expires 7d; } }第二条和第三条是重灾区。.terrain文件在 quantized-mesh 格式下本身就是 gzip 压缩过的二进制服务端如果再套一层Content-Encoding: gzip多数情况下 Cesium 内部的解压逻辑能兜住但会多一次无谓的解压开销少数 nginx 配置下Content-Length会按压缩前的大小写浏览器读到一半判定截断前端表现就是部分瓦片加载失败、地形出现整块的破洞。直接gzip off最省心。expires 7d也值得加。一次典型的地形漫游会请求成百上千个瓦片没有缓存头的话每次刷新都重新拉一遍首次加载能拖到十几秒。4.2 Viewer 里的最小改动前端代码本身很短关键是参数要对// 初始化时直接指定离线地形 const viewer new Cesium.Viewer(cesiumContainer, { // 关掉默认的在线影像换成自己的底图或纯色底 imageryProvider: false, baseLayerPicker: false, // 关键换成自己的地形服务 terrainProvider: new Cesium.CesiumTerrainProvider({ url: http://localhost/terrain/my-terrain/, // 切片时如果生成了法线这里必须为 true否则光照不对 requestVertexNormals: true, // 水面效果只有切片时带了 water mask 才有意义 requestWaterMask: false }) }); // 加一层本地影像或纯色底避免地球是黑的 viewer.imageryLayers.addImageryProvider( new Cesium.SingleTileImageryProvider({ url: /assets/base.png, rectangle: Cesium.Rectangle.fromDegrees(100, 20, 120, 40) }) );url的末尾斜杠不能省。Cesium 拼接瓦片路径时是直接字符串连接少了斜杠会拼成my-terrain5/10/12.terrain这种畸形路径请求全部 404但浏览器控制台只报网络错误看不出是拼接问题。requestVertexNormals要和切片时的-N参数对应。切的时候加了-N不生成法线这里却写成trueCesium 会按有法线的结构解析瓦片读到的字段错位地形可能整片消失或者显示成一堆乱三角。切带法线的版本这里就true切不带法线的快速版这里就false。imageryProvider: false是为了避免 Viewer 初始化时去拉在线影像服务。内网环境拉不到初始化过程会卡住几秒然后抛错整个地球不显示。关掉之后自己加一层本地底图或者干脆留空先看地形轮廓。4.3 相机贴地验证别只看平面截图代码跑起来能看到山不等于地形是对的。截图只能验证「有起伏」验证不了「起伏的高度值是否正确」「位置是否偏移」。这两个才是上线后最容易出问题、也最难排查的。第一层验证相机贴地飞// 飞到目标区域开启贴地模式 viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(110.0, 30.0, 8000), orientation: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(-45), roll: 0 }, duration: 2 }); // 开启碰撞检测相机不会被地形穿透 viewer.scene.screenSpaceCameraController.enableCollisionDetection true; // 关键最小缩放距离设为 1 米能贴到地表检查细节 viewer.scene.screenSpaceCameraController.minimumZoomDistance 1.0;开着碰撞检测往山谷里推相机如果相机能顺着坡面平滑下滑说明地形的高程值和法线都是连续的如果相机突然卡住或者瞬间弹起多半是相邻瓦片的高程在高一级和低一级之间有跳变通常是切片时层级衔接出了问题。第二层验证直接采样高程比对// 在若干已知坐标上采样地形高度 const points [ Cesium.Cartographic.fromDegrees(110.0, 30.0), Cesium.Cartographic.fromDegrees(110.1, 30.1), Cesium.Cartographic.fromDegrees(110.2, 30.2) ]; Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, points) .then((samples) { samples.forEach((p, i) { // 与原始 DEM 在 GDAL 里查到的值逐点对比 console.log(点 ${i}: 经度 ${Cesium.Math.toDegrees(p.longitude).toFixed(4)}, 纬度 ${Cesium.Math.toDegrees(p.latitude).toFixed(4)}, 高程 ${p.height.toFixed(2)} m); }); });sampleTerrainMostDetailed会按当前地形服务能提供的最高细节层级去采样返回的是切片数据里实际存储的高程不是前端插值出来的视觉效果。拿这几个值和在 GDAL 里对原始 DEM 用gdallocationinfo查到的值对比差值在几个采样间距的量级内也就是 2.1 节算出来的那十几二十米说明整条链路是准的如果差值稳定在某个固定数值上那是高程基准的问题第 5 章会专门讲。5. 避坑排查五类翻车现场的现象、原因与解法5.1 地形整体发白、发平像贴了一张纸现象前端加载成功控制台没有报错但地球表面是一片惨白完全没有山体起伏切换视角也不变化。原因.terrain文件被服务端二次压缩。切片工具输出的 quantized-mesh 瓦片本身已经是 gzip 格式Web 服务器默认对二进制流又套了一层压缩Content-Length按压缩前的大小返回浏览器读到一半判定数据截断Cesium 拿到的是残缺的压缩流解析失败后静默降级成平地。这个错误最坑的地方是控制台完全不报错只有一个平淡的球。解决在服务器配置里对.terrain后缀单独关掉压缩。nginx 里就是location ~* \.terrain$ { gzip off; }。验证方法是用curl -I看返回头里有没有Content-Encoding: gzip有就是这个问题。改完清一次浏览器缓存因为旧的错误响应可能还挂在缓存里。5.2 近处正常远处一片空白或者整块缺失现象相机放在数据中心区域细节很好往外拉远或者移到边缘地形直接消失露出的还是椭球体的底色。原因layer.json里的available数组和磁盘上的实际瓦片对不上。最常见于多次切片结果合并的场景——第一批切了 A 区域第二批切了 B 区域直接cp -r堆在同一个目录下但layer.json是第二批覆盖生成的available只记录了 B 区域的范围A 区域虽然文件都在前端按元数据判断后根本不会去请求。解决不要手工拼接多个切片输出目录。正确做法是先把所有 DEM 镶嵌成一块回到 2.3 节一次性切完。如果确实需要分片处理切完之后用脚本扫描实际存在的瓦片目录重新生成完整的available数组每个层级列出最小和最大 X、Y。检查方法是随便挑几个边缘瓦片路径用curl确认返回 200再对照available看这个层级有没有覆盖到。5.3 海面或数据边界冒出规则的高台、尖刺现象沿海区域或者数据覆盖边缘出现一圈高度完全一致、边缘非常笔直的突起或凹陷像有人用尺子画出来的。原因NoData 值被当成真实高程参与切片。原始 DEM 用-32768或-9999标记无效区域预处理时没处理干净切片工具按正常的整型高程读取量化之后在那些位置生成一堆极低或极高的顶点视觉上就是规则的深坑和尖刺。边缘特别直是因为 NoData 区域往往就是数据分幅的矩形边界。解决回到预处理阶段用gdalinfo -stats看清楚 NoData 值到底是多少用gdalwarp -dstnodata或gdal_calc.py统一替换掉。替换之后重新跑一遍 2.4 节的检查确认Min值落在合理的高程范围内。已经切完的瓦片不会自动修正必须整批重切所以这一步宁可在预处理阶段多花十分钟核对。5.4 切片跑到一半进程被杀或者磁盘瞬间打满现象切片命令跑了几个小时突然进程消失终端只留下一行Killed或者磁盘使用率在几分钟内从 20% 冲到 100%。原因层级开太高或者范围太大瓦片数量低估了。瓦片总数不是线性增长每加一级瓦片数量大约翻四倍。一个覆盖几百公里范围的数据从 13 级加到 15 级文件数可能从几万涨到上百万单个瓦片虽小inode 占用和目录遍历开销加起来非常可观。进程被Killed通常是系统 OOM 保护切片工具在处理大栅格时会缓存数据块内存不够就被内核干掉。解决切之前先估算瓦片总量。按 2.1 节的公式算出每级的瓦片宽度用数据范围除以瓦片宽度得出每级的行列数累加所有层级的乘积再乘以单瓦片的平均大小quantized-mesh 大约几十 KB得出磁盘占用。估算结果超过可用空间的 60% 就先降一级或者缩小范围。内存方面-c的线程数不要超过物理核数每个线程都有独立的数据缓存八线程的内存占用差不多是单线程的六到七倍。5.5 地形位置和高度整体偏移现象地形轮廓和底图对不上整体平移了一段距离或者位置是对的但所有高程统一偏高或偏低一个固定值比如整体下沉了三十多米。原因两种不同的偏移。水平方向多半是坐标系问题输入 DEM 带投影但没做gdalwarp切片工具按经纬度硬解释位置就歪了。垂直方向是高程基准问题原始数据用的是大地水准面高正高而 Cesium 按椭球高处理两者之间差一个大地水准面差距在部分区域能达到几十米。解决水平偏移回到 2.2 节用gdalinfo确认输入是GEOGCS还是PROJCS是投影就先转经纬度。垂直偏移需要知道原始数据的基准如果是正高得先用大地水准面模型转成椭球高再切片工具链里可以用gdalwarp配合相应的垂直基准转换参数完成如果拿不到基准信息就用 4.3 节的高程采样法在实际已知点上测出固定偏差在预处理阶段统一加回去。这条没有通用解必须在数据交接时问清楚基准。6. 批量与验收一条脚本跑完切片三个点验精度手动敲命令切一两批还行项目里动辄几十个区域必须脚本化。我现在的习惯是每个区域一个配置文件脚本读配置跑完整条链路中间任何一步失败就中断不留半成品。#!/usr/bin/env bash # slice_terrain.sh —— 输入一块已预处理好的 GeoTIFF输出可用的地形瓦片目录 set -euo pipefail # 任一命令失败立即退出避免带着错误继续往下跑 INPUT_TIF$1 OUTPUT_DIR$2 LAYER_NAME$3 ZOOM_RANGE${4:-0-14} # 默认切 0 到 14 级 # 前置检查确认输入文件存在且是地理坐标系 if [ ! -f $INPUT_TIF ]; then echo 输入文件不存在: $INPUT_TIF 2 exit 1 fi if ! gdalinfo $INPUT_TIF | grep -q GEOGCS; then echo 输入不是地理坐标系先跑 gdalwarp 转 EPSG:4326 2 exit 1 fi mkdir -p $OUTPUT_DIR # 先切不带法线的快速版本用于确认范围和层级是否正确 ctb-tile -f Mesh -C -N \ -l $LAYER_NAME \ -z $ZOOM_RANGE \ -c $(nproc) \ -o ${OUTPUT_DIR}/preview \ $INPUT_TIF echo 预览版切片完成: ${OUTPUT_DIR}/preview echo 确认无误后去掉 -N 参数重跑正式版脚本里set -euo pipefail这行是核心。默认的 bash 在命令失败时会继续往下执行切片工具报错退出脚本却接着打印「完成」很容易误判。加上这行任何一步失败立即终止-u还能挡住变量名写错导致的空参数。正式版就是去掉-N再跑一次输出到另一个目录两版对比着看。预览版验证范围、层级、layer.json字段正式版带法线用于最终交付。分两次跑看起来多花时间但比切完发现层级错了重来要划算得多。验收环节我固定抽三类点每类至少三个检查类型抽样位置验收标准高程精度平地、山腰、山顶各一点与原始 DEM 差值小于采样间距边缘完整性数据范围四角向内一格均有瓦片无 404无空白层级衔接同一位置在相邻两级各取一点两级高程差小于 5 米高程比对用gdallocationinfo在原始栅格上取值# 在原始 DEM 上查某个经纬度的高程-val 输出像素值 gdallocationinfo -val -wgs84 mosaic_filled.tif 110.0 30.0拿这个值和 4.3 节前端采样出来的值对照。差值稳定在采样间距以内就是正常的因为切片过程本身有量化损失不可能完全一致。如果差值稳定在某个固定值上回去看 5.5 节的高程基准问题。边缘完整性就用curl逐个请求四个角的瓦片路径看返回码。层级衔接最容易出问题的地方是数据范围的边界同一块地在 13 级和 14 级分别采样高程跳变超过 5 米说明两级瓦片的数据源不一致多半是分层切片时中间某一级漏了或者切了两次。血泪经验是验收一定要在交付前跑完别指望上线后再补。地形服务一旦接进业务系统前端七八个页面都在用出问题时改动成本比在切片阶段返工高一个数量级。从那以后我每次切完地形都要把这三类点强制走一遍哪怕同一块数据已经切过十次。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑