I have a HTML/Leaflet page utilising BetterWMS script (https://gist.github.com/rclark/6908938). The script returns GetFeatureInfo over WMS layers. 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 my full html file including BetterWMS script and relevant WMSs (all queryable).

Any tips about how to enhance the BetterWMS script so as to use with WMS layers published by servers in question?

<!DOCTYPE html>
    <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>

        html, body {
            height: 100%;
            margin: 0;
        #map {
            width: 1200px;
            height: 800px;

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


    // 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);
          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) {
      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 })

    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);


1 Answer 1


It has nothing to do with QGIS Server or MapServer but how these two sample servers are set up:

For both the CORS-Header 'Access-Control-Allow-Origin' is missing:


The browser console shows the following message:

enter image description here

I'm afraid the only thing you can do is to ask the server administrators if they are willing to set this header.

  • Alternative solution would be to use local proxy server to fetch GetFeatureInfo.
    – TomazicM
    Commented Jun 22, 2021 at 12:27
  • Thank you very much @pathmapper. I had a chance to test it on another Qgis Server. As you mentioned, after enabling CORS on Apache, feature info started to be obtained.
    – Melanie
    Commented Jun 25, 2021 at 16:56

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