diff --git a/examples/getfeature-wfs.html b/examples/getfeature-wfs.html new file mode 100644 index 0000000000..31ef5cdc34 --- /dev/null +++ b/examples/getfeature-wfs.html @@ -0,0 +1,80 @@ + + + + + WFS: GetFeature Example (GeoServer) + + + + +

WFS GetFeature Example (GeoServer)

+ +
+
+ +

+ 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 @@
  • Control/DragFeature.html
  • Control/DragPan.html
  • Control/DrawFeature.html
  • +
  • Control/GetFeature.html
  • Control/KeyboardDefaults.html
  • Control/LayerSwitcher.html
  • Control/ModifyFeature.html
  • diff --git a/theme/default/style.css b/theme/default/style.css index 7db68ec5e6..ca1d66602f 100644 --- a/theme/default/style.css +++ b/theme/default/style.css @@ -321,3 +321,10 @@ div.olControlMousePosition { -moz-user-select: none; } +/** + * Cursor styles + */ + +.olCursorWait { + cursor: wait; +} \ No newline at end of file