GetFeature.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. /* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the Clear BSD license.
  3. * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
  4. * full text of the license. */
  5. /**
  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
  11. */
  12. /**
  13. * Class: OpenLayers.Control.GetFeature
  14. * Gets vector features for locations underneath the mouse cursor. Can be
  15. * configured to act on click, hover or dragged boxes. Uses an
  16. * <OpenLayers.Protocol> that supports spatial filters to retrieve
  17. * features from a server and fires events that notify applications of the
  18. * selected features.
  19. *
  20. * Inherits from:
  21. * - <OpenLayers.Control>
  22. */
  23. OpenLayers.Control.GetFeature = OpenLayers.Class(OpenLayers.Control, {
  24. /**
  25. * APIProperty: protocol
  26. * {<OpenLayers.Protocol>} Required. The protocol used for fetching
  27. * features.
  28. */
  29. protocol: null,
  30. /**
  31. * APIProperty: multipleKey
  32. * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
  33. * the <multiple> property to true. Default is null.
  34. */
  35. multipleKey: null,
  36. /**
  37. * APIProperty: toggleKey
  38. * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
  39. * the <toggle> property to true. Default is null.
  40. */
  41. toggleKey: null,
  42. /**
  43. * Property: modifiers
  44. * {Object} The event modifiers to use, according to the current event
  45. * being handled by this control's handlers
  46. */
  47. modifiers: null,
  48. /**
  49. * APIProperty: multiple
  50. * {Boolean} Allow selection of multiple geometries. Default is false.
  51. */
  52. multiple: false,
  53. /**
  54. * APIProperty: click
  55. * {Boolean} Use a click handler for selecting/unselecting features. If
  56. * both <click> and <box> are set to true, the click handler takes
  57. * precedence over the box handler if a box with zero extent was
  58. * selected. Default is true.
  59. */
  60. click: true,
  61. /**
  62. * APIProperty: single
  63. * {Boolean} Tells whether select by click should select a single
  64. * feature. If set to false, all matching features are selected.
  65. * If set to true, only the best matching feature is selected.
  66. * This option has an effect only of the <click> option is set
  67. * to true. Default is true.
  68. */
  69. single: true,
  70. /**
  71. * APIProperty: clickout
  72. * {Boolean} Unselect features when clicking outside any feature.
  73. * Applies only if <click> is true. Default is true.
  74. */
  75. clickout: true,
  76. /**
  77. * APIProperty: toggle
  78. * {Boolean} Unselect a selected feature on click. Applies only if
  79. * <click> is true. Default is false.
  80. */
  81. toggle: false,
  82. /**
  83. * APIProperty: clickTolerance
  84. * {Integer} Tolerance for the filter query in pixels. This has the
  85. * same effect as the tolerance parameter on WMS GetFeatureInfo
  86. * requests. Will be ignored for box selections. Applies only if
  87. * <click> or <hover> is true. Default is 5. Note that this not
  88. * only affects requests on click, but also on hover.
  89. */
  90. clickTolerance: 5,
  91. /**
  92. * APIProperty: hover
  93. * {Boolean} Send feature requests on mouse moves. Default is false.
  94. */
  95. hover: false,
  96. /**
  97. * APIProperty: box
  98. * {Boolean} Allow feature selection by drawing a box. If set to
  99. * true set <click> to false to disable the click handler and
  100. * rely on the box handler only, even for "zero extent" boxes.
  101. * See the description of the <click> option for additional
  102. * information. Default is false.
  103. */
  104. box: false,
  105. /**
  106. * APIProperty: maxFeatures
  107. * {Integer} Maximum number of features to return from a query in single mode
  108. * if supported by the <protocol>. This set of features is then used to
  109. * determine the best match client-side. Default is 10.
  110. */
  111. maxFeatures: 10,
  112. /**
  113. * Property: features
  114. * {Object} Hash of {<OpenLayers.Feature.Vector>}, keyed by fid, holding
  115. * the currently selected features
  116. */
  117. features: null,
  118. /**
  119. * Proeprty: hoverFeature
  120. * {<OpenLayers.Feature.Vector>} The feature currently selected by the
  121. * hover handler
  122. */
  123. hoverFeature: null,
  124. /**
  125. * APIProperty: handlerOptions
  126. * {Object} Additional options for the handlers used by this control. This
  127. * is a hash with the keys "click", "box" and "hover".
  128. */
  129. handlerOptions: null,
  130. /**
  131. * Property: handlers
  132. * {Object} Object with references to multiple <OpenLayers.Handler>
  133. * instances.
  134. */
  135. handlers: null,
  136. /**
  137. * Property: hoverResponse
  138. * {<OpenLayers.Protocol.Response>} The response object associated with
  139. * the currently running hover request (if any).
  140. */
  141. hoverResponse: null,
  142. /**
  143. * Property: filterType
  144. * {<String>} The type of filter to use when sending off a request.
  145. * Possible values:
  146. * OpenLayers.Filter.Spatial.<BBOX|INTERSECTS|WITHIN|CONTAINS>
  147. * Defaults to: OpenLayers.Filter.Spatial.BBOX
  148. */
  149. filterType: OpenLayers.Filter.Spatial.BBOX,
  150. /**
  151. * Constant: EVENT_TYPES
  152. *
  153. * Supported event types:
  154. * beforefeatureselected - Triggered when <click> is true before a
  155. * feature is selected. The event object has a feature property with
  156. * the feature about to select
  157. * featureselected - Triggered when <click> is true and a feature is
  158. * selected. The event object has a feature property with the
  159. * selected feature
  160. * beforefeaturesselected - Triggered when <click> is true before a
  161. * set of features is selected. The event object is an array of
  162. * feature properties with the features about to be selected.
  163. * Return false after receiving this event to discontinue processing
  164. * of all featureselected events and the featuresselected event.
  165. * featuresselected - Triggered when <click> is true and a set of
  166. * features is selected. The event object is an array of feature
  167. * properties of the selected features
  168. * featureunselected - Triggered when <click> is true and a feature is
  169. * unselected. The event object has a feature property with the
  170. * unselected feature
  171. * clickout - Triggered when when <click> is true and no feature was
  172. * selected.
  173. * hoverfeature - Triggered when <hover> is true and the mouse has
  174. * stopped over a feature
  175. * outfeature - Triggered when <hover> is true and the mouse moves
  176. * moved away from a hover-selected feature
  177. */
  178. EVENT_TYPES: ["featureselected", "featuresselected", "featureunselected",
  179. "clickout", "beforefeatureselected", "beforefeaturesselected",
  180. "hoverfeature", "outfeature"],
  181. /**
  182. * Constructor: OpenLayers.Control.GetFeature
  183. * Create a new control for fetching remote features.
  184. *
  185. * Parameters:
  186. * options - {Object} A configuration object which at least has to contain
  187. * a <protocol> property (if not, it has to be set before a request is
  188. * made)
  189. */
  190. initialize: function(options) {
  191. // concatenate events specific to vector with those from the base
  192. this.EVENT_TYPES =
  193. OpenLayers.Control.GetFeature.prototype.EVENT_TYPES.concat(
  194. OpenLayers.Control.prototype.EVENT_TYPES
  195. );
  196. options.handlerOptions = options.handlerOptions || {};
  197. OpenLayers.Control.prototype.initialize.apply(this, [options]);
  198. this.features = {};
  199. this.handlers = {};
  200. if(this.click) {
  201. this.handlers.click = new OpenLayers.Handler.Click(this,
  202. {click: this.selectClick}, this.handlerOptions.click || {});
  203. }
  204. if(this.box) {
  205. this.handlers.box = new OpenLayers.Handler.Box(
  206. this, {done: this.selectBox},
  207. OpenLayers.Util.extend(this.handlerOptions.box, {
  208. boxDivClassName: "olHandlerBoxSelectFeature"
  209. })
  210. );
  211. }
  212. if(this.hover) {
  213. this.handlers.hover = new OpenLayers.Handler.Hover(
  214. this, {'move': this.cancelHover, 'pause': this.selectHover},
  215. OpenLayers.Util.extend(this.handlerOptions.hover, {
  216. 'delay': 250
  217. })
  218. );
  219. }
  220. },
  221. /**
  222. * Method: activate
  223. * Activates the control.
  224. *
  225. * Returns:
  226. * {Boolean} The control was effectively activated.
  227. */
  228. activate: function () {
  229. if (!this.active) {
  230. for(var i in this.handlers) {
  231. this.handlers[i].activate();
  232. }
  233. }
  234. return OpenLayers.Control.prototype.activate.apply(
  235. this, arguments
  236. );
  237. },
  238. /**
  239. * Method: deactivate
  240. * Deactivates the control.
  241. *
  242. * Returns:
  243. * {Boolean} The control was effectively deactivated.
  244. */
  245. deactivate: function () {
  246. if (this.active) {
  247. for(var i in this.handlers) {
  248. this.handlers[i].deactivate();
  249. }
  250. }
  251. return OpenLayers.Control.prototype.deactivate.apply(
  252. this, arguments
  253. );
  254. },
  255. /**
  256. * Method: selectClick
  257. * Called on click
  258. *
  259. * Parameters:
  260. * evt - {<OpenLayers.Event>}
  261. */
  262. selectClick: function(evt) {
  263. var bounds = this.pixelToBounds(evt.xy);
  264. this.setModifiers(evt);
  265. this.request(bounds, {single: this.single});
  266. },
  267. /**
  268. * Method: selectBox
  269. * Callback from the handlers.box set up when <box> selection is on
  270. *
  271. * Parameters:
  272. * position - {<OpenLayers.Bounds>}
  273. */
  274. selectBox: function(position) {
  275. var bounds;
  276. if (position instanceof OpenLayers.Bounds) {
  277. var minXY = this.map.getLonLatFromPixel(
  278. new OpenLayers.Pixel(position.left, position.bottom)
  279. );
  280. var maxXY = this.map.getLonLatFromPixel(
  281. new OpenLayers.Pixel(position.right, position.top)
  282. );
  283. bounds = new OpenLayers.Bounds(
  284. minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
  285. );
  286. } else {
  287. if(this.click) {
  288. // box without extent - let the click handler take care of it
  289. return;
  290. }
  291. bounds = this.pixelToBounds(position);
  292. }
  293. this.setModifiers(this.handlers.box.dragHandler.evt);
  294. this.request(bounds);
  295. },
  296. /**
  297. * Method selectHover
  298. * Callback from the handlers.hover set up when <hover> selection is on
  299. *
  300. * Parameters:
  301. * evt {Object} - event object with an xy property
  302. */
  303. selectHover: function(evt) {
  304. var bounds = this.pixelToBounds(evt.xy);
  305. this.request(bounds, {single: true, hover: true});
  306. },
  307. /**
  308. * Method: cancelHover
  309. * Callback from the handlers.hover set up when <hover> selection is on
  310. */
  311. cancelHover: function() {
  312. if (this.hoverResponse) {
  313. this.protocol.abort(this.hoverResponse);
  314. this.hoverResponse = null;
  315. OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
  316. }
  317. },
  318. /**
  319. * Method: request
  320. * Sends a GetFeature request to the WFS
  321. *
  322. * Parameters:
  323. * bounds - {<OpenLayers.Bounds>} bounds for the request's BBOX filter
  324. * options - {Object} additional options for this method.
  325. *
  326. * Supported options include:
  327. * single - {Boolean} A single feature should be returned.
  328. * Note that this will be ignored if the protocol does not
  329. * return the geometries of the features.
  330. * hover - {Boolean} Do the request for the hover handler.
  331. */
  332. request: function(bounds, options) {
  333. options = options || {};
  334. var filter = new OpenLayers.Filter.Spatial({
  335. type: this.filterType,
  336. value: bounds
  337. });
  338. // Set the cursor to "wait" to tell the user we're working.
  339. OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait");
  340. var response = this.protocol.read({
  341. maxFeatures: options.single == true ? this.maxFeatures : undefined,
  342. filter: filter,
  343. callback: function(result) {
  344. if(result.success()) {
  345. if(result.features.length) {
  346. if(options.single == true) {
  347. this.selectBestFeature(result.features,
  348. bounds.getCenterLonLat(), options);
  349. } else {
  350. this.select(result.features);
  351. }
  352. } else if(options.hover) {
  353. this.hoverSelect();
  354. } else {
  355. this.events.triggerEvent("clickout");
  356. if(this.clickout) {
  357. this.unselectAll();
  358. }
  359. }
  360. }
  361. // Reset the cursor.
  362. OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
  363. },
  364. scope: this
  365. });
  366. if(options.hover == true) {
  367. this.hoverResponse = response;
  368. }
  369. },
  370. /**
  371. * Method: selectBestFeature
  372. * Selects the feature from an array of features that is the best match
  373. * for the click position.
  374. *
  375. * Parameters:
  376. * features - {Array(<OpenLayers.Feature.Vector>)}
  377. * clickPosition - {<OpenLayers.LonLat>}
  378. * options - {Object} additional options for this method
  379. *
  380. * Supported options include:
  381. * hover - {Boolean} Do the selection for the hover handler.
  382. */
  383. selectBestFeature: function(features, clickPosition, options) {
  384. options = options || {};
  385. if(features.length) {
  386. var point = new OpenLayers.Geometry.Point(clickPosition.lon,
  387. clickPosition.lat);
  388. var feature, resultFeature, dist;
  389. var minDist = Number.MAX_VALUE;
  390. for(var i=0; i<features.length; ++i) {
  391. feature = features[i];
  392. if(feature.geometry) {
  393. dist = point.distanceTo(feature.geometry, {edge: false});
  394. if(dist < minDist) {
  395. minDist = dist;
  396. resultFeature = feature;
  397. if(minDist == 0) {
  398. break;
  399. }
  400. }
  401. }
  402. }
  403. if(options.hover == true) {
  404. this.hoverSelect(resultFeature);
  405. } else {
  406. this.select(resultFeature || features);
  407. }
  408. }
  409. },
  410. /**
  411. * Method: setModifiers
  412. * Sets the multiple and toggle modifiers according to the current event
  413. *
  414. * Parameters:
  415. * evt {<OpenLayers.Event>}
  416. */
  417. setModifiers: function(evt) {
  418. this.modifiers = {
  419. multiple: this.multiple || (this.multipleKey && evt[this.multipleKey]),
  420. toggle: this.toggle || (this.toggleKey && evt[this.toggleKey])
  421. };
  422. },
  423. /**
  424. * Method: select
  425. * Add feature to the hash of selected features and trigger the
  426. * featureselected and featuresselected events.
  427. *
  428. * Parameters:
  429. * features - {<OpenLayers.Feature.Vector>} or an array of features
  430. */
  431. select: function(features) {
  432. if(!this.modifiers.multiple && !this.modifiers.toggle) {
  433. this.unselectAll();
  434. }
  435. if(!(OpenLayers.Util.isArray(features))) {
  436. features = [features];
  437. }
  438. var cont = this.events.triggerEvent("beforefeaturesselected", {
  439. features: features
  440. });
  441. if(cont !== false) {
  442. var selectedFeatures = [];
  443. var feature;
  444. for(var i=0, len=features.length; i<len; ++i) {
  445. feature = features[i];
  446. if(this.features[feature.fid || feature.id]) {
  447. if(this.modifiers.toggle) {
  448. this.unselect(this.features[feature.fid || feature.id]);
  449. }
  450. } else {
  451. cont = this.events.triggerEvent("beforefeatureselected", {
  452. feature: feature
  453. });
  454. if(cont !== false) {
  455. this.features[feature.fid || feature.id] = feature;
  456. selectedFeatures.push(feature);
  457. this.events.triggerEvent("featureselected",
  458. {feature: feature});
  459. }
  460. }
  461. }
  462. this.events.triggerEvent("featuresselected", {
  463. features: selectedFeatures
  464. });
  465. }
  466. },
  467. /**
  468. * Method: hoverSelect
  469. * Sets/unsets the <hoverFeature>
  470. *
  471. * Parameters:
  472. * feature - {<OpenLayers.Feature.Vector>} the feature to hover-select.
  473. * If none is provided, the current <hoverFeature> will be nulled and
  474. * the outfeature event will be triggered.
  475. */
  476. hoverSelect: function(feature) {
  477. var fid = feature ? feature.fid || feature.id : null;
  478. var hfid = this.hoverFeature ?
  479. this.hoverFeature.fid || this.hoverFeature.id : null;
  480. if(hfid && hfid != fid) {
  481. this.events.triggerEvent("outfeature",
  482. {feature: this.hoverFeature});
  483. this.hoverFeature = null;
  484. }
  485. if(fid && fid != hfid) {
  486. this.events.triggerEvent("hoverfeature", {feature: feature});
  487. this.hoverFeature = feature;
  488. }
  489. },
  490. /**
  491. * Method: unselect
  492. * Remove feature from the hash of selected features and trigger the
  493. * featureunselected event.
  494. *
  495. * Parameters:
  496. * feature - {<OpenLayers.Feature.Vector>}
  497. */
  498. unselect: function(feature) {
  499. delete this.features[feature.fid || feature.id];
  500. this.events.triggerEvent("featureunselected", {feature: feature});
  501. },
  502. /**
  503. * Method: unselectAll
  504. * Unselect all selected features.
  505. */
  506. unselectAll: function() {
  507. // we'll want an option to supress notification here
  508. for(var fid in this.features) {
  509. this.unselect(this.features[fid]);
  510. }
  511. },
  512. /**
  513. * Method: setMap
  514. * Set the map property for the control.
  515. *
  516. * Parameters:
  517. * map - {<OpenLayers.Map>}
  518. */
  519. setMap: function(map) {
  520. for(var i in this.handlers) {
  521. this.handlers[i].setMap(map);
  522. }
  523. OpenLayers.Control.prototype.setMap.apply(this, arguments);
  524. },
  525. /**
  526. * Method: pixelToBounds
  527. * Takes a pixel as argument and creates bounds after adding the
  528. * <clickTolerance>.
  529. *
  530. * Parameters:
  531. * pixel - {<OpenLayers.Pixel>}
  532. */
  533. pixelToBounds: function(pixel) {
  534. var llPx = pixel.add(-this.clickTolerance/2, this.clickTolerance/2);
  535. var urPx = pixel.add(this.clickTolerance/2, -this.clickTolerance/2);
  536. var ll = this.map.getLonLatFromPixel(llPx);
  537. var ur = this.map.getLonLatFromPixel(urPx);
  538. return new OpenLayers.Bounds(ll.lon, ll.lat, ur.lon, ur.lat);
  539. },
  540. CLASS_NAME: "OpenLayers.Control.GetFeature"
  541. });