L’interrogazione di uno strato WMS avviene, secondo la specifica OGC, soltanto su base puntuale. Ovvero non è possibile sfruttare i servizi WMS per eseguire interrogazione relative ad un’area o in base ad una geometria (esempio poligono). Per questo tipo d’interrogazioni possono essere usati i servizi WFS oppure, come spesso avviene, API proprie esposte tramite applicazioni server specifiche.

L’interrogazione delle feature, presenti ad una determinata coordinata per un dato layer, avviene tramite richieste di tipo GetFeatureInfo. I principali parametri stadard della richiesta sono:

  • QUERY_LAYERS: elenco di uno o più layer, esposti dal servizio, che si vuole interrogare
  • I,J oppure X,Y: coordinate immagine (pixel) del punto dell’immagine cliccato per l’interrogazione. I,J sono i parametri dalla versione 1.3.0 dello standard WMS
  • WIDTH,HEIGHT: altezza e larghezza dell’immagine interrogata
  • BBOX: coordinate geografiche dell’area corrispondente alla porzione di immagine interrogata
  • CRS: sistema di riferimento delle coordinate BBOX
  • FEATURE_COUNT: numero massimo di elementi che si vogliono ottenere dall’interrogazione

Ogni server cartografico poi aggiunge ulteriori parametri custom, non standard, per specificare aspetti dell’interrogazione non previsti dallo standard, ad esempio per impostare filtri sulle feature che si vogliono interrogare.

Vediamo passo passo come costruire l’interrogazione in OpenLayers e come gestirne i risultati. Per prima cosa dobbiamo ottenere le coordinate della posizione della mappa cliccata dall’utente:

map.on('click', function(evt) {
    queryLayer(evt.coordinate); // chiamiamo una nostra funzione per eseguire l'interrogazione
});

Nella nostra funzione costruiamo la chiamata e la eseguiamo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function queryLayer(coordinate){
    var resolution = map.getView().getResolution();
    var projection = map.getView().getProjection();
    var info_format = "text/gml";
    var queryUrl = catasto.getSource().getGetFeatureInfoUrl(coordinate, resolution, projection, {
        INFO_FORMAT: info_format
    });

    queryUrl = utils.getProxiedUrl(queryUrl, PROXY_URL);
    
    // eseguo la chiamata
    $.get(queryUrl, function(response){
        var gml = utils.getGML(response);
        var format = new ol.format.WMSGetFeatureInfo(({
          layers: ["rt_cat.idcatfabbr.rt"]
        }));
        var features = format.readFeatures(gml);
        if (feature.length) {
            showResults(features);
        }
    })
};

L’URL della richiesta GetFeatureInfo la costruiamo tramite la funzione ol.source.ImageWMS.getGetFeatureInfoUrl, che richiede una serie di parametri minimi che sono la risoluzione della mappa al momento dell’interrogazione, le coordinate geografiche del punto interrogato, il CRS con cui verrà calcolato il BBOX della richiesta, e il formato della risposta. Il formato dovrà essere uno tra quelli esposti dal servizio WMS, verificabile tramite una richiesta GetCapabilities. Per il layer “catasto” dei servizi WMS di Geoscopio (Regione Toscana), che usiamo nell’esempio, le capabilities ci indicano che i formati disponibili sono “text/gml”, “text/html” e “text/plain”. Nel nostro esempio useremo il formato GML.

Un problema che si pone quando si cerca di accedere a risorse servite su un dominio diverso da quello della nostra applicazione, e quindi da cui origina il nostro client, sono le policy di Cross-Origin. I browser, per questioni di sicurezza, bloccano qualsiasi richiesta AJAX (ovvero tramite XMLHttpRequest) verso domini diversi dal proprio, e anche nel caso di una richiesta GetFeatureInfo eseguita da OL non avremmo modo di ottenere i risultati cercati. Le soluzioni più comuni per ovviare al problema sono:

  • far sì che il server che vogliamo interrogare abbia abilitato gli header CORS per le risorse richieste. Questi header HTTP fanno sì che il browser non blocchi la chiamata e lasci passare la richiesta. Il server si assume la responsabilità di far accedere alle risorse anche dal di fuori del proprio dominio. Ovviamente questo è pssibile solo se si può agire sul server.
  • usare un proprio proxy. Dirigiamo le richieste del client ad un proxy esposto dal nostro server (anche un semplice script PHP, come faremo noi), il quale le girerà poi al server di destinazione. Questo tornerà la risposta al proxy il quale infine le girerà al nostro client. In questo modo bypassiamo le restrizioni di sicurezza del browser perché le richieste saranno rivolte ad un oggetto che si trova sullo stesso dominio del client.
  • un approccio ormai sempre meno usato prevede l’uso di callback JSONP. Sorvoliamo su questa soluzione perché normalmente le precedenti sono le più efficaci e semplici da realizzare.

