Cesium 离线加载全球影像(Imagery)与地形(Terrain):ETOPO、GEBCO

73 阅读13分钟

海洋 / 军工 / 仿真

一、地形

ETOPO1

特点:

  • 分辨率:1 arc-minute(≈1.8km)
  • 许可证:开源,可商用

1.下载数据集

数据集:ETOPO1 Global Relief Model

下载数据集

  由上图可见,首先有两种选择:Ice Surface(推荐) 与 Bedrock。前者是表面高度(视觉更自然),陆地、海洋连续,适合做地形影像;后者冰盖被“挖掉”,主要用于冰川研究。

  选择了 Ice Surface 之后,又有两种选择:grid-registered(推荐)与 cell-registered。前者与经纬度网格严格对齐;后者是由前者衍生出来的,为做数值模拟而生,用它做可视化会有半个像素的偏移(该影像分辨率约1.8km,则半像素偏移大约是900m的偏移)。

点击georeferenced tiff链接后跳转到下载页面:

下载页面

下载压缩包到本地,并解压,得到 ETOPO1_Ice_g_geotiff.tif。

2.数据集切片

可以使用 Cesium Terrain Builder(CTB) 来生成 .terrain 文件。我这里用的是 docker 版的CTB(tum-gis/cesium-terrain-builder-docker )。

  1. 下载docker并启动
  2. 拉取CTB镜像:docker pull tumgis/ctb-quantized-mesh
  3. 运行容器并将本机文件夹挂载到容器的data目录下:docker run -it --name ctb -v "D:/path/to/ETopoFolder:/data" tumgis/ctb-quantized-mesh(这里 -v的参数是<本地路径>:<容器内路径>,后面跟着一个空格再加上<镜像名>,本地路径必须是绝对路径)
  4. 上条命令执行完后,默认进入到容器的data目录,此时可以通过ls查看当前目录下的文件,有个ETOPO1_Ice_g_geotiff.tif
  5. 通过mkdir -p tiles创建一个名为tiles的文件夹,用来存放切片后的数据
  6. 切片:ctb-tile -f Mesh -C -N -o tiles ETOPO1_Ice_g_geotiff.tif。如果tif文件较大,这一过程会比较漫长,这里这个tif有445MB,切片花了将近1小时。正常切完是这样的输出:0...10...20...30...40...50...60...70...80...90...100 - done. 可以打开本机上刚刚挂载那个目录(D:/path/to/ETopoFolder/tiles)能看到切片后的文件夹与里面的文件
  7. 生成 layer.json 说明文件:ctb-tile -f Mesh -C -N -l -o tiles ETOPO1_Ice_g_4326.tif。这条命令只是多了个-l的参数,跑完后可在本机刚刚挂载那个目录(D:/path/to/ETopoFolder/tiles)里看到有个layer.json文件

可能遇到的问题:

(1). ctb-tile -f Mesh -C -N -o tiles ETOPO1_Ice_g_geotiff.tif报错:Error: The source dataset does not have a spatial reference system assigned. 这是说文件没有SRS(spatial reference system,空间参考系),此时执行gdalinfo ETOPO1_Ice_g_geotiff.tif可以看到Coordinate System is 这样的空输出,可以执行gdal_translate -a_srs EPSG:4326 ETOPO1_Ice_g_geotiff.tif ETOPO1_Ice_g_4326.tif来指定SRS,此时通过gdalinfo ETOPO1_Ice_g_geotiff.tif查看新文件的信息,可以看到Coordinate System is: GEOGCS["WGS 84", DATUM["WGS_1984", SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]], AUTHORITY["EPSG","6326"]], PRIMEM["Greenwich",0], UNIT["degree",0.0174532925199433], AUTHORITY["EPSG","4326"]]这样的输出,然后可以拿这个新文件切片了。

(2). 切片时,如果文件较大,那么这一过程会比较漫长,而且CPU、内存占用较高,此时做其他操作很卡。例如之前我切过1个7GB的tif,花了两三个小时。而且最终只生成了2-10级的切片,也就是说0、1级切片没有,我检查了一下发现2级切片也不全,索性rm -r 2删除了2这个文件夹。接下来就是要单独生成0-2级切片:ctb-tile -f Mesh -C -N -o tiles ETOPO1_Ice_g_4326.tif -s 2 -e 0,参数的意义,可以通过ctb-tile --help来查看。然后我这条命令也报错了,这里官方文档也提到了:tum-gis/cesium-terrain-builder-docker, 他说,在处理大型数据集时,在低缩放级别上可能有溢出问题,建议使用 gdal_translate 由现在的7G的tif文件创建出一个低分辨率的、所占空间更小的tif文件,而后在新的tif文件上执行切片。按他说的来,执行gdal_translate -outsize 5% 5% ETOPO1_Ice_g_4326.tif ETOPO1_lower.tif将原文件的宽高各缩小至5%,再用新的tif生成低缩放级别的terrain文件:ctb-tile -f Mesh -C -N -o terrain ETOPO1_lower.tif -s 2 -e 0

3.nginx发布切片

server {
    listen       8114;
    server_name  localhost;

    root  D:/Map/ETOPO/terrain;
    autoindex on; 
    location / {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";

        location ~* .terrain$ {
            add_header Content-Disposition 'attachment;filename=$arg_filename';
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
            add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
            add_header Content-Encoding "gzip";
            add_header Content-Type "application/octet-stream";
        }
    }
}

4.Cesium加载地形

我这里用的cesium版本为1.83,里面有些API可能在新版本里弃用了。建议大家用旧版本的cesium,除非你用到了新版本中独有的特性。因为我在开发某个项目时,用了一个第三方对于cesium封装的库,该库使用了cesium@1.131,然后凡是有cesium的页面都卡顿,凡是没有cesium的页面就正常,而且我把cesium@1.131换成cesium@1.83就不卡顿了。

const viewer = new Cesium.Viewer('cesiumContainer', {
  infoBox: false, // * Blocked script execution in 'about:blank' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
  geocoder: false, // * 右上角搜索按钮
  homeButton: false, // * 右上角地图恢复到初始页面按钮
  sceneModePicker: false, // * 右上角2D和3D之间的切换
  baseLayerPicker: false, // * 右上角图层选择器
  navigationHelpButton: false, // * 右上角帮助按钮
  animation: false, // * 左下角圆盘 速度控制器
  creditsDisplay: false, // * 商标版权与数据源
  timeline: false, // * 页面下方的时间条
  fullscreenButton: false, // * 右下角全屏按钮,
  selectionIndicator: false, // 禁用选中指示器
  imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
    url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
    enablePickFeatures: false,
  }),
  terrainProvider: new Cesium.CesiumTerrainProvider({
    url: 'http://localhost:8114/', // 自定义地形服务的 URL
  })
});

// 地形夸张
viewer.scene.globe.terrainExaggeration = 10;

5.运行时效果

海底地形

GEBCO

特点:

  • 分辨率:15 arc-second(≈460m)
  • 许可证:开源,可商用

1.下载数据集

数据集:gebco_2025_geotiff

image.png

下载压缩包 gebco_2025_geotiff.zip 到本地,解压后得到如下文件:

image.png

2.数据集切片

像前面一样,依然采用docker版的CTB来切片。

  1. 下载docker => 拉取CTB镜像 => 运行容器并挂载目录。这些跟前面一样不再赘述。
  2. 默认进入到容器的data目录,可以通过ls命令查看当前目录下的文件,有8个tif,按 经纬度四象限 × 南北半球 切成 8 块。
  3. 虚拟拼接gdalbuildvrt tiles.vrt *.tif 这条命令把所有tif文件拼接成一个总的虚拟的数据集。
  4. 切片:ctb-tile -f Mesh -C -N -o tiles tiles.vrt由于这个数据集很大,我从13:48开始切片,到14:18用了半小时才切了5%,这么算下来得10个小时才能切完。
  5. 后续生成layer.json等操作跟前面一样。

可能遇到的问题:

