Terra Draw

Use Terra Draw to draw a geometry in various forms such as point, line or polygon on your map with undo/redo functionality enabled.

<script lang="ts">
  import { MapLibre, GlobeControl } from 'svelte-maplibre-gl';
  import { TerraDraw, type UndoRedoOptions } from '@svelte-maplibre-gl/terradraw';
  import type { TerraDraw as Draw } from 'terra-draw';
  import {
    TerraDrawCircleMode,
    TerraDrawRectangleMode,
    TerraDrawFreehandMode,
    TerraDrawAngledRectangleMode,
    TerraDrawLineStringMode,
    TerraDrawPolygonMode,
    TerraDrawPointMode,
    TerraDrawSectorMode,
    TerraDrawSelectMode,
    TerraDrawSensorMode
  } from 'terra-draw';
  import RotateCcw from '@lucide/svelte/icons/rotate-ccw';
  import RotateCw from '@lucide/svelte/icons/rotate-cw';

  const defaultSelectFlags = {
    feature: {
      draggable: true,
      coordinates: {
        deletable: true,
        midpoints: true,
        draggable: true
      }
    }
  };
  const modes = [
    new TerraDrawSelectMode({
      flags: {
        point: defaultSelectFlags,
        linestring: defaultSelectFlags,
        polygon: defaultSelectFlags,
        freehand: defaultSelectFlags,
        circle: defaultSelectFlags,
        rectangle: defaultSelectFlags,
        sector: defaultSelectFlags,
        sensor: defaultSelectFlags,
        'angled-rectangle': defaultSelectFlags
      }
    }),
    new TerraDrawPointMode(),
    new TerraDrawLineStringMode(),
    new TerraDrawPolygonMode(),
    new TerraDrawCircleMode(),
    new TerraDrawSectorMode(),
    new TerraDrawSensorMode(),
    new TerraDrawRectangleMode(),
    new TerraDrawFreehandMode(),
    new TerraDrawAngledRectangleMode()
  ];
  const modeNames = modes.map((mode) => mode.mode);
  let mode = $state('select');
  let selected: string | number | null = $state(null);
  let draw: Draw | undefined = $state.raw();
  let undoRedo: UndoRedoOptions = {
    // Enable mode/session history with a bounded stack.
    modeLevel: { maxStackSize: 100 },
    sessionLevel: { maxStackSize: 100 },
    // Enable TerraDraw's default keyboard shortcuts.
    keyboardShortcuts: {}
  };
  let terraDrawUndoSize: number = $state(0);
  let terraDrawRedoSize: number = $state(0);
</script>

<MapLibre
  class="h-[55vh] min-h-75"
  style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
  zoom={2}
  center={{ lng: 60, lat: 20 }}
>
  <!-- Terra Draw -->
  <TerraDraw
    {mode}
    {modes}
    bind:draw
    onselect={(featureId) => (selected = featureId)}
    ondeselect={() => (selected = null)}
    onhistory={({ undoSize, redoSize }) => {
      terraDrawUndoSize = undoSize;
      terraDrawRedoSize = redoSize;
    }}
    {undoRedo}
  />

  <!-- Draw controls -->
  <div
    role="group"
    aria-label="Draw controls"
    class="absolute top-3 left-3 z-10 flex flex-col items-stretch gap-1 rounded bg-background/60 p-3 text-sm backdrop-blur-sm"
  >
    {#each modeNames as modeName (modeName)}
      <button
        type="button"
        aria-pressed={mode === modeName}
        class="inline-flex h-7 items-center justify-start rounded-md border border-border bg-background px-2.5 text-sm font-medium transition-colors hover:bg-muted aria-pressed:border-primary aria-pressed:bg-primary aria-pressed:text-primary-foreground"
        onclick={() => (mode = modeName)}
      >
        {modeName}
      </button>
    {/each}
    {#if selected}
      <button
        type="button"
        aria-label="Remove selected feature"
        class="mt-1 inline-flex h-7 items-center justify-center rounded-md border border-destructive/30 bg-destructive/10 px-2.5 text-sm font-medium text-destructive transition-colors hover:bg-destructive/20 dark:border-red-400/40 dark:bg-red-400/15 dark:text-red-200 dark:hover:bg-red-400/25"
        onclick={() => {
          if (!selected) return;
          draw?.removeFeatures([selected]);
          draw?.deselectFeature(selected);
        }}>Remove</button
      >
    {/if}
  </div>

  <!-- Undo Redo Controls -->
  {#if undoRedo}
    <div class="absolute top-3 left-1/2 -translate-x-1/2 z-10 flex justify-center mx-auto">
      <div
        role="group"
        aria-label="Undo and redo controls"
        class="inline-flex items-center gap-1 rounded-lg border border-border/40 bg-background/80 p-1.5 backdrop-blur-sm"
      >
        <button
          type="button"
          onclick={() => draw?.undo()}
          class="inline-flex h-7 items-center justify-center gap-1 rounded-md border border-border bg-background px-2.5 text-sm font-medium transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50"
          disabled={terraDrawUndoSize === 0}
        >
          <RotateCcw class="w-3" />
          Undo
        </button>

        <button
          type="button"
          onclick={() => draw?.redo()}
          class="inline-flex h-7 items-center justify-center gap-1 rounded-md border border-border bg-background px-2.5 text-sm font-medium transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50"
          disabled={terraDrawRedoSize === 0}
        >
          Redo
          <RotateCw class="w-3" />
        </button>
      </div>
    </div>
  {/if}

  <GlobeControl />
</MapLibre>
Our examples use Tailwind CSS and shadcn-svelte.