2

I have a HTML/Leaflet page utilising BetterWMS script below. The script returns GetFeatureInfo over WMS layer. Unfortunately it is not working with WMSs published by Qgis Server and MapServer, although ArcGIS Server and GeoServer services work fine. Below, you can find example services published by QGIS/MapServer.

QGIS Server Service:
https://ows.geo.hu-berlin.de/cgi-bin/qgis_mapserv.fcgi?request=GetCapabilities&service=WMS
layer: sen2

MapServer Service:
https://demo.mapserver.org/cgi-bin/wms?request=GetCapabilities&service=WMS
layer: cities

Any tips about how to fix the script so as to use with these services as well?

L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({
  
  onAdd: function (map) {
    // Triggered when the layer is added to a map.
    //   Register a click listener, then do all the upstream WMS things
    L.TileLayer.WMS.prototype.onAdd.call(this, map);
    map.on('click', this.getFeatureInfo, this);
  },
  
  onRemove: function (map) {
    // Triggered when the layer is removed from a map.
    //   Unregister a click listener, then do all the upstream WMS things
    L.TileLayer.WMS.prototype.onRemove.call(this, map);
    map.off('click', this.getFeatureInfo, this);
  },
  
  getFeatureInfo: function (evt) {
    // Make an AJAX request to the server and hope for the best
    var url = this.getFeatureInfoUrl(evt.latlng),
      showResults = L.Util.bind(this.showGetFeatureInfo, this);
    $.ajax({
      url: url,
      success: function (data, status, xhr) {
        var err = typeof data === 'string' ? null : data;
        //Fix for blank popup window
        var doc = (new DOMParser()).parseFromString(data, "text/html"); 
        if (doc.body.innerHTML.trim().length > 0)
          showResults(err, evt.latlng, data);
      },
      error: function (xhr, status, error) {
        showResults(error);
      }
    });
  },
  
  getFeatureInfoUrl: function (latlng) {
    // Construct a GetFeatureInfo request URL given a point
    var point = this._map.latLngToContainerPoint(latlng, this._map.getZoom()),
        size = this._map.getSize(),
        
        params = {
          request: 'GetFeatureInfo',
          service: 'WMS',
          srs: 'EPSG:4326',
          styles: this.wmsParams.styles,
          transparent: this.wmsParams.transparent,
          version: this.wmsParams.version,      
          format: this.wmsParams.format,
          bbox: this._map.getBounds().toBBoxString(),
          height: size.y,
          width: size.x,
          layers: this.wmsParams.layers,
          query_layers: this.wmsParams.layers,
          info_format: 'text/html'
        };
    
    params[params.version === '1.3.0' ? 'i' : 'x'] = point.x;
    params[params.version === '1.3.0' ? 'j' : 'y'] = point.y;
    
    return this._url + L.Util.getParamString(params, this._url, true);
  },
  
  showGetFeatureInfo: function (err, latlng, content) {
    if (err) { console.log(err); return; } // do nothing if there's an error

    // Otherwise show the content in a popup, or something.
    L.popup({ maxWidth: 800, minWidth: 0, autoPan: false })
      .setLatLng(latlng)
      .setContent(content)
      .openOn(this._map);
  }
});

L.tileLayer.betterWms = function (url, options) {
  return new L.TileLayer.BetterWMS(url, options);  
};

Here is my full html file including scripts and WMSs (all queryable):

<!DOCTYPE html>
<html>
<head>
    
    <title>Layers Control Tutorial - Leaflet</title>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    <link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />

    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/>
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>

    <style>
        html, body {
            height: 100%;
            margin: 0;
        }
        #map {
            width: 1200px;
            height: 800px;
        }
    </style>

    
</head>
<body>

<div id='map'></div>

