WMS 'GetFeatureInfo' function in LeafletJS

This is an example of an easy method to implement a custom GetFeatureInfo function on any WMS layer loaded with Leaflet JS.

Screenshot

Technologies Used: Web (HTML,JavaScript,CSS) and Leaflet JS

Introduction

The WMS GetFeatureInfo is not supported by Leaflet out of the box, despite the fact it is very useful and provides equivalent performance and accuracy to WFS GetFeature. Furthermore, it saves the step of having to get the WFS data as JSON and converting it to a Leaflet layer using L.GeoJSON.

Before attempting to add this functionality to your map, make sure the WMS service you are using is in fact queryable. This can be determined by a GetCapabilities request to the WMS service (e.g. GeoServer), specifing the service, WMS version, and the request. Example:

              https://demo.boundlessgeo.com/geoserver/ows?service=WMS&version=1.3.0&request=GetCapabilities
            

In the XML file generated, verify that for the layer you wish to query the queryable parameter is equal to 1. Likewise, keep in mind the CRS of the layer as this must be match the CRS in the GetFeatureInfo request:

DEM Figure

If it is, then you can query the layer using a GetFeatureInfo request. The following JavaScript code shows a Leaflet implementation:

Step 1: Load several WMS layers as a tile layer


              var wmsUrl = 'https://demo.boundlessgeo.com/geoserver/ows?';
            

              var countryLayer = L.tileLayer.wms(wmsUrl, {
                layers: 'opengeo:countries',
                transparent: false,
                format: 'image/png',
                maxZoom: 20
              }).addTo(map);

            var roadsLayer = L.tileLayer.wms(wmsUrl, {
                layers: 'ne:ne_10m_roads',
                transparent: true,
                format: 'image/png',
                maxZoom: 20
               }).addTo(map);
            
Step 2: Create a function that formulates a dynamic GetFeatureInfo request to the WMS based on a clicked location
  function identify (e,layers) {
                     var BBOX = map.getBounds().toBBoxString();
                     var WIDTH = map.getSize().x;
                     var HEIGHT = map.getSize().y;
                     var X = Math.round(map.layerPointToContainerPoint(e.layerPoint).x);
                     var Y = Math.round(map.layerPointToContainerPoint(e.layerPoint).y);
                     var getUrl = wmsUrl+'SERVICE=WMS&VERSION=1.3.0&REQUEST=GetFeatureInfo'+'&layers='+layers+'&BBOX='+BBOX+'&FEATURE_COUNT=5&info_format=application/json&HEIGHT='+HEIGHT+'&WIDTH='+WIDTH+'&query_layers='+layers+'&SRS=EPSG:4326&X='+X+'&Y='+Y;

    $.ajax({
       url: getUrl,
       success: function (data, status, xhr) {
          var result = data; //all the GetFeatureInfo result compiled into one json file
          var txt ="";
          for (i=0; i<result.features.length; i++) {
             txt = txt.concat(""+result.features[i].id.split('.')[0].toUpperCase()+ ":"+ '
    ' + formatFeaturecontent(result.features[i]) + '
    '); //JSON.stringify(result.features[i].properties) - get ALL Data
          };

       if (txt != ""){
          L.popup()
          .setLatLng(e.latlng)
          .setContent(txt)
          .openOn(map);
        }

       },
       error: function (xhr, status, error) {
         console.log(error);
       }
     });
     }
   
Step 3: Create function that returns and formats content properties differently for each layer. If layer is not addressed by a ‘case’, return all properties found

            function formatFeaturecontent(featureJSON) {
               layerName = featureJSON.id.split('.')[0]; //Remove everything after the period in the layer name
               switch(layerName) { //Extract only relevant data for each layer finding
                  case "countries":
                     return "Name: "+featureJSON.properties.sovereignt + "
            "+ "Type: "+ featureJSON.properties.type+"
            "+ "Population Estimate: "+ featureJSON.properties.pop_est.toLocaleString()+"
            "+ "Income Group: "+ featureJSON.properties.income_grp+"
            ";
                  case "ne_10m_roads":
                     return "Road Name: "+featureJSON.properties.name + "
            "+ "Length in KM: "+ featureJSON.properties.length_km+"
            "+ "Road Type: "+ featureJSON.properties.type+"
            "+ "Country: "+ featureJSON.properties.sov_a3+"
            "+ "Continent: "+ featureJSON.properties.continent;
                  default:
                     return JSON.stringify(featureJSON.properties);
               }
             }
          
Step 4: Create a function to get all layer names from a user-clicked location

              function getLayerNames(clickedLocation) {
                var layersUrl =[];
                var layers = clickedLocation.target._layers
                for (var layer in layers){
                    if (layers[layer]._url==wmsUrl) {
                        layersUrl.push(layers[layer].options.layers);
                    }
                 };
             return layersUrl.toLocaleString();
            }
          
Step 5:
Finally, add a click event to the map to trigger the identify function:

            var clickLocation;
            map.on('click', function(e) {
               clickedLocation=e;
               layers=getLayerNames(e);
               identify(e, layers);
            });
          

There you have it! The identify function should run every time the user clicks on a location with one or more of the WMS layers. Furthermore, if a click event contains more result from more than one WMS layer, this implementation will aggregate this data and display in the pop-up.

Back to my projects →