leafletJS heatmap

Come realizzare una heatmap con LeafletJS

Le heatmaps o “mappe di concentrazione” (letteralmente: mappe di calore) sono un modo di rappresentazione molto usato per mostrare la distribuzione spaziale di un certo fenomeno per densità di punti o intensità. Quando occorre valutare a colpo d’occhio l’andamento di centinaia o migliaia o anche milioni di dati, l’uso delle heatmaps è molto utile ; in pratica esse sostituiscono i dati (punti) con diversi colori che differiscono di tonalità e/o di intensità a seconda della loro densità.

Non confondiamo le mappe di concentrazione con le mappe tematiche (coroplete), cioè quelle in cui delle aree ben individuate (con dei confini precisati) vengono colorate in modo diverso per descrivere la distribuzione geografica di una grandezza (per esempio l’altitudine nei DEM) o un certo fenomeno: ad ogni colore viene assegnato un significato, un valore, intervallo di valori o in generale una categoria.

mappa tematica e heatmap

Le mappe di concentrazione invece usano il colore per visualizzare la maggiore/minore densità o intensità di dati puntuali e non presentano dei confini precisati. Il colore sfuma in modo graduale a secondo di come varia la densità (la concentrazione di punti) dell’aspetto considerato e ad ogni colore/gradazione non è associata una classe di valori. Generalmente (ma non è detto) si passa dai colori caldi, prossimi al rosso, per indicare alte densità a quelli via via più freddi, prossimi al blu, per indicare basse densità.

gradiente heatmap

Una heatmap si costruisce quindi, partendo da un insieme di oggetti (punti) ognuno geolocalizzato da una coppia di coordinate (long, lat) o (X,Y). Possiamo dire che si passa da una mappa vettoriale costituita da punti ad una mappa raster, la heatmap appunto,  in cui il colore rappresenta la densità di quei punti nell’area geografica su cui sono distribuiti.

Può succedere che ad ogni punto sia anche associato un certo valore numerico rappresentante il peso o l’intensità di un fenomeno in quel punto. Per fare un esempio, se vengono registrati i punti in cui, in una data area geografica, si è verificato l’avvistamento di una certa specie animale, ad ogni punto può essere associato un peso che indica il numero di avvistamenti in esso verificatisi.

In questo caso la heatmap dipenderà non solo dalla densità dei punti ma anche dal valore dell’intensità ad essi associato; ovvero l’intensità viene interpretata come un fattore moltiplicativo della densità.

Esistono diverse librerie javascript per realizzare heatmaps, in particolare per LeafletJS abbiamo dei plugins che trovate elencati nel suo sito ufficiale in questa pagina . Potete provarli e vedere quale preferite o quale si adatta meglio alle vostre esigenze; qui vi parlo solo dei due più efficaci e nel contempo semplici da usare: leaflet.heat e heatmap.js

heatmap plugins

Plugin leaflet.heat (di V. Agafonkin)

E’ senz’altro il più leggero in termini di codice ed anche uno dei plugin più diffusi; trovate tutto (libreria, sorgente, esempi) su github in questo repository. In pratica l’unico oggetto definito è L.heatLayer(latlngs, options) che appunto realizza una heatmap come layer raster; i suoi argomenti sono:

  • latlngs: un vettore costituito dall’elenco delle coordinate e relativo peso (lat, long, peso) dei punti che si vogliono mappare; la presenza del peso (o intensità) associato ad ogni punto è prevista ma non necessaria;
  • options: le opzioni disponibili (radius, blur, … etc) per variare l’aspetto grafico della heatmap generata; quando non indicate, vengono presi i valori di default.

Per usarlo è sufficiente includere nella pagina HTML la sua libreria leaflet-heat.js che si può scaricare dal repository prima menzionato, dentro la cartella “dist“. Ovviamente il riferimento (src) che indichiamo per lo script, sarà il percorso dove lo salviamo in locale.
Qui un esempio di codice minimo per realizzare una heatmap con questo plugin:

Se, come abbiamo detto, vogliamo modificare l’aspetto della heatmap generata, le opzioni disponibili sono le seguenti:

  • minOpacity: tenuto conto che l’opacità di una heatmap aumenta al crescere della densità (o intensità) dei punti, questo è il suo valore minimo (default uguale a 0.05);
  • maxZoom: è il livello di zoom al quale i punti raggiungono la massima intensità (default uguale al maxZoom della mappa);
  • max: il massimo valore d’intensità che possono avere i punti; se non viene specificato (default uguale a 1.0) tale valore può variare tra 0.0 e 1.0 ;
  • radius: il raggio dell’area circolare (in pixel) attorno a ciascun punto; cambiandolo cambia, a parità di zoom, la visibilità della heatmap ed il clustering dei punti (default uguale a 25);
  • blur: intensità dell’effetto sfocato dell’area attorno ai punti; riducendolo i bordi diventano sempre più netti (default uguale a 15);
  • gradient: il gradiente di colore con cui si passa dai bassi valori agli alti valori di densità/intensità (default è {0.4:”blue”, 0.6:”cyan”, 0.7:”lime”, 0.8:”yellow”, 1.0:”red”} cioè dal blu al rosso, passando dai verdi e i gialli).

Le principali sono: radius, blur e MaxZoom; le diverse combinazioni di questi parametri influiscono direttamente sull’aspetto (granulosità) e sulle dimensioni delle aree della heatmap al variare dello zoom. Il valore di maxZoom dovrebbe essere uguale al livello di zoom per cui l’aspetto della heatmap è ottimale; se si sceglie troppo grande, il “calore” potrebbe non risultare visibile a livelli di zoom  più piccoli. Se si imposta il maxZoom della mappa, questa opzione può essere trascurata, perchè essa assume di default lo stesso valore.

In ogni caso, per capire meglio il significato delle diverse opzioni, partendo dal loro valore di default, vi consiglio di variarle una per volta e vedere come cambia di conseguenza la heatmap generata. Qui di seguito un esempio in cui vediamo la sintassi con esse cui vanno specificate, quando si istanzia l’oggetto L.heatLayer:

Se volete cambiare la colorazione standard della vostra heatmap, dovete definire un gradiente diverso da quello di defualt; questo è un esempio:

I colori possono essere indicati con le solite regole valide in JavaScript, cioè: con la loro denominazione standard, i codici esadecimali del tipo #FF34C1 oppure nella forma rgb(122, 255, 64). I punti di stop del gradiente devono variare tra 0.0 ed 1.0 e potete sceglierne quanti volete (minimo 2) anche se in genere se ne fissano 3 o 4 al massimo. In rete potete trovare tanti tool che vi possono aiutare a costruire un gradiente di colori come preferite, visualizzando una preview; uno è questo: http://angrytools.com/gradient .

Per illustrare come funziona questo plugin, qui c’è un semplice esempio che realizza una heatmap con la distribuzione dei bar nell’area della città di Milano (fonte dati: OSM): heatmap bar MI – esempio 1.

heatmap bar Milano

Può succedere che avete un insieme di punti con le sole coordinate e volete associare a ciascuno uno stesso valore di intensità prima di passarlo al L.heatLayer; qui vediamo un semplice esempio di come si può fare (associamo a tutti peso=3.4):

Per questo plugin leaflet.heat sono previsti i seguenti quattro metodi:

  • setOptions(options): per impostare nuove opzioni alla heatmap (*):
  • addLatLng(latlng): per aggiungere un nuovo punto (le coordinate + event. peso) alla heatmap (*);
  • setLatLngs(latlngs): per resettare tutti i dati (i punti) della heatmap (*);
  • redraw(): per ridisegnare la heatmap;

(*) viene poi automaticamente anche ridisegnata la mappa senza bisogno di richiamare il redraw().

Questi metodi sono utili  per cambiare dinamicamente la heatmap o perchè si vuole cambiare il suo aspetto grafico o perchè si cambia il dataset di punti interessato. Per esempio nella pagina della mappa si possono mettere dei controlli (selettore, bottoni, .. etc) che permettono all’utente di interagire e cambiare dinamicamente  l’insieme di punti da mappare.

 

Plugin leaflet-heatmap (di P. Wied)

Molto simile al precedente, questo plugin sviluppato da Patrick Wied lo trovate presentato in questa pagina: plugin-leaflet-layer.html . E’ basato sulla più generale libreria heatmap.js (che non è solo per Leaflet), quindi per poterlo usare dovete scaricare sia questa libreria sia il plugin vero e proprio per Leaflet. Li trovate entrambi su github in questo repository e precisamente:

  • la libreria heatmap.js nella cartella “build“;
  • il plugin leaflet-heatmap.js nella cartella “plugins/leaflet-heatmap

