cesium直接加载TIFF地图影像

项目场景:

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


问题描述

做之前先上网查看了一下有没有相关的文档,查到了某大佬写的文https://blog.csdn.net/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上的仅有的方式,不知道有咩有其他方法,欢迎留言。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
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百度地图影像的操作,可以实现高精度的三维地图浏览。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值