GetFeature.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the 2-clause BSD license.
  3. * See license.txt in the OpenLayers distribution or repository 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. /**
  130. * Property: handlers
  131. * {Object} Object with references to multiple <OpenLayers.Handler>
  132. * instances.
  133. */
  134. handlers: null,
  135. /**
  136. * Property: hoverResponse
  137. * {<OpenLayers.Protocol.Response>} The response object associated with
  138. * the currently running hover request (if any).
  139. */
  140. hoverResponse: null,
  141. /**
  142. * Property: filterType
  143. * {<String>} The type of filter to use when sending off a request.
  144. * Possible values:
  145. * OpenLayers.Filter.Spatial.<BBOX|INTERSECTS|WITHIN|CONTAINS>
  146. * Defaults to: OpenLayers.Filter.Spatial.BBOX
  147. */
  148. filterType: OpenLayers.Filter.Spatial.BBOX,
  149. /**
  150. * APIProperty: events
  151. * {<OpenLayers.Events>} Events instance for listeners and triggering
  152. * control specific events.
  153. *
  154. * Register a listener for a particular event with the following syntax:
  155. * (code)
  156. * control.events.register(type, obj, listener);
  157. * (end)
  158. *
  159. * Supported event types (in addition to those from <OpenLayers.Control.events>):
  160. * beforefeatureselected - Triggered when <click> is true before a
  161. * feature is selected. The event object has a feature property with
  162. * the feature about to select
  163. * featureselected - Triggered when <click> is true and a feature is
  164. * selected. The event object has a feature property with the
  165. * selected feature
  166. * beforefeaturesselected - Triggered when <click> is true before a
  167. * set of features is selected. The event object is an array of
  168. * feature properties with the features about to be selected.
  169. * Return false after receiving this event to discontinue processing
  170. * of all featureselected events and the featuresselected event.
  171. * featuresselected - Triggered when <click> is true and a set of
  172. * features is selected. The event object is an array of feature
  173. * properties of the selected features
  174. * featureunselected - Triggered when <click> is true and a feature is
  175. * unselected. The event object has a feature property with the
  176. * unselected feature
  177. * clickout - Triggered when when <click> is true and no feature was
  178. * selected.
  179. * hoverfeature - Triggered when <hover> is true and the mouse has
  180. * stopped over a feature
  181. * outfeature - Triggered when <hover> is true and the mouse moves
  182. * moved away from a hover-selected feature
  183. */
  184. /**
  185. * Constructor: OpenLayers.Control.GetFeature
  186. * Create a new control for fetching remote features.
  187. *
  188. * Parameters:
  189. * options - {Object} A configuration object which at least has to contain
  190. * a <protocol> property (if not, it has to be set before a request is
  191. * made)
  192. */
  193. initialize: function(options) {
  194. options.handlerOptions = options.handlerOptions || {};
  195. OpenLayers.Control.prototype.initialize.apply(this, [options]);
  196. this.features = {};
  197. this.handlers = {};
  198. if(this.click) {
  199. this.handlers.click = new OpenLayers.Handler.Click(this,
  200. {click: this.selectClick}, this.handlerOptions.click || {});
  201. }
  202. if(this.box) {
  203. this.handlers.box = new OpenLayers.Handler.Box(
  204. this, {done: this.selectBox},
  205. OpenLayers.Util.extend(this.handlerOptions.box, {
  206. boxDivClassName: "olHandlerBoxSelectFeature"
  207. })
  208. );
  209. }
  210. if(this.hover) {
  211. this.handlers.hover = new OpenLayers.Handler.Hover(
  212. this, {'move': this.cancelHover, 'pause': this.selectHover},
  213. OpenLayers.Util.extend(this.handlerOptions.hover, {
  214. 'delay': 250,
  215. 'pixelTolerance': 2
  216. })
  217. );
  218. }
  219. },
  220. /**
  221. * Method: activate
  222. * Activates the control.
  223. *
  224. * Returns:
  225. * {Boolean} The control was effectively activated.
  226. */
  227. activate: function () {
  228. if (!this.active) {
  229. for(var i in this.handlers) {
  230. this.handlers[i].activate();
  231. }
  232. }
  233. return OpenLayers.Control.prototype.activate.apply(
  234. this, arguments
  235. );
  236. },
  237. /**
  238. * Method: deactivate
  239. * Deactivates the control.
  240. *
  241. * Returns:
  242. * {Boolean} The control was effectively deactivated.
  243. */
  244. deactivate: function () {
  245. if (this.active) {
  246. for(var i in this.handlers) {
  247. this.handlers[i].deactivate();
  248. }
  249. }
  250. return OpenLayers.Control.prototype.deactivate.apply(
  251. this, arguments
  252. );
  253. },
  254. /**
  255. * Method: selectClick
  256. * Called on click
  257. *
  258. * Parameters:
  259. * evt - {<OpenLayers.Event>}
  260. */
  261. selectClick: function(evt) {
  262. var bounds = this.pixelToBounds(evt.xy);
  263. this.setModifiers(evt);
  264. this.request(bounds, {single: this.single});
  265. },
  266. /**
  267. * Method: selectBox
  268. * Callback from the handlers.box set up when <box> selection is on
  269. *
  270. * Parameters:
  271. * position - {<OpenLayers.Bounds>|Object} An OpenLayers.Bounds or
  272. * an object with a 'left', 'bottom', 'right' and 'top' properties.
  273. */
  274. selectBox: function(position) {
  275. var bounds;
  276. if (position instanceof OpenLayers.Bounds) {
  277. var minXY = this.map.getLonLatFromPixel({
  278. x: position.left,
  279. y: position.bottom
  280. });
  281. var maxXY = this.map.getLonLatFromPixel({
  282. x: position.right,
  283. y: position.top
  284. });
  285. bounds = new OpenLayers.Bounds(
  286. minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
  287. );
  288. } else {
  289. if(this.click) {
  290. // box without extent - let the click handler take care of it
  291. return;
  292. }
  293. bounds = this.pixelToBounds(position);
  294. }
  295. this.setModifiers(this.handlers.box.dragHandler.evt);
  296. this.request(bounds);
  297. },
  298. /**
  299. * Method: selectHover
  300. * Callback from the handlers.hover set up when <hover> selection is on
  301. *
  302. * Parameters:
  303. * evt - {Object} event object with an xy property
  304. */
  305. selectHover: function(evt) {
  306. var bounds = this.pixelToBounds(evt.xy);
  307. this.request(bounds, {single: true, hover: true});
  308. },
  309. /**
  310. * Method: cancelHover
  311. * Callback from the handlers.hover set up when <hover> selection is on
  312. */
  313. cancelHover: function() {
  314. if (this.hoverResponse) {
  315. this.protocol.abort(this.hoverResponse);
  316. this.hoverResponse = null;
  317. OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
  318. }
  319. },
  320. /**
  321. * Method: request
  322. * Sends a GetFeature request to the WFS
  323. *
  324. * Parameters:
  325. * bounds - {<OpenLayers.Bounds>} bounds for the request's BBOX filter
  326. * options - {Object} additional options for this method.
  327. *
  328. * Supported options include:
  329. * single - {Boolean} A single feature should be returned.
  330. * Note that this will be ignored if the protocol does not
  331. * return the geometries of the features.
  332. * hover - {Boolean} Do the request for the hover handler.
  333. */
  334. request: function(bounds, options) {
  335. options = options || {};
  336. var filter = new OpenLayers.Filter.Spatial({
  337. type: this.filterType,
  338. value: bounds
  339. });
  340. // Set the cursor to "wait" to tell the user we're working.
  341. OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait");
  342. var response = this.protocol.read({
  343. maxFeatures: options.single == true ? this.maxFeatures : undefined,
  344. filter: filter,
  345. callback: function(result) {
  346. if(result.success()) {
  347. if(result.features.length) {
  348. if(options.single == true) {
  349. this.selectBestFeature(result.features,
  350. bounds.getCenterLonLat(), options);
  351. } else {
  352. this.select(result.features);
  353. }
  354. } else if(options.hover) {
  355. this.hoverSelect();
  356. } else {
  357. this.events.triggerEvent("clickout");
  358. if(this.clickout) {
  359. this.unselectAll();
  360. }
  361. }
  362. }
  363. // Reset the cursor.
  364. OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
  365. },
  366. scope: this
  367. });
  368. if(options.hover == true) {
  369. this.hoverResponse = response;
  370. }
  371. },
  372. /**
  373. * Method: selectBestFeature
  374. * Selects the feature from an array of features that is the best match
  375. * for the click position.
  376. *
  377. * Parameters:
  378. * features - {Array(<OpenLayers.Feature.Vector>)}
  379. * clickPosition - {<OpenLayers.LonLat>}
  380. * options - {Object} additional options for this method
  381. *
  382. * Supported options include:
  383. * hover - {Boolean} Do the selection for the hover handler.
  384. */
  385. selectBestFeature: function(features, clickPosition, options) {
  386. options = options || {};
  387. if(features.length) {
  388. var point = new OpenLayers.Geometry.Point(clickPosition.lon,
  389. clickPosition.lat);
  390. var feature, resultFeature, dist;
  391. var minDist = Number.MAX_VALUE;
  392. for(var i=0; i<features.length; ++i) {
  393. feature = features[i];
  394. if(feature.geometry) {
  395. dist = point.distanceTo(feature.geometry, {edge: false});
  396. if(dist < minDist) {
  397. minDist = dist;
  398. resultFeature = feature;
  399. if(minDist == 0) {
  400. break;
  401. }
  402. }
  403. }
  404. }
  405. if(options.hover == true) {
  406. this.hoverSelect(resultFeature);
  407. } else {
  408. this.select(resultFeature || features);
  409. }
  410. }
  411. },
  412. /**
  413. * Method: setModifiers
  414. * Sets the multiple and toggle modifiers according to the current event
  415. *
  416. * Parameters:
  417. * evt - {<OpenLayers.Event>}
  418. */
  419. setModifiers: function(evt) {
  420. this.modifiers = {
  421. multiple: this.multiple || (this.multipleKey && evt[this.multipleKey]),
  422. toggle: this.toggle || (this.toggleKey && evt[this.toggleKey])
  423. };
  424. },
  425. /**
  426. * Method: select
  427. * Add feature to the hash of selected features and trigger the
  428. * featureselected and featuresselected events.
  429. *
  430. * Parameters:
  431. * features - {<OpenLayers.Feature.Vector>} or an array of features
  432. */
  433. select: function(features) {
  434. if(!this.modifiers.multiple && !this.modifiers.toggle) {
  435. this.unselectAll();
  436. }
  437. if(!(OpenLayers.Util.isArray(features))) {
  438. features = [features];
  439. }
  440. var cont = this.events.triggerEvent("beforefeaturesselected", {
  441. features: features
  442. });
  443. if(cont !== false) {
  444. var selectedFeatures = [];
  445. var feature;
  446. for(var i=0, len=features.length; i<len; ++i) {
  447. feature = features[i];
  448. if(this.features[feature.fid || feature.id]) {
  449. if(this.modifiers.toggle) {
  450. this.unselect(this.features[feature.fid || feature.id]);
  451. }
  452. } else {
  453. cont = this.events.triggerEvent("beforefeatureselected", {
  454. feature: feature
  455. });
  456. if(cont !== false) {
  457. this.features[feature.fid || feature.id] = feature;
  458. selectedFeatures.push(feature);
  459. this.events.triggerEvent("featureselected",
  460. {feature: feature});
  461. }
  462. }
  463. }
  464. this.events.triggerEvent("featuresselected", {
  465. features: selectedFeatures
  466. });
  467. }
  468. },
  469. /**
  470. * Method: hoverSelect
  471. * Sets/unsets the <hoverFeature>
  472. *
  473. * Parameters:
  474. * feature - {<OpenLayers.Feature.Vector>} the feature to hover-select.
  475. * If none is provided, the current <hoverFeature> will be nulled and
  476. * the outfeature event will be triggered.
  477. */
  478. hoverSelect: function(feature) {
  479. var fid = feature ? feature.fid || feature.id : null;
  480. var hfid = this.hoverFeature ?
  481. this.hoverFeature.fid || this.hoverFeature.id : null;
  482. if(hfid && hfid != fid) {
  483. this.events.triggerEvent("outfeature",
  484. {feature: this.hoverFeature});
  485. this.hoverFeature = null;
  486. }
  487. if(fid && fid != hfid) {
  488. this.events.triggerEvent("hoverfeature", {feature: feature});
  489. this.hoverFeature = feature;
  490. }
  491. },
  492. /**
  493. * Method: unselect
  494. * Remove feature from the hash of selected features and trigger the
  495. * featureunselected event.
  496. *
  497. * Parameters:
  498. * feature - {<OpenLayers.Feature.Vector>}
  499. */
  500. unselect: function(feature) {
  501. delete this.features[feature.fid || feature.id];
  502. this.events.triggerEvent("featureunselected", {feature: feature});
  503. },
  504. /**
  505. * Method: unselectAll
  506. * Unselect all selected features.
  507. */
  508. unselectAll: function() {
  509. // we'll want an option to supress notification here
  510. for(var fid in this.features) {
  511. this.unselect(this.features[fid]);
  512. }
  513. },
  514. /**
  515. * Method: setMap
  516. * Set the map property for the control.
  517. *
  518. * Parameters:
  519. * map - {<OpenLayers.Map>}
  520. */
  521. setMap: function(map) {
  522. for(var i in this.handlers) {
  523. this.handlers[i].setMap(map);
  524. }
  525. OpenLayers.Control.prototype.setMap.apply(this, arguments);
  526. },
  527. /**
  528. * Method: pixelToBounds
  529. * Takes a pixel as argument and creates bounds after adding the
  530. * <clickTolerance>.
  531. *
  532. * Parameters:
  533. * pixel - {<OpenLayers.Pixel>}
  534. */
  535. pixelToBounds: function(pixel) {
  536. var llPx = pixel.add(-this.clickTolerance/2, this.clickTolerance/2);
  537. var urPx = pixel.add(this.clickTolerance/2, -this.clickTolerance/2);
  538. var ll = this.map.getLonLatFromPixel(llPx);
  539. var ur = this.map.getLonLatFromPixel(urPx);
  540. return new OpenLayers.Bounds(ll.lon, ll.lat, ur.lon, ur.lat);
  541. },
  542. CLASS_NAME: "OpenLayers.Control.GetFeature"
  543. });