Skip to content

Instantly share code, notes, and snippets.

@rclark
Last active January 28, 2024 01:18
Show Gist options
  • Star 91 You must be signed in to star a gist
  • Fork 35 You must be signed in to fork a gist
  • Save rclark/6908938 to your computer and use it in GitHub Desktop.
Save rclark/6908938 to your computer and use it in GitHub Desktop.
Leaflet WMS + GetFeatureInfo
<!doctype html>
<html>
<head>
<title>WMS GetFeatureInfo</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.css" />
<!--[if lte IE 8]>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.ie.css" />
<![endif]-->
<script src="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.js"></script>
<style type="text/css">
html, body, #map {
margin: 0px;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="L.TileLayer.BetterWMS.js"></script>
<script>
var map = L.map('map', {
center: [34,-111],
zoom: 7
});
var url = 'http://data.azgs.az.gov/arizona/wms';
L.tileLayer('http://{s}.tiles.mapbox.com/v3/rclark.map-wm3i8w1a/{z}/{x}/{y}.png').addTo(map);
L.tileLayer.betterWms(url, {
layers: 'azgs:mapunitpolys',
transparent: true,
format: 'image/png'
}).addTo(map);
</script>
</body>
</html>

There are a bunch of reasons why this is convoluted, mostly in building the URL to make the request:

  1. You have to rely on an AJAX request, this example uses jQuery
  2. To make a GetFeatureInfo request, you must provide a BBOX for a image, and the pixel coordinates for the part of the image that you want info from. A couple of squirrely lines of Leaflet code can give you that.
  3. Output formats. The info_format parameter in the request. We don't know a priori which will be supported by a WMS that we might make a request to. See Geoserver's docs for what formats are available from Geoserver. That won't be the same from WMS to WMS, however.
  4. WMS services return XML docs when there's a mistake in the request or in processing. This sends an HTTP 200, which jQuery doesn't think is an error.
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;
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})
.setLatLng(latlng)
.setContent(content)
.openOn(this._map);
}
});
L.tileLayer.betterWms = function (url, options) {
return new L.TileLayer.BetterWMS(url, options);
};
@nmtoken
Copy link

nmtoken commented Jan 20, 2022

@arminus there is no way to know what features exist (if any) in the underlying data that is used to create the WMS image, without querying the map layer or layers though a GetFeatureInfo request; a WMS map image has no attributes. You can though determine if a layer is intended to be queried by parsing the GetCapabilities response for the service and checking whether the layer is marked as queryable <Layer queryable="1". There's no reason particularly why a base map can't be queryable.

@emmexx
Copy link

emmexx commented Mar 8, 2022

I created a layer that uses cql_filter and env and the styling server side displays features based on said cql_filter and on values of env.
I had to add those 2 parameters in getFeatureInfoUrl to get BetterWMS getFeatureInfo to work correctly.

if(this.wmsParams.env)	
{
     params['env'] = this.wmsParams.env
}
if(this.wmsParams.cql_filter)	
{
     params['cql_filter'] = this.wmsParams.cql_filter
}

@ultramenid
Copy link

Super awesome!

Question: How do i select a single value of the wms? So, i want just one popup, for one value. Somewhere there is a selection needed, but how??

Please help :)

did you solve this issue?

@inzamambaig
Copy link

inzamambaig commented Jan 24, 2023

Super awesome!
Question: How do i select a single value of the wms? So, i want just one popup, for one value. Somewhere there is a selection needed, but how??
Please help :)

did you solve this issue?

@ultramenid Did you find any solution?

image

@inzamambaig
Copy link

@ultramenid I Found the solution. In getFeatureInfoUrl function just specify the attributes you want to see in propertyName parameter. e.g propertyName: "attr1,attr2,attr3,attr4,attr5"

@ragnarheidar
Copy link

Amazing! Works out of the box with jquery 3.6.3 and Leaflt 1.9.2. Thanks 👍

@ydrea
Copy link

ydrea commented Nov 4, 2023

I needed pictures in (html) popups, so I made the following changes:

  // with thumbs
	showGetFeatureInfo: function (
		err,
		latlng,
		content
		// signaturaFromUrl
	) {
		if (err) {
			console.log(err);
			return;
		}

		var tempDiv = document.createElement('div');
		tempDiv.innerHTML = content;
		var rows = tempDiv.querySelectorAll('tr');

		// var shouldFlyTo = false; // Initialize the flag

		for (var i = 0; i < rows.length; i++) {
			var row = rows[i];
			var cells = row.querySelectorAll('th, td');

			// console.log(header);

			if (cells.length >= 2) {
				var header = cells[0].textContent.trim();
				var value = cells[1].textContent.trim();

				if (header === 'signatura') {
					console.log('Header:', header);
					console.log('Value:', value);
				}

				if (header === 'foto_url') {
					var imgUrl = value.replace(/["']/g, '');
					var thumbUrl;
					var publicIndex = imgUrl.indexOf('/public/');
					if (publicIndex !== -1) {
						thumbUrl =
							imgUrl.slice(0, publicIndex + '/public/'.length) +
							'thumbs/' +
							imgUrl.slice(publicIndex + '/public/'.length);
					}
					console.log(imgUrl, thumbUrl);

					var newRow = document.createElement('tr');
					newRow.style.width = '100%';
					var newCell = document.createElement('td');
					newCell.colSpan = 2;
					var image = document.createElement('img');
					image.src = thumbUrl;
					image.alt = 'thumb';
					image.style.minWidth = '300px';
					image.style.width = '100%';
					image.style.marginTop = '8px';

					newCell.appendChild(image);
					newRow.appendChild(newCell);
					//
					row.parentNode.replaceChild(newRow, row);
				} else {
					if (value === 'NULL' || value === '') {
						row.parentNode.removeChild(row);
					}
				}
			}
		}

		var filteredContent = tempDiv.innerHTML;
		// console.log(filteredContent);
		L.popup({ minWidth: '500px', width: '100%' })
			.setLatLng(latlng)
			.setContent(filteredContent)
			.openOn(this._map);

	},


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment