Cesium加载本地影像(Imagery)与地形(Terrain)

1,069 阅读5分钟

1.背景

本地有自己的影像跟地形,影像是 .png,地形是 .tif。

2.加载影像

影像被分为两个文件夹:world1_out、world2_out,分别存放北半球跟南半球的数据。

在nginx服务器上发布影像数据:

server {
  listen       8089;
  server_name  localhost;

  # 配置北半球影像路径
  location /world1/ {
      alias  C:/Users/Administrator/Documents/earth/world_mark/world1_out/;  
      autoindex on;
      add_header Access-Control-Allow-Origin *;
      add_header Cache-Control "no-cache,must-revalidate";
  }

  # 配置南半球影像路径
  location /world2/ {
      alias  C:/Users/Administrator/Documents/earth/world_mark/world2_out/;  
      autoindex on;
      add_header Access-Control-Allow-Origin *;
      add_header Cache-Control "no-cache,must-revalidate";
  }
}

在 Cesium 中添加影像图层:

this.viewer = new Cesium.Viewer('cesiumContainer', {});

// 添加北半球影像图层
this.viewer.imageryLayers.addImageryProvider(
  new Cesium.TileMapServiceImageryProvider({
    url: process.env.VUE_APP_MAP_API_Image + '/world1',
    fileExtension: 'png',
    minimumLevel: 0,
    maximumLevel: 8,
    rectangle: Cesium.Rectangle.fromDegrees(-180.0, 0.0, 180.0, 90.0), // 北半球范围
  })
);

// 添加南半球影像图层
this.viewer.imageryLayers.addImageryProvider(
  new Cesium.TileMapServiceImageryProvider({
    url: process.env.VUE_APP_MAP_API_Image + '/world2',
    fileExtension: 'png',
    minimumLevel: 0,
    maximumLevel: 8,
    rectangle: Cesium.Rectangle.fromDegrees(-180.0, -90.0, 180.0, 0.0),
  })
);

3.加载地形

地形是 .tif 文件,在 Cesium 中,当你使用 Cesium.CesiumTerrainProvider 来加载地形数据时,Cesium 默认期望你提供的数据是 Quantized Mesh 或 Heightmap 格式。这两种格式是 Cesium 支持的标准地形格式(.terrain文件)。

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

①.下载 docker 并启动

地址:docs.docker.com/desktop/ins…

(补充说明:docker里的 container 翻译为“容器”,image 翻译为“镜像”。)

②.打开CMD执行以下命令

方案一:

  1. 拉取CTB镜像:docker pull tumgis/ctb-quantized-mesh
  2. 运行容器:docker run --name ctb -t -i tumgis/ctb-quantized-mesh:latest /bin/bash(这一步如果不想运行命令,那么在 docker 图形界面直接点击启动就行,好像是这样的,我也是刚接触,不太确定)
  3. 上一步成功后,命令行会进入容器内部,由于还有命令需要在外面执行,所以需要在命令行退出容器。而我目前不知道怎么退出,索性直接关闭cmd再重新打开
  4. 将地形原始文件(elevation.tif,约7GB)copy到容器里:docker cp C:/path/to/elevation.tif ctb:/data
  5. 在命令行执行这条命令来进入容器内部:docker exec -it ctb bash
  6. 上条命令执行完后,默认进入到容器的data目录,此时可以通过ls查看当前目录下的文件,有个 elevation.tif
  7. 通过mkdir -p terrain创建一个名为terrain的文件夹,用来存放切片后的数据
  8. 切片:ctb-tile -f Mesh -C -N -o terrain elevation.tif,如果tif文件较大,这一过程会比较漫长,而且CPU、内存占用较高,此时做其他操作很卡。我这个文件约有7GB,好像过了两三个小时执行结束了,只不过并不是以成功结束的,而是失败了。不过我通过cd terrain进入terrain文件夹,执行ls查看文件后,发现生成了2~10级的地形文件,只不过0级、1级没生成,而且2级文件不全,我索性把2级文件删了rm -r 2
  9. 生成 layer.json 说明文件:ctb-tile -f Mesh -C -N -l -o terrain elevation.tif,这条命令只是多了个-l的参数
  10. 将terrain文件copy到本机:找一个合适的文件夹,运行cmd,执行docker cp ctb:/data/terrain .,这会将docker容器里data文件夹下的terrain文件夹copy到当前目录下。这个过程也比较漫长,我用了两三个小时。

上述方案是我对照其他博客做的,我在写这篇文章的时候,看到docker CTB的readme里的方法好像不一样,于是也试了一下。他介绍的方法免去了将文件从本地复制到容器内这一过程,取而代之的是将本地文件“挂载”到容器中,这样能节省一部分时间。

方案二(docker CTB readme):

  1. 拉取CTB镜像:docker pull tumgis/ctb-quantized-mesh
  2. 运行容器并将本机地形文件夹挂载到容器的data目录下:docker run -it --name ctb -v "C:/path/to/terrainFolder:/data" tumgis/ctb-quantized-mesh(这里 -v的参数是<本地路径>:<容器内路径>,后面跟着一个空格再加上<镜像名>,本地路径必须是绝对路径),这个操作,我理解的是,就像本机跟虚拟机共享文件一样。如果只需要挂载一个文件(如 merge.tif)而非整个文件夹,可以直接指定文件路径:docker run -it --name ctb -v "C:/Users/Administrator/Documents/earth/merge.tif:/data/merge.tif" tumgis/ctb-quantized-mesh
  3. 这里跟方案一中第6~10步相同。

至此拿到了Cesium可加载的地形文件,目录是这样的:

  • terrain
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • layer.json

由于0~2级的terrain文件没有生成,所以我又尝试着单独生成一下。由于有用的文件我已经copy到本机了,所以我在docker上把这些文件都删除了rm -r 3rm layer.json

生成0~2级的terrain文件:ctb-tile -f Mesh -C -N -o terrain elevation.tif -s 2 -e 0,如果不明白参数的意义,可以通过ctb-tile --help来查看。我执行上述命令时报错了,这里官方文档也提到了(github.com/tum-gis/ces… ), 他说,在处理大型数据集时,在低缩放级别上可能有溢出问题,建议使用 gdal_translate 由现在的7G的tif文件创建出一个低分辨率的、所占空间更小的tif文件,而后在新的tif文件上执行切片。

说干就干,执行gdal_translate -outsize 5% 5% elevation.tif elevation_lower.tif将原文件的宽高各缩小至5%,再用新的tif生成低缩放级别的terrain文件:ctb-tile -f Mesh -C -N -o terrain elevation_lower.tif -s 2 -e 0,之后再将生成的文件夹一个一个copy到本机上。

③.在nginx服务器上发布地形数据:

server {
  listen       8090;
  server_name  localhost;

  root  C:/Users/Administrator/Documents/earth/terrainFiles;
  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";
      }
  }
}

④.在 Cesium 中添加地形:

this.viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: new Cesium.CesiumTerrainProvider({
    url: process.env.VUE_APP_MAP_API_Terrain, // 自定义地形服务的 URL
  }),
});

vue项目 .env.xxx 文件里:

VUE_APP_MAP_API_Image = 'http://localhost:8089'
VUE_APP_MAP_API_Terrain = 'http://localhost:8090'