Geovisualisation
La géovisualisation prolonge la cartographie thématique en tirant parti des possibilités offertes par le numérique. Comme la cartographie classique, elle vise à représenter et à interpréter des phénomènes géographiques à partir de données, mais elle y ajoute l’interactivité. Grâce aux technologies web, il devient possible d’explorer les données à différentes échelles, de modifier les variables représentées en temps réel, ou encore de croiser cartes et graphiques. La géovisualisation transforme ainsi la carte en un outil d’exploration et d’analyse autant qu’en un support de communication, renouvelant profondément les pratiques de la cartographie thématique à l’ère du numérique.
1 - Avec Leaflet
Tout d’abord, revoyons comment on afficher un geoJSON
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<div id="map"></div>
#map {
height: 500px;
}
// Modules
import { json } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
const d3 = { json };
// Fonction de carto
async function main() {
const data = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/paris.geojson",
;
)const map = L.map("map");
const layer = L.geoJSON(data).addTo(map);
.fitBounds(layer.getBounds());
map
}// Appel de la fonction
main();
Avec le parametre style
, on peut completement configurer la carte. Par exemple, on peut modifier le code précédent en ajoutant :
const style = {
fillColor: "red",
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
}
et
const layer = L.geoJSON(data, {style}).addTo(map);
1 -Carte choroplethe
Avec leaflet, pour réaliser une carte choroplethe, on a donc besoin d’une foncton qui va affecter une couleur en fonction d’une donnée.
Par exemple :
// Modules
import { json } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
const d3 = { json };
// Fonction de carto
async function main() {
const data = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/paris.geojson",
;
)console.log(data.features.map((d) => d.properties));
const map = L.map("map");
const layer = L.geoJSON(data, { style }).addTo(map);
.fitBounds(layer.getBounds());
map
}// Appel de la fonction
main();
// Helpers
function getColor(value) {
return value > 200000
? "#7a0177"
: value > 150000
? "#c51b8a"
: value > 100000
? "#f768a1"
: value > 50000
? "#fa9fb5"
: value > 25000
? "#fcc5c0"
: "#feebe2";
}
function style(feature) {
return {
fillColor: getColor(feature.properties.population), // ou autre attribut
weight: 1,
color: "white",
fillOpacity: 1,
;
} }
NB : Avec des bibliothèques comme statsbreaks
ou dicopal
, on peut evidemment améliorer la fonction de discrétisation.
On peut aussi utiliser le plugin leaflet-choropleth
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://timwis.com/leaflet-choropleth/dist/choropleth.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<div id="map"></div>
import { json } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
const d3 = { json };
async function main() {
const data = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/paris.geojson",
;
)console.log(data.features.map((d) => d.properties));
// Création de la carte
const map = L.map("map");
// Ajout d’un fond
.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
Lattribution: "© OpenStreetMap contributors",
.addTo(map);
})
console.log(data);
// Création du choroplèthe
const choropleth = L.choropleth(data, {
valueProperty: "population", // attribut existant
scale: ["#f0f9e8", "#08589e"], // gamme de couleurs
steps: 6,
mode: "q", // q = quantiles
style: {
color: "#fff",
weight: 1,
fillOpacity: 0.8,
,
}onEachFeature: function (feature, layer) {
.bindPopup(
layer`<strong>${feature.properties.l_ar}</strong><br>Population: ${feature.properties.population}`,
;
),
}.addTo(map);
})
// Ajuste le zoom sur la couche
.fitBounds(choropleth.getBounds());
map
}
main();
2 - Cercles proportionnels
import { json } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
const d3 = { json };
async function main() {
// Chargement du GeoJSON
const data = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/paris.geojson",
;
)
// Création de la carte
const map = L.map("map");
.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
Lattribution: "© OpenStreetMap contributors",
.addTo(map);
})
// Ajoute les polygones de base (contours)
const arrLayer = L.geoJSON(data, {
style: {
color: "white",
weight: 1,
fillColor: "#ccc",
fillOpacity: 0.2,
,
}.addTo(map);
})
.fitBounds(arrLayer.getBounds());
map
// Fonction pour convertir la population en rayon (pixels)
function getRadius(pop) {
return Math.sqrt(pop) / 30; // ajuster le diviseur selon l’échelle
}
// Boucle sur chaque arrondissement
.features.forEach((f) => {
dataconst centroid = turf.centroid(f);
const [lon, lat] = centroid.geometry.coordinates;
const pop = f.properties.population;
// Ajoute un cercle proportionnel
.circleMarker([lat, lon], {
Lradius: getRadius(pop),
color: "#e41a1c",
fillColor: "#e41a1c",
fillOpacity: 0.6,
weight: 1,
}).bindPopup(`<strong>${f.properties.l_ar}</strong><br>Population : ${pop}`)
.addTo(map);
;
})
}
main();
Avec Leaflet, on peut faire beaucpup de choses. Et il existe beaucoup d’exemples en ligne. Par contre, ca necessite de savoir coder. Il n’y a pas de types de cartes thématiques clé en main à Et c’est assez verbeux. Heureusement, il existe d’autres solutions.
2 - Avec geoviz
geoviz
est une bibliothèque JavaScript (en développement) permettant de concevoir des cartes thématiques. Elle fournit un ensemble de fonctions compatibles avec d3
. Conçue pour être intuitive et concise, elle permet de gérer différentes couches géographiques (points, lignes, polygones), différents modes de représentations (symboles proportionnels, choroplethe, typologies, symboles, etc) et différentes couches d’habillage (echelle, nord, tuiles, graticule, etc) afin de créer de jolies cartes. Un des principaux avantage est agalement de pouvoir gérer les systèmes de projection. En résumé, les cartes conçues avec geoviz sont :
🌳 Thématiques ✒️ Vectoriales 👆 Interactives ♻️ Interopérables 🔎 Zoomables
- Le code est ici : https://github.com/riatelab/geoviz
- La documentation est là : https://riatelab.github.io/geoviz/
- Et on trouvera ici de nombreux exemples : https://observablehq.com/@neocartocnrs/geoviz
On installe la bibliothèque de la façon suivante :
<script src="https://cdn.jsdelivr.net/npm/geoviz@0.8.2" charset="utf-8"></script>
ou
import * as geoviz from "https://cdn.jsdelivr.net/npm/geoviz@0.8.2/+esm";
1 - Principes
Et voici une premiere carte :
<div id = "map"></div>
// Les modules
import * as geoviz from "https://cdn.jsdelivr.net/npm/geoviz@0.8.2/+esm";
import { json } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
const d3 = { json };
// La fonction de carto
async function drawMap() {
// Chargement du GeoJSON
const data = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/paris.geojson",
;
)// Création de la carte
let svg = geoviz.create({ domain: data });
// Ajout d'une couche
.path({ data });
svg// Return dans le div
document.querySelector("#map").append(svg.render());
}drawMap();
Dans la fonction create'()
, on peut definir le domaine d’affichage (domain
), la taille de la carte (width
), les marges (margin
) et la couleur de fond (background
). Il est également possible de définir la projection (projection
). Pour comprendre comment fonctionnent les projections, vous pouvez regarder ici.
Voici un exemple :
// Les modules
import * as geoviz from "https://cdn.jsdelivr.net/npm/geoviz@0.8.2/+esm";
import { json } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
const d3 = { json };
// La fonction de carto
async function drawMap() {
// Chargement du GeoJSON
const data = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/world_209.geojson",
;
)// Création de la carte
let svg = geoviz.create({ projection: "NaturalEarth1", width: 500 });
// Ajout d'une couche
.path({ data });
svg// Return dans le div
document.querySelector("#map").append(svg.render());
}drawMap();
Dans la fonction svg.path()
, on peut aussi changer le style de la couche grace aux parametres SVG. Par exemple :
.path,({ data, fill:"#CCC", fillOpacity:0.7, stroke:"white" }); svg
- Essayez d’autres projections. Par exemple
"Mercator"
,"Bertin1953"
,"Spilhaus"
"Polar"
- Mettez une couleur de fond
- Changez le style de la couche pays
- Rendez la carte zoomable en mettant dans la fonction
create()
:zoomable:true
2 - Couches d’habillage
Avec geoviz
, on peut supproposer différentes couches comme dans un SIG. Certaines sont prêtes à l’emploi. Comme :
.outline() // La forme de la Terre
svg.graticule() // Les lignes de latitude et longitude
svg.earth() // Fond de carte d'habillage Natural Earth (⚠️ fonction asynchrone. Utiliser await) svg
On peut également ajouter des couches d’habillage
.header() // Titre de la carte
svg.footer() // Sources
svg.north() // Flèche nord
svg.scalebar() // Barre d'échelle svg
Reportez-vous à la documentation pour connaitre les paramètres (https://riatelab.github.io/geoviz/global.html).
Ainsi, il est possible de réaliser rapidement une jolie carte d’édition.
let svg = geoviz.create({ projection: "Dodecahedral", width: 600 });
.header({ text: "Où est le Nord ?" });
svg.outline({ fill: "#38896F" });
svg.graticule({ step: 20, stroke: "white" });
svg.path({ datum: data, fill: "white", fillOpacity: 0.3 });
svg.footer({ text: "Source: Nicolas Lambert, 2025" });
svgdocument.querySelector("#map").append(svg.render());
Essayez de créer une jolie carte sur Paris en changeant les différents paramètres.
// Les modules
import * as geoviz from "https://cdn.jsdelivr.net/npm/geoviz@0.8.2/+esm";
import { json } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
const d3 = { json };
async function drawMap() {
// Chargement du GeoJSON
const data = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/paris.geojson",
;
)// A vous de jouer
//...
}drawMap();
3 - Interactivité
Avec geoviz
, il est possible de rendre la carte zoomable en activant le paramètre zoomable:true
dans la fcontion create()
. Avec la couche geoviz.tile()
, il est possible de d’ajouter des tuiles OpenStreetMap (⚠️ on bascule automatiquement sur la projection de Meractor).
async function drawMap() {
// Chargement du GeoJSON
const data = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/paris.geojson",
;
)let svg = geoviz.create({
domain:data,
margin:20,
projection: "Mercator",
zoomable: true,
width: 400,
;
}).tile();
svg.path({ datum: data, fill: "none", stroke: "red", fillOpacity: 0.3 });
svgdocument.querySelector("#map").append(svg.render());
}drawMap();
1 - Sur la carte du Monde, faites une carte avec la projection Orthographic
et le paramètre zoomable:true
.
2 - Essayez avec zoomable:"versor"
3 - Changez la projection. Par exemple : “NaturalEarth1”
Que constatez-vous ?
Avec le paramètre tip:true
, il est également possible de créer des infobules sur n’importe quelle couche.
Essayez par exemple :
.path(data, tip:true) svg
Vous pouvez aussi ecrire tip:"$name"
pour afficher la valeur du champ name.
Ou bien, écrire une expression plus complexes avec les backticks, comme :
tip : `This country is $name
It is located in $region
Its population is $pop`
4 - Symbologie
Dans geoviz
, il y a une fonction couteau suisse qui s’appelle la fonction plot
. Elle permet de réaliser toutes les cartes produites précédement. Par exemple :
let svg = geoviz.create({ projection: "Dodecahedral", width: 600 });
.plot({ type: "header", text: "Où est le Nord ?" });
svg.plot({ type: "outline", fill: "#38896F" });
svg.plot({ type: "graticule", step: 20, stroke: "white" });
svg.plot({ type: "path", datum: data, fill: "white", fillOpacity: 0.3 });
svg.plot({ type: "footer", text: "Source: Nicolas Lambert, 2025" });
svgdocument.querySelector("#map").append(svg.render());
La fonction plot contient également des types pour la carto thématique.
type: "prop"
: Carte par symboles proportionnels. https://riatelab.github.io/geoviz/global.html#plot/proptype: "choro"
: Carte choroplèthe. https://riatelab.github.io/geoviz/global.html#plot/chorotype: "typo"
: Carte de typologie. https://riatelab.github.io/geoviz/global.html#plot/typotype: "propchoro"
: Carte avec des symboles proportionnels coloriés (quanti). https://riatelab.github.io/geoviz/global.html#plot/propchorotype: "proptypo"
: Carte avec des symboles proportionnels coloriés (quali). https://riatelab.github.io/geoviz/global.html#plot/proptypo
Pour commencer une carte statistique, quel que soit le logiciel utilisé, il est souvent necessaire de réaliser une jointure entre le fond de carte et les données attributaires. Cette opération est assez compliquée à réaliser en pure JavaScript. Heureusement, geoviz propose une fonction pour cela.
Voir un exemple ici : https://observablehq.com/@neocartocnrs/handle-geometries
// Les modules
import * as geoviz from "https://cdn.jsdelivr.net/npm/geoviz@0.8.2/+esm";
import { json, csv } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
const d3 = { json, csv };
// La fonction de carto
async function drawMap() {
// Chargement du GeoJSON
const countries = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/world_209.geojson",
;
)
// Chargement des données statistiques
const stats = await d3.csv(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/stats_countries.csv",
;
)// Jointure
const data = geoviz.tool.merge({
geom: countries, // fond de carte
geom_id: "ISO3", // identifiant dans le fond de carte
data: stats, // tableau de données
data_id: "id", // identifiant dans le tableau de données
.featureCollection;
})
// Creation de la carte
//...
}drawMap();
Nous pouvons maintenant réaliser des cartes thématiques
4.2 - Symbole sproportionnelsLe type prop
permet de réaliser des cartes par symboles proportionnels.
.plot({ type: "prop", data: data, var: "pop" }); svg
Les symboles sont configurable. Essayez d’ajouter les parametres k:10
ou symbol:"square"
. Essayez aussi dodge: true
On peut aussi configurer la légende avec les paramètres leg_*
. Par exemple : leg_pos
ou leg_title
.plot({ type: "prop", data: data, var: "pop", leg_pos:[20,200], leg_title:"Population, 2020" }); svg
Le type choro
permet de réaliser des cartes thématiques. Il permet facilement de choisir une méthode de discrétisation et une palete de couleur. Par défaut la discrétisation choisie est la méthode des quantiles.
.plot({ type: "choro", data: data, var: "gdppc" }); svg
Avec l’attribut method
, on peut changer la méthode de discrétisation : ‘quantile’, ‘q6’, ‘equal’, ‘jenks’, ‘msd’, ‘geometric’, ‘headtail’, ‘pretty’, ‘arithmetic’ or ‘nestedmeans’
Avec l’attribut colors
, on peut changer de palette de couleurs : “ArmyRose_7”, “Earth_7”, “Fall_7”, “Geyser_7”, “TealRose_7”, “Temps_7”, “Tropic_7”, “BluGrn_7”, “BluYl_7”, “BrwnYl_7”, “BurgYl_7”, “Burg_7”, “DarkMint_7”, “Emrld_7”, “Magenta_7”, “Mint_7”, “OrYel_7”, “Peach_7”, “PinkYl_7”, “PurpOr_7”… Voir dicopal
.plot({
svgtype: "choro",
data: data,
var: "gdppc",
method: "jenks",
nb: 4,
colors: "PinkYl",
; })
Avec type: "propchoro"
, on peut facilement effectuer des combinaisons.
.plot({
svgtype: "propchoro",
data: data,
var1: "pop",
var2: "gdppc",
k:30,
method: "headtail",
nb: 6,
colors: "Reds",
leg1_title:"Population",
leg2_title:"Richesse"
; })
Le principe est le même avec les autres types. Se référer à la documentation :https://riatelab.github.io/geoviz
4.5 - Petit exemple de cartographie interactiveEn remobilisant ce qu’on a appris jusqu’ici, on peut essayer de créer une carte interactive avec un slider qui permet de faire varier la taille des cercles.
<div>
<input type="range" id="size" name="volume" min="10" max="100" value="50" />
<label for="size" id="labelsize">Radius max (50)</label>
</div>
<div id = "map"></div>
// Les modules
import * as geoviz from "https://cdn.jsdelivr.net/npm/geoviz@0.8.2/+esm";
import { json, csv } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
const d3 = { json, csv };
// La fonction de carto
async function drawMap() {
// Chargement du GeoJSON
const countries = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/world_209.geojson",
;
)
// Chargement des données statistiques
const stats = await d3.csv(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/stats_countries.csv",
;
)
// On identifie le slider
const slider = document.getElementById("size");
// On crée une fonction qui dessine les cercles
function drawCircles() {
return svg.plot({
id: "bubbles", // <- Il faut définir un identifiant. TryEssayez de le supprimer....
type: "prop",
symbol: "circle",
data: data,
var: "pop",
fill: "#F13C47",
k: +slider.value,
leg_title: "Population",
;
})
}// Jointure
const data = geoviz.tool.merge({
geom: countries,
geom_id: "ISO3",
data: stats,
data_id: "id",
.featureCollection;
})
// Creation de la carte
let svg = geoviz.create({ projection: "NaturalEarth1", width: 600 });
.path({ data: countries, fill: "#CCC" });
svgdrawCircles();
document.querySelector("#map").append(svg.render());
// Interactivité
.addEventListener("input", function () {
sliderdocument.getElementById("labelsize").innerHTML =
`Radius max (${slider.value})`;
drawCircles();
;
})
}
// Et voilà !
drawMap();
- Créez une carte thématique sur Paris (conseil : utilisez la projection de Mercator.)
- Vous avez à disposition la variable
"population"
(quanti rel) et la variable"density"
(quanti abs). - Ajoutez des couches d’habillage
- Essayez de la rendre interactive (
zoomable
,tip
, etc.)
<div id = "map"></div>
// Modules
import { json } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
const d3 = { json };
// Fonction de carto
async function main() {
const paris = await d3.json(
"https://raw.githubusercontent.com/neocarto/Webmapping/refs/heads/main/data/paris.geojson",
;
)// Créez votre carte ici
// ...
}
// Appel de la fonction
main();