Une heatmap transforme un nuage de coordonnées en une surface de densité lisible immédiatement. Les couleurs chaudes signalent les zones où les événements se concentrent, les couleurs froides celles où ils se raréfient. L'utilisateur saisit la tendance avant même de lire une étiquette. C'est pour cela que les heatmaps sont le choix par défaut pour analyser la fréquentation piétonne, prévoir la demande de livraison, signaler la criminalité ou étudier les prix de l'immobilier.
Ce tutoriel construit une heatmap interactive de A à Z avec le SDK MapAtlas. Vous structurerez des données de points en GeoJSON pondéré, afficherez une couche de densité avec un dégradé de couleur personnalisé, ajouterez un curseur d'intensité et terminerez par un exemple concret de demande de livraison. À la fin, vous disposerez d'un modèle réutilisable à intégrer dans n'importe quel projet JavaScript ou React.
Si vous découvrez le SDK MapAtlas, lisez d'abord Comment ajouter des cartes interactives à votre site web. Cet article couvre l'installation, l'initialisation de la carte et les marqueurs. Ce tutoriel reprend là où il s'arrête.
Quand utiliser une heatmap
La heatmap est le bon outil quand vous disposez d'un grand nombre de localisations ponctuelles et que vous voulez montrer une densité, pas l'identité de chaque point. Au-delà de quelques centaines de points, les marqueurs individuels perdent leur sens, alors qu'une heatmap révèle une structure qu'aucun empilement d'épingles ne ferait apparaître.
Cas d'usage courants :
- Analyse de la fréquentation piétonne : choix d'emplacements commerciaux, urbanisme, modélisation des foules lors d'événements. Enregistrez un ping GPS pour chaque client qui franchit une porte, et la heatmap montre quelles zones d'une ville, d'un centre commercial ou d'un lieu attirent le plus de monde.
- Demande de livraison : agrégez l'origine des commandes par code postal ou par coordonnées brutes pour montrer aux dispatcheurs où la demande se concentre. Ces données alimentent directement le découpage des zones et l'affectation des livreurs.
- Criminalité et incidents : tableaux de bord d'analyse policière, cartes de risque pour l'assurance et outils de signalement pour la sécurité publique utilisent tous des heatmaps de densité. Elles communiquent le risque spatial sans noyer l'utilisateur sous les marqueurs.
- Gradients de prix immobiliers : combinée à des valeurs pondérées, une heatmap montre où les prix sont les plus élevés dans une ville. Le tutoriel sur la carte immobilière explique comment construire des outils cartographiques dédiés à l'immobilier.
Si votre question est « où les choses se passent-elles le plus ? », la heatmap est la réponse.
Format des données : des points GeoJSON pondérés
La couche heatmap de MapAtlas lit une FeatureCollection GeoJSON standard composée de features Point. Chaque feature peut porter une propriété weight qui module sa contribution à la surface de densité. Une commande de 10 articles produit plus de chaleur qu'une commande d'un seul article, et un grand pôle de transport génère plus de passage qu'une rue secondaire.
const demandData = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.8952, 52.3702] },
properties: { weight: 8, zone: "centrum" }
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.9123, 52.3601] },
properties: { weight: 3, zone: "oost" }
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.8801, 52.3780] },
properties: { weight: 12, zone: "west" }
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.9041, 52.3540] },
properties: { weight: 1, zone: "south" }
}
]
};
La valeur weight est sans unité. Normalisez-la en fonction de l'étendue de votre jeu de données. Si votre zone la plus demandée génère 500 commandes et la plus calme 10, ramenez ces valeurs sur une échelle de 1 à 10 avant de les transmettre. La heatmap reste ainsi parlante, même entre des jeux de données aux volumes absolus très différents.
Sans propriété weight, chaque point compte autant que les autres et la heatmap reflète uniquement le nombre de points par zone.
Prérequis
Avant de commencer :
- Une clé API MapAtlas (inscription gratuite, sans carte bancaire)
- Node.js 18+ pour les projets basés sur npm, ou une simple page HTML si vous préférez le CDN
Étape 1 : installer le SDK et initialiser la carte
Installez le SDK :
npm install @mapmetrics/mapmetrics-gl
Ou chargez-le via le CDN dans un fichier HTML simple :
<link
rel="stylesheet"
href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css"
/>
<script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>
Ajoutez un conteneur avec une hauteur définie :
<div id="map" style="width: 100%; height: 600px;"></div>
Initialisez la carte avec un style sombre de votre compte. Un fond sombre fait ressortir les dégradés de la heatmap, c'est pourquoi il sert de base privilégiée pour les visualisations de densité :
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const map = new mapmetricsgl.Map({
container: 'map',
style: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_DARK_STYLE_ID/style.json&token=YOUR_API_KEY',
center: [4.9041, 52.3676], // Amsterdam
zoom: 12,
});
Étape 2 : ajouter la couche heatmap
Déclarez votre GeoJSON comme source, puis ajoutez une couche heatmap qui la lit. Le callback map.on('load', ...) garantit que le style a fini de charger avant que vous le modifiiez.
map.on('load', () => {
map.addSource('demand', {
type: 'geojson',
data: demandData,
});
map.addLayer({
id: 'demand-heatmap',
type: 'heatmap',
source: 'demand',
paint: {
// Weight each point by its 'weight' property (defaults to 1 if absent)
'heatmap-weight': [
'interpolate', ['linear'],
['get', 'weight'],
0, 0,
12, 1
],
// Radius in pixels; larger = smoother but less precise
'heatmap-radius': 30,
// Overall opacity of the heatmap layer
'heatmap-opacity': 0.85,
},
});
});
Vous avez maintenant une heatmap fonctionnelle. Les zones bleu-vert correspondent à une faible densité, et le dégradé par défaut vire au jaune puis au rouge quand la densité augmente. L'étape suivante remplace cette palette par défaut par un jeu de couleurs choisi.
Étape 3 : personnaliser le dégradé de couleur
La propriété heatmap-color associe les valeurs de densité (de 0 à 1) à des couleurs, avec la même expression interpolate que dans le reste du système de paint de MapAtlas. La densité 0 est transparente : le fond de carte reste visible dans les zones vides.
map.addLayer({
id: 'demand-heatmap',
type: 'heatmap',
source: 'demand',
paint: {
'heatmap-weight': [
'interpolate', ['linear'],
['get', 'weight'],
0, 0,
12, 1
],
'heatmap-radius': 30,
'heatmap-opacity': 0.85,
'heatmap-color': [
'interpolate', ['linear'],
['heatmap-density'],
0, 'rgba(0, 0, 255, 0)', // transparent at zero density
0.2, 'rgba(0, 128, 255, 0.6)',
0.4, 'rgba(0, 230, 200, 0.7)',
0.6, 'rgba(100, 230, 0, 0.8)',
0.8, 'rgba(255, 200, 0, 0.9)',
1.0, 'rgba(255, 50, 0, 1)' // bright red at peak density
],
},
});
Ce dégradé va du bleu (clairsemé) au rouge (dense) en passant par le vert et le jaune. Il correspond au modèle mental que la plupart des utilisateurs ont acquis avec les cartes radar météo et l'imagerie thermique. Si votre application suit une charte graphique, remplacez les valeurs RGB par vos propres paliers : l'interpolation assure automatiquement des transitions fluides.
Conseil de design : pour les tableaux de bord professionnels et les outils d'analyse, gardez l'extrémité basse densité presque transparente. Le fond de carte reste lisible dans les zones calmes, et le regard se porte naturellement vers les zones chaudes.
Étape 4 : ajouter un curseur de rayon pour un contrôle interactif
Le rayon de la heatmap détermine jusqu'où chaque point « rayonne ». Un petit rayon (10 à 15 px) fait apparaître des concentrations fines, un grand rayon (50 à 80 px) produit une surface plus large et plus lisse. Chaque cas d'usage appelle une valeur par défaut différente : la fréquentation piétonne d'un centre-ville dense demande un petit rayon, la demande de livraison à l'échelle d'un pays entier un grand.
Donnez aux utilisateurs un curseur pour ajuster ce rayon à la volée :
<div id="controls" style="position: absolute; top: 16px; left: 16px; z-index: 1;
background: white; padding: 12px 16px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.2);">
<label style="font-size: 14px; font-weight: 600;">
Radius: <span id="radius-value">30</span>px
</label>
<br />
<input id="radius-slider" type="range" min="5" max="80" value="30" style="width: 180px; margin-top: 6px;" />
</div>
Reliez le curseur à setPaintProperty, qui met à jour la couche sans la recréer :
const slider = document.getElementById('radius-slider');
const radiusLabel = document.getElementById('radius-value');
slider.addEventListener('input', () => {
const radius = Number(slider.value);
radiusLabel.textContent = radius;
map.setPaintProperty('demand-heatmap', 'heatmap-radius', radius);
});
setPaintProperty applique la mise à jour en direct, sans scintillement. La heatmap est recalculée sur le GPU dans la même image. Ce modèle fonctionne pour toutes les propriétés de paint : opacité, intensité, paliers de couleur.
Étape 5 : adapter l'intensité au niveau de zoom
Aux faibles niveaux de zoom (vue de toute la ville), les points proches se superposent fortement et la heatmap peut paraître uniformément saturée. À fort zoom (vue de la rue), les mêmes points s'écartent et la surface de densité paraît clairsemée. Une compensation de l'intensité liée au zoom garde la visualisation lisible à tous les niveaux.
paint: {
// ...other paint properties...
'heatmap-intensity': [
'interpolate', ['linear'],
['zoom'],
8, 1, // low zoom: normal intensity
14, 3 // high zoom: boost intensity to compensate for point spread
],
'heatmap-radius': [
'interpolate', ['linear'],
['zoom'],
8, 20, // small radius at city scale
14, 50 // larger radius at street scale
],
},
Les deux propriétés utilisent la même expression interpolate sur zoom. Les valeurs entre les paliers sont interpolées linéairement, la transition reste donc fluide pendant que l'utilisateur zoome.
Exemple concret : heatmap de la demande de livraison
Voici une implémentation complète et autonome pour un tableau de bord de demande de livraison. Les commandes arrivent d'une API sous forme de FeatureCollection GeoJSON. La heatmap se met à jour chaque fois que l'utilisateur modifie le filtre horaire.
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const API_KEY = 'YOUR_API_KEY';
const map = new mapmetricsgl.Map({
container: 'map',
style: `https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_DARK_STYLE_ID/style.json&token=${API_KEY}`,
center: [4.9041, 52.3676],
zoom: 11,
});
async function loadOrders(hourFrom, hourTo) {
const res = await fetch(
`/api/orders/geojson?hour_from=${hourFrom}&hour_to=${hourTo}`
);
return res.json(); // returns a GeoJSON FeatureCollection
}
map.on('load', async () => {
const orders = await loadOrders(8, 12); // morning peak
map.addSource('orders', { type: 'geojson', data: orders });
map.addLayer({
id: 'order-heatmap',
type: 'heatmap',
source: 'orders',
paint: {
'heatmap-weight': [
'interpolate', ['linear'], ['get', 'items'],
0, 0, 20, 1
],
'heatmap-color': [
'interpolate', ['linear'], ['heatmap-density'],
0, 'rgba(0, 0, 255, 0)',
0.2, 'rgba(0, 128, 255, 0.5)',
0.5, 'rgba(0, 230, 150, 0.8)',
0.8, 'rgba(255, 200, 0, 0.9)',
1.0, 'rgba(255, 50, 0, 1)'
],
'heatmap-radius': [
'interpolate', ['linear'], ['zoom'],
8, 20, 14, 50
],
'heatmap-intensity': [
'interpolate', ['linear'], ['zoom'],
8, 1, 14, 3
],
'heatmap-opacity': 0.9,
},
});
// Time-of-day filter buttons
document.querySelectorAll('[data-hour-range]').forEach((btn) => {
btn.addEventListener('click', async () => {
const [from, to] = btn.dataset.hourRange.split('-').map(Number);
const newOrders = await loadOrders(from, to);
map.getSource('orders').setData(newOrders);
});
});
});
Appeler setData sur une source existante remplace le GeoJSON sans recréer la couche, et la heatmap se redessine automatiquement. Ce modèle s'applique à tout filtre temporel : heure de la journée, jour de la semaine, conditions météo.
Pour la couche de planification d'itinéraires qui accompagne généralement une heatmap de demande, consultez le tutoriel sur la Route Optimization API. Superposer une tournée optimisée à une heatmap de demande donne aux dispatcheurs une vue opérationnelle complète sur un seul écran.
Conseils de performance pour les grands jeux de données
La couche heatmap de MapAtlas s'appuie sur WebGL et s'affiche rapidement, mais le pipeline de données qui l'alimente peut devenir un goulet d'étranglement à grande échelle.
Pré-agrégez côté serveur pour les très grands jeux de données. Si vous avez des millions de pings GPS, ne les envoyez pas tous au navigateur. Lancez une agrégation spatiale côté serveur (grille hexagonale H3, quadtree ou simple arrondi sur une grille) qui ramène 1 million de points bruts à 10 000 cellules portant un champ count et un champ weight. Aux niveaux de zoom habituels, la heatmap sera quasiment identique, et elle se chargera en une fraction du temps.
Diffusez les mises à jour de manière incrémentale. Pour les données en direct (fréquentation en temps réel, commandes passées en direct), utilisez setData avec une fenêtre glissante de points récents plutôt que d'accumuler un objet GeoJSON qui grossit sans fin. Fixez un nombre maximal de points dans la source et supprimez les anciens enregistrements.
Définissez un maxzoom sur la source. Ajouter maxzoom: 14 à votre appel addSource indique au SDK de ne plus demander de données de tuiles au-delà du zoom 14. Pour une heatmap, l'effet est souvent limité, puisque la couche lit une source GeoJSON unique et non des données tuilées, mais cela évite des retraitements inutiles aux niveaux de zoom élevés.
Gardez des expressions de paint simples. Le dégradé heatmap-color est converti une seule fois en texture de rampe de couleurs au moment où vous le définissez : les paliers de couleur supplémentaires ne coûtent donc presque rien à chaque image. Sur mobile, un heatmap-radius élevé et un grand nombre de points pèsent bien plus lourd, ajustez-les en premier.
Initialisez la carte en différé. Placez toute l'initialisation de la carte dans un callback IntersectionObserver pour qu'elle ne s'exécute que lorsque le conteneur entre dans la zone visible. Le bundle du SDK sort ainsi du chargement initial de la page, ce qui est particulièrement utile sur les pages marketing où la carte se trouve sous la ligne de flottaison.
Pour aller plus loin sur les bonnes pratiques de performance cartographique, dont le chargement différé et le clustering, la checklist de production de Comment ajouter des cartes interactives à votre site web couvre la liste complète.
Intégration React
Encapsuler la heatmap dans un composant React suit le même modèle que pour toute carte MapAtlas : initialisez-la dans useEffect, exposez l'état du curseur et du filtre via useState, et nettoyez au démontage.
import { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
export function DeliveryHeatmap({ geojson, apiKey }) {
const containerRef = useRef(null);
const mapRef = useRef(null);
const [radius, setRadius] = useState(30);
useEffect(() => {
const map = new mapmetricsgl.Map({
container: containerRef.current,
style: `https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_DARK_STYLE_ID/style.json&token=${apiKey}`,
center: [4.9041, 52.3676],
zoom: 11,
});
mapRef.current = map;
map.on('load', () => {
map.addSource('orders', { type: 'geojson', data: geojson });
map.addLayer({
id: 'order-heatmap',
type: 'heatmap',
source: 'orders',
paint: {
'heatmap-radius': radius,
'heatmap-opacity': 0.9,
},
});
});
return () => map.remove();
}, [apiKey]);
// Update radius without remounting the map
useEffect(() => {
const map = mapRef.current;
if (!map || !map.getLayer('order-heatmap')) return;
map.setPaintProperty('order-heatmap', 'heatmap-radius', radius);
}, [radius]);
return (
<div>
<div style={{ padding: '8px 0' }}>
<label>
Radius: {radius}px
<input
type="range" min={5} max={80} value={radius}
onChange={e => setRadius(Number(e.target.value))}
style={{ marginLeft: 8, width: 160 }}
/>
</label>
</div>
<div ref={containerRef} style={{ width: '100%', height: '600px' }} />
</div>
);
}
Dans Next.js, importez ce composant avec dynamic(() => import('./DeliveryHeatmap'), { ssr: false }) pour éviter les erreurs de rendu côté serveur dues à ce SDK réservé au navigateur.
Que construire ensuite
Vous disposez maintenant d'une heatmap interactive fonctionnelle, avec des données pondérées, un dégradé de couleur personnalisé, un curseur de rayon et une intensité liée au zoom. Voici comment la faire évoluer :
- Superposez une couche d'itinéraires à la heatmap pour comparer les tournées prévues à la surface de demande. Le tutoriel sur la Route Optimization API détaille l'implémentation complète.
- Ajoutez une animation temporelle : faites défiler des instantanés horaires avec une boucle
setIntervalqui appellesetDatasur la source. La carte de densité statique devient un time-lapse qui montre comment la demande se déplace au fil de la journée. - Combinez-la avec des données de prix immobiliers pour montrer les gradients de prix par quartier. Le tutoriel sur la carte immobilière présente les modèles de données pondérées pour les plateformes immobilières.
- Consultez la page des tarifs MapAtlas pour choisir l'offre adaptée à votre trafic de production.
La référence complète du SDK et d'autres exemples sont disponibles sur docs.mapatlas.xyz.
Questions fréquentes
Quelle est la différence entre une heatmap et une carte choroplèthe ?
Une heatmap visualise la densité de points en fusionnant les points proches dans un dégradé de couleur continu. Elle convient aux coordonnées brutes, comme des pings GPS ou des lieux d'événements. Une carte choroplèthe colore des zones géographiques prédéfinies (pays, codes postaux, secteurs de recensement) selon une valeur statistique. Utilisez une heatmap si vous avez de nombreux points individuels, et une carte choroplèthe si vos données sont déjà agrégées par région.
Combien de points de données une heatmap JavaScript peut-elle gérer ?
La couche heatmap de MapAtlas est rendue sur le GPU via WebGL : elle gère des dizaines de milliers de points de manière fluide sur du matériel courant. Pour des jeux de données bien plus volumineux, à partir de plusieurs centaines de milliers de points, agrégez vos données côté serveur dans une grille de résolution inférieure et passez à une couche GeoJSON fill-extrusion ou circle pour de meilleures performances sur les appareils modestes.
Puis-je utiliser les heatmaps de MapAtlas gratuitement ?
Oui. MapAtlas propose une offre gratuite qui inclut le rendu des tuiles, les couches GeoJSON et les couches heatmap. L'offre gratuite couvre le développement et la production à faible volume. Consultez mapatlas.eu/pricing pour le détail des offres.
Les heatmaps fonctionnent-elles sur les navigateurs mobiles ?
Oui. Le SDK MapAtlas utilise WebGL pour le rendu, pris en charge par tous les navigateurs mobiles modernes, dont Safari sur iOS et Chrome sur Android. Pour de très grands jeux de données sur des mobiles d'entrée de gamme, réduisez le nombre de points ou augmentez le rayon de la heatmap pour garder un affichage fluide.
À lire aussi :
- Comment ajouter des cartes interactives à votre site web
- Créer une carte immobilière avec clustering
- Route Optimization API : réduire les coûts de livraison de 30 %
- Commencer gratuitement
Questions fréquemment posées
Quelle est la différence entre une heatmap et une carte choroplèthe ?
Une heatmap visualise la densité de points en fusionnant les points proches dans un dégradé de couleur continu, idéal pour des coordonnées brutes comme des pings GPS ou des lieux d'événements. Une carte choroplèthe colore des zones géographiques prédéfinies (pays, codes postaux, secteurs de recensement) selon une valeur statistique. Utilisez une heatmap si vous avez de nombreux points individuels, et une carte choroplèthe si vos données sont déjà agrégées par région.
Combien de points de données une heatmap JavaScript peut-elle gérer ?
La couche heatmap de MapAtlas est rendue sur le GPU via WebGL : elle gère des dizaines de milliers de points de manière fluide sur du matériel courant. Pour des jeux de données bien plus volumineux, à partir de plusieurs centaines de milliers de points, agrégez vos données côté serveur dans une grille de résolution inférieure et passez à une couche GeoJSON fill-extrusion ou circle pour de meilleures performances sur les appareils modestes.
Puis-je utiliser les heatmaps de MapAtlas gratuitement ?
Oui. MapAtlas propose une offre gratuite qui inclut le rendu des tuiles, les couches GeoJSON et les couches heatmap. L'offre gratuite couvre le développement et la production à faible volume. Consultez mapatlas.eu/pricing pour le détail des offres.
Les heatmaps fonctionnent-elles sur les navigateurs mobiles ?
Oui. Le SDK MapAtlas utilise WebGL pour le rendu, pris en charge par tous les navigateurs mobiles modernes, dont Safari sur iOS et Chrome sur Android. Pour de très grands jeux de données sur des mobiles d'entrée de gamme, réduisez le nombre de points ou augmentez le rayon de la heatmap (qui fusionne visuellement davantage de points) pour garder un affichage fluide.

