前言
最近碰到了一个需求,需要通过 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 即可。
然后,我们再次尝试运行一下代码,就会得到下图所示的场景了:
OpenLayers是一个开源的JavaScript库,用于在Web浏览器中呈现交互式地图。它本身并不提供导出地图为TIFF图片的功能,但可以结合其他库或服务来实现这个功能。一种可能的解决方案是使用Canvas和FileSaver.js库。Canvas可以将网页上的内容渲染为图像,而FileSaver.js库可以将生成的图像保存到本地。在生成TIFF文件上我选择geotiff.js这个库,GeoTIFF.js可以在TIFF图像中嵌入地理元数据,并且支持多种地图投影坐标系。
1. 前言
在TIFF文件结构详解中,我们得知TIFF是Tagged Image File Format的缩写。Tiff对GeoTiff的支持已写入了Tiff6.0,也就是说,GeoTiff是一种Tiff6.0文件,它继承了在Tiff6.0规范中的相应部分,所有的GeoTiff特有的信息都编码在Tiff的一些预留Tag(标签)中,它没有自己的IFD(图像文件目录)、二进制结构以及其它一些对Tiff来说不可见的信息。
用来描述GeoTiff流行的众多影射参数及类型信息,如果每一个信息都采用一个标签那将至少需要
1、 tiff 文件的基本格式
TIFF(Tag Image File Format) 图像文件说明:
TIFF 文件是由许多的标签 (tag) 组成 , 在 Adobe 的有关 tiff6.0 的说明中,将 (tag) 的解释称各种标签所对应的数值,而在文件中各个标签的实际...
文章目录geoserver将TIF上传至geoserver新建工作区新建数据源新建图层查看发布的tif
geoserver
geoserver安装及跨域问题解决方案:https://blog.csdn.net/weixin_43598687/article/details/124005542
将TIF上传至geoserver
启动geoserver服务,并进入geoserver主页。
新建工作区
点击工作区,再点击添加新的工作区。
填入工作区名称以及命名空间URI,点击保存。
新建数据源
文章目录加载geoserver发布的WMS服务加载KMZ/KML文件加载JSON文件在指定经纬度添加标签和地名
加载geoserver发布的WMS服务
geoserver发布服务详见:https://blog.csdn.net/weixin_43598687/article/details/124102651
var provider1 = new Cesium.WebMapServiceImageryProvider({
url:'http://localhost:8080/geoserver/t
DTM=vq1; %二维矩阵
rasterSize=size(DTM); %矩阵大小
R = georefcells(latlim,lonlim,rasterSize); %latlim,lon...
Cesium加载3DTiles的一些问题Cesium加载3DTiles的基本方法踩坑过程使用官网tileset.json数据加载为空使用Cesium.IonResource.fromAssetId(5741)无法加载其他
Cesium加载3DTiles的基本方法
const viewer = new Cesium.Viewer("cesiumContainer");
const tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTi
一、GeoTiff
GeoTiff是很常见的一种遥感影像数据格式,它是一种栅格数据,这种数据是以像素点矩阵的形式存储的。
TIF文件中的空间信息包括投影信息和仿射变换信息。其中,仿射变换(经纬度等)的信息分别为:左上角的坐标(经度和维度)、东西和南北方向的像素分辨率(也就是每个像素点的经纬度偏移量)以及图像的旋转系数(正北方向时为零)。这样,每个像素点的坐标就可以通过它与左上角的偏移量和每个像素的分辨率值计算出来。
切图产生的新图的投影信息是不变的,但仿射信息会发生变化,因为左上角的原点可能跟原图不一样了。