Skip to content

Map of communes

Download the hub's geographic reference, list the 553 communes with their population, and place them on a map from the available centroids.

Goal: a page that lists Senegal's communes with their 2023 population and locates them on a map. No API call: the hub's file is enough.

The data

The Geographic reference dataset holds 734 places: 14 regions, 46 departments, 116 arrondissements, 5 cities and 553 communes. Download: lieux.json (also in CSV and XLSX on the dataset page).

Each place has id, level, name, name_source, parent_id, population_2023, lat and lon.

  • Hierarchy and population: ANSD, directory of localities RGPH-5 2023, CC-BY 4.0 licence.
  • Centroids (lat, lon): OCHA COD-AB Senegal via HDX, CC BY-IGO licence (non-official source).

Communes have no centroid

HDX provides centroids down to arrondissements, not for communes. Their lat and lon are empty. This recipe therefore places each commune on its arrondissement's point, or else its department's. The map shows an area, not the commune's exact location.

Prepare the points

A function groups communes under the first ancestor that has a centroid. Put it in prepare.js.

JavaScript
export function prepare(places) {
	const byId = new Map(places.map((p) => [p.id, p]));
	const nameOf = (p) => p.name ?? p.name_source;
	// A commune has no centroid: walk up to the first ancestor that has one.
	const ancestorWithPoint = (p) => {
		for (let a = byId.get(p.parent_id); a; a = byId.get(a.parent_id)) if (a.lat != null) return a;
		return null;
	};
	const points = new Map();
	for (const commune of places.filter((p) => p.level === 'commune')) {
		const ancestor = ancestorWithPoint(commune);
		if (!ancestor) continue;
		const point = points.get(ancestor.id) ?? { name: nameOf(ancestor), level: ancestor.level, lat: ancestor.lat, lon: ancestor.lon, communes: [] };
		point.communes.push({ name: nameOf(commune), population: commune.population_2023 });
		points.set(ancestor.id, point);
	}
	return [...points.values()];
}

In version 1.0.0 of the dataset, the 553 communes spread over 130 points: 85 arrondissements, 40 departments and 5 regions.

The page

  1. Copy lieux.json next to your page. A local copy pins the version you use.

  2. Create map.html:

    HTML
    <!doctype html>
    <html lang="en">
    <head>
    	<meta charset="utf-8" />
    	<meta name="viewport" content="width=device-width, initial-scale=1" />
    	<title>Communes of Senegal</title>
    	<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
    	<style>
    		#map { height: 60vh; }
    	</style>
    </head>
    <body>
    	<h1>Communes of Senegal</h1>
    	<div id="map" role="region" aria-label="Map of communes"></div>
    	<p>Sources: ANSD, RGPH-5 2023, via 221 (CC-BY 4.0). Centroids: OCHA COD-AB via HDX, non-official source (CC BY-IGO).</p>
    	<h2>List</h2>
    	<ul id="list"></ul>
    	<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
    	<script type="module">
    		import { prepare } from './prepare.js';
    
    		const places = await (await fetch('./lieux.json')).json();
    		const points = prepare(places);
    		const fmt = new Intl.NumberFormat('en-GB');
    
    		const map = L.map('map').setView([14.5, -14.5], 7);
    		L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap' }).addTo(map);
    
    		for (const p of points) {
    			const total = p.communes.reduce((s, c) => s + (c.population ?? 0), 0);
    			const popup = document.createElement('p');
    			popup.textContent = `${p.name} (${p.level}): ${p.communes.length} commune(s), ${fmt.format(total)} inhabitants`;
    			L.circleMarker([p.lat, p.lon], { radius: 4 + Math.sqrt(total) / 60 }).bindPopup(popup).addTo(map);
    		}
    
    		// The list stays the reference view: usable by keyboard and screen reader.
    		const list = document.querySelector('#list');
    		points
    			.flatMap((p) => p.communes.map((c) => ({ ...c, area: p.name })))
    			.sort((a, b) => a.name.localeCompare(b.name, 'fr'))
    			.forEach((c) => {
    				const li = document.createElement('li');
    				li.textContent = `${c.name}, ${c.area}, ${fmt.format(c.population ?? 0)} inhabitants`;
    				list.append(li);
    			});
    	</script>
    </body>
    </html>
  3. Serve the folder (for example npx serve .). A file opened directly cannot load lieux.json.

OpenStreetMap tiles have their own terms of use: read the tile usage policy before going to production.

Attribution

The CC-BY 4.0 licence requires citing the source. Use the credit "ANSD, RGPH-5 2023, via 221". For centroids, credit "OCHA, COD-AB Sénégal, via HDX (CC BY-IGO)".

What next?