github heatmap.js

La documentazione completa della libreria heatmap.js invece la trovate qui .

Il formato previsto da questo plugin per definire il dataset di punti per i quali creare la heatmap,  è il seguente:

si tratta di un vettore di coordinate nella forma {lat: …., long: …., value: …}, dove value è l’intensità (o peso) associata al punto.

L’oggetto che realizza la heatmap si chiama HeatmapOverlay e va istanziato indicando le opzioni di configurazione che ne definiscono l’aspetto; dopodichè gli viene assegnato l’elenco dei punti da rappresentare col metodo SetData({max: …, min: …, data: …}).

I parametri max e min (opzionali) servono per indicare i valori massimo e minimo che possono assumere i pesi dei punti mappati; sulla base di questi viene tarata l’intensità di colorazione della heatmap.

Per vedere il plugin funzionante, questo è un esempio sempre relativo alla distribuzione dei bar a Milano: heatmap bar MI – esempio 2 .

heatmap plugin Wied

Le possibili opzioni di configurazione sono le seguenti:

  • scaleRadius: indica se il radius deve essere scalato oppure no in accordo col livello di zoom della mappa (deafult è false);
  • radius: se scaleRadius=false è il raggio in pixel dell’area circolare intorno ai punti; se scaleRadius=true è il raggio misurato nella scala della mappa al dato livello di zoom (default uguale a 15);
  • blur: stesso significato visto per il plugin precedente ma può variare tra 0 e 1 (default è uguale a 0.85);
  • opacity: indica il livello di opacità globale della heatmap con un valore compreso tra 0 e 1 (default è uguale a 0.6); se specificato annulla gli effetti di maxOpacity e minOpacity;
  • maxOpacity: se opacity non è specificato (ovvero ha il valore di default) indica, con un valore compreso tra 0 e 1, il livello di opacità dei punti della heatmap con densità/intensità più alte;
  • minOpacity: se opacity non è specificato (ovvero ha il valore di default) indica, con un valore compreso tra 0 e 1, il livello di opacità dei punti della heatmap con densità/intensità più basse;
  • useLocalExtrema: serve per indicare se di devono considerare (false) i valori max e min dell’intensità dei punti indicati con SetData(..), oppure quelli effettivi o locali (true) dei punti presenti nella vista corrente della mappa; di default è uguale a false.
  • gradient: stesso significato visto per il plugin precedente;
  • latField: è il nome del campo usato nel dataset dei punti per indicare la latitudine (default è lat);
  • lngField: è il nome del campo usato nel dataset dei punti per indicare la longitudine (default è lng);
  • valueField:  è il nome del campo usato nel dataset dei punti per indicare il peso/intensità (default è value);

rispetto al precedente plugin leaflet.heat abbiamo più opzioni, quindi alcune possibilità in più di regolare l’aspetto della heatmap; questo può essere un motivo per cui preferire questo plugin.

Qualche spiegazione in più è utile per l’opzione scaleRadius, per capire bene che tipo di risultati comporta e quindi come va usata. Vi dico subito che nella maggior parte dei casi, si sceglie uguale a false (il suo valore di default).

Quando essa è impostata a false (il valore di default), il valore di radius  va inteso in pixel ed è indipendente dal livello di zoom a cui si visualizza la mappa. Valori tipici sono allora 5, 6, 8, 15, … etc, cioè sicuramente maggiori di 1.

Quando invece è impostata a true, il valore di radius è espresso rispetto al livello di zoom  della mappa, cioè esso cambia in proporzione. Ecco allora che il suo valore va scelto in base a qual’è la scala (il livello di zoom) preferenziale della mappa:

  • se la scala è piccola (valori di zoom piccoli) il valore di radius deve essere grande, altrimenti non si vede nulla;
  • se la scala è grande (valori di zoom grandi) il valore di radius deve essere abbastanza piccolo (anche molto minore di 1)  altrimenti si vede un’unica zona colorata che ricopre tutto.

