Display a color COG in various color spaces.

Source file
100% © EOX

Cloud Optimized GeoTIFFs are regular GeoTIFF files whose layout is optimized for remote access. They allow streaming the image without tiling it beforehand. The GeoTIFFSource image source supports both elevation and color data, as well as 8-bit, 16-bit and 32-bit pixels. Optionally, you can use the convertToRGB constructor option to decode images not in the RGB color space, such as CMYK or YCbCR images.

index.js
import { MapControls } from "three/examples/jsm/controls/MapControls.js";

import Extent from "@giro3d/giro3d/core/geographic/Extent.js";
import GeoTIFFSource from "@giro3d/giro3d/sources/GeoTIFFSource.js";
import Instance from "@giro3d/giro3d/core/Instance.js";
import ColorLayer from "@giro3d/giro3d/core/layer/ColorLayer.js";
import Map from "@giro3d/giro3d/entities/Map.js";
import Inspector from "@giro3d/giro3d/gui/Inspector.js";

function bindDropDown(id, onChange) {
  const element = document.getElementById(id);
  if (!(element instanceof HTMLSelectElement)) {
    throw new Error(
      "invalid binding element: expected HTMLSelectElement, got: " +
        element.constructor.name,
    );
  }

  element.onchange = () => {
    onChange(element.value);
  };

  const callback = (v) => {
    element.value = v;
    onChange(element.value);
  };

  return [callback, element.value, element];
}

const extent = new Extent(
  "EPSG:3857",
  1818329.448,
  1987320.77,
  6062229.082,
  6231700.791,
);
const center = extent.centerAsVector3();

const instance = new Instance({
  target: "view",
  crs: extent.crs,
  backgroundColor: 0x0a3b59,
});

instance.view.camera.position.set(center.x, center.y, 250000);

const controls = new MapControls(instance.view.camera, instance.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.2;
controls.target.set(center.x, center.y + 1, center.z);
instance.view.setControls(controls);

const map = new Map({ extent: extent.withRelativeMargin(0.1) });
instance.add(map);

// Data coming from the same source as
// https://openlayers.org/en/latest/examples/cog-math-multisource.html
const sources = {
  // LZW compression, RGB colorspace
  rgb: new GeoTIFFSource({
    url: "https://3d.oslandia.com/giro3d/rasters/TCI.tif",
    crs: extent.crs,
    channels: [0, 1, 2],
  }),
  // LZW compression, RGB colorspace, 8-bit alpha band
  rgba: new GeoTIFFSource({
    url: "https://3d.oslandia.com/giro3d/rasters/TCI-alpha.tif",
    crs: extent.crs,
    channels: [0, 1, 2, 3],
  }),
  // JPEG compression, YCbCr colorspace
  ycbcr: new GeoTIFFSource({
    url: "https://3d.oslandia.com/giro3d/rasters/TCI-YCbCr.tif",
    crs: extent.crs,
  }),
  // JPEG compression, YCbCr colorspace, 1-bit mask band
  "ycbcr-mask": new GeoTIFFSource({
    url: "https://3d.oslandia.com/giro3d/rasters/TCI-YCbCr-mask.tif",
    crs: extent.crs,
  }),
};

function updateSource(name) {
  map.forEachLayer((layer) => map.removeLayer(layer, { disposeLayer: true }));

  const layer = new ColorLayer({
    name: "color-layer",
    source: sources[name],
    extent,
  });
  map.addLayer(layer);
}

Inspector.attach("inspector", instance);

bindDropDown("source-file", updateSource);

updateSource("rgb");
index.html
<!doctype html>
<html lang="en">
  <head>
    <title>Cloud Optimized GeoTIFF (COG)</title>
    <meta charset="UTF-8" />
    <meta name="name" content="cog_color" />
    <meta
      name="description"
      content="Display a color COG in various color spaces."
    />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <link rel="icon" href="https://giro3d.org/images/favicon.svg" />
    <link
      href="https://giro3d.org/assets/bootstrap-custom.css"
      rel="stylesheet"
    />
    <script src="https://giro3d.org/assets/bootstrap.bundle.min.js"></script>
    <link
      rel="stylesheet"
      type="text/css"
      href="https://giro3d.org/next/css/example.css"
    />
  </head>

  <body>
    <div id="view" class="m-0 p-0 w-100 h-100"></div>
    <div
      id="inspector"
      class="position-absolute top-0 start-0 mh-100 overflow-auto"
    ></div>

    <div class="side-pane-with-status-bar">
      <!-- Source file selector -->
      <div class="card">
        <div class="card-body">
          <div class="input-group">
            <span class="input-group-text flex-grow-1">Source file</span>
            <select
              class="btn btn-outline-primary btn-sm"
              id="source-file"
              autocomplete="off"
            >
              <option selected value="rgb">RGB (LZW)</option>
              <option value="rgba">RGBA (LZW)</option>
              <option value="ycbcr">YCbCr (JPEG)</option>
              <option value="ycbcr-mask">YCbCr + mask (JPEG)</option>
            </select>
          </div>
        </div>
      </div>
    </div>

    <script type="module" src="index.js"></script>
    <script>
      /* activate popovers */
      const popoverTriggerList = [].slice.call(
        document.querySelectorAll('[data-bs-toggle="popover"]'),
      );
      popoverTriggerList.map(
        // bootstrap is used as script in the template, disable warning about undef
        // eslint-disable-next-line no-undef
        (popoverTriggerEl) =>
          new bootstrap.Popover(popoverTriggerEl, {
            trigger: "hover",
            placement: "left",
            content: document.getElementById(
              popoverTriggerEl.getAttribute("data-bs-content"),
            ).innerHTML,
            html: true,
          }),
      );
    </script>
  </body>
</html>
package.json
{
    "name": "cog_color",
    "dependencies": {
        "@giro3d/giro3d": "git+https://gitlab.com/giro3d/giro3d.git"
    },
    "devDependencies": {
        "vite": "^3.2.3"
    },
    "scripts": {
        "start": "vite",
        "build": "vite build"
    }
}