WMSGetFeatureInfo.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  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/Hover.js
  9. * @requires OpenLayers/Request.js
  10. * @requires OpenLayers/Format/WMSGetFeatureInfo.js
  11. */
  12. /**
  13. * Class: OpenLayers.Control.WMSGetFeatureInfo
  14. * The WMSGetFeatureInfo control uses a WMS query to get information about a point on the map. The
  15. * information may be in a display-friendly format such as HTML, or a machine-friendly format such
  16. * as GML, depending on the server's capabilities and the client's configuration. This control
  17. * handles click or hover events, attempts to parse the results using an OpenLayers.Format, and
  18. * fires a 'getfeatureinfo' event with the click position, the raw body of the response, and an
  19. * array of features if it successfully read the response.
  20. *
  21. * Inherits from:
  22. * - <OpenLayers.Control>
  23. */
  24. OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
  25. /**
  26. * APIProperty: hover
  27. * {Boolean} Send GetFeatureInfo requests when mouse stops moving.
  28. * Default is false.
  29. */
  30. hover: false,
  31. /**
  32. * APIProperty: drillDown
  33. * {Boolean} Drill down over all WMS layers in the map. When
  34. * using drillDown mode, hover is not possible, and an infoFormat that
  35. * returns parseable features is required. Default is false.
  36. */
  37. drillDown: false,
  38. /**
  39. * APIProperty: maxFeatures
  40. * {Integer} Maximum number of features to return from a WMS query. This
  41. * sets the feature_count parameter on WMS GetFeatureInfo
  42. * requests.
  43. */
  44. maxFeatures: 10,
  45. /**
  46. * APIProperty: clickCallback
  47. * {String} The click callback to register in the
  48. * {<OpenLayers.Handler.Click>} object created when the hover
  49. * option is set to false. Default is "click".
  50. */
  51. clickCallback: "click",
  52. /**
  53. * APIProperty: output
  54. * {String} Either "features" or "object". When triggering a getfeatureinfo
  55. * request should we pass on an array of features or an object with with
  56. * a "features" property and other properties (such as the url of the
  57. * WMS). Default is "features".
  58. */
  59. output: "features",
  60. /**
  61. * APIProperty: layers
  62. * {Array(<OpenLayers.Layer.WMS>)} The layers to query for feature info.
  63. * If omitted, all map WMS layers with a url that matches this <url> or
  64. * <layerUrls> will be considered.
  65. */
  66. layers: null,
  67. /**
  68. * APIProperty: queryVisible
  69. * {Boolean} If true, filter out hidden layers when searching the map for
  70. * layers to query. Default is false.
  71. */
  72. queryVisible: false,
  73. /**
  74. * APIProperty: url
  75. * {String} The URL of the WMS service to use. If not provided, the url
  76. * of the first eligible layer will be used.
  77. */
  78. url: null,
  79. /**
  80. * APIProperty: layerUrls
  81. * {Array(String)} Optional list of urls for layers that should be queried.
  82. * This can be used when the layer url differs from the url used for
  83. * making GetFeatureInfo requests (in the case of a layer using cached
  84. * tiles).
  85. */
  86. layerUrls: null,
  87. /**
  88. * APIProperty: infoFormat
  89. * {String} The mimetype to request from the server. If you are using
  90. * drillDown mode and have multiple servers that do not share a common
  91. * infoFormat, you can override the control's infoFormat by providing an
  92. * INFO_FORMAT parameter in your <OpenLayers.Layer.WMS> instance(s).
  93. */
  94. infoFormat: 'text/html',
  95. /**
  96. * APIProperty: vendorParams
  97. * {Object} Additional parameters that will be added to the request, for
  98. * WMS implementations that support them. This could e.g. look like
  99. * (start code)
  100. * {
  101. * radius: 5
  102. * }
  103. * (end)
  104. */
  105. vendorParams: {},
  106. /**
  107. * APIProperty: format
  108. * {<OpenLayers.Format>} A format for parsing GetFeatureInfo responses.
  109. * Default is <OpenLayers.Format.WMSGetFeatureInfo>.
  110. */
  111. format: null,
  112. /**
  113. * APIProperty: formatOptions
  114. * {Object} Optional properties to set on the format (if one is not provided
  115. * in the <format> property.
  116. */
  117. formatOptions: null,
  118. /**
  119. * APIProperty: handlerOptions
  120. * {Object} Additional options for the handlers used by this control, e.g.
  121. * (start code)
  122. * {
  123. * "click": {delay: 100},
  124. * "hover": {delay: 300}
  125. * }
  126. * (end)
  127. */
  128. /**
  129. * Property: handler
  130. * {Object} Reference to the <OpenLayers.Handler> for this control
  131. */
  132. handler: null,
  133. /**
  134. * Property: hoverRequest
  135. * {<OpenLayers.Request>} contains the currently running hover request
  136. * (if any).
  137. */
  138. hoverRequest: null,
  139. /**
  140. * APIProperty: events
  141. * {<OpenLayers.Events>} Events instance for listeners and triggering
  142. * control specific events.
  143. *
  144. * Register a listener for a particular event with the following syntax:
  145. * (code)
  146. * control.events.register(type, obj, listener);
  147. * (end)
  148. *
  149. * Supported event types (in addition to those from <OpenLayers.Control.events>):
  150. * beforegetfeatureinfo - Triggered before the request is sent.
  151. * The event object has an *xy* property with the position of the
  152. * mouse click or hover event that triggers the request.
  153. * nogetfeatureinfo - no queryable layers were found.
  154. * getfeatureinfo - Triggered when a GetFeatureInfo response is received.
  155. * The event object has a *text* property with the body of the
  156. * response (String), a *features* property with an array of the
  157. * parsed features, an *xy* property with the position of the mouse
  158. * click or hover event that triggered the request, and a *request*
  159. * property with the request itself. If drillDown is set to true and
  160. * multiple requests were issued to collect feature info from all
  161. * layers, *text* and *request* will only contain the response body
  162. * and request object of the last request.
  163. */
  164. /**
  165. * Constructor: <OpenLayers.Control.WMSGetFeatureInfo>
  166. *
  167. * Parameters:
  168. * options - {Object}
  169. */
  170. initialize: function(options) {
  171. options = options || {};
  172. options.handlerOptions = options.handlerOptions || {};
  173. OpenLayers.Control.prototype.initialize.apply(this, [options]);
  174. if(!this.format) {
  175. this.format = new OpenLayers.Format.WMSGetFeatureInfo(
  176. options.formatOptions
  177. );
  178. }
  179. if(this.drillDown === true) {
  180. this.hover = false;
  181. }
  182. if(this.hover) {
  183. this.handler = new OpenLayers.Handler.Hover(
  184. this, {
  185. 'move': this.cancelHover,
  186. 'pause': this.getInfoForHover
  187. },
  188. OpenLayers.Util.extend(this.handlerOptions.hover || {}, {
  189. 'delay': 250
  190. }));
  191. } else {
  192. var callbacks = {};
  193. callbacks[this.clickCallback] = this.getInfoForClick;
  194. this.handler = new OpenLayers.Handler.Click(
  195. this, callbacks, this.handlerOptions.click || {});
  196. }
  197. },
  198. /**
  199. * Method: getInfoForClick
  200. * Called on click
  201. *
  202. * Parameters:
  203. * evt - {<OpenLayers.Event>}
  204. */
  205. getInfoForClick: function(evt) {
  206. this.events.triggerEvent("beforegetfeatureinfo", {xy: evt.xy});
  207. // Set the cursor to "wait" to tell the user we're working on their
  208. // click.
  209. OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait");
  210. this.request(evt.xy, {});
  211. },
  212. /**
  213. * Method: getInfoForHover
  214. * Pause callback for the hover handler
  215. *
  216. * Parameters:
  217. * evt - {Object}
  218. */
  219. getInfoForHover: function(evt) {
  220. this.events.triggerEvent("beforegetfeatureinfo", {xy: evt.xy});
  221. this.request(evt.xy, {hover: true});
  222. },
  223. /**
  224. * Method: cancelHover
  225. * Cancel callback for the hover handler
  226. */
  227. cancelHover: function() {
  228. if (this.hoverRequest) {
  229. this.hoverRequest.abort();
  230. this.hoverRequest = null;
  231. }
  232. },
  233. /**
  234. * Method: findLayers
  235. * Internal method to get the layers, independent of whether we are
  236. * inspecting the map or using a client-provided array
  237. */
  238. findLayers: function() {
  239. var candidates = this.layers || this.map.layers;
  240. var layers = [];
  241. var layer, url;
  242. for(var i = candidates.length - 1; i >= 0; --i) {
  243. layer = candidates[i];
  244. if(layer instanceof OpenLayers.Layer.WMS &&
  245. (!this.queryVisible || layer.getVisibility())) {
  246. url = OpenLayers.Util.isArray(layer.url) ? layer.url[0] : layer.url;
  247. // if the control was not configured with a url, set it
  248. // to the first layer url
  249. if(this.drillDown === false && !this.url) {
  250. this.url = url;
  251. }
  252. if(this.drillDown === true || this.urlMatches(url)) {
  253. layers.push(layer);
  254. }
  255. }
  256. }
  257. return layers;
  258. },
  259. /**
  260. * Method: urlMatches
  261. * Test to see if the provided url matches either the control <url> or one
  262. * of the <layerUrls>.
  263. *
  264. * Parameters:
  265. * url - {String} The url to test.
  266. *
  267. * Returns:
  268. * {Boolean} The provided url matches the control <url> or one of the
  269. * <layerUrls>.
  270. */
  271. urlMatches: function(url) {
  272. var matches = OpenLayers.Util.isEquivalentUrl(this.url, url);
  273. if(!matches && this.layerUrls) {
  274. for(var i=0, len=this.layerUrls.length; i<len; ++i) {
  275. if(OpenLayers.Util.isEquivalentUrl(this.layerUrls[i], url)) {
  276. matches = true;
  277. break;
  278. }
  279. }
  280. }
  281. return matches;
  282. },
  283. /**
  284. * Method: buildWMSOptions
  285. * Build an object with the relevant WMS options for the GetFeatureInfo request
  286. *
  287. * Parameters:
  288. * url - {String} The url to be used for sending the request
  289. * layers - {Array(<OpenLayers.Layer.WMS)} An array of layers
  290. * clickPosition - {<OpenLayers.Pixel>} The position on the map where the mouse
  291. * event occurred.
  292. * format - {String} The format from the corresponding GetMap request
  293. */
  294. buildWMSOptions: function(url, layers, clickPosition, format) {
  295. var layerNames = [], styleNames = [];
  296. for (var i = 0, len = layers.length; i < len; i++) {
  297. if (layers[i].params.LAYERS != null) {
  298. layerNames = layerNames.concat(layers[i].params.LAYERS);
  299. styleNames = styleNames.concat(this.getStyleNames(layers[i]));
  300. }
  301. }
  302. var firstLayer = layers[0];
  303. // use the firstLayer's projection if it matches the map projection -
  304. // this assumes that all layers will be available in this projection
  305. var projection = this.map.getProjection();
  306. var layerProj = firstLayer.projection;
  307. if (layerProj && layerProj.equals(this.map.getProjectionObject())) {
  308. projection = layerProj.getCode();
  309. }
  310. var params = OpenLayers.Util.extend({
  311. service: "WMS",
  312. version: firstLayer.params.VERSION,
  313. request: "GetFeatureInfo",
  314. exceptions: firstLayer.params.EXCEPTIONS,
  315. bbox: this.map.getExtent().toBBOX(null,
  316. firstLayer.reverseAxisOrder()),
  317. feature_count: this.maxFeatures,
  318. height: this.map.getSize().h,
  319. width: this.map.getSize().w,
  320. format: format,
  321. info_format: firstLayer.params.INFO_FORMAT || this.infoFormat
  322. }, (parseFloat(firstLayer.params.VERSION) >= 1.3) ?
  323. {
  324. crs: projection,
  325. i: parseInt(clickPosition.x),
  326. j: parseInt(clickPosition.y)
  327. } :
  328. {
  329. srs: projection,
  330. x: parseInt(clickPosition.x),
  331. y: parseInt(clickPosition.y)
  332. }
  333. );
  334. if (layerNames.length != 0) {
  335. params = OpenLayers.Util.extend({
  336. layers: layerNames,
  337. query_layers: layerNames,
  338. styles: styleNames
  339. }, params);
  340. }
  341. OpenLayers.Util.applyDefaults(params, this.vendorParams);
  342. return {
  343. url: url,
  344. params: OpenLayers.Util.upperCaseObject(params),
  345. callback: function(request) {
  346. this.handleResponse(clickPosition, request, url);
  347. },
  348. scope: this
  349. };
  350. },
  351. /**
  352. * Method: getStyleNames
  353. * Gets the STYLES parameter for the layer. Make sure the STYLES parameter
  354. * matches the LAYERS parameter
  355. *
  356. * Parameters:
  357. * layer - {<OpenLayers.Layer.WMS>}
  358. *
  359. * Returns:
  360. * {Array(String)} The STYLES parameter
  361. */
  362. getStyleNames: function(layer) {
  363. // in the event of a WMS layer bundling multiple layers but not
  364. // specifying styles,we need the same number of commas to specify
  365. // the default style for each of the layers. We can't just leave it
  366. // blank as we may be including other layers that do specify styles.
  367. var styleNames;
  368. if (layer.params.STYLES) {
  369. styleNames = layer.params.STYLES;
  370. } else {
  371. if (OpenLayers.Util.isArray(layer.params.LAYERS)) {
  372. styleNames = new Array(layer.params.LAYERS.length);
  373. } else { // Assume it's a String
  374. styleNames = layer.params.LAYERS.replace(/[^,]/g, "");
  375. }
  376. }
  377. return styleNames;
  378. },
  379. /**
  380. * Method: request
  381. * Sends a GetFeatureInfo request to the WMS
  382. *
  383. * Parameters:
  384. * clickPosition - {<OpenLayers.Pixel>} The position on the map where the
  385. * mouse event occurred.
  386. * options - {Object} additional options for this method.
  387. *
  388. * Valid options:
  389. * - *hover* {Boolean} true if we do the request for the hover handler
  390. */
  391. request: function(clickPosition, options) {
  392. var layers = this.findLayers();
  393. if(layers.length == 0) {
  394. this.events.triggerEvent("nogetfeatureinfo");
  395. // Reset the cursor.
  396. OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
  397. return;
  398. }
  399. options = options || {};
  400. if(this.drillDown === false) {
  401. var wmsOptions = this.buildWMSOptions(this.url, layers,
  402. clickPosition, layers[0].params.FORMAT);
  403. var request = OpenLayers.Request.GET(wmsOptions);
  404. if (options.hover === true) {
  405. this.hoverRequest = request;
  406. }
  407. } else {
  408. this._requestCount = 0;
  409. this._numRequests = 0;
  410. this.features = [];
  411. // group according to service url to combine requests
  412. var services = {}, url;
  413. for(var i=0, len=layers.length; i<len; i++) {
  414. var layer = layers[i];
  415. var service, found = false;
  416. url = OpenLayers.Util.isArray(layer.url) ? layer.url[0] : layer.url;
  417. if(url in services) {
  418. services[url].push(layer);
  419. } else {
  420. this._numRequests++;
  421. services[url] = [layer];
  422. }
  423. }
  424. var layers;
  425. for (var url in services) {
  426. layers = services[url];
  427. var wmsOptions = this.buildWMSOptions(url, layers,
  428. clickPosition, layers[0].params.FORMAT);
  429. OpenLayers.Request.GET(wmsOptions);
  430. }
  431. }
  432. },
  433. /**
  434. * Method: triggerGetFeatureInfo
  435. * Trigger the getfeatureinfo event when all is done
  436. *
  437. * Parameters:
  438. * request - {XMLHttpRequest} The request object
  439. * xy - {<OpenLayers.Pixel>} The position on the map where the
  440. * mouse event occurred.
  441. * features - {Array(<OpenLayers.Feature.Vector>)} or
  442. * {Array({Object}) when output is "object". The object has a url and a
  443. * features property which contains an array of features.
  444. */
  445. triggerGetFeatureInfo: function(request, xy, features) {
  446. this.events.triggerEvent("getfeatureinfo", {
  447. text: request.responseText,
  448. features: features,
  449. request: request,
  450. xy: xy
  451. });
  452. // Reset the cursor.
  453. OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
  454. },
  455. /**
  456. * Method: handleResponse
  457. * Handler for the GetFeatureInfo response.
  458. *
  459. * Parameters:
  460. * xy - {<OpenLayers.Pixel>} The position on the map where the
  461. * mouse event occurred.
  462. * request - {XMLHttpRequest} The request object.
  463. * url - {String} The url which was used for this request.
  464. */
  465. handleResponse: function(xy, request, url) {
  466. var doc = request.responseXML;
  467. if(!doc || !doc.documentElement) {
  468. doc = request.responseText;
  469. }
  470. var features = this.format.read(doc);
  471. if (this.drillDown === false) {
  472. this.triggerGetFeatureInfo(request, xy, features);
  473. } else {
  474. this._requestCount++;
  475. if (this.output === "object") {
  476. this._features = (this._features || []).concat(
  477. {url: url, features: features}
  478. );
  479. } else {
  480. this._features = (this._features || []).concat(features);
  481. }
  482. if (this._requestCount === this._numRequests) {
  483. this.triggerGetFeatureInfo(request, xy, this._features.concat());
  484. delete this._features;
  485. delete this._requestCount;
  486. delete this._numRequests;
  487. }
  488. }
  489. },
  490. CLASS_NAME: "OpenLayers.Control.WMSGetFeatureInfo"
  491. });