Questo significa  pure che, se scaleRadius=false al variare dello zoom la heatmap cambia forma per effetto del clustering dei punti; se invece  scaleRadius=true al variare dello zoom la heatmap si ingrandisce/rimpicciolisce ma non cambia forma e contorni.

Questo esempio che mette a confronto due heatmap uguali (stesso set di punti) ma con diverso valore di scaleRadius, chiarisce meglio quanto scritto prima.

Si è già visto il metodo fondamentale setData(…) che serve per instanziare il dataset da rappresentare nella heatmap, eventualmente indicando anche i valori max e min delle intensità dei punti. Oltre a questo, l’altro metodo previsto per l’oggetto HeatmapOverlay è addData(…) che serve per aggiungere altri punti alla heatmap già instanziata.

Il formato con cui esprimere i punti è lo stesso di quello usato per setData(..), quindi per esempio:

la differenza è che con addData(..) non sono più  previsti i parametri max e min. Dopo l’aggiunta dei punti, la heatmap viene automaticamente ridisegnata.

Se invece si vuole totalmente cambiare la heatmap, cioè resettare il precedente ed assegnare un nuovo insieme di punti, allor, bisogna usare sempre setData(..).

Come usare un vettore di punti geoJSON

Abbiamo visto qual’è il formato con cui, per i due plugin, va indicato l’elenco (o meglio l’array) di punti per i quali si vuole rappresentare una heatmap; ricordiamolo:

  • per leaflet.heat è: [[45.664, 17.034, 4], [45.324, 17.521, 3], …]
  • per leaflet-heatmap è: [{lat:45.664, lng:17.034, value:4}, {lat:45.324, lng:17.521, value:3}, …]

Ciò detto, può capitare abbastanza spesso, che i punti da mappare ci vengano forniti da un vettore in formato geoJSON  contenente le coordinate dei punti (nel dato sistema di riferimento) più eventuali altri campi che rappresentano le proprietà del vettore.

Volendo, possiamo estrapolare le coordinate dei punti presenti nel file geoJSON e poi trasformarle in un array avente il formato previsto dai plugin; per esempio ci sono diversi tool che consentono di trasformare un file geoJSON in un file CSV o Excel (questo è uno: geojson_to_csv ). Dopodichè da qui, con qualche sostituzione, si può facilmente ricostruire ed ottenere in formato testo, la struttura dati desiderata.

Ma possiamo anche risparmiarci questo lavoro e scrivere direttamente in JavaScript una funzione che legge il file geoJSON e ci estrae le coordinate dei punti in esso contenuti nel formato desiderato. Innanzitutto è conveniente e più comodo trasformare il file .geoJSON in un file .js che contiene il vettore geoJSON definito come variabile:

  1. cambiare l’estensione del file da .geojson a .js (per es. distributori.geojson –> distributori.js)
  2. aprire con un editor il file .js e definire il contenuto come variabile assegnandogli un nome; in pratica basta antemporre alla “{” con cui inizia il geojson, un’assegnazione tipo: var nome_var={

Adesso possiamo richiamarci questa variabile (che riferisce il nostro vettore geoJSON) dentro il file HTML che realizza la heatmap e  definire una funzione che effettua un parsing  del geoJSON e produce le coordinate nel formato che vogliamo. Qui di seguito il codice che spiega come:

così abbiamo un array di coordinate con peso (nell’ es. si chiama ‘punti_mappa’) che possiamo assegnare alla nostra heatmap:

Per vedere l’ esempio completo clicca qui: heatmap_geojson.

Quanto appena visto lo abbiamo applicato con il plugin leaflet.heat, ma ovviamente lo stesso metodo si può usare anche con l’altro plugin di P. Wied; basta modificare opportunamente la funzione che fa il parsing, per restituirci le coordinate nell’altro formato. Ecco qui un esempio:

La funzione vista,  estrae dal geoJSON solo le coordinate dei punti e poi assegna a tutti lo stesso valore di intensità. Ma ovviamente nulla vieta che possiamo modificarla per derivare anche questo valore dal geoJSON, facendolo corrispondere ad una delle proprietà del vettore. In questo modo avremmo per ogni punto, un valore di peso associato ad una sua caratteristica.

Per fare un esempio, se vogliamo legare l’intensità dei punti ad una proprietà che si chiama “num_livelli”, il codice potrebbe essere questo:

 

condividi: