Skip to content
Draw and Edit Shapes

Draw and Edit Shapes in Maptoolkit Maps JS

Drawing on the map covers a whole class of features: a delivery zone, a search area, a planned route, a field boundary. Terra Draw provides the drawing and editing, and its MapLibre adapter works with Maptoolkit Maps JS unchanged, because a Maps JS map is a MapLibre map. Pick a tool, draw, then use Edit to move shapes and drag or add corners.

const { TerraDraw, TerraDrawPolygonMode, TerraDrawLineStringMode, TerraDrawRectangleMode,
            TerraDrawPointMode, TerraDrawSelectMode } = terraDraw;
    const { TerraDrawMapLibreGLAdapter } = terraDrawMaplibreGlAdapter;

    const API_KEY = "YOUR_API_KEY";

    const map = new maptoolkit.Map({
      container: "map",
      apiKey: API_KEY,
      style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
      center: [11.3945, 47.2735],
      zoom: 14,
      attributionControl: { compact: false },
    });

    // Editing is switched on per geometry type in the select mode.
    const editable = {
      feature: {
        draggable: true,
        coordinates: { draggable: true, midpoints: true, deletable: true },
      },
    };

    const draw = new TerraDraw({
      adapter: new TerraDrawMapLibreGLAdapter({ map }),
      modes: [
        new TerraDrawPolygonMode(),
        new TerraDrawLineStringMode(),
        new TerraDrawRectangleMode(),
        new TerraDrawPointMode(),
        new TerraDrawSelectMode({
          flags: {
            polygon: editable,
            linestring: editable,
            rectangle: { feature: { draggable: true, coordinates: { resizable: "opposite" } } },
            point: { feature: { draggable: true } },
          },
        }),
      ],
    });

    function setMode(mode) {
      draw.setMode(mode);
      document.querySelectorAll("#tools [data-mode]").forEach((button) =>
        button.classList.toggle("active", button.dataset.mode === mode));
    }

    function showGeoJSON() {
      // While a shape is being edited, the snapshot also holds the edit handles.
      const features = draw.getSnapshot().filter((feature) => feature.properties.mode !== "select");
      document.getElementById("geojson").value = features.length
        ? JSON.stringify({ type: "FeatureCollection", features })
        : "";
    }

    map.on("load", () => {
      draw.start();

      // A shape saved earlier. Its `mode` property decides which mode owns it.
      draw.addFeatures([{
        type: "Feature",
        properties: { mode: "polygon" },
        geometry: {
          type: "Polygon",
          coordinates: [[[11.3905, 47.2755], [11.3985, 47.2755], [11.3985, 47.2715], [11.3905, 47.2715], [11.3905, 47.2755]]],
        },
      }]);

      setMode("select");
      showGeoJSON();
    });

    draw.on("finish", showGeoJSON);
    draw.on("change", (ids, type) => { if (type === "delete") showGeoJSON(); });

    document.querySelectorAll("#tools [data-mode]").forEach((button) =>
      button.addEventListener("click", () => setMode(button.dataset.mode)));

    document.getElementById("delete").addEventListener("click", () => {
      const selected = draw.getSnapshot().filter((feature) => feature.properties.selected);
      draw.removeFeatures(selected.map((feature) => feature.id));
    });

    document.getElementById("clear").addEventListener("click", () => draw.clear());
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
  <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />
  <script src="https://cdn.jsdelivr.net/npm/terra-draw@1.35.0/dist/terra-draw.umd.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/terra-draw-maplibre-gl-adapter@1.4.1/dist/terra-draw-maplibre-gl-adapter.umd.js"></script>
  <style>
    html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
    #map { width: 100%; height: 100%; }
    #tools {
      position: absolute; top: 10px; left: 10px; z-index: 1; display: flex; flex-wrap: wrap; gap: 4px;
      max-width: 60%; font: 13px sans-serif;
    }
    #tools button {
      padding: 6px 10px; border: none; border-radius: 4px; background: #fff; cursor: pointer;
      box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
    }
    #tools button.active { background: #303f7e; color: #fff; }
    #geojson {
      position: absolute; top: 10px; right: 10px; z-index: 1; width: 260px; height: 140px;
      font: 11px/1.4 monospace; padding: 6px; border: none; border-radius: 4px;
      box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
    }
  </style>
