E se volessi Gauss-Boaga?

Prima di addentrarci sui layer cartografici prendiamoci del tempo per capire bene come funzionano i Sistemi di Riferimento in OL. A differenza di altre note librerie, nelle quali questo aspetto è del tutto ignorato (tanto tutti usano Google Mercator) oppure delegato a plugin aggiuntivi, OL fornisce un ampio supporto alla gestione dei sistemi di riferimento della mappa e dei nostri layer geografici, anche per quanto riguarda le funzioni di trasformazione di coordinate e riproiezione dei layer tra SR diversi.

Diciamo da subito che OL, di per sé, conosce solo le caratteristiche dei due principali SR globali: EPSG:3857 (quello di default alla creazione di una mappa) e EPSG:4326. Tuttavia con il supporto della ottima libreria proj4js, abbiamo la possibilità di definire e usare qualsiasi sistema di riferimento, posto che se ne conosca la definizione PROJ4. Una ricca fonte per ottenere tali definizioni, ed includerle direttamente nella nostra applicazione, è epsg.io. Ma andiamo per passi, vediamo anzitutto come si definisce e si gestisce una proiezione in OL.

ol.proj.Projection

In OL una proiezione viene gestita tramite un oggetto ol.proj.projection. Questo tipo di oggetto viene inizializzato tramite il codice EPSG della proiezione / SR che gli vogliamo associare.

Es.:

var google_mercator = ol.prol.Projection("EPSG:3857"); // crea una proiezione Google Mercator
var wgs84 = ol.proj.Projection("ESPG:4326"); // crea una proiezione WGS84

OL è in grado di istanziare queste due proiezioni semplicemente dal codice EPSG, perché la loro definizione è già inclusa nella libreria. Ma se volessi istanziare, ad es., una proiezione Gauss Boaga Ovest? Potrei pensare di fare così:

var gb_ovest = ol.prol.Projection("EPSG:3003"); // crea una proiezione Gauss Boaga Ovest

Ma in realtà otteremo solo parte di quanto ci potremmo aspettare. OL utilizzerà il codice della nostra proiezione, ad es. quando dovrà chiedere dati ai servizi OGC, ma non sarà in grado di fare alcuna trasformazione di coordinate, perché non ha idea di cosa sia e come sia fatta una propiezione Gauss Boaga Ovest. Sa solo che è un qualcosa identificato dal codice EPSG 3003.

Nel caso avessimo bisogno di eseguire trasformazioni di coordinare dovremo aiutare OL tramite proj4js. Includendo questa libreria nella nostra applicazione e registrando in essa la definizione PROJ4 della nostra proiezione, OL potrà delegare a proj4js le nostre richieste di trasformazione da/verso altri SR.

La definizione della proiezione Gauss Boaga Ovest è la seguente:

proj4.defs("EPSG:3003","+proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +towgs84=-104.1,-49.1,-9.9,0.971,-2.917,0.714,-11.68 +units=m +no_defs");

Supponiamo di conoscere le coordinate GB Ovest di Firenze (nel codice assegnate alla variabile firenze_gbw) e di voler centrare la nostra mappa con OSM su Firenze. Dal momento che alla nostra mappa è stata associata di default la proiezione EPSG:3857, dovremo trasformare le coordinate di Firenze in coordinate Google Mercator.

  1. Includiamo la libreria proj4js, prendendola direttamente da cdnjs (riga 3)
  2. Registriamo in proj4js la definizione della nostra proiezione. Per farlo sfruttiamo un’utilità offerta dal servizio epsg.io: inserendo un link al codice Javascript del nostro codice EPSG, epsg.io inserirà direttamente il codice per eseguire la registrazione proj4.defs(...)
  3. Assegniamo a firenze_gbw le coordinate GB Ovest, in forma di array
  4. Creiamo un oggetto proiezione di OL assegnandogli il codice EPSG:3003. A questo punto OL cercherà questo codice all’interno delle proprie definizioni e, se presente, di proj4js.
  5. Tramite la funzione statica ol.proj.transform eseguiamo la trasformazione di coordinate, passandogli le coordinaate di partenza, la proiezione di origine e quella di destinazione. Notate che per l’origine abbiamo usato la nostra istanza gb_ovest_proj, mentre per la destinazione usiamo “EPSG:3857”. Questa funzione accetta entrambi i tipi di argomenti. Nel caso gli venga passato solo il codice provvederà lei a istanziare la proiezione (solo se non è già presente nella cache delle proiezioni).
  6. Assegniamo alla proprietà di configurazione center di ol.View le coordinate trasformate, e aumentiamo un po’ il livello di zoom