(1). 0...10...20...30...40...50...60...70...80...90...Killed. 没有切完,AI说可能是可能内存不够导致的。打开切片观察最里层的文件,最里层最后一个是1024.terrain、512.terrain这种2的整数倍的文件,说明这几层是切完了的,我从10层检查到3层都没问题,第二层虽然是以4.terrain结束的,但是前面没有0-3的terrain文件,而第0层、第1层压根没有。那这样直接删除第二层并重新生成0-2层:ctb-tile -f Mesh -C -N -o tiles tiles.vrt -s 2 -e 0,0...10...20..Killed 这里也kill了。那按照前面ETOPO1问题(2)中所说的,先调低分辨率再试一下:gdal_translate -outsize 5% 5% tiles.vrt tiles_lower.vrt => ctb-tile -f Mesh -C -N -o tiles tiles_lower.vrt -s 2 -e 0这次成功了。别忘了生成layer.json文件。

3.nginx发布切片

同上

server {
    listen       8116;
    server_name  localhost;

    root  D:/Map/GEBCO/terrain;
    autoindex on; 
    location / {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";

        location ~* .terrain$ {
            add_header Content-Disposition 'attachment;filename=$arg_filename';
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
            add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
            add_header Content-Encoding "gzip";
            add_header Content-Type "application/octet-stream";
        }
    }
}

4.Cesium加载地形

同上

const viewer = new Cesium.Viewer('cesiumContainer', {
  infoBox: false, // * Blocked script execution in 'about:blank' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
  geocoder: false, // * 右上角搜索按钮
  homeButton: false, // * 右上角地图恢复到初始页面按钮
  sceneModePicker: false, // * 右上角2D和3D之间的切换
  baseLayerPicker: false, // * 右上角图层选择器
  navigationHelpButton: false, // * 右上角帮助按钮
  animation: false, // * 左下角圆盘 速度控制器
  creditsDisplay: false, // * 商标版权与数据源
  timeline: false, // * 页面下方的时间条
  fullscreenButton: false, // * 右下角全屏按钮,
  selectionIndicator: false, // 禁用选中指示器
  imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
    url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
    enablePickFeatures: false,
  }),
  terrainProvider: new Cesium.CesiumTerrainProvider({
    url: 'http://localhost:8116/', // 自定义地形服务的 URL
  })
});

// 地形夸张
viewer.scene.globe.terrainExaggeration = 10;

5.运行时效果

这个地形切片最大层级为10,ETOPO1切片最大层级为8,很明显下图中的山峰的棱角格外分明(相较于ETOPO1)。

image.png

二、影像

ETOPO1

1.下载数据集

同上,得到 ETOPO1_Ice_g_geotiff.tif ,但这是DEM(高程数据),不是影像(RGB/Byte),所以需要 DEM 先变影像,影像再切瓦片。

2.给地形上色

可以使用GDAL来上色,我这里用的是 docker 版的 gdal

  1. 下载docker并启动
  2. 拉取gdal镜像:docker pull osgeo/gdal:ubuntu-full-3.6.3
  3. 运行容器并将本机文件夹挂载到容器的data目录下:docker run -it --name gdal -v "D:/path/to/ETopoFolder:/data" osgeo/gdal:ubuntu-full-3.6.3(这里 -v的参数是<本地路径>:<容器内路径>,后面跟着一个空格再加上<镜像名>,本地路径必须是绝对路径)
  4. 上条命令执行完后,默认进入到容器的根目录,通过cd data进入data目录,可以通过ls查看当前目录下的文件,有个ETOPO1_Ice_g_geotiff.tif
  5. 先解决坐标系的问题:gdal_translate -a_srs EPSG:4326 ETOPO1_Ice_g_geotiff.tif ETOPO1_Ice_g_4326.tif
  6. 然后创建颜色映射文件
    cat > color.txt << 'EOF'
    # 海洋突出配色方案
    # 海洋:4级蓝色,详细分级
    -10000  10  20  80   # 深海
     -4000  40  70 130   # 中深海
     -1000  80 130 180   # 浅海
         0 120 180 220   # 海岸
    
    # 陆地:5级,柔和颜色
         1 200 210 170   # 沿海区(很窄)
        50 170 180 140   # 低地(平原)
       300 140 150 120   # 丘陵
      1000 120 120 100   # 山地
      2000 100  95  90   # 高山
      4000 180 180 180   # 雪山
    EOF
    
    执行完成后可以通过cat color.txt看一眼是不是有了
  7. 染色:gdaldem color-relief ETOPO1_Ice_g_4326.tif color.txt relief_color.tif,得到输出文件 relief_color.tif ,可以在本机查看这个tif(可以当成普通图片查看)

