cesium 直接加载 geotiff 影像图

前言

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

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

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

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

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

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

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

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

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

几个小问题

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

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

  1. 如果 cesium 支持贴 tif 后缀的图,那么皆大欢喜,只要想方设法解析到 geotiff 的坐标范围信息,然后调用 cesium 提供的加载单张图作为图层的 api,再传入范围信息,即可正常的加载该 geotiff 图。
  2. 如果很不幸,cesium 不支持贴 tif 后缀的图,那么我们就得先解析 geotiff 文件,想办法获取到相关的地理信息和像素信息,拿到像素信息和地理信息以后,像第一种情形一样处理,无非就是多了一步将像素信息处理成 cesium 可以支持的图像信息而已。
  3. 我们该如何解析出 geotiff 内部的信息呢?

接下来,就让我们对提出的问题,一个个尝试解决方案,如果能够迎刃而解,那么用 cesium 加载影像图,不是如同探囊取物么!

尝试寻找解决方案

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

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

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

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

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

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

原始影像图

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


image.png

解析 geotiff 文件

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


image.png

我们先调用 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 坐标系,范围如图示:


image.png

转换坐标的方法

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


image.png

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

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

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

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

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

http://epsg.io/

首页长这样:

image.png

点击进入这个 transform coordinates 页面:

image.png

我们试着输入一个坐标:

image.png

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


image.png

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

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

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

image.png

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


image.png

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

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

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 里面去了。


image.png

调整颜色

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

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

这是为什么呢?

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

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

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

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

那么怎么处理呢?

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

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

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

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

image.png

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

后记

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

  1. 一般情况下,geotiff 影像图都非常的大,我们的示例并未考虑到影像图的大小对系统的影响。
  2. 我们这里只考虑了单文件的情况,有时候,geotiff 的表示形式,存在多文件的情况。
  3. 可以尝试对配色进行修改,从而调出不同的风格的影像图,这是个很酷的功能。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容