A questo punto la mappa è in grado di posizionarsi correttamente su Firenze.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<body>
  <div id="map" class="map"></div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.4.4/proj4.js"></script>
  <script src="http://epsg.io/3003.js"></script>
  <script src="../assets/js/ol-debug.js"></script>
  <script>
    var firenze_gbw = [1681450.785672498, 4848967.319261061];
    
    var gb_ovest_proj = new ol.proj.Projection({
      code: "EPSG:3003"
    });
    
    var firenze_gmerc = ol.proj.transform(firenze_gbw, gb_ovest_proj, "EPSG:3857");
    
    var map = new ol.Map({
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        })
      ],
      target: 'map',
      view: new ol.View({
        center: firenze_gmerc,
        zoom: 10
      })
    });
  </script>
</body>

Come già detto l’inclusione di proj4js e della definizione del SR è necessaria solo se abbiamo necessità di eseguire trasformazioni e/o riproiezioni lato client. Se vogliamo solo assegnare un SR alla mappa o ad un layer è sufficiente creare una ol.proj.Projection assegnandogli il codice EPSG.

In questo esempio abbiamo eseguito una trasformazione usando direttamente i valori delle coordinate. Come vedremo più avanti è possibile eseguire trasformazioni anche direttamente su singoli elementi geometrici (punti, linee e poligoni) o su interi strati vettoriali e raster.

Calcolo di distanze e aree sulla sfera

In OpenLayers tutti i calcoli di lunghezza e area delle geometrie lineari e poligonali sono effettuati sul piano, senza considerare la sfera e tantomeno lo sferoide. Questa approssimazione può essere sufficiente per piccole porzioni di territorio ma, com’è noto, l’errore diventa significativo già per distanze di qualche decina di chilometri. Un’approssimazione migliore consiste nell’eseguire i calcoli su una sfera. Per questo OpenLayers offre la classe ol.Sphere, che permette di calcolra distanze e aree con i metodi della trigonometria sferica tramite i metodi ol.Sphrere.getLength e ol.Sphere.getArea.

Se non diversamente specificato viene creata una sfera con raggio pari al raggio medio dell’ellissoide WGS84, ed i calcoli assumono che le geometrie siano rappresentate secondo il sistema di riferimento EPSG:3857.

var wgs84Sphere= new ol.Sphere();

var Firenze_Siena = (new ol.geom.LineString([
  [11.254167, 43.771389],
  [11.331389, 43.318333]
])).transform("EPSG:4326", "EPSG:3857");

var distanza_planare = Firenze_Siena.getLength(); // -> ~70.11 Km
var distanza_sferica = ol.Sphere.getLength(Firenze_Siena); // -> ~50.76 Km

Per questo calcolo OL implementa l’algoritmo dell’haversine . Per dei risultati più precisi si dovrebbe utilizzare l’ellissoide ad esempio con le formule di Vincenty ma in prima approssimazione i risultati forniti dal calcolo sferico sono molto più realistici di quelli ottenuti dalla formula pitagorica sul piano.

Scala

Una domanda che viene posta spesso dagli utenti dei servizi WebGIS è di poter visualizzare o impostare la scala geografica della mappa. Si capisce questa esigenza, data dalla possibilità di determinare esattamente tale valore sia su supporti fisici che all’interno dei software desktop GIS. Sul web questa richiesta si scontra con un problema fondamentale: non è possibile sapere esattamente la risoluzione (DPI - dots per inch) dello schermo su cui si sta visualizzando la pagina Web. Spieghiamo cosa significa…

Per poter determinare la dimensione di un elemento grafico rappresentato su supporto digitale è necessario sapere qual’è il rapporto tra il numero di pixel rappresentati sullo schermo e la loro effettiva dimensione metrica. Conoscendo questo rapporto, appunto i dots per inch (punti per pollice o, tramite conversione, per cm o metro), posso sapere la dimensione sullo schermo di un elemento grafico.

