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 CogSource 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.

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

import Extent from '@giro3d/giro3d/core/geographic/Extent.js';
import CogSource from '@giro3d/giro3d/sources/CogSource.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';

// Define geographic extent: CRS, min/max X, min/max Y
const extent = new Extent('EPSG:3857', 1818329.448, 1987320.77, 6062229.082, 6231700.791);
const center = extent.centerAsVector3();

// `viewerDiv` will contain Giro3D' rendering area (the canvas element)
const viewerDiv = document.getElementById('viewerDiv');

// Instantiate Giro3D
const instance = new Instance(viewerDiv, {
    crs: extent.crs(),
    renderer: {
        clearColor: 0x0a3b59,

// Instantiate the camera
instance.camera.camera3D.position.set(center.x, center.y, 250000);

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


// Construct a map and add it to the instance
const map = new Map('planar', { extent: extent.withRelativeMargin(0.1) });

// 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 CogSource({
        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 CogSource({
        url: 'https://3d.oslandia.com/giro3d/rasters/TCI-alpha.tif',
        crs: extent.crs(),
        channels: [0, 1, 2, 3],
    // JPEG compression, YCbCr colorspace
    ycbcr: new CogSource({
        url: 'https://3d.oslandia.com/giro3d/rasters/TCI-YCbCr.tif',
        crs: extent.crs(),
    // JPEG compression, YCbCr colorspace, 1-bit mask band
    'ycbcr-mask': new CogSource({
        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 });

Inspector.attach(document.getElementById('panelDiv'), instance);

const sourceSelector = document.getElementById('source');
sourceSelector.onchange = () => updateSource(sourceSelector.value);
<!DOCTYPE html>
<html lang="en">

  <meta charset="UTF-8">
  <title>Giro3D - Cloud Optimized GeoTIFF (COG)</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
    body {
      padding: 0;
      margin: 0;
      width: 100vw;
      height: 100vh;

    #viewerDiv {
      width: 100%;
      height: 100%;

    #panelDiv {
      position: absolute;
      top: 0;
      left: 0;

  <div id="viewerDiv"></div>
  <div id="panelDiv"></div>
  <div class="m-2 position-absolute top-0 end-0">
    <!-- Source file selector -->
    <div class="card m-1">
        <div class="container m-1">
            <fieldset class="row p-1">
                <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" 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>

  <script type="module" src="index.js"></script>
    /* activate popovers */
    const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
      // 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,

  "name": "cog_color",
  "dependencies": {
    "@giro3d/giro3d": "0.36.0",
    "colormap": "^2.3.2",
    "@turf/turf": "^6.5.0"
  "devDependencies": {
    "vite": "^3.2.3"
  "scripts": {
    "start": "vite",
    "build": "vite build"