Skip to content

Add a Color Relief Layer

Color relief (also called hypsometric tinting) paints the map with colors based on elevation — lowlands are green, mid-elevations are brown, mountain peaks are white. This makes elevation immediately visible at a glance.

No three.js or external libraries needed. This uses a standard raster layer with a color expression.

How Color Relief Works

Color relief paints the terrain surface with colors that represent altitude:

🌊 Water / low    → Deep blue / dark green
🌿 Plains         → Light green
🌄 Hills          → Yellow / brown
⛰️  Mountains      → Dark brown / grey
🏔️  Peaks          → White / light grey

The simplest approach is to use a hillshade layer with carefully chosen shadow/highlight colors, combined with a semi-transparent base map.

Adding Color Relief with Hillshade

javascript
map.addSource('dem', {
  type: 'raster-dem',
  tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
  tileSize: 256,
  encoding: 'terrarium',
  maxzoom: 15,
});

map.addLayer({
  id: 'color-relief',
  type: 'hillshade',
  source: 'dem',
  paint: {
    'hillshade-shadow-color': '#2d6a4f',   // low elevation → green
    'hillshade-highlight-color': '#f8f9fa', // high elevation → white
    'hillshade-accent-color': '#8B4513',    // steep slopes → brown
    'hillshade-exaggeration': 0.8,
    'hillshade-illumination-anchor': 'viewport',
  },
});

Make the Base Map Semi-Transparent

To blend color relief with the base map:

javascript
// Reduce base map opacity so relief colors show through
map.setPaintProperty('osm', 'raster-opacity', 0.4);

// Full opacity to hide relief
map.setPaintProperty('osm', 'raster-opacity', 1);

Complete Example

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
    <script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
    <style>body { margin: 0; } #map { height: 100vh; width: 100%; }</style>
  </head>
  <body>
    <div id="map"></div>
    <script>
      const map = new mapmetricsgl.Map({
        container: 'map',
        zoom: 5,
        center: [13, 47],
        style: {
          version: 8,
          sources: {
            osm: {
              type: 'raster',
              tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
              tileSize: 256,
              attribution: '&copy; OpenStreetMap Contributors',
              maxzoom: 19,
            },
            dem: {
              type: 'raster-dem',
              tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
              tileSize: 256,
              encoding: 'terrarium',
              maxzoom: 15,
            },
          },
          layers: [
            { id: 'osm', type: 'raster', source: 'osm', paint: { 'raster-opacity': 0.4 } },
            {
              id: 'color-relief',
              type: 'hillshade',
              source: 'dem',
              paint: {
                'hillshade-shadow-color': '#2d6a4f',
                'hillshade-highlight-color': '#f8f9fa',
                'hillshade-accent-color': '#8B4513',
                'hillshade-exaggeration': 0.8,
                'hillshade-illumination-anchor': 'viewport',
              },
            },
          ],
        },
      });

      map.addControl(new mapmetricsgl.NavigationControl(), 'top-right');
    </script>
  </body>
</html>
jsx
import React, { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

const ColorReliefLayer = () => {
  const mapContainer = useRef(null);
  const map = useRef(null);

  useEffect(() => {
    if (map.current) return;
    map.current = new mapmetricsgl.Map({
      container: mapContainer.current,
      zoom: 5,
      center: [13, 47],
      style: {
        version: 8,
        sources: {
          osm: {
            type: 'raster',
            tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
            tileSize: 256,
            attribution: '&copy; OpenStreetMap Contributors',
            maxzoom: 19,
          },
          dem: {
            type: 'raster-dem',
            tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
            tileSize: 256,
            encoding: 'terrarium',
            maxzoom: 15,
          },
        },
        layers: [
          { id: 'osm', type: 'raster', source: 'osm', paint: { 'raster-opacity': 0.4 } },
          {
            id: 'color-relief',
            type: 'hillshade',
            source: 'dem',
            paint: {
              'hillshade-shadow-color': '#2d6a4f',
              'hillshade-highlight-color': '#f8f9fa',
              'hillshade-accent-color': '#8B4513',
              'hillshade-exaggeration': 0.8,
              'hillshade-illumination-anchor': 'viewport',
            },
          },
        ],
      },
    });

    map.current.addControl(new mapmetricsgl.NavigationControl(), 'top-right');
    return () => { map.current?.remove(); map.current = null; };
  }, []);

  return <div ref={mapContainer} style={{ height: '100vh', width: '100%' }} />;
};

export default ColorReliefLayer;

For more information, visit the MapMetrics GitHub repository.