+ Shows how to use the GetFeature control to select features from a
+ WMS layer. Click or drag a box to select features, use the Shift key to
+ add features to the selection, use the Ctrl key to toggle a feature's
+ selected status. Note that this control also has a hover option, which is
+ enabled in this example. This gives you a visual feedback by loading the
+ feature underneath the mouse pointer from the WFS, but causes a lot of
+ GetFeature requests to be issued.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js
index 9d16029336..e755788845 100644
--- a/lib/OpenLayers.js
+++ b/lib/OpenLayers.js
@@ -245,6 +245,7 @@
"OpenLayers/Format/WMC/v1_1_0.js",
"OpenLayers/Format/WMSGetFeatureInfo.js",
"OpenLayers/Layer/WFS.js",
+ "OpenLayers/Control/GetFeature.js",
"OpenLayers/Control/MouseToolbar.js",
"OpenLayers/Control/NavToolbar.js",
"OpenLayers/Control/PanPanel.js",
diff --git a/lib/OpenLayers/Control/GetFeature.js b/lib/OpenLayers/Control/GetFeature.js
new file mode 100644
index 0000000000..ad846cbb5b
--- /dev/null
+++ b/lib/OpenLayers/Control/GetFeature.js
@@ -0,0 +1,557 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Handler/Box.js
+ * @requires OpenLayers/Handler/Hover.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Control.GetFeature
+ * Gets vector features for locations underneath the mouse cursor. Can be
+ * configured to act on click, hover or dragged boxes. Uses an
+ * that supports spatial filters (BBOX) to retrieve
+ * features from a server and fires events that notify applications of the
+ * selected features.
+ *
+ * Inherits from:
+ * -
+ */
+OpenLayers.Control.GetFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: protocol
+ * {} Required. The protocol used for fetching
+ * features.
+ */
+ protocol: null,
+
+ /**
+ * APIProperty: multipleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the property to true. Default is null.
+ */
+ multipleKey: null,
+
+ /**
+ * APIProperty: toggleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the property to true. Default is null.
+ */
+ toggleKey: null,
+
+ /**
+ * Property: modifiers
+ * {Object} The event modifiers to use, according to the current event
+ * being handled by this control's handlers
+ */
+ modifiers: null,
+
+ /**
+ * APIProperty: multiple
+ * {Boolean} Allow selection of multiple geometries. Default is false.
+ */
+ multiple: false,
+
+ /**
+ * APIProperty: click
+ * {Boolean} Use a click handler for selecting/unselecting features.
+ * Default is true.
+ */
+ click: true,
+
+ /**
+ * APIProperty: clickout
+ * {Boolean} Unselect features when clicking outside any feature.
+ * Applies only if is true. Default is true.
+ */
+ clickout: true,
+
+ /**
+ * APIProperty: toggle
+ * {Boolean} Unselect a selected feature on click. Applies only if
+ * is true. Default is false.
+ */
+ toggle: false,
+
+ /**
+ * APIProperty: clickTolerance
+ * {Integer} Tolerance for the BBOX query in pixels. This has the
+ * same effect as the tolerance parameter on WMS GetFeatureInfo
+ * requests. Will be ignored for box selections. Applies only if
+ * is true. Default is 5.
+ */
+ clickTolerance: 5,
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Send feature requests on mouse moves. Default is false.
+ */
+ hover: false,
+
+ /**
+ * APIProperty: box
+ * {Boolean} Allow feature selection by drawing a box.
+ */
+ box: false,
+
+ /**
+ * APIProperty: maxFeatures
+ * {Integer} Maximum number of features to return from a query, if
+ * supported by the . Default is 10.
+ */
+ maxFeatures: 10,
+
+ /**
+ * Property: features
+ * {Object} Hash of {}, keyed by fid, holding
+ * the currently selected features
+ */
+ features: null,
+
+ /**
+ * Proeprty: hoverFeature
+ * {} The feature currently selected by the
+ * hover handler
+ */
+ hoverFeature: null,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Additional options for the handlers used by this control. This
+ * is a hash with the keys "click", "box" and "hover".
+ */
+ handlerOptions: null,
+
+ /**
+ * Property: handlers
+ * {Object} Object with references to multiple
+ * instances.
+ */
+ handlers: null,
+
+ /**
+ * Property: hoverRequest
+ * {} contains the currently running hover request
+ * (if any).
+ */
+ hoverRequest: null,
+
+ /**
+ * Constant: EVENT_TYPES
+ *
+ * Supported event types:
+ * beforefeatureselected - Triggered when is true before a
+ * feature is selected. The event object has a feature property with
+ * the feature about to select
+ * featureselected - Triggered when is true and a feature is
+ * selected. The event object has a feature property with the
+ * selected feature
+ * featureunselected - Triggered when is true and a feature is
+ * unselected. The event object has a feature property with the
+ * unselected feature
+ * clickout - Triggered when when is true and no feature was
+ * selected.
+ * hoverfeature - Triggered when is true and the mouse has
+ * stopped over a feature
+ * outfeature - Triggered when is true and the mouse moves
+ * moved away from a hover-selected feature
+ */
+ EVENT_TYPES: ["featureselected", "featureunselected", "clickout",
+ "beforefeatureselected", "hoverfeature", "outfeature"],
+
+ /**
+ * Constructor:
+ *
+ * Parameters:
+ * options - {Object} A configuration object which at least has to contain
+ * a property
+ */
+ initialize: function(options) {
+ // concatenate events specific to vector with those from the base
+ this.EVENT_TYPES =
+ OpenLayers.Control.GetFeature.prototype.EVENT_TYPES.concat(
+ OpenLayers.Control.prototype.EVENT_TYPES
+ );
+
+ options.handlerOptions = options.handlerOptions || {};
+
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.features = {};
+
+ this.handlers = {};
+
+ if(this.click) {
+ this.handlers.click = new OpenLayers.Handler.Click(this,
+ {click: this.selectSingle}, this.handlerOptions.click || {})
+ };
+
+ if(this.box) {
+ this.handlers.box = new OpenLayers.Handler.Box(
+ this, {done: this.selectBox},
+ OpenLayers.Util.extend(this.handlerOptions.box, {
+ boxDivClassName: "olHandlerBoxSelectFeature"
+ })
+ );
+ }
+
+ if(this.hover) {
+ this.handlers.hover = new OpenLayers.Handler.Hover(
+ this, {'move': this.cancelHover, 'pause': this.selectHover},
+ OpenLayers.Util.extend(this.handlerOptions.hover, {
+ 'delay': 250
+ })
+ );
+ }
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (!this.active) {
+ for(var i in this.handlers) {
+ this.handlers[i].activate();
+ }
+ }
+ return OpenLayers.Control.prototype.activate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active) {
+ for(var i in this.handlers) {
+ this.handlers[i].deactivate();
+ }
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: unselectAll
+ * Unselect all selected features. To unselect all except for a single
+ * feature, set the options.except property to the feature.
+ *
+ * Parameters:
+ * options - {Object} Optional configuration object.
+ */
+ unselectAll: function(options) {
+ // we'll want an option to supress notification here
+ var feature;
+ for(var i=this.features.length-1; i>=0; --i) {
+ feature = this.features[i];
+ if(!options || options.except != feature) {
+ this.unselect(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: selectSingle
+ * Called on click
+ *
+ * Parameters:
+ * evt - {}
+ */
+ selectSingle: function(evt) {
+ // Set the cursor to "wait" to tell the user we're working on their click.
+ OpenLayers.Element.addClass(this.map.div, "olCursorWait");
+
+ var bounds = this.pixelToBounds(evt.xy);
+
+ this.setModifiers(evt);
+ this.request(bounds, {single: true});
+ },
+
+ /**
+ * Method: selectBox
+ * Callback from the handlers.box set up when selection is on
+ *
+ * Parameters:
+ * position - {}
+ */
+ selectBox: function(position) {
+ if (position instanceof OpenLayers.Bounds) {
+ var minXY = this.map.getLonLatFromPixel(
+ new OpenLayers.Pixel(position.left, position.bottom)
+ );
+ var maxXY = this.map.getLonLatFromPixel(
+ new OpenLayers.Pixel(position.right, position.top)
+ );
+ var bounds = new OpenLayers.Bounds(
+ minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
+ );
+
+ this.setModifiers(this.handlers.box.dragHandler.evt);
+ this.request(bounds);
+ }
+ },
+
+ /**
+ * Method selectHover
+ * Callback from the handlers.hover set up when selection is on
+ *
+ * Parameters:
+ * evt {Object} - event object with an xy property
+ */
+ selectHover: function(evt) {
+ var bounds = this.pixelToBounds(evt.xy);
+ this.request(bounds, {single: true, hover: true});
+ },
+
+ /**
+ * Method: cancelHover
+ * Callback from the handlers.hover set up when selection is on
+ */
+ cancelHover: function() {
+ if (this.hoverRequest) {
+ this.hoverRequest.abort();
+ this.hoverRequest = null;
+ }
+ },
+
+ /**
+ * Method: request
+ * Sends a GetFeature request to the WFS
+ *
+ * Parameters:
+ * bounds - {} bounds for the request's BBOX filter
+ * options - {Object} additional options for this method.
+ *
+ * Supported options include:
+ * single - {Boolean} A single feature should be returned.
+ * Note that this will be ignored if the protocol does not
+ * return the geometries of the features.
+ * hover - {Boolean} Do the request for the hover handler.
+ */
+ request: function(bounds, options) {
+ options = options || {};
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: bounds
+ });
+
+ var response = this.protocol.read({
+ maxFeatures: options.single == true ? this.maxFeatures : undefined,
+ filter: filter,
+ callback: function(result) {
+ if(result.code == 1) {
+ if(result.features.length) {
+ if(options.single == true) {
+ this.selectBestFeature(result.features,
+ bounds.getCenterLonLat(), options);
+ } else {
+ this.select(result.features);
+ }
+ } else if(options.hover) {
+ this.hoverSelect();
+ } else {
+ this.events.triggerEvent("clickout");
+ if(this.clickout) {
+ this.unselectAll();
+ }
+ }
+ }
+ // Reset the cursor.
+ OpenLayers.Element.removeClass(this.map.div, "olCursorWait");
+ },
+ scope: this
+ });
+ if(options.hover == true) {
+ this.hoverRequest = response.priv;
+ }
+ },
+
+ /**
+ * Method: selectBestFeature
+ * Selects the feature from an array of features that is the best match
+ * for the click position.
+ *
+ * Parameters:
+ * features - {Array()}
+ * clickPosition - {}
+ * options - {Object} additional options for this method
+ *
+ * Supported options include:
+ * hover - {Boolean} Do the selection for the hover handler.
+ */
+ selectBestFeature: function(features, clickPosition, options) {
+ options = options || {};
+ if(features.length) {
+ var point = new OpenLayers.Geometry.Point(clickPosition.lon,
+ clickPosition.lat);
+ var feature, resultFeature, dist;
+ var minDist = Number.MAX_VALUE;
+ for(var i=0; i}
+ */
+ setModifiers: function(evt) {
+ this.modifiers = {
+ multiple: this.multiple || (this.multipleKey && evt[this.multipleKey]),
+ toggle: this.toggle || (this.toggleKey && evt[this.toggleKey])
+ }
+ },
+
+ /**
+ * Method: select
+ * Add feature to the hash of selected features and trigger the
+ * featureselected event.
+ *
+ * Parameters:
+ * features - {} or an array of features
+ */
+ select: function(features) {
+ if(!this.modifiers.multiple && !this.modifiers.toggle) {
+ this.unselectAll();
+ }
+ if(!(features instanceof Array)) {
+ features = [features];
+ }
+
+ var feature;
+ for(var i=0, len=features.length; i
+ *
+ * Parameters:
+ * feature - {} the feature to hover-select.
+ * If none is provided, the current will be nulled and
+ * the outfeature event will be triggered.
+ */
+ hoverSelect: function(feature) {
+ var fid = feature ? feature.fid || feature.id : null;
+ var hfid = this.hoverFeature ?
+ this.hoverFeature.fid || this.hoverFeature.id : null;
+
+ if(hfid && hfid != fid) {
+ this.events.triggerEvent("outfeature",
+ {feature: this.hoverFeature});
+ this.hoverFeature = null;
+ }
+ if(fid && fid != hfid) {
+ this.events.triggerEvent("hoverfeature", {feature: feature});
+ this.hoverFeature = feature;
+ }
+ },
+
+ /**
+ * Method: unselect
+ * Remove feature from the hash of selected features and trigger the
+ * featureunselected event.
+ *
+ * Parameters:
+ * feature - {}
+ */
+ unselect: function(feature) {
+ delete this.features[feature.fid || feature.id];
+ this.events.triggerEvent("featureunselected", {feature: feature});
+ },
+
+ /**
+ * Method: unselectAll
+ * Unselect all selected features.
+ */
+ unselectAll: function() {
+ // we'll want an option to supress notification here
+ for(var fid in this.features) {
+ this.unselect(this.features[fid]);
+ }
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {}
+ */
+ setMap: function(map) {
+ for(var i in this.handlers) {
+ this.handlers[i].setMap(map);
+ }
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: pixelToBounds
+ * Takes a pixel as argument and creates bounds after adding the
+ * .
+ *
+ * Parameters:
+ * pixel - {}
+ */
+ pixelToBounds: function(pixel) {
+ var llPx = pixel.add(-this.clickTolerance/2, this.clickTolerance/2);
+ var urPx = pixel.add(this.clickTolerance/2, -this.clickTolerance/2);
+ var ll = this.map.getLonLatFromPixel(llPx);
+ var ur = this.map.getLonLatFromPixel(urPx);
+ return new OpenLayers.Bounds(ll.lon, ll.lat, ur.lon, ur.lat);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.GetFeature"
+});
diff --git a/tests/Control/GetFeature.html b/tests/Control/GetFeature.html
new file mode 100644
index 0000000000..75803bccaf
--- /dev/null
+++ b/tests/Control/GetFeature.html
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/list-tests.html b/tests/list-tests.html
index 52338708f8..11b8a53456 100644
--- a/tests/list-tests.html
+++ b/tests/list-tests.html
@@ -14,6 +14,7 @@