How do I highlight a feature in my map?

This really depends on what you mean by a "feature" and what you mean by "hightlighting". Are the features vector features (say a WFS layer) or are they WMS so you have to fetch the feature separately? Are you familiar with WMS and SLD styling? Do you need the feature to behave when someone mouses over it or clicks on it? All of these questions are important in deciding which highlighting technology is appropriate for your case.


WFS features overlaid atop a WMS layer

Overview

If you have a WMS Layer (or something equally "rasterish") and want to overlay a Vector feature over top of it, this would be your method.

Your map contains a Vector layer, which contains no features. At some later point in time, you make a WFS GetFeature call to fetch features' geometry (the code below is intended for multiple features, just remember not to push it). The WFS results are passed to the Vector layer, and are drawn.

Pros

  • Vector layer means client-side styles.
  • Vector layer means the ability of the features to react to mouse events such as hovering and clicking.
  • It is also very easy to accomplish.

Cons

  • Requires that the same data layer be available via WFS (for fetching the vector info) and via WMS (or whatever you're using for the image) so that the overlaid vector feature matches the raster-drawn feature -- then again, this may not be a concern if you intend for the two layers to mismatch.
  • Has a low limit on the number and complexity of features. More than a few hundred vertices, and you're likely to crash their browser. Remember that transferring vector data is inherently slow.
  • Vector layers do not support labelling of the highlighted features. That would require some other mechanism for the labels.

Code

  // define a blank Vector layer and add it to the map
  var highlight_style = { fillColor:'#99CCFF', strokeColor:'#3399FF', fillOpacity:0.7 };
  hilites = new OpenLayers.Layer.Vector("Highlighted",
                {isBaseLayer:false, features:[], visibility:true, style:highlight_style}
            );
  map.addLayer(hilites);

  // define the WFS server which will fill requests
  var wfs_url        = '/cgi-bin/mapserv?map=/maps/spraywatch2/wms/mapfile.map&SERVICE=WFS&VERSION=1.0.0';
  /* highlightFeatures() makes the WFS request, then highlight_them() callback does the real work
   * Args for WFS filter: typename, attribute, value
   */
  function highlightFeatures(typename,attribute,value) {
   var wfsurl = wfs_url + '&REQUEST=getfeature&typename=' + typename +
           '&Filter=<Filter><PropertyIsEqualTo><PropertyName>'+attribute+'</PropertyName><Literal>'+value+'</Literal></PropertyIsEqualTo></Filter>';
   OpenLayers.loadURL(wfsurl,'',null,highlight_them);
  }
  function highlight_them(response) {
                          // use the GML parser to turn the XML into a list of Feature objects
                          var features = new OpenLayers.Format.GML().read(response.responseText);
                          // have the Vector layer purge its feature list, replace them with the new ones
                          hilites.destroyFeatures();
                          hilites.addFeatures(features);
                          hilites.setVisibility(true);
                       };
   }

And an example of usage would be:

  highlightFeatures('parcels','parcel_id','123456')

Highlighting with WMS atop WMS, using Mapserver

Overview

If you have a WMS or Mapserver layer, and would like to simulate highlighting by drawing a second Mapserver layer, this could be your method.

The trick is that Mapserver can accept arbitrary params in a mode=map query, and these params can be interpolated into the mapfile, e.g. into a FILTER definition. We have a Mapserver layer containing FILTER "id IN (%ids%)", we set the JavaScript value of ids and regenerate the layer's URL, then call layer.redraw() to update the highlighting.

Pros

  • Does not have an implicit limit on the number of vertices nor features, so is suitable for numerous complex polygons.
  • Can often be faster than WFS, as there's less data being transferred.
  • Does not require a WFS server.
  • The Mapserver layer could easily incorporate labels as well as the highlighting.

Cons

  • Only works if your WMS server is Mapserver.
  • The resulting layer is WMS, which means no spatial geometry effects, e.g. zooming to the highlighted area.

Code

Define the Mapserver layer in your mapfile. Note the FILTER clause, causing this layer's features to only match the as-yet-undefined list of ids on the query string.

  LAYER
    NAME "highlights"
    STATUS on
    TYPE polygon
    CONNECTIONTYPE postgis
    CONNECTION "host=localhost dbname=xxx user=xxx password=xxx"
    DATA "the_geom from parcels"
    PROCESSING "CLOSE_CONNECTION=DEFER"
    FILTER "id IN (0,%ids%)"

    PROJECTION
      "init=epsg:4326"
    END

    CLASS
      STYLE
        SYMBOL "polygon_solid"
        COLOR   153 204 255
        OUTLINECOLOR 51 153 255
      END
    END
  END

Define the Mapserver layer. Note the additional 'id' flag being passed to Mapserver, which matches that filter in the Mapserver layer.

  layers['highlight'] = new OpenLayers.Layer.MapServer("Highlighting",
                            "/cgi-bin/mapserv?map=/maps/mapfile.map",
                            {layers:'highlights', format:'image/png', 'ids':0 },
                            {isBaseLayer:false, visibility:true, opacity:0.5, transparent:true }
                        );

The following JavaScript defines a list of ID#s, and highlightSelected() will use that list to generate a new URL for the Mapserver layer. Given this, you can create a function which alters the contents of the selected_feature_ids arraym and then calls highlightSelected() to refresh the highlighting.

  var selected_feature_ids = [123,456,789];
  function highlightSelected() {
    var highlight_layer = layers['highlight'];
    var url = highlight_layer.getFullRequestString( {parcelids:selected_feature_ids.join(',')} );
    highlight_layer.url = url;
    highlight_layer.redraw();
  }

WMS layer atop WMS using OGC standards and SLD

Overview

If you have a WMS layer, and would like to simulate highlighting by drawing a second WMS layer, this could be your method.

The trick is that a WMS can serve one (or more) features by using a FILTER in an SLD.

Pros

  • OGC compliant, so should work on any WMS supporting SLD.
  • Does not have an implicit limit on the number of vertices nor features, so is suitable for numerous complex polygons.
  • Can often be faster than WFS, as there's less data being transferred.
  • Does not require a WFS server.
  • The WMS layer could easily incorporate labels as well as the highlighting.
  • It's possible to have different highlight-effects as the styling is includes in the SLD.

Cons

  • Only works if your WMS server accepts SLD.
  • The resulting layer is WMS, which means no spatial geometry effects, e.g. zooming to the highlighted area.
  • requires an extra layer in your mapfile (or other WMS), which is able to retreive ALL geometries from all layers.

Code

Set up a layer in your WMS with the name "query". Be sure that the data definition of this layer is such that all geometries of all layers in the WMS can be accessed (or at least all the geometries you want to be able to highlight). A mapserver example (Note the absence of a FILTER clause; the use of ows_exclude_items to prevent that this layer shows up in a getfeatureinfo-request):

LAYER
	name "query"

	GROUP "query"
	
	PROJECTION
	   "init=epsg:xxxx"
	END

	METADATA
		"ows_group_title"     	"query"
		"ows_title"           	"query"
		"ows_extent"		"xxxx"
		"wms_srs"		"xxxx"
		"ows_exclude_items"   	"all"
		"ows_feature_id"       	"xxxx"
    	END

	STATUS off 
	TYPE polygon
	
	CONNECTIONTYPE postgis
	CONNECTION "host=localhost user=xxx dbname=xxx"
	DATA "the_geom from parcels"
		
	CLASS
		STYLE
				OUTLINECOLOR 0 0 0
		END
	END
	OPACITY 100
	PROCESSING "CLOSE_CONNECTION=DEFER"

END

The layer setup:

var wms_url        = '/cgi-bin/mapserv?map=/maps/spraywatch2/wms/mapfile.map';
wmsHighlight = new OpenLayers.Layer.WMS(
				"HighlightWMS",
				wms_url,
				{'layers': 'myDataLayer',
				'format':'png'},
					{
					    'visibility': false,
					    'isBaseLayer': false
					}
				    );
		 
map.addLayers([wmsHighlight]);

some JavaScript (note the use of the idn, identifying the feature to be highlighted. Note the use of sld:Name element to refer to the WMS layer query which you've set up using mapserver or another WMS.):

function highlightWMS(idn) {
				
			sld = '<?xml version="1.0" encoding="utf-8"?>';
			sld+= '<sld:StyledLayerDescriptor version="1.0.0">';
			sld+= '<sld:NamedLayer><sld:Name>query</sld:Name><sld:UserStyle><sld:Name>query</sld:Name><sld:FeatureTypeStyle><sld:Rule>';
			
			sld+= '<ogc:Filter><ogc:PropertyIsEqualTo>';
			sld+= '<ogc:PropertyName>idn</ogc:PropertyName>';
			sld+= '<ogc:Literal>' + idn + '</ogc:Literal>';
			sld+= '</ogc:PropertyIsEqualTo></ogc:Filter>';
			sld+= '<sld:PolygonSymbolizer><sld:Fill><sld:CssParameter name="fill">#FF00FF</sld:CssParameter><sld:CssParameter name="fill-opacity">0.4</sld:CssParameter></sld:Fill>';
			sld+= '<sld:Stroke><sld:CssParameter name="stroke">#FF00FF</sld:CssParameter><sld:CssParameter name="stroke-opacity">0.4</sld:CssParameter><sld:CssParameter name="stroke-width">2</sld:CssParameter></sld:Stroke></sld:PolygonSymbolizer>';
			sld+= '</sld:Rule></sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer></sld:StyledLayerDescriptor>';		
			
			wmsHighlight.mergeNewParams({layers: 'query',sld_body: sld});
			wmsHighlight.setVisibility(true);
				
			}

function unhighlightWMS() {
			wmsHighlight.mergeNewParams({sld_body: ""});
			wmsHighlight.setVisibility(false);
			}