3.数据集切片

我用的是 osgearth_package 来切片,这个工具在后面我分享的链接里也有。

  1. osgearth_package目录下创建.earth文件 map.earth(注意这里的相对路径要写好)
    <map name="SouthHemisphereMap">
      <image name="MyTiffImage">
        <driver>gdal</driver>
        <url>path/to/relief_color.tif</url>
      </image>
    </map>
    
  2. osgearth_package目录下打开cmd执行osgearth_packages.exe --tms map.earth --out D:/Map/output_tiles --max-level 6 --verbose
  3. 执行完后便可在对应目录(我这里是D:/Map/output_tiles)下看到切片

也可以通过GDAL来切片,不过效果不是很好,还多出很多无用的kml文件:gdal2tiles.py -p geodetic -z 0-6 relief_color.tif tiles

4.nginx发布切片

server {
    listen       8115;
    server_name  localhost;

    location / {
        root  D:/Map/ETOPO/imagery;
        autoindex on;
        add_header Access-Control-Allow-Origin *;
        add_header Cache-Control "no-cache,must-revalidate";
        try_files $uri $uri/ /index.html;
    }
}

5.Cesium加载影像

const viewer = new Cesium.Viewer('cesiumContainer', {
  infoBox: false, // * Blocked script execution in 'about:blank' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
  geocoder: false, // * 右上角搜索按钮
  homeButton: false, // * 右上角地图恢复到初始页面按钮
  sceneModePicker: false, // * 右上角2D和3D之间的切换
  baseLayerPicker: false, // * 右上角图层选择器
  navigationHelpButton: false, // * 右上角帮助按钮
  animation: false, // * 左下角圆盘 速度控制器
  creditsDisplay: false, // * 商标版权与数据源
  timeline: false, // * 页面下方的时间条
  fullscreenButton: false, // * 右下角全屏按钮,
  selectionIndicator: false, // 禁用选中指示器
  imageryProvider: new Cesium.TileMapServiceImageryProvider({
    url: 'http://localhost:8115/', // 自定义影像服务的 URL
    fileExtension: 'png',
    minimumLevel: 0,
    maximumLevel: 6
  }),
  terrainProvider: new Cesium.CesiumTerrainProvider({
    url: 'http://localhost:8114/', // 自定义地形服务的 URL
  })
});

或者采用UrlTemplateImageryProvider:

const viewer = new Cesium.Viewer('cesiumContainer', {
  infoBox: false, // * Blocked script execution in 'about:blank' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
  geocoder: false, // * 右上角搜索按钮
  homeButton: false, // * 右上角地图恢复到初始页面按钮
  sceneModePicker: false, // * 右上角2D和3D之间的切换
  baseLayerPicker: false, // * 右上角图层选择器
  navigationHelpButton: false, // * 右上角帮助按钮
  animation: false, // * 左下角圆盘 速度控制器
  creditsDisplay: false, // * 商标版权与数据源
  timeline: false, // * 页面下方的时间条
  fullscreenButton: false, // * 右下角全屏按钮,
  selectionIndicator: false, // 禁用选中指示器
  // imageryProvider: new Cesium.TileMapServiceImageryProvider({
  //   url: 'http://localhost:8115/',
  //   fileExtension: 'png',
  //   minimumLevel: 0,
  //   maximumLevel: 6
  // }),
  imageryProvider: new Cesium.UrlTemplateImageryProvider({
    url: 'http://localhost:8115/{z}/{x}/{reverseY}.png',
    minimumLevel: 0,
    maximumLevel: 6,
    // tilingScheme: new Cesium.WebMercatorTilingScheme(), // 默认采用投影坐标系(EPSG:3857)
    tilingScheme: new Cesium.GeographicTilingScheme()      // 实际该用地理坐标系(EPSG:4326)
  }),
  terrainProvider: new Cesium.CesiumTerrainProvider({
    url: 'http://localhost:8114/', // 自定义地形服务的 URL
  })
});

