mapbox自定义Source加载COG数据 (Cloud optimized GeoTIFF——云优化GeoTiff)

前言:

        COG(Cloud Optimized GeoTIFF)本质上是一个GeoTIFF文件,但与常规的大文件GeoTIFF相比,它更加适合用于HTTP文件服务器进行数据发布。它不仅仅存储了影像中的原始像素,同时还按照特定的方式对这些像素进行组织,使得客户端可以通过HTTP GET range请求,拿到他们真正需要的影像内容。这种机制可以让感知COG的客户端完全在线处理数据,因为他们可以拿到真正所需要的GeoTIFF的内容,而非下载整个超大的GeoTIFF文件。(原文链接:https://blog.csdn.net/qq_36635746/article/details/121516908)

更多信息参考:COG(Cloud optimized GeoTIFF——云优化GeoTiff)简介与实践_cog tiff-CSDN博客

Openlayer加载COG文件:

        Openlayers更新了WebGLTile图层和GeoTIFFSource数据源,加载非常简单,并且官网也有示例如下:Cloud Optimized GeoTIFF (COG)

import GeoTIFF from 'ol/source/GeoTIFF.js';
import Map from 'ol/Map.js';
import TileLayer from 'ol/layer/WebGLTile.js';

const source = new GeoTIFF({
  sources: [
    {
      url: 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/36/Q/WD/2020/7/S2A_36QWD_20200701_0_L2A/TCI.tif',
    },
  ],
});

const map = new Map({
  target: 'map',
  layers: [
    new TileLayer({
      source: source,
    }),
  ],
  view: source.getView(),
});

mapbox加载COG文件:

        经过一番搜索发现各社区论坛都没有相关内容,mapbox官网也没有相关的文档和例子,经过一番寻觅决定使用mapbox自定义图层和自定义Source的方式尝试自己加载出来。

经过一番寻觅和同事的帮助,我们发现有一个JavaScript库可以在前端处理COG:

geotiff.js

官网:geotiff.js

        这个库支持处理COG,可以通过它动态的传入四至来获取该四至下的tiff影像数据,所以只要在地图上每个瓦片绘制时获取到瓦片行列号,转换为tiff的四至通过geotiffjs来获取到影像数据,再转换为图片绘制到地图上的对应位置即可!

上代码,geotiffjs部分:

tiff = await fromUrl('https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/36/Q/WD/2020/7/S2A_36QWD_20200701_0_L2A/TCI.tif')//依旧使用openlayers示例中的COG数据
let data = await this.tiff.readRasters({
  bbox,//这里传入要查询的四至
  samples:[0],//这里传入要查询的波段信息
  width: tileSize,//瓦片宽高
  height: tileSize,//瓦片宽高
  interleave: true
});

        代码中返回的data就是我们需要的影像数据了,具体的readRasters方法的参数请翻阅官方文档。接下来要做的就是利用mapbox的自定义Source在瓦片渲染时获取到将要渲染的行列号,转换为四至来获取对应位置的tiff数据,并转换成图片渲染到地图上了。

注意

        1.返回的数据为数组格式,里边存储的数据与传入的波段信息相关,也与tiff数据的波段信息相关,如果tiff数据是多波段,是彩色的,那么samples可以传入[0,1,2],返回的数组会是每个像素的rgb,也就是说,每个像素点有三个值,分别是rgb三色。若数据为单波段,则samples只能传入[0],返回的数据长度也为像素点数量,每个像素点仅有一个值。这对后续的转换图片操作很重要。

        2.需注意tiff数据的坐标系,传入的四至需要和tiff数据本来的坐标系相匹配,否则会报错

mapbox自定义Source和自定义Layer

        查看mapbox源码时我发现mapbox自定义有两种方式符合我的需求,一种是customLayer,一种是customSource

        在mapbox官方文档里有相关customLayer的文档和示例,但却没有customSource相关信息,但是在mapbox源码示例里边有相关这两种自定义方案的示例

        目前还没有找到合适的customLayer加载COG的方案,所以以下只写customSource加载COG的方案

customSource加载自定义图片
const CogSource={
  type: "custom",//自定义图层的type需要传custom
  loadTile({ z, x, y }) {//图层的type是raster时,每个瓦片加载都会触发该方法,接下来获取到行列号
    console.log(x,y,z,'行列号')
    //这里根据行列号获取到四至,通过四至去取tiff瓦片数据
    //一系列转换操作最终把tiff瓦片数据转成图片绘制到canvas上面
    const canvas = document.createElement("canvas");
    var ctx = canvas.getContext("2d");
    return canvas;//返回一个自己绘制的canvas,mapbox会自己将图片绘制到对应位置
  }
}
map.addSource("custom-source", CogSource);
map.addLayer({
  id: "custom-source",
  type: "raster",
  source: "custom-source"
});

将二者结合,即可完美的把COG数据加载到mapbox地图上了。以下是全部代码:

页面部分使用vue3:

<template>
  <div style="width: 100vw; height: 100vh" id="map"></div>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { CogSource } from './CogSource'
let map: any = null;
onMounted(async () => {
  const url = "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/36/Q/WD/2020/7/S2A_36QWD_20200701_0_L2A/TCI.tif";//数据在北非(3857)
  
  mapboxgl.accessToken = "";
  map = new mapboxgl.Map({
    container: "map",
    // style: mapImgStyle,
    center: [116.4, 39.9],
    zoom: 9
  });
  const customSource = new CogSource({
    tiffUrl:url,
    tileSize:256
  });
  map.on("load", () => {
    map.addSource("custom-source", customSource);
    map.addLayer({
      id: "custom-source",
      type: "raster",
      source: "custom-source"
    });
  });
});
</script>

js部分:

import { fromUrl } from "geotiff"
function merc(x, y, z){
  // 参考: https://qiita.com/MALORGIS/items/1a9114dd090e5b891bf7
  const GEO_R = 6378137;
  const orgX = -1 * ((2 * GEO_R * Math.PI) / 2);
  const orgY = (2 * GEO_R * Math.PI) / 2;
  const unit = (2 * GEO_R * Math.PI) / Math.pow(2, z);
  const minx = orgX + x * unit;
  const maxx = orgX + (x + 1) * unit;
  const miny = orgY - (y + 1) * unit;
  const maxy = orgY - y * unit;
  return [minx, miny, maxx, maxy];
}
class CogSource{
  tiffUrl = ""//用于存放tiff的路径
  tiff = null//用于存放tiff实体
  tiffImage = null//用于存放tiff实体的Image信息
  tileSize = 256//用于存放瓦片尺寸
  samples = null//用于存放波段数
  type = "custom"
  constructor({tiffUrl,tileSize}) {
    this.tiffUrl = tiffUrl;
    this.tiff = null;
    this.type = "custom";
    this.cache = new Map();
    this.tileSize = tileSize;
  }
  async loadTile({ z, x, y }) {
    if(!this.tiff){//若tiff还未加载则先加载tiff(这里可能存在Promise还未兑现就又重新执行的风险,考虑添加Promise并发锁)
      this.tiff = await fromUrl(this.tiffUrl)
      this.tiffImage = await this.tiff.getImage()
      this.samples = this.tiffImage.getSamplesPerPixel();
    }
    let bbox = merc(x, y, z);//仅支持3857若tiff数据为4490则需要进行转换
    let data = await this.tiff.readRasters({
      bbox,
      samples: this.samples==3?[0, 1, 2]:[0],//进行波段判断
      width: this.tileSize,
      height: this.tileSize,
      interleave: true
    });
    let rgbadata = new Uint8ClampedArray(this.tileSize * this.tileSize * 4);
    if(this.samples==3){
      //data数据为rgb三个一组,但是rgbadata为rgba四个一组(这里可继续拓展功能,根据用户传入的参数调整rgba)
      for (let i = 0; i * 3 < data.length; i++) {
        rgbadata[i * 4 + 0] = data[i * 3 + 0];
        rgbadata[i * 4 + 1] = data[i * 3 + 1];
        rgbadata[i * 4 + 2] = data[i * 3 + 2];
        rgbadata[i * 4 + 3] =
          data[i * 3 + 0] + data[i * 3 + 1] + data[i * 3 + 2] ? 255 : 0;
      }
    }else{
      //单波段则数据为一个一组,rgbadata为rgba四个一组(这里颜色有点问题,应根据波段信息计算颜色)
      for (let i = 0; i < data.length; i++) {
        rgbadata[i * 4 + 0] = data[i];
        rgbadata[i * 4 + 1] = data[i];
        rgbadata[i * 4 + 2] = data[i];
        rgbadata[i * 4 + 3] = data[i] ? 255 : 0;
      }
    }
    const img = new ImageData(
      new Uint8ClampedArray(rgbadata.buffer),
      this.tileSize,
      this.tileSize
    );
    const canvas = document.createElement("canvas");
    canvas.width = canvas.height = this.tileSize;
    var ctx = canvas.getContext("2d");
    ctx.putImageData(img, 0, 0);
    return canvas;
  }
}

export { CogSource }

  • 18
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值