🚧 These docs are a work in progress — content is incomplete and may change without notice.
Skip to content

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.

Loading 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 ​

sh
pnpm add @orbat-mapper/tactical-draw @orbat-mapper/tactical-draw-adapter-maplibre @orbat-mapper/control-measures maplibre-gl
sh
npm install @orbat-mapper/tactical-draw @orbat-mapper/tactical-draw-adapter-maplibre @orbat-mapper/control-measures maplibre-gl

2. Create TacticalDraw ​

Once your MapLibre map has loaded, wrap it in MapLibreAdapter and create one TacticalDraw instance:

ts
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:

ts
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:

ts
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:

ts
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 ​