</head>
<body>
  <div id="map"></div>
  <div id="tools">
    <button data-mode="polygon">Polygon</button>
    <button data-mode="linestring">Line</button>
    <button data-mode="rectangle">Rectangle</button>
    <button data-mode="point">Point</button>
    <button data-mode="select">Edit</button>
    <button id="delete">Delete selected</button>
    <button id="clear">Clear all</button>
  </div>
  <textarea id="geojson" readonly></textarea>
  <script>
    const { TerraDraw, TerraDrawPolygonMode, TerraDrawLineStringMode, TerraDrawRectangleMode,
            TerraDrawPointMode, TerraDrawSelectMode } = terraDraw;
    const { TerraDrawMapLibreGLAdapter } = terraDrawMaplibreGlAdapter;

    const API_KEY = "YOUR_API_KEY";

    const map = new maptoolkit.Map({
      container: "map",
      apiKey: API_KEY,
      style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
      center: [11.3945, 47.2735],
      zoom: 14,
      attributionControl: { compact: false },
    });

    // Editing is switched on per geometry type in the select mode.
    const editable = {
      feature: {
        draggable: true,
        coordinates: { draggable: true, midpoints: true, deletable: true },
      },
    };

    const draw = new TerraDraw({
      adapter: new TerraDrawMapLibreGLAdapter({ map }),
      modes: [
        new TerraDrawPolygonMode(),
        new TerraDrawLineStringMode(),
        new TerraDrawRectangleMode(),
        new TerraDrawPointMode(),
        new TerraDrawSelectMode({
          flags: {
            polygon: editable,
            linestring: editable,
            rectangle: { feature: { draggable: true, coordinates: { resizable: "opposite" } } },
            point: { feature: { draggable: true } },
          },
        }),
      ],
    });

    function setMode(mode) {
      draw.setMode(mode);
      document.querySelectorAll("#tools [data-mode]").forEach((button) =>
        button.classList.toggle("active", button.dataset.mode === mode));
    }

    function showGeoJSON() {
      // While a shape is being edited, the snapshot also holds the edit handles.
      const features = draw.getSnapshot().filter((feature) => feature.properties.mode !== "select");
      document.getElementById("geojson").value = features.length
        ? JSON.stringify({ type: "FeatureCollection", features })
        : "";
    }

    map.on("load", () => {
      draw.start();

      // A shape saved earlier. Its `mode` property decides which mode owns it.
      draw.addFeatures([{
        type: "Feature",
        properties: { mode: "polygon" },
        geometry: {
          type: "Polygon",
          coordinates: [[[11.3905, 47.2755], [11.3985, 47.2755], [11.3985, 47.2715], [11.3905, 47.2715], [11.3905, 47.2755]]],
        },
      }]);

      setMode("select");
      showGeoJSON();
    });

    draw.on("finish", showGeoJSON);
    draw.on("change", (ids, type) => { if (type === "delete") showGeoJSON(); });

    document.querySelectorAll("#tools [data-mode]").forEach((button) =>
      button.addEventListener("click", () => setMode(button.dataset.mode)));

    document.getElementById("delete").addEventListener("click", () => {
      const selected = draw.getSnapshot().filter((feature) => feature.properties.selected);
      draw.removeFeatures(selected.map((feature) => feature.id));
    });

    document.getElementById("clear").addEventListener("click", () => draw.clear());
  </script>
</body>
</html>

Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.

Use the Maptoolkit Connector. Create a Maptoolkit Maps JS map centered on Innsbruck at zoom level 14. Add Terra Draw with buttons for polygon, line, rectangle, point and edit modes, delete and clear buttons, a text box with the drawn shapes as GeoJSON, and one saved polygon loaded at start.

How it works

Terra Draw has no toolbar of its own. It is a set of modes, and the buttons switch between them with draw.setMode(). The built-in names are polygon, linestring, rectangle, point and select. TerraDrawMapLibreGLAdapter takes the Maps JS map as it is and draws the shapes as map layers, and draw.start() waits for the load event because the adapter adds those layers to the style.

Editing is switched on per mode. TerraDrawSelectMode only allows what its flags list. Polygons and lines can be moved, their corners dragged or deleted, and new corners added from the midpoints; rectangles move and resize from the opposite corner; points move.

The snapshot contains the edit handles while you edit. While a shape is selected, draw.getSnapshot() also returns its corner and midpoint handles, as features with mode: "select". showGeoJSON() filters them out so they are never saved as shapes.

Every feature carries a generated id and a mode property. The mode decides which tool owns the shape, so the saved polygon passed to addFeatures() needs mode: "polygon", and the id lets you match a shape to its stored copy after an edit. finish fires when a shape is completed and when an edit ends, and change with the type delete covers removals.

Next steps

A drawn polygon is usually the start of a question: how big is it, and what is inside it. Measure Area and Bearing answers the first for the same kind of coordinates, and Locations Inside an Isochrone tests your own points against a polygon.

To restore a user’s shapes on their next visit, store the exported FeatureCollection and pass its features back to draw.addFeatures(), as the example does for its saved shape.