Web Mapping with Leaflet and MapLibre GL JS: Developer Guide

2026-02-204 min read
LeafletMapLibreJavaScriptweb maps

A web map is a key component of any location-based application: delivery, real estate, tourism, logistics, analytics. The two leading open-source solutions are Leaflet and MapLibre GL JS. Both work with OpenStreetMap but differ fundamentally in architecture.

Leaflet: Web Mapping Classic

Leaflet is a lightweight JavaScript library (42 KB gzipped). It works with raster tiles: the server slices the map into 256x256 pixel PNG tiles, and the browser assembles the mosaic.

<div id="map" style="height: 400px;"></div>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script>
const map = L.map('map').setView([55.7558, 37.6176], 13);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '&copy; OpenStreetMap contributors'
}).addTo(map);
</script>

Leaflet strengths: - Simplicity — 10 lines of code for a basic map. - Ecosystem — 700+ plugins (clustering, drawing, geocoding). - Compatibility — works on any browser, including IE11. - Size — 42 KB, doesn't bloat the page.

Limitations: - 2D only (no tilt or rotation). - Raster tiles — style is fixed on the server. - Performance drops with >10,000 markers.

MapLibre GL JS: Vector Rendering

MapLibre GL JS is an open-source fork of Mapbox GL JS. It renders vector tiles via WebGL directly in the browser. Styles are applied on the client — you can change colors, fonts, and layer visibility without regenerating tiles.

<div id="map" style="height: 400px;"></div>
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" />
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
<script>
const map = new maplibregl.Map({
  container: 'map',
  style: 'https://demotiles.maplibre.org/style.json',
  center: [37.6176, 55.7558],
  zoom: 13,
  pitch: 45  // camera tilt
});
</script>

MapLibre advantages: - 3D effects — tilt, rotation, building extrusion. - Dynamic styles — change map appearance without a server. - Performance — WebGL renders millions of objects. - Vector tiles — less traffic than raster PNGs.

Limitations: - Requires WebGL (doesn't work in old browsers). - More complex setup — JSON style specification. - Larger library size (~200 KB gzipped).

GeoJSON Overlays

Both libraries work excellently with GeoJSON for displaying custom data on top of the base map.

The osm2cdr export API is async: POST /api/render returns a task_id, then poll GET /api/status/{task_id} until status === "completed" and download the file from result_url.

Leaflet + GeoJSON

async function loadGeoJSON(bbox) {
  // 1. Create the export task
  const res = await fetch('https://osm2cdr.ru/api/render', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({format: 'geojson', bbox})
  });
  const {task_id} = await res.json();

  // 2. Poll until completion, return GeoJSON
  while (true) {
    const s = await fetch(`https://osm2cdr.ru/api/status/${task_id}`).then(r => r.json());
    if (s.status === 'completed') return fetch(s.result_url).then(r => r.json());
    if (s.status === 'failed') throw new Error(s.error || 'export failed');
    await new Promise(r => setTimeout(r, 1000));
  }
}

loadGeoJSON([37.5, 55.7, 37.8, 55.8]).then(geojson => {
  L.geoJSON(geojson, {
    style: feature => ({
      color: feature.properties.highway ? '#ff6600' : '#3388ff',
      weight: feature.properties.highway ? 3 : 1,
      fillOpacity: 0.3
    }),
    onEachFeature: (feature, layer) => {
      if (feature.properties.name) {
        layer.bindPopup(feature.properties.name);
      }
    }
  }).addTo(map);
});

MapLibre + GeoJSON

loadGeoJSON([37.5, 55.7, 37.8, 55.8]).then(data => {
  map.on('load', () => {
    map.addSource('osm-data', {type: 'geojson', data});

    map.addLayer({
      id: 'buildings-fill',
      type: 'fill',
      source: 'osm-data',
      filter: ['has', 'building'],
      paint: {
        'fill-color': '#3388ff',
        'fill-opacity': 0.4
      }
    });

    map.addLayer({
      id: 'buildings-3d',
      type: 'fill-extrusion',
      source: 'osm-data',
      filter: ['has', 'building'],
      paint: {
        'fill-extrusion-color': '#3388ff',
        'fill-extrusion-height': ['get', 'height'],
        'fill-extrusion-opacity': 0.6
      }
    });
  });
});

Which Library to Choose

Criterion Leaflet MapLibre GL JS
Learning curve Simple Medium
3D No Yes
Custom styles No (tiles only) Yes (JSON styles)
Size 42 KB ~200 KB
IE11 Yes No
100K+ objects Slow Fast (WebGL)
Offline (PMTiles) Via plugin Native
Best for Simple maps, widgets Complex apps, analytics

Data Sources

Export OpenStreetMap data on osm2cdr.ru in the right format:

  • GeoJSON — for overlays (markers, polygons, lines).
  • PMTiles — for base map without a tile server.
  • SVG — for static maps in UI.
  • Tiles — for classic raster tiles.

Start with GeoJSON for prototyping, then switch to PMTiles for production. Export data at osm2cdr.ru.

← All articles