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);
  map.fitBounds(layer.getBounds());
}
// 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);
  map.fitBounds(layer.getBounds());
}
// 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
  L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
    attribution: "© 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) {
      layer.bindPopup(
        `<strong>${feature.properties.l_ar}</strong><br>Population: ${feature.properties.population}`,
      );
    },
  }).addTo(map);

  // Ajuste le zoom sur la couche
  map.fitBounds(choropleth.getBounds());
}

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");
  L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
    attribution: "© 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);

  map.fitBounds(arrLayer.getBounds());

  // 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
  data.features.forEach((f) => {
    const centroid = turf.centroid(f);
    const [lon, lat] = centroid.geometry.coordinates;
    const pop = f.properties.population;

    // Ajoute un cercle proportionnel
    L.circleMarker([lat, lon], {
      radius: 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

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
  svg.path({ data });
  // 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
  svg.path({ data });
  // 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 :

svg.path,({ data, fill:"#CCC", fillOpacity:0.7, stroke:"white" });
Exercice
  • 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 :

svg.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)

On peut également ajouter des couches d’habillage

svg.header() // Titre de la carte
svg.footer() // Sources
svg.north() // Flèche nord
svg.scalebar() // Barre d'échelle

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 });
svg.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" });
document.querySelector("#map").append(svg.render());
Exercice

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,
  });
  svg.tile();
  svg.path({ datum: data, fill: "none", stroke: "red", fillOpacity: 0.3 });
  document.querySelector("#map").append(svg.render());
}
drawMap();
Exercice

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 :

svg.path(data, tip:true)

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 });
svg.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" });
document.querySelector("#map").append(svg.render());

La fonction plot contient également des types pour la carto thématique.

4.1 - La jointure

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 sproportionnels

Le type prop permet de réaliser des cartes par symboles proportionnels.

svg.plot({ type: "prop", data: data, var: "pop" });

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

svg.plot({ type: "prop", data: data, var: "pop", leg_pos:[20,200], leg_title:"Population, 2020" });
4.3 - Carte choroplethe

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.

svg.plot({ type: "choro", data: data, var: "gdppc" });

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

  svg.plot({
    type: "choro",
    data: data,
    var: "gdppc",
    method: "jenks",
    nb: 4,
    colors: "PinkYl",
  });
4.4 - Combinaissons

Avec type: "propchoro", on peut facilement effectuer des combinaisons.

  svg.plot({
    type: "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 interactive

En 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 });
  svg.path({ data: countries, fill: "#CCC" });
  drawCircles();
  document.querySelector("#map").append(svg.render());

  // Interactivité
  slider.addEventListener("input", function () {
    document.getElementById("labelsize").innerHTML =
      `Radius max (${slider.value})`;
    drawCircles();
  });
}

// Et voilà !
drawMap();
Exercice
  • 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();