<script>

    // BetterWMS (https://gist.github.com/rclark/6908938)
    L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({
      
      onAdd: function (map) {
        // Triggered when the layer is added to a map.
        //   Register a click listener, then do all the upstream WMS things
        L.TileLayer.WMS.prototype.onAdd.call(this, map);
        map.on('click', this.getFeatureInfo, this);
      },
      
      onRemove: function (map) {
        // Triggered when the layer is removed from a map.
        //   Unregister a click listener, then do all the upstream WMS things
        L.TileLayer.WMS.prototype.onRemove.call(this, map);
        map.off('click', this.getFeatureInfo, this);
      },
      
      getFeatureInfo: function (evt) {
        // Make an AJAX request to the server and hope for the best
        var url = this.getFeatureInfoUrl(evt.latlng),
          showResults = L.Util.bind(this.showGetFeatureInfo, this);
        $.ajax({
          url: url,
          success: function (data, status, xhr) {
            var err = typeof data === 'string' ? null : data;
            //Fix for blank popup window
            var doc = (new DOMParser()).parseFromString(data, "text/html"); 
            if (doc.body.innerHTML.trim().length > 0)
              showResults(err, evt.latlng, data);
          },
          error: function (xhr, status, error) {
            showResults(error);
          }
        });
      },
      
      getFeatureInfoUrl: function (latlng) {
        // Construct a GetFeatureInfo request URL given a point
        var point = this._map.latLngToContainerPoint(latlng, this._map.getZoom()),
            size = this._map.getSize(),
            
            params = {
              request: 'GetFeatureInfo',
              service: 'WMS',
              srs: 'EPSG:4326',
              styles: this.wmsParams.styles,
              transparent: this.wmsParams.transparent,
              version: this.wmsParams.version,      
              format: this.wmsParams.format,
              bbox: this._map.getBounds().toBBoxString(),
              height: size.y,
              width: size.x,
              layers: this.wmsParams.layers,
              query_layers: this.wmsParams.layers,
              info_format: 'text/html'
            };
        
        params[params.version === '1.3.0' ? 'i' : 'x'] = point.x;
        params[params.version === '1.3.0' ? 'j' : 'y'] = point.y;
        
        return this._url + L.Util.getParamString(params, this._url, true);
      },
      
      showGetFeatureInfo: function (err, latlng, content) {
        if (err) { console.log(err); return; } // do nothing if there's an error

        // Otherwise show the content in a popup, or something.
        L.popup({ maxWidth: 800, minWidth: 0, autoPan: false })
          .setLatLng(latlng)
          .setContent(content)
          .openOn(this._map);
      }
    });

    L.tileLayer.betterWms = function (url, options) {
      return new L.TileLayer.BetterWMS(url, options);  
    };
    // BetterWMS
        
    var mbAttr = 'Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, ' +
            'Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
        mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw';

    var grayscale   = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr}),
        streets  = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr});
        
    var arcgis = L.tileLayer.betterWms('https://nowcoast.noaa.gov/arcgis/services/nowcoast/forecast_meteoceanhydro_pts_zones_geolinks/MapServer/WMSServer',{
        maxZoom: 21, format: 'image/png', layers: '1', transparent: true});

    var geoserver = L.tileLayer.betterWms('https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Land_Cover_L48/wms',{
        maxZoom: 21, format: 'image/png', layers: 'NLCD_2016_Land_Cover_L48', transparent: true});      
        
    var qgis = L.tileLayer.betterWms('https://ows.geo.hu-berlin.de/cgi-bin/qgis_mapserv.fcgi',{
        maxZoom: 21, format: 'image/png', layers: 'sen2', transparent: true});  

    var mapserver = L.tileLayer.betterWms('https://demo.mapserver.org/cgi-bin/wms',{
        maxZoom: 21, format: 'image/png', layers: 'cities', transparent: true});    

    var map = L.map('map', {
        center: [40, -98],
        zoom: 5,
        layers: [grayscale]
    });

    var baseLayers = {
        "Grayscale": grayscale,
        "Streets": streets,
    };

    var overlays = {
        "Arcgis Server": arcgis,
        "Geoserver": geoserver,
        "Qgis Server": qgis,
        "MapServer": mapserver,
    };

    L.control.layers(baseLayers, overlays, { collapsed: false }).addTo(map);
</script>



</body>
</html>
8
  • 1
    Can you give example GetFeatureInfo requests, that are generated by your script for those services.
    – nmtoken
    Commented Jun 19, 2021 at 6:11
  • You have a srs parameter, but note for a WMS version 1.3.0 the name of the parameter is crs
    – nmtoken
    Commented Jun 19, 2021 at 6:13
  • You have hard coded epsg:4326, are your maps in that projection,
    – nmtoken
    Commented Jun 19, 2021 at 6:16
  • Any errors in the browser debugger console or network section?
    – TomazicM
    Commented Jun 19, 2021 at 9:43
  • I attached my html file above. I hope it helps..
    – Melanie
    Commented Jun 19, 2021 at 15:29

1 Answer 1

2

I managed to get L.TileLayer.BetterWMS extension working with both WMS services.

