Maps - Deck.gl - Heatmap

Création d'un Layer de type heatmap, gestion des paramètres radius pixel, intensity et threshold
deck.gllatest mapbox-gljs1.13.2 d3js7.x
Sources :

Introduction

Est-il possible de positionner plus de 950 000 points sur une carte sans en ralentir l'utilisation ? Avec Deck.gl nous allons voir que la réponse à cette question est positive. A condition d'avoir une connexion correcte, le fichier de données associé à cette visualisation fait 17.4 Mo ! Une fois chargé, l'interface est fluide et la variation des paramètres de la heatmap se fait instantanément. En cliquant sur l'image ci-dessus vous pouvez explorer ces paramètres. Dans ce tutoriel, nous allons étudier la construction de cette carte et l'utilité de ces paramètres.

Les données

Les données proviennent du site data.gouv.fr. Elles correspondent aux anomalies signalées par les contributeurs-utilisateurs de l'application Dans Ma Rue sur 13 mois glissant. Elles prennent la forme d'un fichier CSV de 330 Mo qui contient pour chaque anomalie sa géolocalisation, son adresse ainsi que son type et sous-type (par exemple Graffitis, tags, affiches et autocollants;Affiches agrafées sur un mobilier urbain). Nous avons uniquement conservé les données de géolocalisation que nous avons arrondi à 6 chiffres après la virgule, soit environ un mètre de précision. Voici les données avant/après :

numero;type;soustype;adresse;code_postal;ville;arrondissement;conseilquartier;datedecl;anneedecl;moisdecl;prefixe;intervenant;id_dmr;geo_shape;geo_point_2d
66247;Autos, motos, vélos...;Automobile ou autre véhicule motorisé en stationnement gênant;25 Rue Duphot, 75001 PARIS;75008;Paris 8;8;ELYSEES - MADELEINE;2021-04-28;2021;4;Androï;DPMP-SCOP;G2021D066247;"{""coordinates"": [2.325178405257188, 48.86914399599695], ""type"": ""Point""}";48.86914399599695,2.325178405257188
63498;Objets abandonnés;Autres objets encombrants abandonnés;63 Rue du Théâtre, 75015 PARIS;75015;Paris 15;15;EMERIAU - ZOLA;2021-05-27;2021;5;IOPage DMR Paris.fr application;Ramen en tant que prestataire de DansMaRue;A2021E063498;"{""coordinates"": [2.289912999097352, 48.84824400172112], ""type"": ""Point""}";48.84824400172112,2.289912999097352
64755;Autos, motos, vélos...;Automobile ou autre véhicule motorisé en stationnement gênant;194 Quai de Bercy, 75012 PARIS;75012;Paris 12;12;BERCY;2021-05-27;2021;5;IOPage DMR Paris.fr application;DPMP-SCOP;A2021E064755;"{""coordinates"": [2.379043294597086, 48.83588999970327], ""type"": ""Point""}";48.83588999970327,2.379043294597086
long,lat
2.3251784,48.86914
2.2899129,48.84824
2.3790432,48.83588

Le code complet

Voici le code complet permettant de créer une heatmap avec Deck.gl. Par rapport au tutoriel précédent, nous avons simplement ajouté l'import de D3, le chargement du fichier de données et la construction du Layer.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
	<head>
		<meta charset="UTF-8">
		<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.13.2/mapbox-gl.css' rel='stylesheet' />
		<script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
		<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v1.13.2/mapbox-gl.js"></script>
		<script src="https://d3js.org/d3.v7.min.js"></script>
	</head>
	<style>
		body {margin: 0; width: 100vw; height: 100vh; overflow: hidden;}
	</style>

	<body onload="initialize()">
		<div id="map"></div>
	</body>
</html>
<script type="text/javascript">
	const data = d3.csv("data.csv");
	
    function initialize() {
        const deckgl = new deck.DeckGL({
			mapboxAccessToken: '',
			mapboxApiAccessToken: 'pk.eyJ1IjoiZXJpY2ZyaWdvdCIsImEiOiJjbDNuemZ0N3AwaHNqM2NwbGgwa2Mwb2VjIn0.tM5L_HYtapsLJ8vKbFkxNA',
			mapStyle: 'mapbox://styles/mapbox/dark-v10',
			initialViewState: {
				longitude: 2.333,
				latitude: 48.853,
				zoom: 12,
				minZoom: 3,
				maxZoom: 20,
				pitch: 0,
				bearing: 0
			},
			controller: true,
			layers: [new deck.HeatmapLayer({
				data,
				getPosition: d => [Number(d.long), Number(d.lat)],
				aggregation: 'SUM',
				radiusPixels: 10,
				intensity: 1,
				threshold: 0.05
			})]
		});
    }
</script>

La propriété layers de notre objet DeckGL prend en entrée un tableau permettant ainsi de superposer plusieurs layers. Il est également possible de la définir plus tard et de manière indépendante.

deckgl.setProps({
    layers: [heatmapLayer]
});

Si vous regardez le code source associé à notre visualisation, celui-ci est plus complet puisqu'il permet la variation des paramètres du Layer heatmap. Nous avons aussi ajouté dans la partie HTML un panneau fournissant des informations sur le jeu de données.

Les paramètres du Layer heatmap

C'est ici que se trouve la partie la plus importante de ce tutoriel. En maitrisant les paramètres du Layer heatmap vous pourrez construire précisément la visualisation dont vous avez besoin. Par défaut nous avons choisi un radiusPixel et une intensity assez faibles car ils rendent visible la bonne gestion par Deck.gl de centaines de milliers de données. En les modifiant nous obtenons un résultat très différent plus proche des heatmaps habituels et qui rend mieux compte de la densité des données (radiusPixel = 50, intensity = 1.5 et threshold = 0.2).

Version plus habituelle de la heatmap

radiusPixel (par défaut 30)

Ce paramètre détermine la taille du cercle (en pixels) auquel sont rattachés les différents éléments représentés. Plus sa valeur est grande et moins vous aurez de cercles dessinés. Il faut choisir une taille qui permette à certains cercles de contenir de nombreux éléments et à d'autres moins d'éléments sachant que suivant le niveau de zoom, chaque cercle en contient plus ou moins.

intensity (par défaut 1)

L'intensité permet de faire varier la couleur de chaque pixel d'un certcle en modifiant son poids (poids qui est déterminé par le nombre d'éléments qu'il contient). Une intensité supérieure à 1 va déplacer la plage de couleurs vers les plus élevées (dans notre cas le rouge) et une intensité inférieure à 1 va au contraire les faire aller vers les couleurs plus faibles (le jaune). Le poids final d'un pixel est donné par le calcul : poids de départ * intensity. Ce paramètre permet de corriger certains "défauts" dans les données. Si elles contiennent par exemple une zone avec beaucoup d'éléments et que toutes les autres en possèdent moins, vous obtiendrez une heatmap avec une zone rouge et tout le reste en jaune. En augmentant l'intensité, vous pourrez avoir plus de nuances de couleurs car vous allez augmenter le poids des pixels contenant moins d'éléments, sachant que la zone qui contient beaucoup d'éléments reste associée à la même couleur "maximale". Le paramètre colorDomain complète ces explications.

threshold (par défaut 0.05)

Ce paramètre gère l'opacité des pixels dessinés. Une valeur de 0.1 signifie que tous les pixels dont le poids est inférieur à 10% du poids max verront leur opacité diminuer. Dans l'image ci-dessus, le paramètre threshold est à 0.2 (donc 20%) permettant de mieux voir les zones à forte densité.

colorRange (par défaut colorbrewer 6-class YlOrRd )

La palette de couleur est représentée par un tableau de couleurs [couleur1, couleur2,...]. Chaque couleur est définie selon le format [r, g, b, [a]] avec a représentant l'opacité. Pour construire cette palette vous devez donc fournir un tableau complet avec le code rgb de chaque couleur. Il est possible d'utiliser les palettes de couleurs de D3JS, toutes celles de la forme d3.schemeName[k] avec le code de conversion suivant.

function getColorRange(scheme, size) {
    let result = [];
    for (const htmlColor of d3[scheme][size]) {
        let rgbColor = d3.color(htmlColor).rgb();
        result.push([rgbColor.r, rgbColor.g, rgbColor.b]);
    }
    return result;
}

Il suffit ensuite d'ajouter dans la construction de la heatmap le paramètre colorRange. Le paramètre size doit être compris entre 3 et 11.

colorRange: getColorRange('schemeRdYlBu', 9)

colorDomain (par défaut null)

Ce paramètre prend en entrée un tableau de deux valeurs [minValue, maxValue] et permet le mapping entre les poids des pixels et la liste des couleurs fournis par colorRange. Ainsi, lorsque ce paramètre est défini un pixel avec un poids égal à minValue se voit attribuer la première couleur du tableau colorRange et un pixel dont le poids est égal à maxValue se voit attribuer la dernière couleur de ce tableau. Entre les deux une interpolation linéaire est réalisée.

D'autres paramètres sont disponibles !

La documentation de Deck.gl sur les heatmap (en anglais) fournit des informations supplémentaires et précise d'autres paramètres qui peuvent être utilisés.

Conclusion

Nous terminons ici ce tutoriel sur les heatmap. Par l'utilisation du processeur de la carte graphique Deck.gl permet bien de représenter des centaines de milliers de points sur une carte, ce qui ne serait pas possible de manière aussi fluide avec Leaflet ou D3JS seuls. Comme toujours la section commentaire est présente juste en dessous pour échanger sur le sujet.

COMMENTAIRES