3D Terrain and Sky

Show elevation in actual 3D with the sky.

Based on: https://maplibre.org/maplibre-gl-js/docs/examples/sky-with-fog-and-terrain/

<script lang="ts">
  import {
    HillshadeLayer,
    MapLibre,
    RasterDEMTileSource,
    Sky,
    Terrain,
    TerrainControl,
    GlobeControl,
    Light
  } from 'svelte-maplibre-gl';
  import { Checkbox } from '$lib/components/ui/checkbox/index.js';
  import { Label } from '$lib/components/ui/label/index.js';
  import { Slider } from '$lib/components/ui/slider/index.js';
  import * as Tabs from '$lib/components/ui/tabs/index.js';

  let mode: 'terrain' | 'sky' = $state('terrain');
  // Terrain
  let exaggeration = $state(1.0);
  let hillshade = $state(0.7);
  let shadowColor = $state('#004050');
  let accentColor = $state('#aaff00');
  let highlightColor = $state('#ffffff');
  let direction = $state(0.0);
  // Sky
  let skyEnabled = $state(true);
  let skyColor = $state('#001560');
  let horizonColor = $state('#0090c0');
  let fogColor = $state('#ffffff');
  let skyHorizonBlend = $state(0.5);
  let horizonFogBlend = $state(0.7);
  let fogGroundBlend = $state(0.5);
</script>

<MapLibre
  class="h-[55vh] min-h-[300px]"
  style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
  zoom={12}
  pitch={72}
  maxPitch={85}
  center={{ lng: 11.39085, lat: 47.3 }}
>
  <GlobeControl />
  <Light anchor="map" />
  {#if skyEnabled}
    <Sky
      sky-color={skyColor}
      horizon-color={horizonColor}
      fog-color={fogColor}
      sky-horizon-blend={skyHorizonBlend}
      horizon-fog-blend={horizonFogBlend}
      fog-ground-blend={fogGroundBlend}
      atmosphere-blend={0.5}
    />
  {/if}
  <!-- Terrain -->
  <RasterDEMTileSource
    id="terrain"
    tiles={['https://demotiles.maplibre.org/terrain-tiles/{z}/{x}/{y}.png']}
    minzoom={0}
    maxzoom={12}
    attribution="<a href='https://earth.jaxa.jp/en/data/policy/'>AW3D30 (JAXA)</a>"
  >
    <TerrainControl position="top-right" />
    <Terrain {exaggeration} />
  </RasterDEMTileSource>
  <!-- Hillshade -->
  <RasterDEMTileSource
    tiles={['https://demotiles.maplibre.org/terrain-tiles/{z}/{x}/{y}.png']}
    minzoom={0}
    maxzoom={12}
    attribution="<a href='https://earth.jaxa.jp/en/data/policy/'>AW3D30 (JAXA)</a>"
  >
    <HillshadeLayer
      paint={{
        'hillshade-exaggeration': hillshade,
        'hillshade-shadow-color': shadowColor,
        'hillshade-accent-color': accentColor,
        'hillshade-highlight-color': highlightColor,
        'hillshade-illumination-anchor': 'map',
        'hillshade-illumination-direction': direction
      }}
    />
  </RasterDEMTileSource>

  <div
    class="absolute left-3 top-3 z-10 flex min-w-[200px] flex-col items-stretch gap-1 rounded bg-background/60 p-3 text-sm backdrop-blur-sm"
  >
    <Tabs.Root bind:value={mode} class="flex h-full flex-col">
      <Tabs.List class="grid w-full grid-cols-2">
        <Tabs.Trigger value="terrain">Terrain</Tabs.Trigger>
        <Tabs.Trigger value="sky">Sky</Tabs.Trigger>
      </Tabs.List>
      <Tabs.Content value="terrain" class="min-h-0 shrink overflow-scroll pt-1">
        <div class="mb-4 flex flex-col items-center space-y-2 px-2">
          <Label for="pitch" class="leading-none">Exaggeration ({exaggeration.toFixed(2)})</Label>
          <Slider type="single" id="pitch" bind:value={exaggeration} min={0} max={2} step={0.01} />
        </div>
        <div class="mb-3 flex flex-col items-center space-y-2 px-2">
          <Label for="hillshade" class="leading-none">Hillshade ({hillshade.toFixed(2)})</Label>
          <Slider type="single" id="hillshade" bind:value={hillshade} min={0} max={1} step={0.01} />
        </div>
        <div class="mb-1 flex items-center justify-between space-x-2">
          <Label for="shadow-sm" class="leading-none">Shadow</Label>
          <input type="color" id="shadow-sm" bind:value={shadowColor} />
        </div>
        <div class="mb-1 flex items-center justify-between space-x-2">
          <Label for="accent" class="leading-none">Accent</Label>
          <input type="color" id="accent" bind:value={accentColor} />
        </div>
        <div class="mb-2 flex items-center justify-between space-x-2">
          <Label for="highlight" class="leading-none">Highlight</Label>
          <input type="color" id="highlight" bind:value={highlightColor} />
        </div>
        <div class="mb-3 flex flex-col items-center space-y-2 px-2">
          <Label for="direction" class="leading-none">Direction ({direction})</Label>
          <Slider type="single" id="direction" bind:value={direction} min={0} max={360} />
        </div>
      </Tabs.Content>
      <Tabs.Content value="sky" class="min-h-0 shrink overflow-scroll pt-1">
        <div class="mb-2 flex items-center space-x-2 self-center justify-self-center">
          <Checkbox id="skyEnabled" bind:checked={skyEnabled} />
          <Label for="skyEnabled" class="leading-none">Enable Sky</Label>
        </div>
        <div class="mb-1 flex items-center justify-between space-x-2">
          <Label for="skyColor" class="leading-none">Sky color</Label>
          <input type="color" id="skyColor" bind:value={skyColor} />
        </div>
        <div class="mb-3 flex flex-col items-center space-y-2 px-2">
          <Label for="skyHorizonBlend" class="leading-none">sky-horizon-blend ({skyHorizonBlend})</Label>
          <Slider type="single" id="skyHorizonBlend" bind:value={skyHorizonBlend} min={0} max={1} step={0.01} />
        </div>
        <div class="mb-1 flex items-center justify-between space-x-2">
          <Label for="horizonColor" class="leading-none">Horizon color</Label>
          <input type="color" id="horizonColor" bind:value={horizonColor} />
        </div>
        <div class="mb-3 flex flex-col items-center space-y-2 px-2">
          <Label for="horizonFogBlend" class="leading-none">horizon-fog-blend ({horizonFogBlend})</Label>
          <Slider type="single" id="horizonFogBlend" bind:value={horizonFogBlend} min={0} max={1} step={0.01} />
        </div>
        <div class="mb-1 flex items-center justify-between space-x-2">
          <Label for="fogColor" class="leading-none">Fog color</Label>
          <input type="color" id="fogColor" bind:value={fogColor} />
        </div>
        <div class="mb-3 flex flex-col items-center space-y-2 px-2">
          <Label for="fogGroundBlend" class="leading-none">fog-ground-blend ({fogGroundBlend})</Label>
          <Slider type="single" id="fogGroundBlend" bind:value={fogGroundBlend} min={0} max={1} step={0.01} />
        </div>
      </Tabs.Content>
    </Tabs.Root>
  </div>
</MapLibre>
Our examples use Tailwind CSS and shadcn-svelte.