First service https://ows.geo.hu-berlin.de/cgi-bin/qgis_mapserv.fcgi?request=GetCapabilities&service=WMS has only one CRS available, and that's unprojected EPSG:4326, so map has to be set to that one. Second problem with this service is CORS problem when requesting feature info. For that purpose I used original code for L.TileLayer.BetterWMS extension, which has also options for using proxy server for such cases. One line had to be corrected (see in the code below). But of course you need simple proxy server for this solution. Correction was also needed in pixel coordinates sent. They have to be rounded.

So code used for L.TileLayer.BetterWMS extension was:

L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({
  onAdd: function (map) {
    L.TileLayer.WMS.prototype.onAdd.call(this, map);
    map.on('click', this.getFeatureInfo, this);
  },
  onRemove: function (map) {
    L.TileLayer.WMS.prototype.onRemove.call(this, map);
    map.off('click', this.getFeatureInfo, this);
  },
  getFeatureInfo: function (evt) {
    console.log('click');
    var url = this.getFeatureInfoUrl(evt.latlng),
        showResults = L.Util.bind(this.showGetFeatureInfo, this);
    $.ajax({
      url: url,
      success: function (data, status, xhr) {
        var err = typeof data === 'string' ? null : data;
        showResults(err, evt.latlng, data);
      },
      error: function (xhr, status, error) {
        showResults(error);  
      }
    });
  },
  getFeatureInfoUrl: function (latlng) {
    var point = this._map.latLngToContainerPoint(latlng, this._map.getZoom()),
        size = this._map.getSize(),
        params = {
          request: 'GetFeatureInfo',
          service: 'WMS',
          srs: 'EPSG:4326',
          styles: this.wmsParams.styles,
          transparent: this.wmsParams.transparent,
          version: this.wmsParams.version,      
          format: this.wmsParams.format,
          bbox: this._map.getBounds().toBBoxString(),
          height: size.y,
          width: size.x,
          layers: this.wmsParams.layers,
          query_layers: this.wmsParams.layers,
          info_format: 'text/html'
        };
    params[params.version === '1.3.0' ? 'i' : 'x'] = Math.round(point.x);  // rounding added
    params[params.version === '1.3.0' ? 'j' : 'y'] = Math.round(point.y);  // rounding added
    
    var url = this._url + L.Util.getParamString(params, this._url, true);
    
    if(typeof this.wmsParams.proxy !== "undefined") {
      console.log(this.wmsParams.proxy, this.wmsParams.proxyParamName);
      if(typeof this.wmsParams.proxyParamName == "undefined") { // corrected, was !==
        this.wmsParams.proxyParamName = 'url';
      }
      _proxy = this.wmsParams.proxy + '?' + this.wmsParams.proxyParamName + '=';
      url = _proxy + encodeURIComponent(url);
    } 
    return url;
  },
  
  showGetFeatureInfo: function (err, latlng, content) {
    if (err) { console.log(err); return; }
    L.popup({ maxWidth: 800})
      .setLatLng(latlng)
      .setContent(content)
      .openOn(this._map);
  }
});

L.tileLayer.betterWms = function (url, options) {
  return new L.TileLayer.BetterWMS(url, options);  
};

Code for layer display:

var map = L.map('map', {
  crs: L.CRS.EPSG4326,
  center: [34,-111],
  zoom: 6
});

L.tileLayer('https://api.maptiler.com/maps/basic-4326/256/{z}/{x}/{y}.png?key=myKey').addTo(map);

var url = 'https://ows.geo.hu-berlin.de/cgi-bin/qgis_mapserv.fcgi';      
L.tileLayer.betterWms(url, {
  layers: 'sen2',
  transparent: true,
  format: 'image/png',
  proxy: 'lib/php/proxy.php',
  proxyParamName: 'site'
}).addTo(map);

Result:

enter image description here

The second WMS service https://mrdata.usgs.gov/services/mrds?request=GetCapabilities&service=WMS worked out of the box, you just to have in mind zoom levels available for particular layer. Layer mrds-low is available for zoom levels 5 or less, layer mrds-high is available for zoom levels 6 or higher.

To use both in the same map, code could look something like this:

var map = L.map('map', {
  center: [34,-111],
  zoom: 6
});

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);

var url = 'https://mrdata.usgs.gov/services/mrds';      
L.tileLayer.betterWms(url, {
  layers: 'mrds-high',
  transparent: true,
  format: 'image/png',
  minZoom: 6
}).addTo(map);

L.tileLayer.betterWms(url, {
  layers: 'mrds-low',
  transparent: true,
  format: 'image/png',
  maxZoom: 5
}).addTo(map);

Result (formatting of info would require some tweaking):

enter image description here

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.