var centimetri = 20 // dimensione mappa in centimetri
var dimensione_pixels = 600; // dimensione mappa in pixel
var dpc = dimensione_pixels / centimetri // -> 30 risoluzione schermo in pixel per centimetro
var centimetri_pixel = 1 / dpc // centrimentri per pixel

Se a questo si abbina l’informazione della risoluzione della nostra mappa, che in OL significa il rapporto metri / pixel del contenuto della mappa, avremmo tutto quello che ci serve per sapere la scala della mappa:

var dimensione_metri = 1500 // dimensione in metri del contenuto della mappa
var dimensione_pixels = 600; // dimensione mappa in pixel
var risoluzione = dimensione_metri /dimensione_pixels; // risoluzione mappa in metri per pixel
var rapporto_scala = risoluzione / centimetri_pixel * 100 // -> 7500 rapporto di scala della mappa


"Scala di una mappa"

Questo in teoria, ma come abbiamo detto ci manca l’informazione di base: nessuno ci sa dire, all’interno di un browser web, i DPI dello schermo. Ne consegue che non è possibile determinare con precisione la scala di una mappa all’interno di una pagina Web. Per poter ottenere un valore indicativo si usano valori standard di DPI. Spesso si usa, ad esempio, il valore DPI ottenuto dallo standard WMS di OGC. WMS 1.3 definisce una dimensione standard del pixel di 0.28mm, da cui si può ricavare un DPI di 25.4/0.28 = 90.71 pixel / inch.

Risoluzione e zoom

Direttamente collegato al concetto di risoluzione geografica della mappa (metri / pixel) c’è quello di zoom. L’aumento del livello di zoom corrisponde ad una diminuzione del valore della risoluzione, perché a parità di pixel sarà rappresentata una porzione più piccola di mappa. In OpenLayers, se non definito altrimenti, i livelli di zoom sono calcolati automaticamente sulla base di un livello minimo e un livello massimo di zoom e un rapporto tra livelli successivi di un fattore 2. In altre parole ad ogni passaggio tra livelli di zoom adiacenti la risoluzione si raddoppia o si dimezza.

Negli esempi con una mappa con sistema di riferimento ESPG:3857 il calcolo delle risoluzioni (e quindi degli zoom) avviene più o meno così:

var estensione_proj = [-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244] // estensione su cui è definita la proiezione
var larghezza = 20037508.342789244 * 2; // larghezza totale in metri dell'estensione
var maxResolution = larghezza / 256 // -> 156543.03392804097 larghezza totale diviso la larghezza, in pixel, di una singola tile
var resolutions = [];
for(var i = 0; i < 29; i++) { // calcolo la risoluzione tra un livello minimo 0 e un massimo di 28 (sono il numero di default di livelli di zoom)
  resolutions.push(maxResolution / Math.pow(2,i)); // risoluzione / 2^0; risoluzione / 2^1; ecc.
};
console.log(resolutions); // stampo l'array nella console

Il risultato è che le risoluzioni della mappa precalcolate sono le seguenti:

0: 156543.03392804097
1: 78271.51696402048
2: 39135.75848201024
3: 19567.87924100512
4: 9783.93962050256
5: 4891.96981025128
6: 2445.98490512564
7: 1222.99245256282
8: 611.49622628141
9: 305.748113140705
10: 152.8740565703525
11: 76.43702828517625
12: 38.21851414258813
13: 19.109257071294063
14: 9.554628535647032
15: 4.777314267823516
16: 2.388657133911758
17: 1.194328566955879
18: 0.5971642834779395
19: 0.29858214173896974
20: 0.14929107086948487
21: 0.07464553543474244
22: 0.03732276771737122
23: 0.01866138385868561
24: 0.009330691929342804
25: 0.004665345964671402
26: 0.002332672982335701
27: 0.0011663364911678506
28: 0.0005831682455839253

Nel momento in cui andiamo a indicare, come nell’esempio precedente, un livello di zoom 10 stiamo dicendo alla mappa di settare la risoluzione a 152.874 metri / pixel. Qual’è la sua scala? Se usiamo il pixel standard (0.28 mm/pixel) la scala sarà approssimativamente 1:545978:

var rapporto_scala = 152.874 / 0.00028; // -> scala di riduzione 1:545978