Cesium离线地形切片:DEM转quantized-mesh实战 简介这份资源面向需要在无网络环境下用 Cesium 展示三维地形的开发者与 GIS 学习者针对把 DEM 高程 tif 数据转换为离线 terrain 瓦片这一关键环节提供从运行环境搭建到瓦片产出的整套程序与配套说明文档。压缩包共 11 个文件约 45.9MB以 msi 与 exe 安装包Python、GDAL、PIL、NumPy 等地理处理依赖、两个 py 转换脚本、pdf 操作说明为主另附 json 配置、terrain 样例与 demdata 示例数据目录所需组件一次备齐。目前已有 3095 人学习下载。读者可对照说明逐步完成瓦片生成、坐标适配与目录组织再把瓦片移入工程并在 Cesium 中配置地形提供者随包的示例数据与结果目录结构清晰、便于对照验证路径设置、投影匹配、内存与磁盘占用以及 GDAL 报错等问题也能在文档中找到思路适合作为离线地形落地方案的实操参考。1. 离线地形这件事为什么换底图解决不了相机切进山体的问题内网演示前一周影像瓦片已经全部本地化地球转起来很顺直到镜头推到山口相机直接切进地面——山峰是平的峡谷也是平的。换底图解决不了这个问题缺的是地形。这套资源包解决的就是这一环把公开 DEM 或自己手里的高程数据转成 Cesium 能直接读取的离线 terrain 切片附带程序和一份能照着敲的操作说明。核心词落在 Cesium、离线地形、quantized-mesh 三个点上。它适合三类人内网或断网环境做三维 GIS 演示的开发者、需要把地形数据完全放在自己服务器上的项目组以及想知道.terrain文件里究竟装了什么、不愿只当调包侠的工程师。整条流水线是命令行驱动的GDAL 负责前处理切片器负责出瓦片跑通一次之后一个省的数据大概是一夜的事。2. 拆开这个包quantized-mesh 与 heightmap 两套格式怎么选拿到资源包先别急着跑命令花十分钟把两种地形格式的差别搞清楚能省掉后面一半的返工。Cesium 认的离线地形格式主要有两代老的 heightmap-1.0 和新的 quantized-mesh-1.0。资源包里两套切片参数都给了但默认走 mesh原因下面会讲清楚。2.1 quantized-mesh-1.0 的二进制结构与 layer.jsonquantized-mesh 是当前 Cesium 的默认地形格式扩展名同样是.terrain但内容是一段紧凑的二进制。打开一个文件前 88 个字节是固定头部顺序大致是中心点经纬高三个 double、最小最大高程两个 float、包围球四个 double、Horizon Occlusion Point四个 double。头部之后是顶点数据每个瓦片的顶点坐标被量化到 16 位整数区间再用 zig-zag 编码做差分压缩所以同一个瓦片里相邻顶点越密集压缩率越高。顶点之后是索引数据用高水位标记编码三角形索引最后是高程数据之前的对齐填充。真正让它在工程上值钱的是两段可选扩展octvertexnormals每个顶点带一个八面体编码的法线一共 2 字节。开了它Cesium 才能算出正确的地形光照山体的明暗过渡才有立体感不开地形就是一片均匀的哑光。watermask一张小图标记哪些像素是水面。海岸线、湖泊边缘会因此获得正确的反射效果。layer.json是这个格式的说明书切片器生成它Cesium 靠它决定去哪拿数据。关键字段如下字段作用常见取值与坑format声明切片格式必须是quantized-mesh-1.0写错整个地形加载不出来version数据版本号与tiles模板里的{version}对应改数据后要同步改scheme瓦片编号方案tms时 y 轴从南往北与 XYZ 相反这是 404 的头号来源tiles瓦片 URL 模板一般是[{z}/{x}/{y}.terrain?v{version}]projection投影切片器输出EPSG:4326别手改成 3857bounds数据覆盖范围由 DEM 实际范围算出写全地球会导致请求大量空瓦片available每级有效瓦片区间缺失时 Cesium 会按 bounds 全量请求首屏变慢extensions扩展声明与切片时开的参数必须一致否则法线读出来是乱码available这一段最容易被忽略。它记录每一层级实际存在瓦片的 x/y 起止范围Cesium 在飞行定位和 LOD 调度时会先查这张表。手工拼装layer.json的时候如果漏了它表现是地形能显示但首屏加载要等很久网络面板里一堆 404。2.2 heightmap-1.0 还在什么场景下值得留heightmap 是上一代方案每张切片本质是一张 65×65 的 PNG。高度值拆成两个字节R 通道存高位、G 通道存低位读出来做一个线性映射还原成米。它有几个绕不开的先天限制一是精度受 16 位量化约束高程跨度大的区域比如从海平面到 5000 米垂直分辨率会变粗二是瓦片边缘需要和邻接瓦片共享一圈像素切片器做重采样时容易在接缝处出现细小的台阶三是水面完全靠额外的watermask目录单独提供多一套文件要维护。那它什么时候还值得用我一般会在三种情况下退回去项目锁死在很低版本的 Cesium客户端不认 mesh 的扩展字段目标机器是性能很弱的终端PNG 解码比解析二进制网格更省事或者只是需要在球面上铺一层粗糙的起伏当背景压根不关心法线和光照。除此之外没有理由不用 mesh——同样范围的数据heightmap 的体积通常是 mesh 的两三倍而细节还不如后者。2.3 包里的程序清单与依赖版本核对资源包里的东西不多但要全部凑齐才能跑通逐项对一遍再开工组件作用检查方式GDAL 命令行套件DEM 重投影、拼接、格式转换gdalinfo --versioncesium-terrain-builder核心切片器提供ctb-tilectb-tile --help一份 DEM 数据源高程GeoTIFF 或 VRTgdalinfo dem.tif看投影与波段静态文件服务本地验证切片是否可访问Python 自带 http.server 或 Node 任意静态服务一份layer.json模板手改字段时对照文本编辑器打开看结构依赖版本这块有一条血泪经验GDAL 主版本跨代时某些老版本切片器链接的动态库会对不上跑起来直接报符号缺失。docker 镜像能规避大部分这类问题把 DEM 目录挂进去切片输出目录也挂出来环境隔离在容器里宿主机不用装一堆开发包。如果坚持本机编译先确认 GDAL 的开发头文件和运行时是同一个版本gdal-config --version和gdalinfo --version输出必须一致。注意不要用系统包管理器装一个 GDAL、再用源码编译一个不同版本的 GDAL两者共存时动态链接器会挑错那个报错信息通常和地形毫无关系排查起来非常费劲。3. 从 DEM 到 .terrain 切片一条能跑通的命令行流水线这一章是整篇的核心按顺序敲完就能出成果。整个流程分三段DEM 预处理把源数据整理成切片器能吃的干净输入切片把高程栅格切成金字塔本地服务把结果暴露成一个 Cesium 能请求的 URL。三段之间任何一段偷懒后面都会以很奇怪的方式报错。3.1 DEM 预处理重投影、拼接与 nodata 归整切片器对输入的要求比想象中严格必须是地理坐标EPSG:4326、单一波段、无 nodata 空洞或空洞已明确标注、最好是按纬度分带的整块数据。手上拿到的 DEM 往往是 UTM 投影的分幅 GeoTIFF直接喂进去会得到错位的地形所以先统一处理。# 1. 看源数据的投影、范围、nodata 和统计值先摸清底细 gdalinfo -stats -nomd input_dem.tif # 2. 多幅 DEM 拼成一张虚拟栅格不占磁盘空间 # -srcnodata / -vrtnodata 把各幅不一致的空洞值统一成同一个 gdalbuildvrt -resolution highest \ -srcnodata -32768 -vrtnodata -32768 \ mosaic.vrt tile_01.tif tile_02.tif tile_03.tif # 3. 重投影到 EPSG:4326并裁剪到目标经度范围 # -r cubic 是重采样方式高程数据用三次卷积比最近邻平滑 gdalwarp -t_srs EPSG:4326 \ -r cubic -multi -wo NUM_THREADSALL_CPUS \ -dstnodata -32768 \ -te 100.0 20.0 120.0 45.0 \ mosaic.vrt warped.tif # 4. 压成一张自包含的 GeoTIFF方便反复切片 gdal_translate -co COMPRESSDEFLATE -co TILEDYES -co BIGTIFFIF_SAFER \ warped.tif dem_4326.tif # 5. 复核投影必须是 4326nodata 值要记下来 gdalinfo dem_4326.tif | head -30逐条说明一下。第 1 步的-stats会顺带算出最小最大值如果看到最小值是-32768而最大值只有几百米基本可以确定 nodata 没被正确识别这一步必须处理掉否则切片出来会有一片深坑。第 2 步用 VRT 而不是直接合并是因为 VRT 只记录文件引用几 GB 的输入拼起来是秒级的改参数重来也不心疼-resolution highest保证分辨率不一致时不会被动降采样。第 3 步的-te是裁剪范围单位是经纬度按实际业务范围裁掉多余部分能显著减少切片数量-dstnodata的值要和上一步统一这里用-32768是因为不少 SRTM 衍生数据用它做空洞标记。第 4 步的TILEDYES对后续切片器的读取性能影响很大分块存储能避免随机读。处理完一定要检查两件事gdalinfo输出里的Coordinate System is必须包含4326NoData Value要么有明确值要么整行不存在。这两条不满足后面的坑会以「地形是平的」这种毫无指向性的症状出现。3.2 ctb-tile 的四个关键参数与目录产物切片器这一层可调的参数不少但真正影响结果的只有几个剩下的交给默认值。# 输出 mesh 格式、自动生成 layer.json、计算顶点法线 # -o 指定输出根目录最后那个参数是输入 DEM ctb-tile -f Mesh \ -C \ -N \ -l terrain \ -c 8 \ -z 12 \ -Z 0 \ -o ./terrain_out \ dem_4326.tif # 查看产物先看层级目录再看最后一级的瓦片数量 find ./terrain_out -maxdepth 1 -type d | sort find ./terrain_out -name *.terrain | wc -l # 检查 layer.json 是否生成、字段是否合理 # 重点看 format、scheme、bounds、available 四段 cat ./terrain_out/layer.json | python -m json.tool | head -40参数逐个说。-f Mesh选格式只有大小写写对才会走 quantized-mesh 分支写错会静默回退到 heightmap产物照样生成但扩展名和体积都不一样。-C让切片器顺手把layer.json写出来省掉手工拼 JSON 的步骤强烈建议开着。-N计算顶点法线对应layer.json里extensions的octvertexnormals开了才有光照代价是每张切片多两字节每顶点整体体积上涨约 10%~15%。-z和-Z是最大最小层级0 级是一张覆盖全球的瓦片12 级大概对应几十米的地面分辨率具体取多少后面在性能那章细说。-c是并发线程数给到物理核数就够了开太大反而因为磁盘随机写互相拖慢。-o是输出根目录注意它不是「输出到某个瓦片目录」而是整个地形数据集的根里面会自动生成0/、1/这样的层级目录。目录产物长这样根目录下有layer.json然后是0/0/0.terrain、1/0/0.terrain、1/1/0.terrain这样的层级结构。y 轴方向是 TMS 约定从南往北递增和常见的 XYZ 切片相反。这一点在手工往服务器上搬文件、或者自己写静态服务重写 URL 的时候会要命layer.json里的scheme字段必须和实际目录对得上。如果数据量大到一次切不完-R参数可以在中断后从已有瓦片继续跳过已经存在的文件。分区切片也是同一个思路按经度把大区域切成几块分别切片到不同目录最后用layer.json的available字段合并比一次喂一整个国家要稳得多。3.3 本地起服务验证layer.json 与第一张切片的自检切片完成不等于能用。Cesium 加载地形走的是 HTTP 请求直接双击 HTML 用file://打开会被浏览器的同源策略拦掉必须起一个静态服务。# 在 terrain_out 的上一级目录启动静态服务 # 这样 /terrain_out/layer.json 就能直接访问到 python -m http.server 8080 --bind 0.0.0.0 # 用 curl 自检三件事 # 1. layer.json 能否取到Content-Type 是否正常 curl -i http://127.0.0.1:8080/terrain_out/layer.json # 2. 0 级瓦片是否存在TMS 约定 y 从 0 开始 curl -i -o /dev/null -w %{http_code} %{size_download}\n \ http://127.0.0.1:8080/terrain_out/0/0/0.terrain # 3. 抽一张高层瓦片确认 gzip 或二进制响应正常 curl -i -o /dev/null -w %{http_code} %{size_download}\n \ http://127.0.0.1:8080/terrain_out/8/210/98.terrain这三条自检覆盖了绝大多数低级错误。第一条如果返回 404多半是目录层级不对服务启动位置比terrain_out低了一层如果返回的Content-Type是application/octet-stream那反而是对的.terrain本来就是二进制。第二条重点看状态码和体积0/0/0.terrain是整颗地球的包围瓦片一般只有几 KB如果返回 0 字节说明切片器根本没写出根瓦片通常是 DEM 的范围没被正确识别。第三条的坐标要按自己的available范围挑随手填的 x/y 很可能本来就不存在返回 404 不代表流程有问题。浏览器端还有一个容易忽略的点跨域。服务如果和页面不在同一个端口需要带上Access-Control-Allow-Origin。Python 自带的http.server不带这个头简单验证够用但只要页面和地形不同源就会失败。本地调试我一般会写十来行的 Node 静态服务把 CORS 头一次性加上省得来回折腾。4. 接到 Cesium 里TerrainProvider 的参数与坐标对位切片能通过 curl 取到说明数据链路通了接下来是把地形挂到 Viewer 上。这一段代码量不大但参数错一个症状可能是地形不显示、显示成平面、或者光照完全不对。4.1 CesiumTerrainProvider 初始化与 requestVertexNormals// 初始化 Viewer先关掉默认的在线影像和地形避免内网环境请求外网 const viewer new Cesium.Viewer(cesiumContainer, { imageryProvider: false, // 影像稍后单独挂本地瓦片 terrainProvider: undefined, // 关键不传就保持椭球面 baseLayerPicker: false, geocoder: false, animation: false }); // 挂载离线地形。新版本 Cesium 用 fromUrl 返回 Promise async function setupTerrain() { const terrain await Cesium.CesiumTerrainProvider.fromUrl( ./terrain_out, // 指向含 layer.json 的目录 { requestVertexNormals: true, // 与切片时的 -N 参数保持一致 requestWaterMask: false // 切片时没生成 watermask 就必须关掉 } ); viewer.terrainProvider terrain; // 让相机、模型、拾取都真正贴合地形表面 viewer.scene.globe.depthTestAgainstTerrain true; } setupTerrain();requestVertexNormals必须和切片参数严格对应。切片时开了-N、这里却传false地形会显示成没有明暗的灰面反过来切片没开法线、这里传trueCesium 会尝试解析不存在的扩展段轻则法线全为零、重则整块地形报解析错误。requestWaterMask同理除非切片时专门生成了水面遮罩否则一律关掉。depthTestAgainstTerrain这一行是很多人加载完地形后模型被地面穿透的根因。默认值是false此时深度测试只对地形本身生效点、线、模型仍然按椭球面高度绘制于是就出现模型半截埋进山体、标注飘在山谷上方的情况。打开它之后所有图元都会和地形做深度比较代价是拾取计算量上升低端机器上要留意帧率。老版本 Cesium 的写法是new Cesium.CesiumTerrainProvider({ url, requestVertexNormals })同步返回实例。如果项目锁在旧版本用这个形式新版本继续用构造函数也能跑但拿不到fromUrl的错误回调layer.json解析失败时只会静默显示成球面排查起来靠猜。4.2 地形与影像、模型的贴合校准地形挂上之后第一件要做的事是目视校验高度对不对而不是急着加业务图层。// 取地形上的实际高度和 DEM 源数据比对 const carto Cesium.Cartographic.fromDegrees(110.5, 30.2); const updated await Cesium.sampleTerrainMostDetailed( viewer.terrainProvider, [carto] ); console.log(地形高度(米):, updated[0].height.toFixed(2)); // 相机飞到该点上方 3 公里检查是否贴合 viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(110.5, 30.2, 3000), duration: 2 });sampleTerrainMostDetailed会按当前地形数据可用的最高层级去采样返回的高度就是引擎真正会用来放置物体的值。拿这个数和gdalinfo里查到的同一点高程对比差值在几米以内属于重采样正常误差如果差了几十米甚至上百米基本可以断定是垂直基准的问题——源 DEM 用的是正高大地水准面而 Cesium 用的是椭球高全国范围内两者相差几十米是常事。处理方式是在预处理阶段加一步基准改正或者接受这个偏移并在业务侧做统一补偿。影像和地形的贴合是第二个校验点。影像瓦片如果是 Web MercatorEPSG:3857而地形是地理坐标EPSG:4326Cesium 会在渲染时自动重投影绝大多数情况下看不出问题但在高山区域的高层级下可能出现贴图错位几十米。稳妥做法是两者用同一套tilingScheme地形是 4326影像也切成 4326 的瓦片这样从瓦片编号到纹理坐标是一一对应的不会引入重投影误差。模型贴合则是第三件事。clampToGround系列方法依赖地形的实际高度在陡坡上做单点采样会明显不准管线、道路这类线状要素要改成按固定间距采样并逐点贴地或者直接用地形抬升后的几何求交。4.3 多级地形叠加与 tilingScheme 对齐有些项目需要「粗粒度全球地形 细粒度重点区域」两套数据。Cesium 本身不支持在同一个terrainProvider上叠加两套数据常见的工程解法是准备一套统一的切片重点区域用更高精度的 DEM 单独切到更高层级然后手工合并layer.json的available数组让不同 x/y 区间指向不同的瓦片文件。这属于偏手工的活把两份layer.json的available按层级排列同一层出现重叠区间时以高精度那份为准tiles模板保持统一文件按目录结构直接合并。另一条路是用CustomTerrainProvider自己实现requestTileGeometry在运行时根据经纬度决定去哪个数据源取瓦片。灵活但工作量大还要自己处理缓存和错误重试除非有明确的动态切换需求否则不划算。tilingScheme 这块再强调一次地形和影像的tilingScheme最好一致。如果影像用的是WebMercatorTilingScheme而地形是GeographicTilingSchemeCesium 内部会做转换视觉上大多能接受但在跨纬度带和极区会有可见偏差。判断方法是打开viewer.scene.debugShowTiles之类的调试开关看地形和影像的瓦片边界是否重合不重合就是这里的问题。5. 避坑排查五类翻车现场的原因与处理地形这条链路长出错点散落在预处理、切片、服务、渲染四个环节。下面五条是按踩坑频率排的症状看着离谱原因往往很低级。5.1 地球加载出来了但完全是平的一点起伏都没有现象页面正常渲染地形请求在 сети面板里全是 200但相机怎么飞都是个光滑球面。原因基本落在两处。一是layer.json的format字段和实际切片格式不一致或者tiles模板里的路径层级和磁盘目录对不上Cesium 拿到了 200 的响应却解析不出来解析失败会被吞掉退化成椭球面。二是 DEM 的 nodata 值没统一切片器把整片区域当成空值生成了大量高程恒为零的瓦片。解决先在浏览器网络面板里挑一张.terrain请求点开看响应大小几百字节到几 KB 是正常的如果只有几十字节基本是空瓦片。再回到源数据gdalinfo -stats确认最小最大值用gdal_translate -a_nodata强制指定 nodata重新走一遍预处理。5.2 控制台一片 404但文件明明在目录里现象layer.json能取到往下请求瓦片时全部 404路径看起来也没什么问题。原因几乎总是 y 轴方向反了。切片器输出的目录是 TMS 约定y 从南往北递增如果中间用某个工具做过一次 XYZ 转换或者自己写了层重写规则把 y 翻了一遍路径就和layer.json里的scheme声明对不上了。解决打开layer.json看scheme字段值是tms就说明目录结构按 TMS 来。随便挑一个已知存在的瓦片按{z}/{x}/{y}手工拼出 URL 在浏览器里试能取到就说明服务层没问题问题在 Cesium 请求的坐标换算上。中间层如果有反向代理或 CDN 规则把 y 翻转的逻辑先去掉。5.3 页面和地形不同源控制台报跨域错误现象curl能取到layer.json页面里加载地形却报 CORS 相关的错误或者干脆卡在加载中。原因是静态服务没返回Access-Control-Allow-Origin头。file://协议、Python 自带http.server、部分轻量静态服务默认都不带。另外还有一层容易忽略如果.terrain被 gzip 压缩过服务端要正确返回Content-Encoding: gzip否则 Cesium 拿到的是压缩后的字节流解析会失败但错误信息指向别处。解决本地验证用带 CORS 的 Node 静态服务把Access-Control-Allow-Origin: *和Access-Control-Allow-Headers: *都加上生产环境用 Nginx 时location块里补add_header同时确认gzip_types里包含application/octet-stream或者干脆不预压缩、交给 Nginx 动态压缩。5.4 切片跑到一半内存爆掉或者进程被杀现象切一个大范围的 DEM跑到某一层级时机器开始疯狂换页最后进程被 OOM Killer 干掉或者干脆卡死不动。原因是切片器在这一层需要同时持有大量瓦片的上下文输入范围越大、层级越深内存占用增长得越快。一个省的 90 米 DEM 切到 14 级内存峰值轻松上到十几 GB。解决分而治之。按经度把 DEM 切分成若干块每块单独切片到独立目录最后合并。切片时用-z限制最大层级——大多数演示场景 12 级已经够用14 级是给单个城市用的。中断之后用-R续跑不要从头再来。机器内存小就降低并发线程数-c 4比-c 16更稳。5.5 地形明明有起伏模型却悬空或者半截埋进山里现象地形渲染正确山体轮廓清晰但放上去的模型要么飘在半空要么底部被地面切掉一块。原因有两个层面。渲染层面是depthTestAgainstTerrain没打开所有图元仍按椭球面高度绘制和地形的高程完全是两套坐标。数据层面是垂直基准不一致模型用正高、地形用椭球高两者差一个大地水准面差距几十米起步。解决先把viewer.scene.globe.depthTestAgainstTerrain置为true这一步能解决大部分视觉问题。然后挑几个已知高程的控制点用sampleTerrainMostDetailed取地形高度和实测值比对差值稳定在一个常数附近就说明是基准问题需要在数据侧做统一改正或者在模型侧加一个全局的垂直偏移量。提示垂直基准的排查一定要用至少三个分布在不同位置的控制点单点对不上可能只是重采样误差三点都偏同一个方向同一个量级才是系统性偏移。6. 进阶控制切片体积与加载性能的一组实测手法层级上限是这套流程里性价比最高的一个参数。它能同时决定切了多少片、占用多少磁盘和带宽、首屏加载多久。下面这张表是按 90 米分辨率的 DEM、省域范围实测出来的量级不同数据源会有出入但比例关系可以参考层级上限地面网格分辨率瓦片数量量级磁盘占用适用场景10约 150 米千级几十 MB全国或大区背景地形12约 38 米万级数百 MB省域演示、城市群总览14约 10 米十万级数 GB单个城市、园区级精细展示注意第三行有个前提源数据本身的分辨率得跟得上。拿 90 米 SRTM 数据硬切到 14 级得到的是被插值平滑过的假细节瓦片数量翻了十倍视觉上却没有提升纯属浪费。判断方法很简单看 DEM 的像元大小切片层级对应的地面网格比源像元还小的时候就没有继续往上切的必要了。第二个手法是控制请求量。layer.json里的available数组如果缺失或者写成了全范围Cesium 在首屏会按bounds矩形请求一整层瓦片其中大量是不存在的白白等超时。切片器生成layer.json时一般会自动填好但如果做过目录合并、分区拼接这一段大概率要手工重排。写完之后用脚本验证一遍按每一级的startX/startY/endX/endY遍历检查对应目录下是否真的存在文件数量对不上就是这段写错了。# 统计每级实际瓦片数和 layer.json 的 available 区间核对 for z in $(seq 0 12); do total$(find ./terrain_out/$z -name *.terrain 2/dev/null | wc -l) echo level $z: $total tiles done第三个手法是传输层。.terrain是已量化的二进制gzip 还能再压掉 60%~70%因为顶点数据里存在大量重复的坐标增量。做法是在服务端开 gzip 压缩并在响应里带Content-Encoding或者切片完直接对文件做预压缩让服务端按扩展名直接吐压缩后的字节流。预压缩省 CPU但要注意文件名和layer.json里tiles模板的对应关系加不加.gz后缀得统一。第四个手法是首屏体验。viewer.scene.globe.showGroundAtmosphere开着的时候大气散射会在地形加载完成前就渲染一层光晕地形一到位明暗反差会突变得很明显。切到目标区域之前先把地形加载完再淡入大气观感会好很多。另外screenSpaceError这个参数可以直接调默认值偏保守调大一点会显著减少高层瓦片请求代价是远处的山形会显得粗一些。最后一条是习惯问题。从那以后我每切一批地形都先拿一个 1°×1° 的小块跑一遍全流程确认layer.json的字段、目录层级和浏览器里采样到的高度三者对得上再放开整省去切。这个习惯帮我省掉了至少两次「切了六个小时发现化粪池里只有个平球」的局面。希望帮到你。本文还有配套的精品资源点击获取