Alla riga 9 usiamo una funzione di utilità (inclusa nel materiale degli esercizi) per costruire la URL “proxata”. Quando a riga 12 eseguiamo la richiesta, questa verrà diretta al nostro proxy che, in modo del tutto trasparente per il client, ritornerà i risultati generati dal servizio WMS.

Il response normalmente sarà un documento GML (perché così abbiamo richiesto) ma ci può essere il raro caso in cui la risposta sia di tipo Multipart. In questo caso il parser di OL non riuscirebbe ad interpretarne il contenuto, per cui per prima cosa a riga 13 estraiamo il GML, sempre tramite una nostra funzione di utilità.

A questo punto siamo pronti per dare in pasto il GML ad un oggetto ol.format.WMSGetFeatureInfo, la cui unica funzione è quella di interpretare un documento GML ed estrarne i contenuti in forma di array di oggetti ol.Feature. Se effettivamente l’array è popolato (ovvero se si sono ottenuti risultati) siamo pronti per farne l’uso previsto. In questo caso li passiamo ad una nostra funzione in cui li mostreremo su mappa e nella pagina HTML.

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
29
30
var resultsLayer; // layer che conterrà le geometrie dei risultati, per mostrarli in mappa
function showResults(features) {
    if (!resultsLayer) {
        resultsLayer = new ol.layer.Vector({
            source: new ol.source.Vector()
        })
        map.addLayer(resultsLayer);
    }
    
    var resultsSource = resultsLayer.getSource();
    resultsSource.clear();
    resultsSource.addFeatures(features);

    var resultsEl = $("#results");
    resultsEl.empty();
    var feature = features[0];
    var attributes = feature.getProperties();
    
    var tableEl = $("<table>");
    for (key in attributes) {
        if (key == "geometryProperty") {
            continue;
        }
        var trEl = $("<tr>");
        trEl.append($("<td class='fieldname'>" + key + "</td>"));
        trEl.append($("<td>" + attributes[key] + "</td>"));
        tableEl.append(trEl);
    }
    resultsEl.append(tableEl);
}

A riga 1 abbiamo dichiarato una variabile non inizializzata, a cui assegneremo il layer vettoriale tramite il quale mostrare su mappa la geometria della feature ottenuta dall’interrogazione (ammesso che il servizio WMS fornisca la geometria con i risultati).

Se è la prima volta che la nostra funzione viene chiamata costruiamo il layer con il relativo ol.source.Vector vuoto e lo inseriamo nella mappa (righe 4-7). Prendiamo quindi il riferimento alla sorgente vettoriale del layer dei risultati, la puliamo (nel caso fossero presenti feature di risultati precedenti) e aggiungiamo i nuovi elementi ottenuti dall’interrogazione.

Nelle successive righe si costruisce una tabella HTML per mostrare gli attributi della feature. Sorvolando sugli aspetti relativi a jQuery, quello che facciamo è prendere la prima delle eventuali N features ritornate dal server (riga 16), ne prendiamo gli attributi che, ricordiamo, sono in forma di Object Literal Javascript contenente i campi/valori della feature. A riga 20 cicliamo sulle chiavi dell’oggetto attributes (ovvero i nomi dei campi), escludiamo l’eventuale campo “geometryProperty” (presente normalmente nei risultati di una GetFeatureInfo), e usiamo le variabili key e attributes[key] per popolare le celle della tabella dei risultati.

Si tratta chiaramente di un esempio molto semplificato rispetto a quello che scriveremmo in un client reale, perché non vengono gestiti eventuali errori e tanti altri “corner case” che spesso riscontriamo nelle varie casistiche che si possono incontrare.