Cesium 离线地形数据处理与加载:从数据获取到加载

Cesium 离线地形数据处理与加载:从数据获取到加载
SEAlencehe前言
在 Cesium 项目的实际落地场景中,部分项目因部署环境限制(如内网隔离、无网络访问权限)需采用离线部署模式。此时,依赖网络服务商提供的在线地形数据已无法满足需求,需要提前处理并部署离线地形数据。
DEM 原始数据获取(地形数据的基础来源);
数据切片处理 ;
Cesium 离线加载。
DEM 数据获取
DEM(数字高程模型)是描述地表高程信息的基础数据,是生成 Cesium 地形的核心前提,这里使用青花鱼推荐的 Qgis 插件OpenTopography DEM Downloade
安装之后打开插件
插件需要 API KEY,直接注册账号即可,再选择数据源、项目区域、导出地址即可,一般 30m 分辨率已经足够使用了
下载完成的数据
数据切片
笔者这里搜索了各种方案,主要有两种:
开源方案:
ctb-quantized-mesh
优点:免费、跨平台、方便后端部署
缺点:无法自动生成正确的layer.json描述文件(该文件是 Cesium 识别地形瓦片的关键配置文件),需额外处理
商业软件:GISBox、CesiumLab
优点:一键切片
缺点:部分功能收费
ctb-quantized-mesh
这是一个 docker 镜像,项目地址,笔者是 Windows 平台,所以以Docker Desktop为例
拉取镜像
docker pull tumgis/ctb-quantized-mesh
这时候出现 ctb-quantized-mesh 的镜像说明下载成功了
安装
docker run -v <你的硬盘地址>:/data -ti -i tumgis/ctb-quantized-mesh:latest bash
硬盘地址会被映射到容器的data目录下
先检查文件是否被正确挂载
ls -la /data/
可以看到,tif 文件就是我们需要切片的文件
根据项目文档,需要先创建 vrt 文件
gdalbuildvrt tiles.vrt /data/test.tif
生成瓦片,等待完成即可
ctb-tile -f Mesh -C -N -v -o ./terrain/ tiles.vrt
可以看到 terrain 文件夹下已经生成了瓦片
创建 layer.json 描述文件
ctb-tile -f Mesh -C -N -l -o ./terrain/ tiles.vrt
这里比较坑的是描述文件并不正确,范围bounds字段默认是全球范围,瓦片级别maxzoom和minzoom也没有,导致 Cesium 加载失败,项目文档似乎也没有相关配置项,有兴趣的大佬找到解决办法可以留言
ai 老师给出自动化修改描述文件的代码,未经测试,有兴趣的大佬可以研究下
#!/bin/bash
# layer.json文件修正工具
# 用于修正CTB生成的layer.json文件中的bounds和缩放级别信息
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}=== layer.json 修正工具 ===${NC}"
echo ""
# 检查参数
if [ $# -eq 0 ]; then
echo -e "${RED}使用方法: $0 <TIFF文件> [layer.json路径]${NC}"
echo ""
echo "示例:"
echo " $0 aaa.tif # 修正当前目录的layer.json"
echo " $0 aaa.tif /path/to/terrain/layer.json # 指定特定的layer.json文件"
echo ""
exit 1
fi
INPUT_TIFF="$1"
LAYER_JSON="${2:-./layer.json}"
echo "输入TIFF文件: $INPUT_TIFF"
echo "目标layer.json: $LAYER_JSON"
echo ""
# 检查TIFF文件是否存在
if [ ! -f "$INPUT_TIFF" ]; then
echo -e "${RED}错误: TIFF文件 '$INPUT_TIFF' 不存在${NC}"
exit 1
fi
# 检查layer.json是否存在
if [ ! -f "$LAYER_JSON" ]; then
echo -e "${RED}错误: layer.json文件 '$LAYER_JSON' 不存在${NC}"
echo "请确保CTB处理已完成且layer.json文件已生成"
exit 1
fi
echo -e "${YELLOW}步骤1: 分析TIFF文件的地理信息...${NC}"
# 获取TIFF文件的地理信息
GDAL_INFO=$(gdalinfo "$INPUT_TIFF" 2>/dev/null)
if [ $? -ne 0 ]; then
echo -e "${RED}错误: 无法读取TIFF文件信息${NC}"
exit 1
fi
# 提取坐标范围
echo "正在解析地理坐标范围..."
# 获取四个角点的坐标
UL_LON=$(echo "$GDAL_INFO" | grep "Upper Left" | sed 's/.*(\([^,]*\),.*/\1/')
UL_LAT=$(echo "$GDAL_INFO" | grep "Upper Left" | sed 's/.*, \([^)]*\)).*/\1/')
UR_LON=$(echo "$GDAL_INFO" | grep "Upper Right" | sed 's/.*(\([^,]*\),.*/\1/')
UR_LAT=$(echo "$GDAL_INFO" | grep "Upper Right" | sed 's/.*, \([^)]*\)).*/\1/')
LR_LON=$(echo "$GDAL_INFO" | grep "Lower Right" | sed 's/.*(\([^,]*\),.*/\1/')
LR_LAT=$(echo "$GDAL_INFO" | grep "Lower Right" | sed 's/.*, \([^)]*\)).*/\1/')
LL_LON=$(echo "$GDAL_INFO" | grep "Lower Left" | sed 's/.*(\([^,]*\),.*/\1/')
LL_LAT=$(echo "$GDAL_INFO" | grep "Lower Left" | sed 's/.*, \([^)]*\)).*/\1/')
# 计算实际的边界框
MIN_LON=$(echo -e "$UL_LON\n$UR_LON\n$LR_LON\n$LL_LON" | sort -n | head -1)
MAX_LON=$(echo -e "$UL_LON\n$UR_LON\n$LR_LON\n$LL_LON" | sort -n | tail -1)
MIN_LAT=$(echo -e "$UL_LAT\n$UR_LAT\n$LR_LAT\n$LL_LAT" | sort -n | head -1)
MAX_LAT=$(echo -e "$UL_LAT\n$UR_LAT\n$LR_LAT\n$LL_LAT" | sort -n | tail -1)
echo "检测到的实际范围:"
echo " 经度范围: $MIN_LON 到 $MAX_LON"
echo " 纬度范围: $MIN_LAT 到 $MAX_LAT"
# 计算对角线距离用于确定合适的缩放级别
DIAGONAL_DEGREES=$(echo "sqrt(($MAX_LON - $MIN_LON)^2 + ($MAX_LAT - $MIN_LAT)^2)" | bc -l 2>/dev/null || echo "0")
# 根据范围大小估算合适的最大缩放级别
if (( $(echo "$DIAGONAL_DEGREES < 0.5" | bc -l 2>/dev/null || echo "0") )); then
MAX_ZOOM=16
ZOOM_DESC="超精细 (小区域)"
elif (( $(echo "$DIAGONAL_DEGREES < 2" | bc -l 2>/dev/null || echo "0") )); then
MAX_ZOOM=14
ZOOM_DESC="精细 (中小区域)"
elif (( $(echo "$DIAGONAL_DEGREES < 10" | bc -l 2>/dev/null || echo "0") )); then
MAX_ZOOM=12
ZOOM_DESC="中等 (中等区域)"
elif (( $(echo "$DIAGONAL_DEGREES < 30" | bc -l 2>/dev/null || echo "0") )); then
MAX_ZOOM=10
ZOOM_DESC="标准 (大区域)"
else
MAX_ZOOM=8
ZOOM_DESC="粗略 (超大区域)"
fi
echo "建议的缩放级别: 0-$MAX_ZOOM ($ZOOM_DESC)"
echo "对角线距离: $DIAGONAL_DEGREES 度"
echo ""
echo -e "${YELLOW}步骤2: 读取并修正layer.json文件...${NC}"
# 备份原始文件
BACKUP_FILE="$LAYER_JSON.backup.$(date +%Y%m%d_%H%M%S)"
cp "$LAYER_JSON" "$BACKUP_FILE"
echo "已备份原始文件到: $BACKUP_FILE"
# 显示原始文件内容
echo ""
echo "原始layer.json内容:"
echo "================================"
cat "$LAYER_JSON"
echo "================================"
# 使用Python修正JSON文件
echo ""
echo "正在修正JSON文件..."
python3 << EOF
import json
import sys
try:
# 读取原始JSON文件
with open('$LAYER_JSON', 'r') as f:
data = json.load(f)
# 保存原始bounds用于对比
original_bounds = data.get('bounds', 'unknown')
# 修正bounds为实际范围
data['bounds'] = [$MIN_LON, $MIN_LAT, $MAX_LON, $MAX_LAT]
# 添加或修正缩放级别
data['minzoom'] = 0
data['maxzoom'] = $MAX_ZOOM
# 添加center字段(数据中心点)
center_lon = ($MIN_LON + $MAX_LON) / 2
center_lat = ($MIN_LAT + $MAX_LAT) / 2
data['center'] = [center_lon, center_lat, $MAX_ZOOM // 2]
# 保存修正后的文件
with open('$LAYER_JSON', 'w') as f:
json.dump(data, f, indent=2)
print(f"✓ layer.json文件修正成功")
print(f" 原始bounds: {original_bounds}")
print(f" 新bounds: {data['bounds']}")
print(f" 缩放级别: {data['minzoom']} - {data['maxzoom']}")
print(f" 数据中心: {data['center']}")
except Exception as e:
print(f"✗ 修正JSON文件时出错: {e}")
print("恢复备份文件...")
import shutil
shutil.copy('$BACKUP_FILE', '$LAYER_JSON')
sys.exit(1)
EOF
if [ $? -eq 0 ]; then
echo ""
echo -e "${GREEN}修正后的layer.json内容:${NC}"
echo "================================"
cat "$LAYER_JSON"
echo "================================"
echo ""
echo -e "${GREEN}✓ layer.json文件修正完成!${NC}"
echo ""
echo -e "${BLUE}现在您可以在Cesium中这样使用:${NC}"
echo "viewer.terrainProvider = await CesiumTerrainProvider.fromUrl('./$(dirname "$LAYER_JSON")/')"
echo ""
echo -e "${YELLOW}注意事项:${NC}"
echo "1. bounds现在反映实际的地形数据范围"
echo "2. 添加了minzoom(0)和maxzoom($MAX_ZOOM)字段"
echo "3. 添加了center字段用于地图初始视角"
echo "4. 备份文件保存在: $BACKUP_FILE"
else
echo ""
echo -e "${RED}修正失败,已恢复备份文件${NC}"
exit 1
fi
商业软件
这里我推荐 GISBox,免费的基础版功能也足够使用了
选择切片转换 ➡ 地形切片 ➡ 选择 DEM 文件 ➡ 选择输出目录
可以看到描述文件的bounds、maxzoom和minzoom是正确的
数据加载
这里就比较简单了,我直接使用的 Nginx 代理地址
viewer.terrainProvider = await CesiumTerrainProviderEdit.fromUrl(
'http://localhost:666/model/staticData/tile/'
)
加载效果


















