【无标题】

Terrarium 格式Terrain-RGB DEM 生产流程(鞍山案例)

    • [1. 流程总览](#1. 流程总览)
    • [2. 环境准备](#2. 环境准备)
    • [3. 预处理(Float32 高程,EPSG:3857)](#3. 预处理(Float32 高程,EPSG:3857))
      • [3.1 NoData 修复](#3.1 NoData 修复)
      • [3.2 重投影](#3.2 重投影)
      • [3.3 多阶段平滑重采样(30→20→15 m)](#3.3 多阶段平滑重采样(30→20→15 m))
      • [3.4 编码前检查](#3.4 编码前检查)
    • [4. Terrarium RGB 编码](#4. Terrarium RGB 编码)
      • [4.1 推荐:`gdal_calc.py`(一条命令,三波段)](#4.1 推荐:gdal_calc.py(一条命令,三波段))
      • [4.2 编码后校验(必做)](#4.2 编码后校验(必做))
    • [5. 转 COG(修复低 zoom 尖刺)](#5. 转 COG(修复低 zoom 尖刺))
      • [5.1 问题背景](#5.1 问题背景)
      • [5.2 正确命令](#5.2 正确命令)
      • [5.3 COG 校验](#5.3 COG 校验)
    • [6. 发布与切片(可选)](#6. 发布与切片(可选))
      • [6.1 上传 MinIO](#6.1 上传 MinIO)
      • [6.2 XYZ 瓦片(可选)](#6.2 XYZ 瓦片(可选))
      • [6.3 PMTiles(可选)](#6.3 PMTiles(可选))
    • [7. MapLibre 加载](#7. MapLibre 加载)
      • [7.1 COG(maplibre-cog-protocol)](#7.1 COG(maplibre-cog-protocol))
      • [7.2 PMTiles](#7.2 PMTiles)
      • [7.3 注意事项:Demo 须使用 COG,勿直接用普通 TIF](#7.3 注意事项:Demo 须使用 COG,勿直接用普通 TIF)
    • [8. 产物清单](#8. 产物清单)
    • [9. 踩坑速查](#9. 踩坑速查)
    • [10. 相关文档](#10. 相关文档)

整合自 20260629本机操作记录.md20260630dem处理记录.md 及 2026-06-30 编码/COG 修复结论。

案例数据:Copernicus DEM GLO-30 · 鞍山市 · 目标产物供 MapLibre raster-dem + encoding: 'terrarium' 使用。


1. 流程总览

text 复制代码
anshan-dem.tif
  → [1] NoData 修复(dem_clean.tif)
  → [2] 重投影 + 多阶段平滑 → dem_final.tif(15 m)
  → [3] Terrarium RGB 编码(gdal_calc,INTERLEAVE=BAND)
  → [4] COG(OVERVIEW_RESAMPLING=NEAREST)
  → [5] 发布 MinIO / 可选 gdal2tiles / PMTiles
  → [6] MapLibre Demo 验证

关键约定(踩坑总结)

环节 正确做法 错误做法
Terrarium 编码 gdal_calc.py 三分波段公式 rio rgbify -b -32768 -i 1(Mapbox 打包,R 全 0)
GeoTIFF 编码工具 gdal_calc / GDAL Python PyPI rio-rgbify 0.4.0(无 Terrarium)
COG 存储 INTERLEAVE=BAND INTERLEAVE=PIXEL(overview 的 G/B 错位 → 低 zoom 尖刺)
COG 金字塔 OVERVIEW_RESAMPLING=NEAREST 默认 average/bilinear(破坏 RGB 位打包)
XYZ 瓦片 gdal2tiles.py -r near 默认 cubic/bilinear
MapLibre encoding: 'terrarium' encoding: 'mapbox'

Terrarium 解码公式(MapLibre / 校验脚本一致):

text 复制代码
height = R × 256 + G + B / 256 - 32768

编码公式gdal_calc 实现):

text 复制代码
val = clip(elevation, -32768, 32767) + 32768
R = floor(val / 256)
G = floor(val) % 256
B = floor((val - floor(val)) × 256)

2. 环境准备

bash 复制代码
conda create -n gis python=3.10 -y
conda activate gis
conda install -c conda-forge gdal -y
  • 需要 GDAL ≥ 3.2gdal_calc.py--calc 输出多波段)。
  • 不必 依赖 rio-rgbify 产出 Terrarium GeoTIFF;PyPI 0.4.0 不支持 -e terrarium,且 -b -32768 -i 1 会产出错误 RGB。

3. 预处理(Float32 高程,EPSG:3857)

输入:anshan-dem.tif(Copernicus 30 m,鞍山)

终点:dem_final.tif(Float32,EPSG:3857,像元 15 m)。多阶段 cubicspline 降采样(30→20→15 m)为生产推荐,详见 预处理数据--平滑.md §4.5。

3.1 NoData 修复

-md 10 把无数据填充为10像素内的有效数据进行填充

bash 复制代码
gdal_fillnodata.py anshan-dem.tif dem_clean.tif -md 10

3.2 重投影

bash 复制代码
gdalwarp -t_srs EPSG:3857 -r cubicspline dem_clean.tif dem_3857.tif

3.3 多阶段平滑重采样(30→20→15 m)

bash 复制代码
gdalwarp -r cubicspline -tr 30 30 dem_3857.tif dem_s1.tif
gdalwarp -r cubicspline -tr 20 20 dem_s1.tif  dem_s2.tif
gdalwarp -r cubicspline -tr 15 15 dem_s2.tif  dem_final.tif

鞍山实测:dem_final.tif 高程约 -293 -- 659 m

3.4 编码前检查

bash 复制代码
gdalinfo dem_final.tif
# 期望:Band 1 Float32;CRS EPSG:3857;像元约 15 m

4. Terrarium RGB 编码

4.1 推荐:gdal_calc.py(一条命令,三波段)

bash 复制代码
gdal_calc.py -A dem_final.tif \
  --outfile=dem_terrarium_rgb-py-band.tif \
  --type=Byte \
  --overwrite \
  --creation-option=INTERLEAVE=BAND \
  --creation-option=COMPRESS=LZW \
  --creation-option=TILED=YES \
  --calc="floor((numpy.clip(A,-32768,32767)+32768)/256)" \
  --calc="floor((numpy.clip(A,-32768,32767)+32768)%256)" \
  --calc="floor(((numpy.clip(A,-32768,32767)+32768)-floor(numpy.clip(A,-32768,32767)+32768))*256)"

务必 在编码阶段指定 INTERLEAVE=BAND,避免后续 COG 金字塔 G/B 通道错位。

4.2 编码后校验(必做)

bash 复制代码
gdalinfo -stats dem_terrarium_rgb-py-band.tif
检查项 期望(鞍山) 异常说明
Band 1 (Red) MIN/MAX 126--130 MIN=MAX=0 → 用了 Mapbox 版 rio rgbify,需重做
Band 2/3 0--255 ---
Terrarium 解码范围 dem_final.tif 一致 中心采样应几百米级,非 -32640 m

快速 Python 抽查:

bash 复制代码
python3 -c "
from osgeo import gdal
ds=gdal.Open('dem_terrarium_rgb-py-band.tif')
r=ds.GetRasterBand(1).ReadAsArray().astype(float)
g=ds.GetRasterBand(2).ReadAsArray().astype(float)
b=ds.GetRasterBand(3).ReadAsArray().astype(float)
h=r*256+g+b/256-32768
print('R', r.min(), r.max(), 'elev', round(h.min(),1), round(h.max(),1))
"

5. 转 COG(修复低 zoom 尖刺)

5.1 问题背景

INTERLEAVE=PIXEL 的 RGB GeoTIFF 在 gdal_translate -of COG 生成 overview 时,会出现 R 正确、G/B 地理错位

  • 全分辨率:相邻像元高程差 < 20 m
  • 错误 overview:相邻像元可差 400+ m → MapLibre 缩小视角出现「尖刺」,放大后消失

5.2 正确命令

§4 已指定 INTERLEAVE=BAND 时,直接转 COG:

bash 复制代码
gdal_translate dem_terrarium_rgb-py-band.tif dem_terrarium_cog-py-band.tif -of COG \
  -co OVERVIEW_RESAMPLING=NEAREST

避免低 zoom 尖刺的必要条件 :§4 产物为 BAND + 本步 NEAREST。仅执行本命令、而输入为 PIXEL 交错 RGB,仍可能出现尖刺。

若历史产物为 PIXEL 交错,先转 BAND 再出 COG:

bash 复制代码
gdal_translate dem_terrarium_rgb-py-band.tif dem_terrarium_band.tif \
  -co INTERLEAVE=BAND -co COMPRESS=LZW -co TILED=YES

gdal_translate dem_terrarium_band.tif dem_terrarium_cog.tif -of COG \
  -co OVERVIEW_RESAMPLING=NEAREST

5.3 COG 校验

bash 复制代码
gdalinfo dem_terrarium_cog.tif
# 期望:LAYOUT=COG;INTERLEAVE=BAND;有 Overviews

抽查 overview 与全分辨率 RGB 是否一致(同一地理位置):

bash 复制代码
python3 -c "
from osgeo import gdal
ds=gdal.Open('dem_terrarium_cog.tif')
w,h=ds.RasterXSize,ds.RasterYSize
ov=ds.GetRasterBand(1).GetOverview(0)
ow,oh=ov.XSize,ov.YSize
ox,oy=ow//2,oh//2
fx,fy=int(ox*w/ow),int(oy*h/oh)
full=[int(ds.GetRasterBand(i).ReadAsArray(fx,fy,1,1)[0,0]) for i in (1,2,3)]
ovpx=[int(ds.GetRasterBand(i).GetOverview(0).ReadAsArray(ox,oy,1,1)[0,0]) for i in (1,2,3)]
print('full RGB',full,'overview RGB',ovpx,'match',full==ovpx)
"
# 期望:match True

6. 发布与切片(可选)

6.1 上传 MinIO

bash 复制代码
# 示例路径(与 demo 一致)
# http://yourip:9000/gis-test/terrain-test/anshan/dem_terrarium_cog.tif
mc cp dem_terrarium_cog.tif minio/gis-test/terrain-test/anshan/

6.2 XYZ 瓦片(可选)

bash 复制代码
gdal2tiles.py -z 8-15 -r near dem_terrarium_rgb-py-band.tif tiles/

Terrarium 瓦片必须 -r near,与 COG overview 同理:禁止对 RGB 做 bilinear/cubic 重采样。

6.3 PMTiles(可选)

bash 复制代码
pmtiles convert tiles/ dem_terrarium.pmtiles

需预先安装 pmtiles CLI(参见 20260629本机操作记录.md §5 故障排查)。


7. MapLibre 加载

7.1 COG(maplibre-cog-protocol)

javascript 复制代码
maplibregl.addProtocol('cog', MaplibreCOGProtocol.cogProtocol);

map.addSource('dem', {
  type: 'raster-dem',
  url: 'cog://http://yourip:9000/gis-test/terrain-test/anshan/dem_terrarium_cog.tif',
  tileSize: 256,
  encoding: 'terrarium'
});

map.setTerrain({ source: 'dem', exaggeration: 1.5 });

map.addLayer({
  id: 'hillshade',
  type: 'hillshade',
  source: 'dem',
  paint: { 'hillshade-exaggeration': 0.5 }
});

7.2 PMTiles

javascript 复制代码
map.addSource('terrain', {
  type: 'raster-dem',
  url: 'pmtiles://https://{host}/dem_terrarium.pmtiles',
  encoding: 'terrarium',
  tileSize: 256
});

7.3 注意事项:Demo 须使用 COG,勿直接用普通 TIF

本流程 §4 产出的 dem_terrarium_rgb-py-band.tif 仅为中间 GeoTIFF,不可作为 Web Demo 的最终发布物直接加载。

产物 Demo 表现 说明
dem_terrarium_cog.tif(§5 COG) 效果好 cog:// + maplibre-cog-protocol 按需读瓦片,overview 为 NEAREST,低 zoom 地形正常
dem_terrarium_rgb-py-band.tif(仅 TIF) 非常不理想 无 COG 内建金字塔或浏览器端无法高效按范围请求,3D 地形易出现模糊、尖刺、缩放卡顿等问题

结论 :面向 MapLibre Demo / 在线预览时,必须完成 §5 转 COG ,并发布 dem_terrarium_cog.tif;跳过 COG 步骤、直接用编码后的普通 TIF,展示效果无法满足验收要求。参见 demo/terrarium-cog.html 中的 COG 地址配置。


8. 产物清单

text 复制代码
dem_clean.tif              # NoData 修复(§3.1)
dem_3857.tif               # Web Mercator(§3.2)
dem_s1.tif / dem_s2.tif    # 中间尺度(§3.3)
dem_final.tif              # 预处理终点(§3.3)
dem_terrarium_rgb-py-band.tif      # Terrarium 3 波段 Byte RGB,INTERLEAVE=BAND(§4)
dem_terrarium_cog-py-band.tif      # COG 发布物,NEAREST overview(§5)
tiles/                     # 可选 XYZ(§6.2)
dem_terrarium.pmtiles      # 可选 PMTiles(§6.3)

9. 踩坑速查

现象 原因 处理
Demo「RGB 解码高程异常」,R 全 0 rio rgbify -b -32768 -i 1(Mapbox 打包) 改用 §4 gdal_calc
--format terrarium 报错 --format 仅 png/webp(瓦片格式) 不用此参数
-e terrarium 不存在 PyPI rio-rgbify 0.4.0 gdal_calc 出 GeoTIFF
低 zoom 3D 尖刺,放大消失 PIXEL 交错 + 错误 overview §5 BAND + NEAREST
瓦片 3D 异常 gdal2tiles 默认 cubic -r near
Mapbox COG 正常、Terrarium 不行 仅 Terrarium 链路有误 不要混用编码公式
Demo 地形模糊、尖刺或卡顿 直接用 §4 普通 TIF,未转 COG 必须发布 §5 dem_terrarium_cog.tif,见 §7.3

对照组 :同 dem_final.tif 可用 Mapbox 编码快速验证预处理是否正确:

bash 复制代码
rio rgbify -b -10000 -i 0.1 dem_final.tif terrain_rgb.tif
gdal_translate terrain_rgb.tif terrain_rgb_cog.tif -of COG
# Demo: demo/mapbox-cog.html

10. 相关文档

相关推荐
我才是银古5 小时前
给 AI 程序员开「外挂」?这个仓库装了 63 个
gis·skill·ai平台
tntxia5 天前
GIS 坐标系
gis
丷丩20 天前
MapLibre GL JS第50课:用表达式创建虚线渐变线
javascript·gis·mapbox·maplibre gl js
丷丩21 天前
MapLibre GL JS第49课:用表达式创建渐变线
gis·可视化·地图·mapbox·maplibre gl js
丷丩21 天前
MapLibre GL JS第48课:用数据驱动属性样式化线
gis·地图·样式·mapbox·maplibre gl js
丷丩22 天前
MapLibre GL JS第47课:添加动画图标
javascript·gis·动画·mapbox·maplibre
丷丩22 天前
错误处理与容错机制:GeoAI-UP的降级策略设计
架构·gis·容错设计
丷丩23 天前
MapLibre GL JS第46课:用Markers添加自定义图标
gis·可视化·mapbox·maplibre gl js
丷丩24 天前
MapLibre GL JS第44课:生成并添加缺失图标
前端·javascript·gis·mapblibre gl js