Create color maps to interpret elevation, slope and aspect with custom color ramps.

Mode
Mode
Mode
100% © Mapbox

Colormaps are useful to colorize an elevation dataset. You can change the color map's properties dynamically, such as the min and max values, the color gradients, or disable the color map entirely. Color maps can be applied on both elevation layers and color layers.

index.js
/* eslint-disable no-lone-blocks */
import colormap from 'colormap';
import { Color, Vector3 } from 'three';
import { MapControls } from 'three/examples/jsm/controls/MapControls.js';

import Extent from '@giro3d/giro3d/core/geographic/Extent.js';
import Instance from '@giro3d/giro3d/core/Instance.js';
import ElevationLayer from '@giro3d/giro3d/core/layer/ElevationLayer.js';
import ColorLayer from '@giro3d/giro3d/core/layer/ColorLayer.js';
import Interpretation from '@giro3d/giro3d/core/layer/Interpretation.js';
import Map from '@giro3d/giro3d/entities/Map.js';
import Inspector from '@giro3d/giro3d/gui/Inspector.js';
import ColorMap from '@giro3d/giro3d/core/layer/ColorMap.js';
import ColorMapMode from '@giro3d/giro3d/core/layer/ColorMapMode.js';
import TiledImageSource from '@giro3d/giro3d/sources/TiledImageSource.js';
import XYZ from 'ol/source/XYZ.js';



// Defines geographic extent: CRS, min/max X, min/max Y
const extent = Extent.fromCenterAndSize('EPSG:3857', { x: 697313, y: 5591324 }, 30000, 30000);

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

// Creates the Giro3D instance
const instance = new Instance(viewerDiv, { crs: extent.crs() });

// Sets the camera position
const cameraPosition = new Vector3(659567, 5553543, 25175);
instance.camera.camera3D.position.copy(cameraPosition);

// Creates controls
const controls = new MapControls(instance.camera.camera3D, instance.domElement);

// Then looks at extent's center
controls.target = extent.centerAsVector3();
controls.saveState();

controls.enableDamping = true;
controls.dampingFactor = 0.2;
controls.maxPolarAngle = Math.PI / 2.3;

instance.useTHREEControls(controls);

const elevationMin = 780;
const elevationMax = 3574;

function makeColorRamp(preset, nshades) {
    const values = colormap({ colormap: preset, nshades });
    const colors = values.map(v => new Color(v));

    return colors;
}

const colorRamps = {};

function makeColorRamps(discrete) {
    const nshades = discrete ? 10 : 256;

    colorRamps.viridis = makeColorRamp('viridis', nshades);
    colorRamps.jet = makeColorRamp('jet', nshades);
    colorRamps.blackbody = makeColorRamp('blackbody', nshades);
    colorRamps.earth = makeColorRamp('earth', nshades);
    colorRamps.bathymetry = makeColorRamp('bathymetry', nshades);
    colorRamps.magma = makeColorRamp('magma', nshades);
    colorRamps.par = makeColorRamp('par', nshades);

    colorRamps.slope = makeColorRamp('RdBu', nshades);
}

makeColorRamps(false);

// Adds the map that will contain the layers.
const map = new Map('planar', {
    extent,
    segments: 128,
    hillshading: true,
});
instance.add(map);

const key =
    'pk.eyJ1IjoidG11Z3VldCIsImEiOiJjbGJ4dTNkOW0wYWx4M25ybWZ5YnpicHV6In0.KhDJ7W5N3d1z3ArrsDjX_A';
const source = new TiledImageSource({
    source: new XYZ({
        url: `https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=${key}`,
        projection: extent.crs(),
        crossOrigin: 'anonymous',
    }),
});

const elevationLayer = new ElevationLayer({
    name: 'elevation',
    extent,
    source,
    colorMap: new ColorMap(colorRamps.viridis, elevationMin, elevationMax, ColorMapMode.Elevation),
    interpretation: Interpretation.MapboxTerrainRGB,
});

const bottomLayer = new ColorLayer({
    name: 'color',
    extent,
    source,
    colorMap: new ColorMap(colorRamps.jet, elevationMin, elevationMax, ColorMapMode.Elevation),
    interpretation: Interpretation.MapboxTerrainRGB,
});

const topLayer = new ColorLayer({
    name: 'color2',
    extent,
    source,
    colorMap: new ColorMap(colorRamps.earth, elevationMin, elevationMax, ColorMapMode.Elevation),
    interpretation: Interpretation.MapboxTerrainRGB,
});

