MapSnippets logoMapSnippets logo

Drawing Tools

Interactive drawing and editing of shapes on the map Each library has its own approach: here's how they compare.

Quick Recommendations

Leaflet — Best when you need a full-featured drawing toolbar with edit and delete controls via the mature Leaflet.draw plugin.

MapLibre GL JS — Ideal when you need lightweight vertex-click polygon drawing without a plugin dependency, using GeoJSON source updates.

OpenLayers — The right choice when you need built-in drawing with multiple geometry types (point, line, polygon, circle) and snapping support via ol.interaction.Draw.

MapTiler SDK — For MapTiler SDK users, this means MapLibre-style click-to-draw polygons plus built-in MapTiler styling.

At a Glance

LeafletMapLibreOpenLayersSDK
Coordinates[lat, lng][lng, lat]fromLonLat([lng, lat])[lng, lat]
RenderingDOM + Canvas (2D)WebGL (GPU, 60fps)Canvas + WebGLWebGL (GPU, 60fps)
Bundle size~42 KB gzipped~250 KB gzipped~290 KB gzipped (tree-shakeable)~280 KB gzipped (includes MapLibre)
Learning curveEasiest (simple API, great tutorials)Moderate (source/layer model, expressions)Steepest (projections, OGC standards, verbose API)Easy (MapLibre API with convenience helpers)
3D supportNo native 3D (2D only)Full: terrain, fill-extrusion, globe, skyLimited: WebGL layers, no native globeFull (same as MapLibre)
Code lines15272438

Code Snippets

Click a library to expand its code. Each snippet shows only the core logic: the scaffold handles HTML boilerplate.

Leaflet15 linesShow code
arrow_forwardFull example
const map = L.map('map').setView([51.5074, -0.1278], 13);
L.tileLayer('https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=YOUR_MAPTILER_KEY', {
  tileSize: 256,
  attribution: '&copy; <a href="https://www.maptiler.com/">MapTiler</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
}).addTo(map);
const drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
const drawControl = new L.Control.Draw({
  edit: { featureGroup: drawnItems },
  draw: { polygon: true, polyline: true, rectangle: true, circle: true, marker: true }
});
map.addControl(drawControl);
map.on(L.Draw.Event.CREATED, (e) => {
  drawnItems.addLayer(e.layer);
});
MapLibre GL JS27 linesShow code
arrow_forwardFull example
const map = new maplibregl.Map({
  container: 'map',
  style: 'https://api.maptiler.com/maps/streets-v4/style.json?key=YOUR_MAPTILER_KEY',
  center: [14.42, 50.08],
  zoom: 13
});
const coords = [];
const geojson = { type: 'Feature', geometry: { type: 'Polygon', coordinates: [[]] } };
map.on('load', () => {
  map.addSource('draw', { type: 'geojson', data: geojson });
  map.addLayer({ id: 'draw-fill', type: 'fill', source: 'draw', paint: { 'fill-color': '#2196F3', 'fill-opacity': 0.2 } });
  map.addLayer({ id: 'draw-line', type: 'line', source: 'draw', paint: { 'line-color': '#2196F3', 'line-width': 2 } });
  map.addSource('vertices', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
  map.addLayer({ id: 'draw-points', type: 'circle', source: 'vertices', paint: { 'circle-radius': 5, 'circle-color': '#fff', 'circle-stroke-width': 2, 'circle-stroke-color': '#2196F3' } });
});
map.on('click', (e) => {
  coords.push([e.lngLat.lng, e.lngLat.lat]);
  const ring = [...coords, coords[0]];
  geojson.geometry.coordinates = [ring];
  map.getSource('draw').setData(JSON.parse(JSON.stringify(geojson)));
  map.getSource('vertices').setData({ type: 'FeatureCollection', features: coords.map(c => ({ type: 'Feature', geometry: { type: 'Point', coordinates: c } })) });
});
document.getElementById('finish-btn').addEventListener('click', () => {
  if (coords.length < 3) return;
  map.off('click');
  map.getCanvas().style.cursor = '';
});
OpenLayers24 linesShow code
arrow_forwardFull example
var source = new ol.source.Vector();
var drawLayer = new ol.layer.Vector({
  source: source,
  style: new ol.style.Style({
    fill: new ol.style.Fill({ color: 'rgba(230, 57, 70, 0.2)' }),
    stroke: new ol.style.Stroke({ color: '#e63946', width: 2 }),
    image: new ol.style.Circle({ radius: 6, fill: new ol.style.Fill({ color: '#e63946' }) })
  })
});
var map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({ source: new ol.source.XYZ({ url: 'https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=YOUR_MAPTILER_KEY', attributions: '&copy; <a href="https://www.maptiler.com/copyright/">MapTiler</a>' }) }),
    drawLayer
  ],
  view: new ol.View({ center: ol.proj.fromLonLat([2.3522, 48.8566]), zoom: 13 })
});
var draw = new ol.interaction.Draw({ source: source, type: 'Polygon' });
map.addInteraction(draw);
draw.on('drawend', function(evt) {
  var geom = evt.feature.getGeometry();
  var area = ol.sphere.getArea(geom);
  console.log('Area: ' + (area / 1e6).toFixed(2) + ' km²');
});
MapTiler SDK38 linesShow code
arrow_forwardFull example
maptilersdk.config.apiKey = 'YOUR_MAPTILER_KEY';
const map = new maptilersdk.Map({
  container: 'map',
  style: maptilersdk.MapStyle.STREETS,
  center: [14.42, 50.08],
  zoom: 13
});
const coords = [];
const geojson = { type: 'Feature', geometry: { type: 'Polygon', coordinates: [[]] } };
function clickHandler(e) {
  coords.push([e.lngLat.lng, e.lngLat.lat]);
  var ring = coords.concat([coords[0]]);
  geojson.geometry.coordinates = [ring];
  map.getSource('draw').setData(JSON.parse(JSON.stringify(geojson)));
  map.getSource('vertices').setData({ type: 'FeatureCollection', features: coords.map(function(c) { return { type: 'Feature', geometry: { type: 'Point', coordinates: c } }; }) });
}
map.on('load', function() {
  map.addSource('draw', { type: 'geojson', data: geojson });
  map.addLayer({ id: 'draw-fill', type: 'fill', source: 'draw', paint: { 'fill-color': '#9C27B0', 'fill-opacity': 0.2 } });
  map.addLayer({ id: 'draw-line', type: 'line', source: 'draw', paint: { 'line-color': '#9C27B0', 'line-width': 2 } });
  map.addSource('vertices', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
  map.addLayer({ id: 'draw-points', type: 'circle', source: 'vertices', paint: { 'circle-radius': 5, 'circle-color': '#fff', 'circle-stroke-width': 2, 'circle-stroke-color': '#9C27B0' } });
  map.on('click', clickHandler);
});
document.getElementById('finish-btn').addEventListener('click', function() {
  if (coords.length < 3) return;
  map.off('click', clickHandler);
  map.getCanvas().style.cursor = '';
});
document.getElementById('clear-btn').addEventListener('click', function() {
  coords.length = 0;
  geojson.geometry.coordinates = [[]];
  map.getSource('draw').setData(JSON.parse(JSON.stringify(geojson)));
  map.getSource('vertices').setData({ type: 'FeatureCollection', features: [] });
  map.off('click', clickHandler);
  map.on('click', clickHandler);
  map.getCanvas().style.cursor = 'crosshair';
});

Things to Know

Implementation-specific details that matter for drawing tools.

Leaflet

Requires the external Leaflet.draw plugin which provides toolbar UI, edit mode, and delete controls out of the box.

MapLibre GL JS

No built-in draw interaction: the example implements click-to-place-vertex drawing by updating a GeoJSON source manually.

OpenLayers

ol.interaction.Draw is built-in with support for Point, LineString, Polygon, Circle, and freehand drawing modes.

MapTiler SDK

Same manual vertex-click approach as MapLibre since SDK inherits MapLibre's interaction model; no built-in draw plugin.

Which Library to Use?

auto_awesome

Build Maps with AI

Generate code like these examples using mapping library skills in your IDE.