Skip to content

MapLibre GL — Interactive Web Maps

Build interactive, zoomable web maps with just HTML and JavaScript — no server needed, runs in any browser.


Library
MapLibre GL JS
Website
Cost
🟢 100% Free and Open Source
Skill Level
🔵 Basic HTML + JavaScript
Output
Web page with interactive map (HTML file)
Based On
Open-source fork of Mapbox GL JS

Why MapLibre?

  • No API key needed (unlike Google Maps or Mapbox)
  • Works with any tile server — OSM, Bhuvan, CARTO
  • Supports GeoJSON data natively
  • GPU-accelerated rendering — very fast
  • Used by QGIS2Web plugin when exporting MapLibre maps

Complete Working Example: India District Literacy Map

Save this as literacy_map.html and open it in any browser:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>India District Literacy Map</title>

  <!-- MapLibre GL JS from CDN -->
  <link href="https://unpkg.com/maplibre-gl@4.1.2/dist/maplibre-gl.css" rel="stylesheet">
  <script src="https://unpkg.com/maplibre-gl@4.1.2/dist/maplibre-gl.js"></script>

  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: 'Segoe UI', sans-serif; background: #1a1a2e; color: white; }
    #header {
      padding: 12px 20px;
      background: linear-gradient(135deg, #ff6b35, #f7931e);
      display: flex; align-items: center; gap: 12px;
    }
    #header h1 { font-size: 1.2rem; }
    #map { width: 100%; height: calc(100vh - 52px); }
    #legend {
      position: absolute; bottom: 30px; right: 10px;
      background: rgba(0,0,0,0.8); border-radius: 8px;
      padding: 12px 16px; min-width: 160px;
    }
    #legend h4 { font-size: 0.8rem; margin-bottom: 8px; color: #aaa; }
    .legend-item { display: flex; align-items: center; gap: 8px; margin: 4px 0; font-size: 0.8rem; }
    .legend-color { width: 20px; height: 14px; border-radius: 2px; }
    #info {
      position: absolute; top: 70px; left: 10px;
      background: rgba(0,0,0,0.8); border-radius: 8px;
      padding: 10px 14px; font-size: 0.8rem; max-width: 220px;
      pointer-events: none;
    }
  </style>
</head>
<body>

<div id="header">
  <span style="font-size:1.5rem">🗺️</span>
  <h1>India District Literacy Map — Census 2011</h1>
</div>

<div id="map"></div>

<div id="info">
  <strong>Hover over a district</strong><br>
  <span id="district-info" style="color:#aaa">to see details</span>
</div>

<div id="legend">
  <h4>Literacy Rate (%)</h4>
  <div class="legend-item"><div class="legend-color" style="background:#d73027"></div> &lt; 50%</div>
  <div class="legend-item"><div class="legend-color" style="background:#fc8d59"></div> 50–60%</div>
  <div class="legend-item"><div class="legend-color" style="background:#fee090"></div> 60–70%</div>
  <div class="legend-item"><div class="legend-color" style="background:#e0f3f8"></div> 70–80%</div>
  <div class="legend-item"><div class="legend-color" style="background:#91bfdb"></div> 80–90%</div>
  <div class="legend-item"><div class="legend-color" style="background:#4575b4"></div> &gt; 90%</div>
</div>

<script>
  // Initialize MapLibre map
  const map = new maplibregl.Map({
    container: 'map',
    style: {
      version: 8,
      sources: {
        'osm': {
          type: 'raster',
          tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
          tileSize: 256,
          attribution: '© OpenStreetMap contributors'
        }
      },
      layers: [{ id: 'osm-tiles', type: 'raster', source: 'osm' }]
    },
    center: [82, 22],  // Centre of India
    zoom: 4.5
  });

  map.addControl(new maplibregl.NavigationControl(), 'top-right');

  map.on('load', function() {

    // Load GADM India districts GeoJSON
    // Download gadm41_IND_2.json and place in the same folder as this HTML file
    // OR replace with any public GeoJSON URL

    fetch('gadm41_IND_2.json')
      .then(r => r.json())
      .then(geojson => {

        // Add literacy rate as a property (in real use, join your CSV data here)
        // This example uses a placeholder random value — replace with real data
        geojson.features.forEach(f => {
          if (!f.properties.literacy_rate) {
            // Placeholder — replace with real Census data join
            f.properties.literacy_rate = Math.random() * 50 + 40;
          }
        });

        // Add GeoJSON source
        map.addSource('districts', { type: 'geojson', data: geojson });

        // Add fill layer — colour by literacy rate
        map.addLayer({
          id: 'districts-fill',
          type: 'fill',
          source: 'districts',
          paint: {
            'fill-color': [
              'step', ['get', 'literacy_rate'],
              '#d73027',   // < 50
              50, '#fc8d59',
              60, '#fee090',
              70, '#e0f3f8',
              80, '#91bfdb',
              90, '#4575b4'  // > 90
            ],
            'fill-opacity': 0.75
          }
        });

        // Add outline
        map.addLayer({
          id: 'districts-outline',
          type: 'line',
          source: 'districts',
          paint: { 'line-color': '#ffffff', 'line-width': 0.4 }
        });

        // Hover popup
        map.on('mousemove', 'districts-fill', (e) => {
          const props = e.features[0].properties;
          const info = document.getElementById('district-info');
          info.innerHTML = `
            <strong style="color:white">${props.NAME_2}</strong><br>
            State: ${props.NAME_1}<br>
            Literacy: <strong style="color:#f7931e">${props.literacy_rate ? props.literacy_rate.toFixed(1) + '%' : 'N/A'}</strong>
          `;
          map.getCanvas().style.cursor = 'pointer';
        });

        map.on('mouseleave', 'districts-fill', () => {
          document.getElementById('district-info').innerHTML = 'Hover over a district to see details';
          map.getCanvas().style.cursor = '';
        });
      })
      .catch(err => console.error('Could not load GeoJSON:', err));
  });
</script>
</body>
</html>

How to Join Your Real Data

To use real Census data instead of the placeholder random values, add this before the addSource call:

// 1. In your Python/Excel: export a JSON lookup table:
//    {"PUNE": 86.2, "NASHIK": 79.8, ...}
// 2. Load it in JavaScript:

fetch('district_literacy.json')
  .then(r => r.json())
  .then(literacyData => {
    geojson.features.forEach(f => {
      const distName = f.properties.NAME_2.toUpperCase();
      f.properties.literacy_rate = literacyData[distName] || null;
    });
    // Then add source and layers as above...
  });

✏️ Practice Exercise

Exercise 7.3 — Build a Web Map of Your State

Goal: Create an HTML map showing your state's districts with a real indicator.

Steps: 1. Download GADM Level 2 GeoJSON from gadm.org 2. Download Census literacy data from data.gov.in 3. In Python, create a lookup JSON:

import pandas as pd, json
census = pd.read_csv("census_district_literacy_2011.csv")
lookup = dict(zip(census['District'].str.upper(), census['Literacy_Rate']))
json.dump(lookup, open('district_literacy.json', 'w'))
4. Copy the HTML template above → Save as my_map.html 5. Place gadm41_IND_2.json and district_literacy.json in the same folder 6. Replace the placeholder code with the real data join code above 7. Open my_map.html in your browser — share it with anyone!

  • Can you zoom in to your district?
  • Does hovering show the correct literacy rate?
  • Try changing the color scheme — what palette works best?

You've completed Chapter 7! → Back to Home →