cesium直接加载TIFF地图影像

文章讲述了在Cesium中使用TIFF影像时,如何处理uint16类型数据并将其转换为uint8以适应canvas渲染的问题,介绍了坐标转换和数据格式调整的过程,以及存在的颜色偏差和优化方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目场景:

遇到了一个需求: 就是用cesium加载TIFF影像,本来主流的方式是后台切片发布服务,前端直接调用服务加载就行,但是 后台直接给了我一个tif文件,就需要前端来进行绘图渲染了


问题描述

做之前先上网查看了一下有没有相关的文档,查到了某大佬写的文https://blog.youkuaiyun.com/lovefengruoqing/article/details/115306876
给了我思路,但是 执行下来就会遇到各种各样的问题,以下详细说一下:

  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;
  // 我这里拿到的epsg是32651

所以需要对 方位坐标(west, south, east, north) 的数据进行转换为4326格式的数据,这里用的是proj4 库进行的转换

// 定义 EPSG32651 和 EPSG4326 的投影信息
  proj4.defs('EPSG:32651', '+proj=utm +zone=51 +datum=WGS84 +units=m +no_defs');
  proj4.defs('EPSG:4326', '+proj=longlat +datum=WGS84 +no_defs');

  // 进行坐标转换
  const utmCoords = [west, south]; // 组合坐标
  const utmCoords1 = [east, north]; // 组合坐标

  // 注意这两条数据 后面绘制图片区域时要用!!!
  const convertedCoords = proj4('EPSG:32651', 'EPSG:4326', utmCoords);
  const convertedCoords1 = proj4('EPSG:32651', 'EPSG:4326', utmCoords1);

接下来就是要用canvas绘制图像了


  // 注意这一步,有坑***1.0
  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++) {
    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] = 255;
  }
  ctx.putImageData(imageData, 0, 0);
  console.log('imageData', imageData);

利用上面的方式,成功渲染出来一张全部像素都为白色的图像。。。 打印出来的imageData.data里的数据都是255
所以绘制canvas图像出现了问题


原因分析:

1.0 readRasters这个API 返回的数组并不一定为四个通道数组rgba, 这个返回值跟图像的dataType有关,
如果是uint8: 8位无符号整型,数值在0-255 之间则返回的是RGBA四个通道数组,可以用上面的方式正常渲染
如果是uint16:16位无符号整型,则返回的并不是4个通道,会多几个通道,我这里是7个。数值范围也远超0-255,因此不能用上面的方式进行渲染。

于是打印出来了 red,green, blue等数组,发现数值都远远大于255,根据rgba渲染原理 数字是不能超过255的,超过了就按照255来进行渲染, 所以RGBA都为255 图像是全白的。


解决方案:

将uint16的数值转为unit8 的格式以便于显然canvas

  // const minValue = math.min(...red);  这种方法浏览器会崩溃!!!
  // const maxValue = math.max(...red);
  const min = 1000; // 手动设置最小值最大值。
  const max = 4000;
  const redArr = red.map(value => Math.round(((value - min) / (max - min)) * 255));
  const greenArr = green.map(value => Math.round(((value - min) / (max - min)) * 255));
  const blueArr = blue.map(value => Math.round(((value - min) / (max - min)) * 255));

注意我上见的代码, 由于我数据量太大了,一共一亿多条数据,直接取最大值的话浏览器会崩溃停止运行
所以我这里手动定义了最大最小值,为什么我定义这两个数值呢,因为我在QGIS里的Band里查看了具体的最大最小值,
在这里插入图片描述
这里就写了一个大致的区间。并将uint16映射到unit8.组成一个新的数组 替换掉上面的red green blue即可。
这样写图片画出来跟实际图片像素存在一定的偏差,效果没那么完美,只能勉强凑合着用了。效果如图
在这里插入图片描述

完整代码如下:

const showTif = async () => {
  const blob = files.value[0].originFileObj;
  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;

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

  // const minValue = math.min(...red);  这种方法浏览器会崩溃!!!
  // const maxValue = math.max(...red);
  const min = 1000; // 手动设置最小值最大值。
  const max = 4000;
  const redArr = red.map(value => Math.round(((value - min) / (max - min)) * 255));
  const greenArr = green.map(value => Math.round(((value - min) / (max - min)) * 255));
  const blueArr = blue.map(value => Math.round(((value - min) / (max - min)) * 255));

  // 定义 EPSG32651 和 EPSG4326 的投影信息
  proj4.defs('EPSG:32651', '+proj=utm +zone=51 +datum=WGS84 +units=m +no_defs');
  proj4.defs('EPSG:4326', '+proj=longlat +datum=WGS84 +no_defs');

  // 进行坐标转换
  const utmCoords = [west, south]; // 组合坐标
  const utmCoords1 = [east, north]; // 组合坐标

  const convertedCoords = proj4('EPSG:32651', 'EPSG:4326', utmCoords);
  const convertedCoords1 = proj4('EPSG:32651', 'EPSG:4326', utmCoords1);

  // 将像素信息写入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++) {
    imageData.data[i * 4 + 0] = redArr[i];
    imageData.data[i * 4 + 1] = greenArr[i] || 0;
    imageData.data[i * 4 + 2] = blueArr[i] || 0;
    imageData.data[i * 4 + 3] = 255;
  }

  ctx.putImageData(imageData, 0, 0);
  console.log('imageData', imageData);

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

  let rectangle = new Cesium.Rectangle.fromDegrees(...convertedCoords, ...convertedCoords1);
  let du = canvas.toDataURL();
  console.log('du', du, rectangle, canvas);
  viewer.imageryLayers.addImageryProvider(
    new Cesium.SingleTileImageryProvider({
      url: du,
      tileWidth: width,
      tileHeight: height,
      ellipsoid: Cesium.Ellipsoid.WGS84,
      rectangle: rectangle,
    })
  );
  setTimeout(() => {
    const cameraView = {
      destination: rectangle,
      duration: 2.0,
      maximumHeight: 2000,
      pitchAdjustHeight: 2000,
      endTransform: Cesium.Matrix4.IDENTITY,
    };
    viewer.camera.flyTo(cameraView);
  }, 2000);
};

总结:如果是uint16数据类型的TIFF文件,用这种方式能转为在rgba范围内的uint8格式,实际图像跟原图像存在颜色差别,但是这已经是我目前已知的将uint16格式绘制在cesium上的仅有的方式,不知道有咩有其他方法,欢迎留言。

Cesium是一种开源的地理信息系统(GIS)软件开发库,用于在web浏览器中创建三维地球可视化应用程序。Cesium支持多种数据格式,包括基于栅格的图像文件格式,如TIFFTIFF Image File)。 在Cesium加载和显示TIFF影像非常简单。首先,需要将TIFF文件转换为合适的格式,例如创建一个高程图(Elevation Raster)或纹理图(Texture Raster)。这可以使用其他GIS软件,如GDAL或ArcGIS进行处理。 一旦TIFF文件已准备好,可以使用Cesium的ImageryLayer添加它到场景中。以下是加载TIFF影像的基本步骤: 1. 在HTML文件中,添加Cesium的JavaScript库文件和样式表: ```html <link href="https://cesium.com/downloads/cesiumjs/releases/latest/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> <script src="https://cesium.com/downloads/cesiumjs/releases/latest/Build/Cesium/Cesium.js"></script> ``` 2. 创建一个Cesium的Viewer对象来显示场景: ```javascript var viewer = new Cesium.Viewer('cesiumContainer'); ``` 3. 声明一个ImageryLayer变量,并将TIFF文件的URL赋值给它: ```javascript var tiffUrl = 'path/to/your/tiff/image.tif'; var imageryLayer = new Cesium.ImageryLayer(new Cesium.SingleTileImageryProvider({ url : tiffUrl })); ``` 4. 将ImageryLayer添加到Viewer的imageryLayers集合中: ```javascript viewer.imageryLayers.add(imageryLayer); ``` 5. 运行HTML文件,Cesium加载并显示TIFF影像。 通过这些步骤,我们可以很容易地在Cesium加载和显示TIFF影像。根据需要,还可以对影像进行进一步的样式化和操作,以实现更丰富的地球可视化效果。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值