map.addLayer(elevationLayer);
map.addLayer(bottomLayer);
map.addLayer(topLayer);

function updateLayer(prefix, layer) {
    const enableLayer = document.getElementById(`${prefix}-layer-enable`);
    const enableColorMap = document.getElementById(`${prefix}-colormap-enable`);
    const gradient = document.getElementById(`${prefix}-gradient`);

    const colorMap = layer.colorMap;

    layer.visible = enableLayer.checked;
    colorMap.active = enableColorMap.checked;
    colorMap.colors = colorRamps[gradient.value];

    function updateMode(value) {
        switch (value) {
            case 'slope':
                gradient.disabled = true;
                colorMap.colors = colorRamps.slope;
                colorMap.mode = ColorMapMode.Slope;
                colorMap.min = 0;
                colorMap.max = 50;
                break;
            case 'aspect':
                gradient.disabled = true;
                colorMap.colors = colorRamps.slope;
                colorMap.mode = ColorMapMode.Aspect;
                colorMap.min = 0;
                colorMap.max = 360;
                break;
            default:
                gradient.disabled = false;
                colorMap.colors = colorRamps[gradient.value];
                colorMap.mode = ColorMapMode.Elevation;
                colorMap.min = elevationMin;
                colorMap.max = elevationMax;
                break;
        }
    }

    const mode = document.getElementById(`${prefix}-mode`);
    updateMode(mode.value);

    instance.notifyChange(map);
}

function bindControls(prefix, layer) {
    const notify = () => updateLayer(prefix, layer);

    const enableLayer = document.getElementById(`${prefix}-layer-enable`);
    const layerOptions = document.getElementById(`${prefix}-options`);
    enableLayer.onchange = () => {
        notify();
        layerOptions.disabled = !enableLayer.checked;
    };

    const enableColorMap = document.getElementById(`${prefix}-colormap-enable`);
    const colormapOptions = document.getElementById(`${prefix}-colormap-options`);
    enableColorMap.onchange = () => {
        notify();
        colormapOptions.disabled = !enableColorMap.checked;
    };

    const gradient = document.getElementById(`${prefix}-gradient`);
    gradient.onchange = () => {
        notify();
    };

    const mode = document.getElementById(`${prefix}-mode`);
    mode.onchange = () => notify();

    notify();
}

bindControls('elevation', elevationLayer);
bindControls('bottom', bottomLayer);
bindControls('top', topLayer);

const discreteToggle = document.getElementById('discrete-ramps');

discreteToggle.onchange = () => {
    makeColorRamps(discreteToggle.checked);
    updateLayer('elevation', elevationLayer);
    updateLayer('bottom', bottomLayer);
    updateLayer('top', topLayer);
};

Inspector.attach(document.getElementById('panelDiv'), instance);
index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Giro3D - Color maps</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>
  <style>
    body {
      padding: 0;
      margin: 0;
      width: 100vw;
      height: 100vh;
    }

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

    #panelDiv {
      position: absolute;
      top: 0;
      left: 0;
    }
    
  </style>
</head>

<body>
  <div id="viewerDiv"></div>
  <div id="panelDiv"></div>
  <div class="m-2 position-absolute top-0 end-0">
    <!-- General settings -->
    <div class="card m-1">
        <fieldset class="container m-1">
            <!-- Discrete color ramps -->
            <div class="row p-1">
                <div>
                    <div class="form-check form-switch">
                        <input
                            class="form-check-input"
                            type="checkbox"
                            role="switch"
                            id="discrete-ramps"
                        />
                        <label class="form-check-label" for="discrete-ramps"
                            >Use discrete color ramps</label
                        >
                    </div>
                </div>
            </div>
        </fieldset>
    </div>

    <!-- Top color layer -->
    <div class="card m-1">
        <div class="card-header">
            <div class="form-check form-switch">
                <input
                    class="form-check-input"
                    checked
                    type="checkbox"
                    role="switch"
                    id="top-layer-enable"
                />
                <label class="form-check-label" for="top-layer-enable">Top color layer</label>
            </div>
        </div>

        <fieldset class="container m-1" id="top-options">
            <!-- Activate color map -->
            <div class="row p-1">
                <div>
                    <div class="form-check form-switch">
                        <input
                            class="form-check-input"
                            checked
                            type="checkbox"
                            role="switch"
                            id="top-colormap-enable"
                        />
                        <label class="form-check-label" for="top-colormap-enable">Color map</label>
                    </div>
                </div>
            </div>

            <!-- Select color map mode -->
            <fieldset class="row p-1" id="top-colormap-options">
                <div class="input-group mb-3">
                    <span class="input-group-text flex-grow-1">Mode</span>
                    <select class="btn btn-outline-primary btn-sm" id="top-mode">
                        <option>elevation</option>
                        <option>slope</option>
                        <option>aspect</option>
                    </select>
                    <select class="btn btn-outline-primary btn-sm" id="top-gradient">
                        <option selected="selected">viridis</option>
                        <option>jet</option>
                        <option>blackbody</option>
                        <option>earth</option>
                        <option>bathymetry</option>
                        <option>magma</option>
                        <option>par</option>
                    </select>
                </div>
            </fieldset>
        </fieldset>
    </div>

    <!-- Bottom color layer -->
    <div class="card m-1">
        <div class="card-header">
            <div class="form-check form-switch">
                <input
                    class="form-check-input"
                    checked
                    type="checkbox"
                    role="switch"
                    id="bottom-layer-enable"
                />
                <label class="form-check-label" for="bottom-layer-enable">Bottom color layer</label>
            </div>
        </div>

        <fieldset class="container m-1" id="bottom-options">
            <!-- Activate color map -->
            <div class="row p-1">
                <div>
                    <div class="form-check form-switch">
                        <input
                            class="form-check-input"
                            checked
                            type="checkbox"
                            role="switch"
                            id="bottom-colormap-enable"
                        />
                        <label class="form-check-label" for="bottom-colormap-enable"
                            >Color map</label
                        >
                    </div>
                </div>
            </div>

            <!-- Select color map mode -->
            <fieldset class="row p-1" id="bottom-colormap-options">
                <div class="input-group mb-3">
                    <span class="input-group-text flex-grow-1">Mode</span>
                    <select class="btn btn-outline-primary btn-sm" id="bottom-mode">
                        <option>elevation</option>
                        <option>slope</option>
                        <option>aspect</option>
                    </select>
                    <select class="btn btn-outline-primary btn-sm" id="bottom-gradient">
                        <option>viridis</option>
                        <option selected="selected">jet</option>
                        <option>blackbody</option>
                        <option>earth</option>
                        <option>bathymetry</option>
                        <option>magma</option>
                        <option>par</option>
                    </select>
                </div>
            </fieldset>
        </fieldset>
    </div>

    <!-- Elevation layer -->
    <div class="card m-1">
        <div class="card-header">
            <div class="form-check form-switch">
                <input
                    class="form-check-input"
                    checked
                    type="checkbox"
                    role="switch"
                    id="elevation-layer-enable"
                />
                <label class="form-check-label" for="elevation-layer-enable">Elevation layer</label>
            </div>
        </div>

        <fieldset class="container m-1" id="elevation-options">
            <!-- Activate color map -->
            <div class="row p-1">
                <div>
                    <div class="form-check form-switch">
                        <input
                            class="form-check-input"
                            checked
                            type="checkbox"
                            role="switch"
                            id="elevation-colormap-enable"
                        />
                        <label class="form-check-label" for="elevation-colormap-enable"
                            >Color map</label
                        >
                    </div>
                </div>
            </div>

            <!-- Select color map mode -->
            <fieldset class="row p-1" id="elevation-colormap-options">
                <div class="input-group mb-3">
                    <span class="input-group-text flex-grow-1">Mode</span>
                    <select class="btn btn-outline-primary btn-sm" id="elevation-mode">
                        <option>elevation</option>
                        <option>slope</option>
                        <option>aspect</option>
                    </select>
                    <select class="btn btn-outline-primary btn-sm" id="elevation-gradient">
                        <option>viridis</option>
                        <option>jet</option>
                        <option>blackbody</option>
                        <option selected="selected">earth</option>
                        <option>bathymetry</option>
                        <option>magma</option>
                        <option>par</option>
                    </select>
                </div>
            </fieldset>
        </fieldset>
    </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": "colormaps",
  "dependencies": {
    "@giro3d/giro3d": "0.35.0"
  },
  "devDependencies": {
    "vite": "^3.2.3"
  },
  "scripts": {
    "start": "vite",
    "build": "vite build"
  }
}