OpenLayers provides the useful
WMSGetFeatureInfo control. It's designed to work with the standard WMS
GetFeatureInfo request. As per the standard, the control supports querying multiple layers via setting the
layers property.
It's often necessary to define client-side filters for WMS layers, to display only a subset of the layer data in the backing feature type. Usually the filters need to be defined dynamically, based on the application context. When using
GeoServer as the web mapping engine a convenient (but non-standard) way of doing this is to use the
CQL_FILTER WMS parameter. (One might reasonably ask why there isn't an equally simple way to do this in the WMS standard itself, but that's another story). In OpenLayers this parameter can be added dynamically to a layer via the
mergeNewParams method:
lyr.mergeNewParams({'CQL_FILTER': "filter expression" });
Naturally it is necessary to have the GetFeatureInfo control respect the layer filters as well. This is straightforward in the case of a
single layer. The GeoServer CQL_FILTER parameter can be supplied using the
vendorParams property on the WMSGetFeatureInfo control:
infoControl.vendorParams = { 'CQL_FILTER': 'filter expression'};
Since the CQL_FILTER parameter supports a list of filters, it's also straightforward to filter multiple layers
as long as the list of layers queried is static:
infoControl.vendorParams = { 'CQL_FILTER': 'filt-1; filt-2; filt-3'};
But WMSGetFeatureInfo also provides the useful ability to query only
visible layers (via the
queryVisible property). This makes things much trickier, since the list of filter expressions must match the list of layers provided in the
QUERY_LAYERS parameter. There's no built-in way of doing this in OpenLayers itself (not surprisingly, since the CQL_FILTER parameter syntax is specific to GeoServer only).
One way to do this is to build the CQL_FILTER parameter value dynamically uisng the CQL_FILTERs defined for the visible layers. This can be done when the control is invoked, via hooking the
beforegetfeatureinfo event.
Here's a code snippet to do this:
var infoControl;
function initInfoControl()
{
infoControl = new OpenLayers.Control.WMSGetFeatureInfo({
url: wms_url,
title: 'Identify features by clicking',
layers: [
layers....
],
queryVisible: true,
maxFeatures: 3,
infoFormat: 'application/vnd.ogc.gml'
});
infoControl.events.register(
"beforegetfeatureinfo", null, onBeforeGetFeatureInfo);
infoControl.events.register
("getfeatureinfo", null, onGetFeatureInfo);
map.addControl(infoControl);
infoControl.activate();
}
function onBeforeGetFeatureInfo(event)
{
// build CQL_FILTER param list from active info layer CQL_FILTER params
var layers = infoControl.findLayers();
var filter = "";
for (var i = 0, len = layers.length; i < len; i++) {
if (i > 0) filter += ";";
var lyrCQL = layers[i].params.CQL_FILTER
if (lyrCQL != null) {
filter += lyrCQL;
}
}
infoControl.vendorParams = { 'CQL_FILTER': filter };
}
Although climbing up the OpenLayers learning curve often feels like a big struggle, it's important to recognize the very wide set of requirements that the library is trying to address. Due to the nature of spatial data, user interfaces and protocols dealing with it are inherently complex. The more I work with OpenLayers, the more appreciation I have for the fine balance between simplicity and flexibility the designers have achieved. (And if that sounds like I do
not subscribe to the "
Spatial is not special" canard, you're hearing me right!).