cesium 直接加载 geotiff 影像图

前言
最近碰到了一个需求,需要通过 cesium 直接加载 geotiff 影像图。

咋一听,这个需求好像蛮奇怪,cesium 本身本来就支持加载 tile 影像图,也就是所谓的切片地图。原理其实就是,通过 geoserver 等工具,按照一定的规则和坐标系规则,切好对应的切片。

而 cesium 里面,加载瓦片地图也很简单,想要显示哪个区域的地图,就根据对应的规则,去 geoserver 里请求对应的切片。这些逻辑在 cesium 里面,也已经封装好了,直接调用就好了。

但是如果不想发布到 geoserver,想直接通过 cesium,加载 geotiff 影像文件,来预览影像图呢?

说实话,刚开始碰到这个需求,内心也是没底的,毕竟翻遍了 cesium 的 api,也没有发现,其能支持这种加载方式。

而且,geotiff 影像图的格式,对于我来说,也是一片未知的领域。要不是去年开始接触 cesium 和 geoserver,我根本不知道它的存在。

当然,碰到问题,还是得发挥一个程序员的 geek 精神,先搜索下,看有没有人碰到同样的烦恼。

虽然这种方式不常见,但是,还是有同道中人的,但是结果多不理想,甚至有人直接回复,说不支持这种加载方式。

就在我一度想要放弃的时候,忽然有了灵感。

几个小问题
既然 geotiff 本质上是一张图片,文件不太大的图,甚至直接用一些常见的看图软件就能打开,那么想要贴在 cesium 的 globe 上,又有何难呢?

现在摆在面前的有几个问题:

如果 cesium 支持贴 tif 后缀的图,那么皆大欢喜,只要想方设法解析到 geotiff 的坐标范围信息,然后调用 cesium 提供的加载单张图作为图层的 api,再传入范围信息,即可正常的加载该 geotiff 图。
如果很不幸,cesium 不支持贴 tif 后缀的图,那么我们就得先解析 geotiff 文件,想办法获取到相关的地理信息和像素信息,拿到像素信息和地理信息以后,像第一种情形一样处理,无非就是多了一步将像素信息处理成 cesium 可以支持的图像信息而已。
我们该如何解析出 geotiff 内部的信息呢?
接下来,就让我们对提出的问题,一个个尝试解决方案,如果能够迎刃而解,那么用 cesium 加载影像图,不是如同探囊取物么!

尝试寻找解决方案
我们先来找找,看能否找到前端解析 geotiff 的解决方案。

我们知道,如果用桌面软件,查看 geotiff 图像,很多常见的软件都能支持,大到像 arcgis,小到像 windows 看图,都能查看。

但是前端,是否有现成的工具,可以用来解析 geotiff 图像呢?

带着这样的疑问,开始了我们的探寻之旅。

经过一番尝试以后,发现前端有个开源的库—— geotiff.js ,可以用来解析 .tif 格式的文件。

具体 geotiff.js 的 api,在这里就不做过多的介绍了,有兴趣了解的,可以去看下官方提供的 readme 文件,上面有用法的详细说明。

原始影像图
假设现在有个 geotiff 文件,用 IrfanView 文件打开,是这个样子的:

解析 geotiff 文件

geotiff.js 提供了几种写入读取 二进制文件的方式,为了方便使用,我们就尝试采用 fromBlob 的方式。

å¨è¿éæå¥å¾çæè¿°

我们先调用 geotiff.js 提供的 api,将文件读取成 js 对象,再通过对象提供的 getImage api,获取到图像的相关信息。

const tiff = await fromBlob(blob);

let image = await tiff.getImage();
let [west, south, east, north] = image.getBoundingBox();

const code =
  image.geoKeys.ProjectedCSTypeGeoKey ||
  image.geoKeys.GeographicTypeGeoKey;

为了准确的把图贴到 cesium 的球面上去,我们必须要先获取到图像的范围,并且要获取到图像采用的是哪种坐标系。

我们测试的这张图,打印出上述信息,发现采用的是 4527 坐标系,范围如图示:

å¨è¿éæå¥å¾çæè¿° 

转换坐标的方法

现在问题是,cesium 目前已知的,只支持月球、标准的球体和 WGS84 体系的坐标体系,不支持我们的 CGCS2000 坐标系。

å¨è¿éæå¥å¾çæè¿°

怎么办呢?我们必须能找到一个换算的方式,将我们的坐标换算成 WGS84 坐标体系里的点。

可是,由于本身对 gis 专业相关的基础知识的匮乏,对于坐标体系转换,毫无经验,根本不知道怎么转换该如何是好?

虽然,怎么转化,论文里都有,但是等学会那些,再来解决这个问题,都不知道要等到猴年马月去呀。

不过不要着急,我发现了一个网站支持这种服务,提供了这种转换的接口。

不用自己写转换坐标的算法,岂不是很舒服!

http://epsg.io/

首页长这样:
 

å¨è¿éæå¥å¾çæè¿°

点击进入这个 transform coordinates 页面:

å¨è¿éæå¥å¾çæè¿° 

我们试着输入一个坐标:

å¨è¿éæå¥å¾çæè¿° 

返现返回了我们想要的结果,点进去看下位置:

å¨è¿éæå¥å¾çæè¿° 

现在问题是,虽然我们能在页面上获取转换结果,但是总不能每次都打开页面,输入地址,来获取转换后的坐标吧?

无妨,我们打开控制台看一下,转换的过程到底经历了写什么。

我们点一下 transform,发现页面发了一个 ajax 请求,里面包含了一些相关的信息

