Quick start ​
Wrap your map in an engine adapter and pass it to TacticalDraw. In the demo, choose a control measure, select Draw, then select points on the map.
This page builds a complete loop with MapLibre GL JS: draw a measure, show it, click it to edit, and clean up. The same code works with OpenLayers or Leaflet once you change the adapter.
1. Install ​
pnpm add @orbat-mapper/tactical-draw @orbat-mapper/tactical-draw-adapter-maplibre @orbat-mapper/control-measures maplibre-glnpm install @orbat-mapper/tactical-draw @orbat-mapper/tactical-draw-adapter-maplibre @orbat-mapper/control-measures maplibre-gl2. Create TacticalDraw ​
Once your MapLibre map has loaded, wrap it in MapLibreAdapter and create one TacticalDraw instance:
import { TacticalDraw, ignoreAbort } from "@orbat-mapper/tactical-draw";
import { MapLibreAdapter } from "@orbat-mapper/tactical-draw-adapter-maplibre";
import { getDefaultOptions, type ControlMeasure } from "@orbat-mapper/control-measures";
const adapter = new MapLibreAdapter(map); // `map` is a loaded maplibre-gl Map
const td = new TacticalDraw(adapter);3. Draw and render ​
Your app keeps the list of graphics. draw() resolves when the user commits; add the returned graphic to your list, then render the complete list:
let measures: ControlMeasure[] = [];
async function drawAmbush() {
const { graphic } = await td.draw({
kind: "ambush",
options: getDefaultOptions("ambush"),
});
measures = [...measures, graphic];
td.render(measures); // reconcile the committed graphics onto the map
}
drawButton.onclick = () => void drawAmbush().catch(ignoreAbort);Ambush needs exactly three points, so it commits on the third click. Kinds with a variable number of points commit on a double-click; see Commit behavior.
If the user presses Escape, or another draw starts, the promise rejects with a TacticalDrawAbortError. ignoreAbort swallows that error and rethrows any other.
4. Click to edit ​
Subscribe to picks once. When the user clicks a committed measure, start an edit and replace the measure with the result:
td.onGraphicPick(async (event) => {
const measure = measures.find((m) => m.id === event.id);
if (!measure) return;
try {
const { graphic: next } = await td.edit(measure);
measures = measures.map((m) => (m.id === next.id ? next : m));
td.render(measures);
} catch (error) {
ignoreAbort(error);
}
});The edit shows vertex handles and a transform box. Clicking elsewhere on the map commits it. See Edit a control measure.
5. Clean up ​
Call render() after every change to your list. When the map is removed, destroy TacticalDraw before the adapter:
td.destroy(); // aborts any interaction and removes TacticalDraw's layers
adapter.destroy();
map.remove();Change the map engine
Only the adapter constructor changes. Replace MapLibreAdapter with OpenLayersAdapter or LeafletAdapter; see Engines.
Next steps ​
- Draw and Edit cover sessions, commit buttons, and modes.
- Recipes show common UI integrations.
- Optional point symbols add unit symbols to the same map.