6.运行时效果

影像

GEBCO

1.下载数据集

还是用的地形数据集,同样需要染色。

2.给地形上色

  1. 下载docker => 拉取CTB镜像 => 运行容器并挂载目录。这些跟ETOPO1处理方式一样不再赘述。
  2. 默认进入容器根目录,通过cd data进入data目录,创建颜色映射文件:
    cat > color.txt << 'EOF'
    # 海洋突出配色方案
    # 海洋:4级蓝色,详细分级
    -10000  10  20  80   # 深海
     -4000  40  70 130   # 中深海
     -1000  80 130 180   # 浅海
         0 120 180 220   # 海岸
    
    # 陆地:5级,柔和颜色
         1 200 210 170   # 沿海区(很窄)
        50 170 180 140   # 低地(平原)
       300 140 150 120   # 丘陵
      1000 120 120 100   # 山地
      2000 100  95  90   # 高山
      4000 180 180 180   # 雪山
    EOF
    
  3. 染色:gdaldem color-relief tiles.vrt color.txt relief_color.tif 注意这里虽然输入是.vrt,但输出一定是.tif

3.数据集切片

跟ETOPO1一样,采用 osgearth_package 来切片,注意 .earth 文件里的路径。

4.nginx发布切片

server {
    listen       8117;
    server_name  localhost;

    location / {
        root  D:/Map/GEBCO/imagery;
        autoindex on;
        add_header Access-Control-Allow-Origin *;
        add_header Cache-Control "no-cache,must-revalidate";
        try_files $uri $uri/ /index.html;
    }
}

5.Cesium加载影像

const viewer = new Cesium.Viewer('cesiumContainer', {
  infoBox: false, // * Blocked script execution in 'about:blank' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
  geocoder: false, // * 右上角搜索按钮
  homeButton: false, // * 右上角地图恢复到初始页面按钮
  sceneModePicker: false, // * 右上角2D和3D之间的切换
  baseLayerPicker: false, // * 右上角图层选择器
  navigationHelpButton: false, // * 右上角帮助按钮
  animation: false, // * 左下角圆盘 速度控制器
  creditsDisplay: false, // * 商标版权与数据源
  timeline: false, // * 页面下方的时间条
  fullscreenButton: false, // * 右下角全屏按钮,
  selectionIndicator: false, // 禁用选中指示器
  // imageryProvider: new Cesium.TileMapServiceImageryProvider({
  //   url: 'http://localhost:8117/',
  //   fileExtension: 'png',
  //   minimumLevel: 0,
  //   maximumLevel: 6
  // }),
  imageryProvider: new Cesium.UrlTemplateImageryProvider({
    url: 'http://localhost:8117/{z}/{x}/{reverseY}.png',
    minimumLevel: 0,
    maximumLevel: 6,
    // tilingScheme: new Cesium.WebMercatorTilingScheme(), // 默认采用投影坐标系(EPSG:3857)
    tilingScheme: new Cesium.GeographicTilingScheme()      // 实际该用地理坐标系(EPSG:4326)
  }),
  terrainProvider: new Cesium.CesiumTerrainProvider({
    url: 'http://localhost:8116/', // 自定义地形服务的 URL
  })
});

6.运行时效果

由于染色文件跟前面ETOPO1相同,所以影像切片的结果也跟ETOPO1相同,换个染色方案就不一样了。

image.png

三、切片后的数据

大家可以用我切好的数据:Cesium离线地形影像