openlayers – Displaying GeoTIFF Images in OpenLayers 6

geotiff-tiffopenlayers

I'm trying to display a TIFF image in the ol map. To do this, I have to convert the TIFF to PNG.
I'm using geotiffjs to read the metadata and plotty to convert it to canvas and then to PNG.

It works somehow, but the colors are messed up. I got my TIFF file from QGIS (OpenStreetMap), I just selected a random area.

This is how it should look like:
enter image description here

And this is the result I get:
enter image description here

This is my code so far:

Read metadata from tiff file (I'm doing this in the Back-End)

const tiff = await geotiff.fromArrayBuffer(file.buffer);

const image = await tiff.getImage();
const geoKeys = image.getGeoKeys();
const bands = await image.readRasters();
const width = image.getWidth();
const height = image.getHeight();
const bbox = image.getBoundingBox();

return {
    projection: geoKeys.ProjectedCSTypeGeoKey,
    height: height,
    width: width,
    bbox: bbox,
    bands: bands,
    maxValue: bands[0].reduce((prev:number,next:number) => prev<next ? next : prev),
    minValue: bands[0].reduce((prev: number, next: number) => prev>next ? next : prev),
};

Front-End:

...get tiffData from server...

const newCanvas = document.createElement('canvas');

const plot = new plotty.plot({
    canvas: newCanvas,
    data: tiffData.bands[0],
    width: tiffData.width,
    height: tiffData.height,
    domain: [tiffData.minValue, tiffData.maxValue],
    clampLow: true,
    clampHigh: true,
    colorScale: 'earth'
});
plot.render();

const dataURL = newCanvas.toDataURL('image/png');

..add new ImageLayer...

source: new Static({
    url: dataURL,
    imageExtent: tiffData.bbox,
}),

I got my example code from here: https://pbabik.github.io/webraster/

I'm not sure if I need plotty for this, I would prefer to remove this library as I do not really understand what it does exactly. Does anyone know why my colors are wrong or a different solution to display TIFFs in OpenLayers?

Best Answer

You do not need plotty, just copy the rgb data from the geotiff to a canvas and then create a data url:

let width, height, extent;

fetch("data/image.tif")
  .then(function (response) {
    return response.arrayBuffer();
  })
  .then(function (arrayBuffer) {
    return fromArrayBuffer(arrayBuffer);
  })
  .then(function (tiff) {
    return tiff.getImage();
  })
  .then(function (image) {
    width = image.getWidth();
    height = image.getHeight();
    extent = image.getBoundingBox();
    return image.readRGB();
  })
  .then(function (rgb) {
    const canvas = document.createElement("canvas");
    canvas.width = width;
    canvas.height = height;
    const context = canvas.getContext("2d");
    const data = context.getImageData(0, 0, width, height);
    const rgba = data.data;
    let j = 0;
    for (let i = 0; i < rgb.length; i += 3) {
      rgba[j] = rgb[i];
      rgba[j + 1] = rgb[i + 1];
      rgba[j + 2] = rgb[i + 2];
      rgba[j + 3] = 255;
      j += 4;
    }
    context.putImageData(data, 0, 0);

    map.addLayer(
      new ImageLayer({
        source: new Static({
          url: canvas.toDataURL(),
          projection: "EPSG:27700",
          imageExtent: extent
        })
      })
    );
  });

Working example (using promises instead of await) https://codesandbox.io/s/static-image-forked-7435e