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.
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.