raster tiling

Visualizzare raster grandi e zoomabili con Leaflet

Pubblicare una mappa raster con LeafletJS è molto semplice, ma se questa ha un livello di dettaglio alto e copre un’area geografica estesa, le sue dimensioni possono essere così grandi che la visualizzazione nella pagina web risulta lenta e non fluida. La soluzione tipica per questo problema è il map tiling; vediamo come realizzarlo usando un generatore di tiles e un web client basato su LeafletJS.

La classe Leaflet per visualizzare una immagine qualsiasi su una mappa è L.imageOverlay; può trattarsi di una mappa raster o qualunque altra immagine, l’importante è che sia in un formato di quelli supportati (PNG e JPG). Non è necessario che  sia georiferita, ma si deve conoscere la sua estensione geografica (bounding-box), cioè le sue coordinate (lat, long) minime e massime, perchè grazie a esse si posiziona geograficamente:

estensione immagine

Ecco un esempio di dichiarazione di una immagine-mappa avente: lat_min=40.712216, long_min=-74.22655, lat_max=40.773941, long_max=-74.12544

Le coordinate di imageBounds sono espresse in formato decimale nel sistema WGS 84 (EPSG: 4326) cioè quello di default per esprimere delle coordinate in LeafletJS.

Poi naturalmente bisogna indicare il sorgente del file dell’immagine, adoperando il parametro imageUrl, che può essere:

  • remoto e allora è indicato da un URL (http://……);
  • locale e allora è indicato con il suo path relativo (rispetto al file HTML che lo richiama) nel file-system;

Quest’altro è un esempio di dichiarazione di una mappa PNG locale:

esempio image overlay

E’ anche possibile controllare il grado della trasparenza dell’overlay adoperando la proprietà opacity o il metodo setOpacity e indicando un valore compreso tra 0.0 (totalmente trasparente)  e 1.0 (totalmente opaca) che è anche il valore di default. Per esempio:

Tiled web map

Una immagine raster (una  bitmap) ha una grandezza in bytes che dipende, dalle sue dimensioni: larghezza e altezza in pixel  e dal suo livello di definizione o risoluzione. Più precisamente un raster è caratterizzato da due tipi di risoluzione:

  • una geometrica, detta semplicemente risoluzione,  determinata dal numero di pixel contenuti nell’unità di misura di lunghezza che è generalmente il pollice (equivale a 2,54 cm) e si misura in DPI (Dot Per Inch) o PPI (Pixel Per Inch);
  • una tematica, detta profondità, che dipende dal numero di bit adoperati per esprimere il colore di un pixel e si misura in BPP (Bit Per Pixel); quanto più è elevata tanto maggiore è il numero di colori che possono essere rappresentati.

Quanto più è alta la risoluzione, tanto maggiore è il livello dettaglio degli elementi rappresentati sulla mappa; un livello di dettaglio di 5 metri significa una risoluzione maggiore di un livello di dettaglio di 10 metri.  A parità di estensione geografica coperta, la mappa con risoluzione maggiore avrà una dimensione in bytes più grande.

La dimensione di una mappa è un problema, non tanto per lo spazio di memoria (storage) occupato, ma per il tempo impiegato dal web client per  il suo rendering grafico. Ogni volta che la mappa viene spostata (panning) oppure ingrandita/rimpicciolità (zooming), il client deve ripetere il rendering della porzione di mappa visualizzata nel canvas.

La soluzione che si adotta per velocizzare la visualizzazione di immagini di questo tipo è il  tiling: una operazione che, per ogni livello di zoom possibile (0, 1, 2, 3, … etc), divide la mappa in tiles (mattonelle) di dimensioni fisse: in genere 256×256 pixel.

tiles livelli zoom

In altre parole ogni livello di zoom è composto da una diversa raccolta di tiles della stessa mappa; precisamente partendo dal livello minimo, ogni incremento di livello raddoppia nelle due dimensioni il numero di tiles necessarie per rappresentare l’intera mappa:

  • livello zoom 1: n x m tiles;
  • livello zoom 2: 2n x 2m tiles;
  • livello zoom 3: 4n x 4m tiles;
  • …… etc

Qui non scendiamo nei dettagli di come sono organizzate e codificate queste tiles (dipende dallo standard adottato) diciamo solo che il web client per visualizzare delle tiled web map, deve implementare i metodi per caricare di volta in volta le tiles che ricoprono la porzione di mappa interessata al dato livello di zoom e quindi comporre, come in un mosaico, la sua vista. LeafletJS è appunto una delle librerie che consente questo.

map tiling

Creiamo le tiles: GDAL2Tiles

Quindi la prima cosa da fare, se vogliamo pubblicare sul web un mappa raster molto grande, è suddividerla in tiles. Esistono diversi programmi e utility per farlo, uno dei più usati è GDAL2Tiles: uno script in Python (gdal2tiles.py) che partendo da una immagine raster, crea una cartella contenente le corrispondenti tiles per i diversi livelli di zoom previsti, secondo le specifiche OSGeo TMS (Tile Map Service).

GDAL2Tiles è uno script e quindi si esegue da linea di comando (da terminale) su qualunque sistema: Windows, Linux e MacOS; è contenuto nella libreria GDAL e quindi per usarlo, bisogna installarla. Poi, essendo scritto in Python, deve essere presente anche un interprete Python e deve anche essere installata la libreria delle python bindings di GDAL.

Un modo semplice per installare tutto il necessario in Windows è con il noto installer  OSGeo4W; selezionando la modalità di installazione avanzata dei pacchetti  (Advanced Install) e poi procedendo, sotto il gruppo Commandline_Utilities troviamo l’interprete Python e sotto il gruppo libs troviamo i pacchetti della libreria GDAL/OGR  e quello delle GDAL/OGR Python Bindings:

GDAL install

Su un sistema linux Debian/Ubuntu possiamo usare il tradizionale APT (Advanced Packaging Tool), sia per installare l’interprete Python (anche se generalmente è già presente di default) che le librerie gdal e python-gdal. Se è già configurato un repository che contenga i package di GDAL (altrimenti bisogna aggiungerlo nel file /etc/apt/sources.list), l’installazione è quella tipica con apt-get:

Una volta che abbiamo installato tutto correttamente, per eseguire gdal2tiles.py:

  • in Windows apriamo la shell di OSGeo4W e poi digitiamo gdal2tiles (è un .bat che esegue gdal2tiles.py);
  • in Linux apriamo il terminale (console) e poi digitiamo gdal2tiles.py;

in particolare per avere informazioni sulla modalità d’uso (argomenti e opzioni), digitiamo gdal2tiles -h (o su Linux: gdal2tiles.py -h):

gdal2tiles help

Supponiamo allora che il file della nostra mappa da suddividere in tiles si chiami my_bigmap.tif (è un geoTIFF),  ci spostiamo nella directory che la contiene ( … è più comodo così) e poi eseguiamo il comando:

gdal2tiles.py  -p mercator  -z 4-13  my_bigmap.tif  bigmap_tiles      (shell Linux)

gdal2tiles  -p mercator  -z 4-13  my_bigmap.tif  bigmap_tiles      (shell OSGeo4W Windows)

dove l’opzione -p mercator indica quale profilo usare per generale le tiles (… spiegato più avanti), l’opzione – z 4-13 indica che si vogliono le tiles per i  livelli zoom dal 4 al 13 ed infine bigmap_tiles è il nome della cartella dentro cui verranno messe le tiles generate; Se questa non viene indicata, di default verrà creata una cartella con lo stesso nome dell’immagine di input.

Se l’esecuzione parte senza errori, vedremo che prima vengono generate le Base Tiles ovvero quelle per il livello di zoom massimo impostato (nel caso del nostro esempio il liv. 13) e poi le Overview Tiles, quelle relative agli altri livello di zoom indicati (nel caso del nostro esempio i liv. 12, 11, 10, 9, …, 4). Durante la generazione, l’avanzamento viene segnalato con dei numeretti (di 10 in 10) che compaiono a terminale.

Il tempo impiegato per la generazione, dipende sia dalla grandezza in pixel dell’immagine-mappa sia dal livello di zoom massimo scelto. Arrivati alla fine, aprendo la cartella di output, troveremo qualcosa del genere:

bigmap_tiles

C’è una sotto-cartella per ogni livello di zoom desiderato (con dentro  le rispettive tiles) e poi tre visualizzatori (file HTML) con cui si può controllare il rendering delle tiles  al variare dello zoom nel browser: uno realizzato con le API di Google Maps (questo però richiede di avere una vostra API key), uno con Leaflet ed uno con  OpenLayers.

Questi visualizzatori vengono creati di default, ma se non li vogliamo, basta eseguire GDAL2Tiles con l’opzione -w none.

Oltre a permettere di controllare nel browser il risultato della generazione, questi files sono utili perché contengono il codice JavaScript che potremo ricopiarci nel nostro web client per leggere le tiles. Ma attenzione che ad oggi sono usate delle versioni di queste librerie ormai vecchiotte; in particolare viene usata ancora OpenLayers 2, perciò il codice non va più bene per un client scritto con OpenLayers 3 o 4 (dovrete modificarlo). Non so dirvi se in future versioni di GDAL2Tiles, saranno aggiornati.

Profili di tiling

Nell’esempio precedente, abbiamo usato GDAL2Tiles con l’opzione -p mercator; questo è il profilo maggiormente usato ed è anche il valore di default (se non viene specificato). Con esso, le tiles generate usano il sistema di riferimento WGS 84/Sperical-mercator (EPSG: 3857) che ricordiamo è un sistema proiettato, e sono compatibili con i client di mappe più diffusi: Google Maps, Bing Maps, Leaflet, … etc.

L’altra opzione di profilo  (specificato nell’ OSGeo TMS,) è – p geodetic, con il quale le tiles sono generate secondo il sistema globale geografico WGS 84 (EPSG: 4326); in questo caso le tiles possono essere lette da un web client che visualizza i dati geografici e le mappe in EPSG 4326. E’ un caso più raro perchè la maggior parte dei servizi che distribuiscono mappe usano  l’ EPSG 3857, ma comunque può succedere. Per esempio Google Earth usa il WGS 84 (rappresenta la superficie curva della Terra).

Quando usate GDSL2Tiles con l’opzione -p geodetic, viene  generato solo un visualizzatore scritto con OpenLayers 2  e un file doc.kmz che può essere letto in Google Earth.

gdal2tiles geodetic visualizzatori

Il visualizzatore fatto con OL2 (il file openlayers.html) usa come base-map un layer WMS di nome Vmap0 che è appunto rappresentato in WGS 84; ma se lo provate la base-map non si vede, perchè l’URL della risorsa è obsoleto. Se aprite il suo codice  è fate la seguente sostituzione:

risolverete il problema.

Abbiamo sottinteso, ma è meglio rimarcarlo, che i due profili funzionano su raster georeferenziati e puntualizziamo anche che contengano le informazioni sul loro SRS (Spatial Reference System); non ha importanza quale sia e non deve essere necessariamente uguale all’ EPSG 3857 o EPSG 4326, ma nel file deve esserci l’informazione.

Sappiamo infatti che esistono raster georiferiti ma che non includono info sul loro SRS; esempio tipico sono le immagini georiferite (JPG, TIF, PNG, …) con il cosiddetto world file associato (JPGW, TFW, PGW, …).

In casi come questi è necessario eseguire GDAL2Tiles usando l’opzione -s per specificare  l’SRS del raster; per esempio, se il nostro raster è il file  corsica.png con relativo world file corsica.pgw ed ha sistema di riferimento EPSG 3003 (Monte Mario/Italy zona 1), dobbiamo scrivere:

gdal2tiles.py  -p mercator  -s EPSG:3003  -z 4-13   corsica.png 

Se non si specifica l’ SRS infatti ci viene restituita una segnalazione di errore come questa:

 

Un terzo profilo di tiling previsto  è -p raster che in definitiva non serve per delle mappe georiferite,  ma per suddividere in tiles delle immagini qualsiasi molto grandi (foto, disegni, … etc) e poi visualizzare su una pagina web  usando lo strumento zoom come si fa per le mappe geografiche. Praticamente è l’unica opzione di GDAL2Tiles che funziona applicata a delle immagini (PNG, TIF, IPG, .. etc) non georiferite; le altre due (mercator e geodetic) darebbero errore.

Qualunque sia il profilo scelto,  le coordinate dei pixel e delle tiles generate da GDAL2Tiles seguono il formato TMS: origine (0,0) in basso a  sinistra.
Ricordiamo che i sistemi/servizi di tiles seguono due possibili formati:

  • il formato XYZ (detto anche formato Google o slippy map) che è il più diffuso;
  • il formato OSGeo TMS (Tile Map Service);

qui non ci interessa approfondire (in questo mio altro articolo accenno alla differenza che c’è tra di essi); l’unica cosa da sapere è che un web client per leggere le tiles generate con GDAL2Tiles, dovrà essere in grado di gestire questo formato TMS. E’ quello che fanno i visualizzatori di cui abbiamo parlato e che approfondiremo più avanti nel caso di LeafletJS.

I livelli di zoom

GDAL2Tiles permette di scegliere i livelli di zoom per i quali generare le tiles; possiamo scegliere un intervallo di livelli (es: -z 4-13) oppure un solo livello (es: -z 8), oppure non specificare niente e lasciare che il comando generi i livelli zoom di default.

Normalmente, data la mappa raster da scomporre, sappiamo quali sono i livelli di zoom che ci servono; ma se non fosse così come facciamo per sceglierli correttamente? Per farlo è utile sapere qual’è il livello di zoom nativo della mappa di partenza, o se volete qual’è la sua scala originaria.

Noto questo valore nativo, il livello di zoom minimo sarà sicuramente più piccolo e scegliamo noi fino a quale vogliamo scendere; il livello di zoom massimo invece dovrebbe essere uguale o di poco più grande al livello nativo, per il semplice motivo che  se si va troppo oltre (livelli zoom più grandi) l’immagine diventa sempre più sgranata e non restituisce più bene il suo contenuto informativo.

Per esempio se abbiamo una mappa con livello nativo uguale a 10, è bene scegliere un livello zoom massimo uguale a 11 o 12. Ovviamente se non ci importa la qualità dell’immagine troppo ingrandita, siamo comunque liberi di scegliere il livello di zoom massimo che preferiamo.

livello zoom nativo

Ma come si determina il livello di zoom nativo? Esso dipende dalle dimensioni in pixel della mappa e dall’estensione geografica che ricopre (altezza e larghezza in metri) ma anche dal profilo di taglio delle tiles: mercator (EPSG: 3857) o geodetic (EPSG: 4326). Esistono delle formule per calcolarlo ma possiamo farne a meno, perché se eseguiamo  GDAL2Tiles senza specificare l’opzione -z  verranno generati i livelli zoom di default, e quello massimo coincide proprio con il livello nativo.

Per esempio qui vediamo una figura con le cartelle delle tiles con i livelli zoom di default generati per una stessa immagine (cioè senza specificare l’opzione -z) usando una volta il profilo -p mercator  e un’altra il profilo -p geodetic.

livello zoom nativo

MapTiler

Un’alternativa all’uso di GDAL2Tiles da linea di comando è un’applicazione desktop che si chiama MapTiler (www.maptiler.com) ed esiste nelle versioni per Windows, Linux e MacOS con i corrispondenti installer. MapTiler usa come “motore” lo stesso gdal2tiles.py (l’autore è uguale: P Klokan) ma ha una comoda GUI che ne rende l’uso più facile, specie per chi ha poca dimestichezza con i comandi testuali, ed include ulteriori funzionalità.

MapTiler start page

La scelta delle varie opzioni è facilitata dall’interfaccia grafica e dalle guide passo-passo. Sono presenti delle funzioni di preview e di controllo sia sull’immagine da processare che sulle tiles che si vogliono generare; oltre ai profili già visti in GDAL2Tiles, nella modalità avanzata è anche possibile scegliere un profilo di tiling personalizzato.

Anche i visualizzatori HTML sono stati aggiornati e, nel caso di OpenLayers, è presente sia quello per la versione 2 che 3.

Tutto questo però ha un costo perché, anche se c’è una versione gratuita (free), MapTiler è a pagamento con diverse possibilità.

MapTiler costi

Co la versione “free” si hanno delle limitazioni e non si possono scegliere neanche i livelli di zoom; ma la cosa più fastidiosa è che le tiles generate presentano filigranato il logo MapTiler.com, quindi, tranne che per fare dei test, sono praticamente inutilizzabili.

Per dovere di cronaca ho voluto citare MapTiler, perché è giusto sapere che esiste questa alternativa; ma personalmente ritengo che, tranne esigenze particolari, si possa fare a meno di comprarlo ed usare tranquillamente il comando GDAL2Tiles. Poi naturalmente ognuno è libero di fare come preferisce!

Visualizzare le tiles TMS con LeafletJS

Come abbiamo visto uno dei visualizzatori creato da GDAL2Tiles (leaflet.html) è realizzato con LeafletJS; se apriamo il codice vediamo che le tiles generate vengono riferite usando la classe L.tileLayer :

Il parametro fondamentale  è l’ urlTemplate  con il quale si indica la posizione (locale o remota) delle cartelle in cui ci sono le tiles; nel nostro esempio è uguale a ./{z}/{x}/{y}.png  perchè esse si trovano nella stessa directory (./) del file HTML, più in generale bisogna specificare il path relativo, per esempio: ./bigmap_tiles/{z}/{x}/{y}.png. Le tre lettere identificano:

  • {z} il valore del livello zoom –  il nome della cartelle più esterne;
  • {x} il numero colonna della griglia di tiles – il nome dell cartelle dentro a ogni livello zoom;
  • {y} il numero riga della griglia di tiles – il nome dei file immagine (.png) delle singole tile;

albero cartelle tiles

Questa è la struttura delle tiles nel formato TMS e difatti in L.tileLayer è stata specificata la proprietà tms: true, altrimenti le tiles verrebbero considerate nel formato XYZ e quindi non lette correttamente (il layer non appare nella vista mappa).

Altra proprietà presente nell’esempio è il livello di opacità (opacity: 0,7) ma ovviamente questa non è indispensabile; poi se esiste indichiamo anche qual’è l’attribution per la mappa composta.

Questo è tutto! Aggiungendo questo layer nella nostra vista mappa, vedremo che al variare del livello di zoom (tra il valore minimo e massimo che abbiamo previsto) le tiles vengono di volta in volta caricate componendo l’intera immagine; naturalmente la griglia delle tiles è invisibile, a volta appena percepibile.

Ricordiamo ancora una volta che essendo EPSG 3857 il sistema di riferimento di default di LeafletJS, le tiles devono essere state generate con il profilo mercator.

Ecco un semplice esempio di codice in cui il layer di tiles TMS locale viene sovrapposto ad una base-map fornita da un map tile server di OSM (in formato XYZ); notate che nell’impostazione della vista mappa, abbiamo fissato i valori min e max dello zoom uguali a quelli scelti in GDAL2Tiles per generare le tiles:

Un altro esempio completo e funzionante lo potete vedere qui; si tratta di una mappa satellitare (Sentinel 2) che appunto essendo abbastanza estesa e dettagliata (dim. originale circa 255 MBytes) è stata suddivisa in tiles con livello zoom max 15.

 

 

 

 

 

 

 

 

 

 

condividi: