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> < 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> > 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'))
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 →