前言
在 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 1fi
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 1fi
# 检查layer.json是否存在if [ ! -f "$LAYER_JSON" ]; then echo -e "${RED}错误: layer.json文件 '$LAYER_JSON' 不存在${NC}" echo "请确保CTB处理已完成且layer.json文件已生成" exit 1fi
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 1fi
# 提取坐标范围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 << EOFimport jsonimport 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 1fi商业软件
这里我推荐 GISBox,免费的基础版功能也足够使用了
选择切片转换 ➡ 地形切片 ➡ 选择 DEM 文件 ➡ 选择输出目录
可以看到描述文件的bounds、maxzoom和minzoom是正确的
数据加载
这里就比较简单了,我直接使用的 Nginx 代理地址
viewer.terrainProvider = await CesiumTerrainProviderEdit.fromUrl( 'http://localhost:666/model/staticData/tile/',)加载效果




