Change Base Style

This library efficiently preserves dynamic user contents even when the base style changes.

<script lang="ts">
  import {
    HillshadeLayer,
    Light,
    LineLayer,
    MapLibre,
    Projection,
    ImageLoader,
    RasterDEMTileSource,
    Sky,
    Terrain,
    GeoJSONSource,
    SymbolLayer,
    VectorTileSource
  } from 'svelte-maplibre-gl';

  import maplibregl from 'maplibre-gl';
  import { Label } from '$lib/components/ui/label/index.js';
  import * as RadioGroup from '$lib/components/ui/radio-group/index.js';
  import { Switch } from '$lib/components/ui/switch/index.js';
  import type { FeatureCollection } from 'geojson';

  // Base styles
  const STYLES = new Map<string, string | maplibregl.StyleSpecification>([
    ['Voyager', 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json'],
    ['Positron', 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'],
    ['Dark Matter', 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json'],
    ['Demo Tiles', 'https://demotiles.maplibre.org/style.json']
  ]);
  let name = $state('Voyager');
  let style = $derived(STYLES.get(name)!);
  let globe = $state(true);

  let data: FeatureCollection = {
    type: 'FeatureCollection',
    features: [
      {
        type: 'Feature',
        geometry: { type: 'Point', coordinates: [140, 30] },
        properties: { imageName: 'osgeo', year: 2024 }
      }
    ]
  };
</script>

<div class="mb-3 flex items-center justify-between">
  <RadioGroup.Root bind:value={name} class="flex flex-row gap-x-3">
    {#each STYLES as [name]}
      <div class="flex items-center space-x-1">
        <RadioGroup.Item value={name} id={name} />
        <Label class="cursor-pointer" for={name}>{name}</Label>
      </div>
    {/each}
  </RadioGroup.Root>

  <div class="flex items-center space-x-2">
    <Switch id="globe" bind:checked={globe} />
    <Label for="globe">Globe</Label>
  </div>
</div>

<MapLibre class="h-[55vh] min-h-[300px]" {style} zoom={4} maxPitch={80} center={{ lng: 137, lat: 36 }}>
  <!-- User-defined dynamic styles -->
  <Projection type={globe ? 'globe' : undefined} />
  <Light anchor="map" />
  <Sky
    sky-color="#001560"
    horizon-color="#0090c0"
    fog-color="#ffffff"
    sky-horizon-blend={0.9}
    horizon-fog-blend={0.7}
    fog-ground-blend={0.6}
    atmosphere-blend={['interpolate', ['linear'], ['zoom'], 1, 0.6, 3, 0.3, 5, 0]}
  />
  <VectorTileSource
    tiles={['https://jma-assets.mierune.dev/tiles/mete/{z}/{x}/{y}.pbf']}
    minzoom={0}
    maxzoom={13}
    attribution={'<a href="https://www.data.jma.go.jp/developer/gis.html">JMA</a>'}
  >
    <LineLayer
      sourceLayer="city"
      layout={{ 'line-join': 'round', 'line-cap': 'round' }}
      paint={{ 'line-color': '#ff00ff', 'line-width': 1 }}
    />
  </VectorTileSource>
  <RasterDEMTileSource
    tiles={['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png']}
    minzoom={0}
    maxzoom={15}
    encoding="terrarium"
    attribution="<a href='https://github.com/tilezen/joerd/blob/master/docs/attribution.md'>Mapzen (Terrain)</a>"
  >
    <HillshadeLayer paint={{ 'hillshade-exaggeration': 0.2 }} />
  </RasterDEMTileSource>
  {#if !globe}
    <RasterDEMTileSource
      id="terrain"
      tiles={['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png']}
      minzoom={0}
      maxzoom={15}
      encoding="terrarium"
      attribution="<a href='https://github.com/tilezen/joerd/blob/master/docs/attribution.md'>Mapzen (Terrain)</a>"
    >
      <Terrain />
    </RasterDEMTileSource>
  {/if}
  <ImageLoader
    images={{
      osgeo: 'https://maplibre.org/maplibre-gl-js/docs/assets/osgeo-logo.png'
    }}
  >
    <GeoJSONSource {data}>
      <!-- Children components will be added after all images have been loaded -->
      <SymbolLayer
        layout={{
          'text-field': ['get', 'name'],
          'icon-image': ['get', 'imageName'],
          'icon-size': ['number', ['get', 'scale'], 1],
          'icon-text-fit': 'both',
          'icon-overlap': 'always',
          'text-overlap': 'always'
        }}
      />
    </GeoJSONSource>
  </ImageLoader>
</MapLibre>
Our examples use Tailwind CSS and shadcn-svelte.