å¨è¿éæå¥å¾çæè¿°

而返回的结果,正是在 4326 体系下,的经纬度坐标信息:

å¨è¿éæå¥å¾çæè¿° 

既然有了转换方式,可以转换坐标,那么接下来要做的就很简单了。

通过接口,获取该影像图所表示的地理区域的范围:

let { x: w, y: n } = await (
  await fetch(
    `//epsg.io/trans?x=${west}&y=${north}&s_srs=${code}&t_srs=4326`
  )
).json();
let { x: e, y: s } = await (
  await fetch(
    `//epsg.io/trans?x=${east}&y=${south}&s_srs=${code}&t_srs=4326`
  )
).json();

将 geotiff 像素信息写入 canvas

按理说,走到了这一步后,如果 cesium 支持直接加载 geotiff 图为静态的 图层,是最理想的状态,可惜的是,它并不支持。

既然它不支持,我们就要想办法另辟蹊径了。

// 读取像素信息
const [red = [], green = [], blue = []] = await image.readRasters();

// 将像素信息写入canvas
const canvas = document.createElement("canvas");
let width = image.getWidth();
let height = image.getHeight();
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext("2d");
let imageData = ctx.createImageData(width, height);

console.time("写入像素");
for (var i = 0; i < imageData.data.length / 4; i += 1) {
  imageData.data[i * 4 + 0] = red[i];
  imageData.data[i * 4 + 1] = green[i] || 0;
  imageData.data[i * 4 + 2] = blue[i] || 0;
  imageData.data[i * 4 + 3] = red[i] === 0 ? 0 : 255;
}

ctx.putImageData(imageData, 0, 0);

console.timeEnd("写入像素");

 

我们可以通过 image 对象提供的 readRasters 接口,将像素信息读取出来,然后写入 canvas,形成一张前端可以操控的图。

在 cesium 中加载
遗憾的是,cesium 的 SingleTileImageryProvider 接口,并不支持对 canvas 的直接载入,需要转换成图片才能进行操作。

我们可以调用 canvas 自带的 toDataURL 将 canvas 转换成图片,然后传进去即可。
 

let rectangle = Cesium.Rectangle.fromDegrees(w, s, e, n);
let du = canvas.toDataURL();

viewer.imageryLayers.addImageryProvider(
  new Cesium.SingleTileImageryProvider({
    url: du,
    rectangle,
  })
);

viewer.camera.setView({
  destination: rectangle,
});

 这样,我们就成功的将该 geotiff 影像图,直接加载到 cesium 里面去了。

å¨è¿éæå¥å¾çæè¿°

 

调整颜色
到了这一步,我们要做的差不多就结束了。

但是细心的同学可能会发现,加载到 cesium 里的影像图的颜色跟我们前面用软件打开的时候不太一样。

这是为什么呢?

要理解这个问题,可能需要童鞋们去了解下颜色的构成方式。

这里我们采用的是 rgb 的表示方法。

当我们运行代码的时候,进入调试模式,你会发现,

默认这个影像里面,只存储了 R 的信息,G、B 的信息并没有。

那么怎么处理呢?

其实很简单,只需要改一行代码即可:
 

const [red = [], green = red, blue = red] = await image.readRasters();

将 green 和 blue 均赋值一个初始值,等于 red 即可。

然后,我们再次尝试运行一下代码,就会得到下图所示的场景了:

å¨è¿éæå¥å¾çæè¿°

 

 

 

此刻,细心的童鞋就会发现,这与我们之前打开的图一般无二了。

后记
当然,有一些情况,我们这里并没有考虑到,有兴趣的同学可以自己研究下:

一般情况下,geotiff 影像图都非常的大,我们的示例并未考虑到影像图的大小对系统的影响。
我们这里只考虑了单文件的情况,有时候,geotiff 的表示形式,存在多文件的情况。
可以尝试对配色进行修改,从而调出不同的风格的影像图,这是个很酷的功能。

 

 

 

 

 

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Cesium是一款流行的三维虚拟地球平台,可以支持加载各种层数据,包括地形、高程、影像等。而百度地影像数据则是一种精细的卫星遥感影像,能够展现出地球表面的真实情况。因此,借助Cesium平台加载百度地影像数据,可以实现高质量的三维地球浏览体验。 具体实现方式如下: 首先,需要将百度地影像数据转化成Cesium支持的数据格式。Cesium当前支持的影像格式主要有两种,一种是Web Map Service (WMS),另一种是Tile Map Service (TMS),这两种格式都是基于Web的数据服务,通过调用服务接口可以获取到对应的影像数据。 其次,需要在Cesium加载对应的地影像层。代码示例如下: var imageryProvider = new Cesium.WebMapTileServiceImageryProvider({ url: ‘https://your-wms-provider-url’, layers: ‘layer-name’, parameters: { service: ‘WMS’, format: ‘image/jpeg’, transparent: true }, tileWidth: 256, tileHeight: 256, minimumLevel: 0, maximumLevel: 24 }); 其中,url指定WMS服务的地址,layers指定要加载层名称,parameters参数指定数据格式等信息,tileWidth和tileHeight指定每个加载块的像素大小,minimumLevel和maximumLevel指定加载级别范围。 最后,将imageryProvider作为层添加到Cesium场景中即可,代码示例如下: var viewer = new Cesium.Viewer(‘cesiumContainer’); var imageryLayer = new Cesium.ImageryLayer(imageryProvider); viewer.imageryLayers.add(imageryLayer); 这样,就完成了在Cesium加载百度地影像的操作,可以实现高精度的三维地浏览。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值