1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
6 * @requires OpenLayers/Control.js
7 * @requires OpenLayers/Handler/Click.js
8 * @requires OpenLayers/Handler/Box.js
9 * @requires OpenLayers/Handler/Hover.js
10 * @requires OpenLayers/Filter/Spatial.js
14 * Class: OpenLayers.Control.GetFeature
15 * Gets vector features for locations underneath the mouse cursor. Can be
16 * configured to act on click, hover or dragged boxes. Uses an
17 * <OpenLayers.Protocol> that supports spatial filters (BBOX) to retrieve
18 * features from a server and fires events that notify applications of the
22 * - <OpenLayers.Control>
24 OpenLayers.Control.GetFeature = OpenLayers.Class(OpenLayers.Control, {
27 * APIProperty: protocol
28 * {<OpenLayers.Protocol>} Required. The protocol used for fetching
34 * APIProperty: multipleKey
35 * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
36 * the <multiple> property to true. Default is null.
41 * APIProperty: toggleKey
42 * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
43 * the <toggle> property to true. Default is null.
49 * {Object} The event modifiers to use, according to the current event
50 * being handled by this control's handlers
55 * APIProperty: multiple
56 * {Boolean} Allow selection of multiple geometries. Default is false.
62 * {Boolean} Use a click handler for selecting/unselecting features.
68 * APIProperty: clickout
69 * {Boolean} Unselect features when clicking outside any feature.
70 * Applies only if <click> is true. Default is true.
76 * {Boolean} Unselect a selected feature on click. Applies only if
77 * <click> is true. Default is false.
82 * APIProperty: clickTolerance
83 * {Integer} Tolerance for the BBOX query in pixels. This has the
84 * same effect as the tolerance parameter on WMS GetFeatureInfo
85 * requests. Will be ignored for box selections. Applies only if
86 * <click> is true. Default is 5.
92 * {Boolean} Send feature requests on mouse moves. Default is false.
98 * {Boolean} Allow feature selection by drawing a box.
103 * APIProperty: maxFeatures
104 * {Integer} Maximum number of features to return from a query, if
105 * supported by the <protocol>. Default is 10.
111 * {Object} Hash of {<OpenLayers.Feature.Vector>}, keyed by fid, holding
112 * the currently selected features
117 * Proeprty: hoverFeature
118 * {<OpenLayers.Feature.Vector>} The feature currently selected by the
124 * APIProperty: handlerOptions
125 * {Object} Additional options for the handlers used by this control. This
126 * is a hash with the keys "click", "box" and "hover".
128 handlerOptions: null,
132 * {Object} Object with references to multiple <OpenLayers.Handler>
138 * Property: hoverResponse
139 * {<OpenLayers.Protocol.Response>} The response object associated with
140 * the currently running hover request (if any).
145 * Constant: EVENT_TYPES
147 * Supported event types:
148 * beforefeatureselected - Triggered when <click> is true before a
149 * feature is selected. The event object has a feature property with
150 * the feature about to select
151 * featureselected - Triggered when <click> is true and a feature is
152 * selected. The event object has a feature property with the
154 * featureunselected - Triggered when <click> is true and a feature is
155 * unselected. The event object has a feature property with the
157 * clickout - Triggered when when <click> is true and no feature was
159 * hoverfeature - Triggered when <hover> is true and the mouse has
160 * stopped over a feature
161 * outfeature - Triggered when <hover> is true and the mouse moves
162 * moved away from a hover-selected feature
164 EVENT_TYPES: ["featureselected", "featureunselected", "clickout",
165 "beforefeatureselected", "hoverfeature", "outfeature"],
168 * Constructor: OpenLayers.Control.GetFeature
169 * Create a new control for fetching remote features.
172 * options - {Object} A configuration object which at least has to contain
173 * a <protocol> property
175 initialize: function(options) {
176 // concatenate events specific to vector with those from the base
178 OpenLayers.Control.GetFeature.prototype.EVENT_TYPES.concat(
179 OpenLayers.Control.prototype.EVENT_TYPES
182 options.handlerOptions = options.handlerOptions || {};
184 OpenLayers.Control.prototype.initialize.apply(this, [options]);
191 this.handlers.click = new OpenLayers.Handler.Click(this,
192 {click: this.selectSingle}, this.handlerOptions.click || {})
196 this.handlers.box = new OpenLayers.Handler.Box(
197 this, {done: this.selectBox},
198 OpenLayers.Util.extend(this.handlerOptions.box, {
199 boxDivClassName: "olHandlerBoxSelectFeature"
205 this.handlers.hover = new OpenLayers.Handler.Hover(
206 this, {'move': this.cancelHover, 'pause': this.selectHover},
207 OpenLayers.Util.extend(this.handlerOptions.hover, {
216 * Activates the control.
219 * {Boolean} The control was effectively activated.
221 activate: function () {
223 for(var i in this.handlers) {
224 this.handlers[i].activate();
227 return OpenLayers.Control.prototype.activate.apply(
234 * Deactivates the control.
237 * {Boolean} The control was effectively deactivated.
239 deactivate: function () {
241 for(var i in this.handlers) {
242 this.handlers[i].deactivate();
245 return OpenLayers.Control.prototype.deactivate.apply(
251 * Method: unselectAll
252 * Unselect all selected features. To unselect all except for a single
253 * feature, set the options.except property to the feature.
256 * options - {Object} Optional configuration object.
258 unselectAll: function(options) {
259 // we'll want an option to supress notification here
261 for(var i=this.features.length-1; i>=0; --i) {
262 feature = this.features[i];
263 if(!options || options.except != feature) {
264 this.unselect(feature);
270 * Method: selectSingle
274 * evt - {<OpenLayers.Event>}
276 selectSingle: function(evt) {
277 // Set the cursor to "wait" to tell the user we're working on their click.
278 OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait");
280 var bounds = this.pixelToBounds(evt.xy);
282 this.setModifiers(evt);
283 this.request(bounds, {single: true});
288 * Callback from the handlers.box set up when <box> selection is on
291 * position - {<OpenLayers.Bounds>}
293 selectBox: function(position) {
294 if (position instanceof OpenLayers.Bounds) {
295 var minXY = this.map.getLonLatFromPixel(
296 new OpenLayers.Pixel(position.left, position.bottom)
298 var maxXY = this.map.getLonLatFromPixel(
299 new OpenLayers.Pixel(position.right, position.top)
301 var bounds = new OpenLayers.Bounds(
302 minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
305 this.setModifiers(this.handlers.box.dragHandler.evt);
306 this.request(bounds);
312 * Callback from the handlers.hover set up when <hover> selection is on
315 * evt {Object} - event object with an xy property
317 selectHover: function(evt) {
318 var bounds = this.pixelToBounds(evt.xy);
319 this.request(bounds, {single: true, hover: true});
323 * Method: cancelHover
324 * Callback from the handlers.hover set up when <hover> selection is on
326 cancelHover: function() {
327 if (this.hoverResponse) {
328 this.protocol.abort(this.hoverResponse);
329 this.hoverResponse = null;
335 * Sends a GetFeature request to the WFS
338 * bounds - {<OpenLayers.Bounds>} bounds for the request's BBOX filter
339 * options - {Object} additional options for this method.
341 * Supported options include:
342 * single - {Boolean} A single feature should be returned.
343 * Note that this will be ignored if the protocol does not
344 * return the geometries of the features.
345 * hover - {Boolean} Do the request for the hover handler.
347 request: function(bounds, options) {
348 options = options || {};
349 var filter = new OpenLayers.Filter.Spatial({
350 type: OpenLayers.Filter.Spatial.BBOX,
354 var response = this.protocol.read({
355 maxFeatures: options.single == true ? this.maxFeatures : undefined,
357 callback: function(result) {
358 if(result.code == 1) {
359 if(result.features.length) {
360 if(options.single == true) {
361 this.selectBestFeature(result.features,
362 bounds.getCenterLonLat(), options);
364 this.select(result.features);
366 } else if(options.hover) {
369 this.events.triggerEvent("clickout");
376 OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
380 if(options.hover == true) {
381 this.hoverResponse = response;
386 * Method: selectBestFeature
387 * Selects the feature from an array of features that is the best match
388 * for the click position.
391 * features - {Array(<OpenLayers.Feature.Vector>)}
392 * clickPosition - {<OpenLayers.LonLat>}
393 * options - {Object} additional options for this method
395 * Supported options include:
396 * hover - {Boolean} Do the selection for the hover handler.
398 selectBestFeature: function(features, clickPosition, options) {
399 options = options || {};
400 if(features.length) {
401 var point = new OpenLayers.Geometry.Point(clickPosition.lon,
403 var feature, resultFeature, dist;
404 var minDist = Number.MAX_VALUE;
405 for(var i=0; i<features.length; ++i) {
406 feature = features[i];
407 if(feature.geometry) {
408 dist = point.distanceTo(feature.geometry, {edge: false});
411 resultFeature = feature;
419 if(options.hover == true) {
420 this.hoverSelect(resultFeature);
422 this.select(resultFeature || features);
428 * Method: setModifiers
429 * Sets the multiple and toggle modifiers according to the current event
432 * evt {<OpenLayers.Event>}
434 setModifiers: function(evt) {
436 multiple: this.multiple || (this.multipleKey && evt[this.multipleKey]),
437 toggle: this.toggle || (this.toggleKey && evt[this.toggleKey])
443 * Add feature to the hash of selected features and trigger the
444 * featureselected event.
447 * features - {<OpenLayers.Feature.Vector>} or an array of features
449 select: function(features) {
450 if(!this.modifiers.multiple && !this.modifiers.toggle) {
453 if(!(features instanceof Array)) {
454 features = [features];
458 for(var i=0, len=features.length; i<len; ++i) {
459 feature = features[i];
460 if(this.features[feature.fid || feature.id]) {
461 if(this.modifiers.toggle) {
462 this.unselect(this.features[feature.fid || feature.id]);
465 cont = this.events.triggerEvent("beforefeatureselected", {
469 this.features[feature.fid || feature.id] = feature;
471 this.events.triggerEvent("featureselected",
479 * Method: hoverSelect
480 * Sets/unsets the <hoverFeature>
483 * feature - {<OpenLayers.Feature.Vector>} the feature to hover-select.
484 * If none is provided, the current <hoverFeature> will be nulled and
485 * the outfeature event will be triggered.
487 hoverSelect: function(feature) {
488 var fid = feature ? feature.fid || feature.id : null;
489 var hfid = this.hoverFeature ?
490 this.hoverFeature.fid || this.hoverFeature.id : null;
492 if(hfid && hfid != fid) {
493 this.events.triggerEvent("outfeature",
494 {feature: this.hoverFeature});
495 this.hoverFeature = null;
497 if(fid && fid != hfid) {
498 this.events.triggerEvent("hoverfeature", {feature: feature});
499 this.hoverFeature = feature;
505 * Remove feature from the hash of selected features and trigger the
506 * featureunselected event.
509 * feature - {<OpenLayers.Feature.Vector>}
511 unselect: function(feature) {
512 delete this.features[feature.fid || feature.id];
513 this.events.triggerEvent("featureunselected", {feature: feature});
517 * Method: unselectAll
518 * Unselect all selected features.
520 unselectAll: function() {
521 // we'll want an option to supress notification here
522 for(var fid in this.features) {
523 this.unselect(this.features[fid]);
529 * Set the map property for the control.
532 * map - {<OpenLayers.Map>}
534 setMap: function(map) {
535 for(var i in this.handlers) {
536 this.handlers[i].setMap(map);
538 OpenLayers.Control.prototype.setMap.apply(this, arguments);
542 * Method: pixelToBounds
543 * Takes a pixel as argument and creates bounds after adding the
547 * pixel - {<OpenLayers.Pixel>}
549 pixelToBounds: function(pixel) {
550 var llPx = pixel.add(-this.clickTolerance/2, this.clickTolerance/2);
551 var urPx = pixel.add(this.clickTolerance/2, -this.clickTolerance/2);
552 var ll = this.map.getLonLatFromPixel(llPx);
553 var ur = this.map.getLonLatFromPixel(urPx);
554 return new OpenLayers.Bounds(ll.lon, ll.lat, ur.lon, ur.lat);
557 CLASS_NAME: "OpenLayers.Control.GetFeature"