SLDSelect.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  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/Layer/WMS.js
  8. * @requires OpenLayers/Handler/RegularPolygon.js
  9. * @requires OpenLayers/Handler/Polygon.js
  10. * @requires OpenLayers/Handler/Path.js
  11. * @requires OpenLayers/Handler/Click.js
  12. * @requires OpenLayers/Filter/Spatial.js
  13. * @requires OpenLayers/Format/SLD/v1_0_0.js
  14. */
  15. /**
  16. * Class: OpenLayers.Control.SLDSelect
  17. * Perform selections on WMS layers using Styled Layer Descriptor (SLD)
  18. *
  19. * Inherits from:
  20. * - <OpenLayers.Control>
  21. */
  22. OpenLayers.Control.SLDSelect = OpenLayers.Class(OpenLayers.Control, {
  23. /**
  24. * APIProperty: events
  25. * {<OpenLayers.Events>} Events instance for listeners and triggering
  26. * control specific events.
  27. *
  28. * Register a listener for a particular event with the following syntax:
  29. * (code)
  30. * control.events.register(type, obj, listener);
  31. * (end)
  32. *
  33. * Supported event types (in addition to those from <OpenLayers.Control.events>):
  34. * selected - Triggered when a selection occurs. Listeners receive an
  35. * event with *filters* and *layer* properties. Filters will be an
  36. * array of OpenLayers.Filter objects created in order to perform
  37. * the particular selection.
  38. */
  39. /**
  40. * APIProperty: clearOnDeactivate
  41. * {Boolean} Should the selection be cleared when the control is
  42. * deactivated. Default value is false.
  43. */
  44. clearOnDeactivate: false,
  45. /**
  46. * APIProperty: layers
  47. * {Array(<OpenLayers.Layer.WMS>)} The WMS layers this control will work
  48. * on.
  49. */
  50. layers: null,
  51. /**
  52. * Property: callbacks
  53. * {Object} The functions that are sent to the handler for callback
  54. */
  55. callbacks: null,
  56. /**
  57. * APIProperty: selectionSymbolizer
  58. * {Object} Determines the styling of the selected objects. Default is
  59. * a selection in red.
  60. */
  61. selectionSymbolizer: {
  62. 'Polygon': {fillColor: '#FF0000', stroke: false},
  63. 'Line': {strokeColor: '#FF0000', strokeWidth: 2},
  64. 'Point': {graphicName: 'square', fillColor: '#FF0000', pointRadius: 5}
  65. },
  66. /**
  67. * APIProperty: layerOptions
  68. * {Object} The options to apply to the selection layer, by default the
  69. * selection layer will be kept out of the layer switcher.
  70. */
  71. layerOptions: null,
  72. /**
  73. * APIProperty: handlerOptions
  74. * {Object} Used to set non-default properties on the control's handler
  75. */
  76. /**
  77. * APIProperty: sketchStyle
  78. * {<OpenLayers.Style>|Object} Style or symbolizer to use for the sketch
  79. * handler. The recommended way of styling the sketch layer, however, is
  80. * to configure an <OpenLayers.StyleMap> in the layerOptions of the
  81. * <handlerOptions>:
  82. *
  83. * (code)
  84. * new OpenLayers.Control.SLDSelect(OpenLayers.Handler.Path, {
  85. * handlerOptions: {
  86. * layerOptions: {
  87. * styleMap: new OpenLayers.StyleMap({
  88. * "default": {strokeColor: "yellow"}
  89. * })
  90. * }
  91. * }
  92. * });
  93. * (end)
  94. */
  95. sketchStyle: null,
  96. /**
  97. * APIProperty: wfsCache
  98. * {Object} Cache to use for storing parsed results from
  99. * <OpenLayers.Format.WFSDescribeFeatureType.read>. If not provided,
  100. * these will be cached on the prototype.
  101. */
  102. wfsCache: {},
  103. /**
  104. * APIProperty: layerCache
  105. * {Object} Cache to use for storing references to the selection layers.
  106. * Normally each source layer will have exactly 1 selection layer of
  107. * type OpenLayers.Layer.WMS. If not provided, layers will
  108. * be cached on the prototype. Note that if <clearOnDeactivate> is
  109. * true, the layer will no longer be cached after deactivating the
  110. * control.
  111. */
  112. layerCache: {},
  113. /**
  114. * Constructor: OpenLayers.Control.SLDSelect
  115. * Create a new control for selecting features in WMS layers using
  116. * Styled Layer Descriptor (SLD).
  117. *
  118. * Parameters:
  119. * handler - {<OpenLayers.Class>} A sketch handler class. This determines
  120. * the type of selection, e.g. box (<OpenLayers.Handler.Box>), point
  121. * (<OpenLayers.Handler.Point>), path (<OpenLayers.Handler.Path>) or
  122. * polygon (<OpenLayers.Handler.Polygon>) selection. To use circle
  123. * type selection, use <OpenLayers.Handler.RegularPolygon> and pass
  124. * the number of desired sides (e.g. 40) as "sides" property to the
  125. * <handlerOptions>.
  126. * options - {Object} An object containing all configuration properties for
  127. * the control.
  128. *
  129. * Valid options:
  130. * layers - Array({<OpenLayers.Layer.WMS>}) The layers to perform the
  131. * selection on.
  132. */
  133. initialize: function(handler, options) {
  134. OpenLayers.Control.prototype.initialize.apply(this, [options]);
  135. this.callbacks = OpenLayers.Util.extend({done: this.select,
  136. click: this.select}, this.callbacks);
  137. this.handlerOptions = this.handlerOptions || {};
  138. this.layerOptions = OpenLayers.Util.applyDefaults(this.layerOptions, {
  139. displayInLayerSwitcher: false,
  140. tileOptions: {maxGetUrlLength: 2048}
  141. });
  142. if (this.sketchStyle) {
  143. this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
  144. this.handlerOptions.layerOptions,
  145. {styleMap: new OpenLayers.StyleMap({"default": this.sketchStyle})}
  146. );
  147. }
  148. this.handler = new handler(this, this.callbacks, this.handlerOptions);
  149. },
  150. /**
  151. * APIMethod: destroy
  152. * Take care of things that are not handled in superclass.
  153. */
  154. destroy: function() {
  155. for (var key in this.layerCache) {
  156. delete this.layerCache[key];
  157. }
  158. for (var key in this.wfsCache) {
  159. delete this.wfsCache[key];
  160. }
  161. OpenLayers.Control.prototype.destroy.apply(this, arguments);
  162. },
  163. /**
  164. * Method: coupleLayerVisiblity
  165. * Couple the selection layer and the source layer with respect to
  166. * layer visibility. So if the source layer is turned off, the
  167. * selection layer is also turned off.
  168. *
  169. * Context:
  170. * - {<OpenLayers.Layer>}
  171. *
  172. * Parameters:
  173. * evt - {Object}
  174. */
  175. coupleLayerVisiblity: function(evt) {
  176. this.setVisibility(evt.object.getVisibility());
  177. },
  178. /**
  179. * Method: createSelectionLayer
  180. * Creates a "clone" from the source layer in which the selection can
  181. * be drawn. This ensures both the source layer and the selection are
  182. * visible and not only the selection.
  183. *
  184. * Parameters:
  185. * source - {<OpenLayers.Layer.WMS>} The source layer on which the selection
  186. * is performed.
  187. *
  188. * Returns:
  189. * {<OpenLayers.Layer.WMS>} A WMS layer with maxGetUrlLength configured to 2048
  190. * since SLD selections can easily get quite long.
  191. */
  192. createSelectionLayer: function(source) {
  193. // check if we already have a selection layer for the source layer
  194. var selectionLayer;
  195. if (!this.layerCache[source.id]) {
  196. selectionLayer = new OpenLayers.Layer.WMS(source.name,
  197. source.url, source.params,
  198. OpenLayers.Util.applyDefaults(
  199. this.layerOptions,
  200. source.getOptions())
  201. );
  202. this.layerCache[source.id] = selectionLayer;
  203. // make sure the layers are coupled wrt visibility, but only
  204. // if they are not displayed in the layer switcher, because in
  205. // that case the user cannot control visibility.
  206. if (this.layerOptions.displayInLayerSwitcher === false) {
  207. source.events.on({
  208. "visibilitychanged": this.coupleLayerVisiblity,
  209. scope: selectionLayer});
  210. }
  211. this.map.addLayer(selectionLayer);
  212. } else {
  213. selectionLayer = this.layerCache[source.id];
  214. }
  215. return selectionLayer;
  216. },
  217. /**
  218. * Method: createSLD
  219. * Create the SLD document for the layer using the supplied filters.
  220. *
  221. * Parameters:
  222. * layer - {<OpenLayers.Layer.WMS>}
  223. * filters - Array({<OpenLayers.Filter>}) The filters to be applied.
  224. * geometryAttributes - Array({Object}) The geometry attributes of the
  225. * layer.
  226. *
  227. * Returns:
  228. * {String} The SLD document generated as a string.
  229. */
  230. createSLD: function(layer, filters, geometryAttributes) {
  231. var sld = {version: "1.0.0", namedLayers: {}};
  232. var layerNames = [layer.params.LAYERS].join(",").split(",");
  233. for (var i=0, len=layerNames.length; i<len; i++) {
  234. var name = layerNames[i];
  235. sld.namedLayers[name] = {name: name, userStyles: []};
  236. var symbolizer = this.selectionSymbolizer;
  237. var geometryAttribute = geometryAttributes[i];
  238. if (geometryAttribute.type.indexOf('Polygon') >= 0) {
  239. symbolizer = {Polygon: this.selectionSymbolizer['Polygon']};
  240. } else if (geometryAttribute.type.indexOf('LineString') >= 0) {
  241. symbolizer = {Line: this.selectionSymbolizer['Line']};
  242. } else if (geometryAttribute.type.indexOf('Point') >= 0) {
  243. symbolizer = {Point: this.selectionSymbolizer['Point']};
  244. }
  245. var filter = filters[i];
  246. sld.namedLayers[name].userStyles.push({name: 'default', rules: [
  247. new OpenLayers.Rule({symbolizer: symbolizer,
  248. filter: filter,
  249. maxScaleDenominator: layer.options.minScale})
  250. ]});
  251. }
  252. return new OpenLayers.Format.SLD({srsName: this.map.getProjection()}).write(sld);
  253. },
  254. /**
  255. * Method: parseDescribeLayer
  256. * Parse the SLD WMS DescribeLayer response and issue the corresponding
  257. * WFS DescribeFeatureType request
  258. *
  259. * request - {XMLHttpRequest} The request object.
  260. */
  261. parseDescribeLayer: function(request) {
  262. var format = new OpenLayers.Format.WMSDescribeLayer();
  263. var doc = request.responseXML;
  264. if(!doc || !doc.documentElement) {
  265. doc = request.responseText;
  266. }
  267. var describeLayer = format.read(doc);
  268. var typeNames = [];
  269. var url = null;
  270. for (var i=0, len=describeLayer.length; i<len; i++) {
  271. // perform a WFS DescribeFeatureType request
  272. if (describeLayer[i].owsType == "WFS") {
  273. typeNames.push(describeLayer[i].typeName);
  274. url = describeLayer[i].owsURL;
  275. }
  276. }
  277. var options = {
  278. url: url,
  279. params: {
  280. SERVICE: "WFS",
  281. TYPENAME: typeNames.toString(),
  282. REQUEST: "DescribeFeatureType",
  283. VERSION: "1.0.0"
  284. },
  285. callback: function(request) {
  286. var format = new OpenLayers.Format.WFSDescribeFeatureType();
  287. var doc = request.responseXML;
  288. if(!doc || !doc.documentElement) {
  289. doc = request.responseText;
  290. }
  291. var describeFeatureType = format.read(doc);
  292. this.control.wfsCache[this.layer.id] = describeFeatureType;
  293. this.control._queue && this.control.applySelection();
  294. },
  295. scope: this
  296. };
  297. OpenLayers.Request.GET(options);
  298. },
  299. /**
  300. * Method: getGeometryAttributes
  301. * Look up the geometry attributes from the WFS DescribeFeatureType response
  302. *
  303. * Parameters:
  304. * layer - {<OpenLayers.Layer.WMS>} The layer for which to look up the
  305. * geometry attributes.
  306. *
  307. * Returns:
  308. * Array({Object}) Array of geometry attributes
  309. */
  310. getGeometryAttributes: function(layer) {
  311. var result = [];
  312. var cache = this.wfsCache[layer.id];
  313. for (var i=0, len=cache.featureTypes.length; i<len; i++) {
  314. var typeName = cache.featureTypes[i];
  315. var properties = typeName.properties;
  316. for (var j=0, lenj=properties.length; j < lenj; j++) {
  317. var property = properties[j];
  318. var type = property.type;
  319. if ((type.indexOf('LineString') >= 0) ||
  320. (type.indexOf('GeometryAssociationType') >=0) ||
  321. (type.indexOf('GeometryPropertyType') >= 0) ||
  322. (type.indexOf('Point') >= 0) ||
  323. (type.indexOf('Polygon') >= 0) ) {
  324. result.push(property);
  325. }
  326. }
  327. }
  328. return result;
  329. },
  330. /**
  331. * APIMethod: activate
  332. * Activate the control. Activating the control will perform a SLD WMS
  333. * DescribeLayer request followed by a WFS DescribeFeatureType request
  334. * so that the proper symbolizers can be chosen based on the geometry
  335. * type.
  336. */
  337. activate: function() {
  338. var activated = OpenLayers.Control.prototype.activate.call(this);
  339. if(activated) {
  340. for (var i=0, len=this.layers.length; i<len; i++) {
  341. var layer = this.layers[i];
  342. if (layer && !this.wfsCache[layer.id]) {
  343. var options = {
  344. url: layer.url,
  345. params: {
  346. SERVICE: "WMS",
  347. VERSION: layer.params.VERSION,
  348. LAYERS: layer.params.LAYERS,
  349. REQUEST: "DescribeLayer"
  350. },
  351. callback: this.parseDescribeLayer,
  352. scope: {layer: layer, control: this}
  353. };
  354. OpenLayers.Request.GET(options);
  355. }
  356. }
  357. }
  358. return activated;
  359. },
  360. /**
  361. * APIMethod: deactivate
  362. * Deactivate the control. If clearOnDeactivate is true, remove the
  363. * selection layer(s).
  364. */
  365. deactivate: function() {
  366. var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
  367. if(deactivated) {
  368. for (var i=0, len=this.layers.length; i<len; i++) {
  369. var layer = this.layers[i];
  370. if (layer && this.clearOnDeactivate === true) {
  371. var layerCache = this.layerCache;
  372. var selectionLayer = layerCache[layer.id];
  373. if (selectionLayer) {
  374. layer.events.un({
  375. "visibilitychanged": this.coupleLayerVisiblity,
  376. scope: selectionLayer});
  377. selectionLayer.destroy();
  378. delete layerCache[layer.id];
  379. }
  380. }
  381. }
  382. }
  383. return deactivated;
  384. },
  385. /**
  386. * APIMethod: setLayers
  387. * Set the layers on which the selection should be performed. Call the
  388. * setLayers method if the layer(s) to be used change and the same
  389. * control should be used on a new set of layers.
  390. * If the control is already active, it will be active after the new
  391. * set of layers is set.
  392. *
  393. * Parameters:
  394. * layers - {Array(<OpenLayers.Layer.WMS>)} The new set of layers on which
  395. * the selection should be performed.
  396. */
  397. setLayers: function(layers) {
  398. if(this.active) {
  399. this.deactivate();
  400. this.layers = layers;
  401. this.activate();
  402. } else {
  403. this.layers = layers;
  404. }
  405. },
  406. /**
  407. * Function: createFilter
  408. * Create the filter to be used in the SLD.
  409. *
  410. * Parameters:
  411. * geometryAttribute - {Object} Used to get the name of the geometry
  412. * attribute which is needed for constructing the spatial filter.
  413. * geometry - {<OpenLayers.Geometry>} The geometry to use.
  414. *
  415. * Returns:
  416. * {<OpenLayers.Filter.Spatial>} The spatial filter created.
  417. */
  418. createFilter: function(geometryAttribute, geometry) {
  419. var filter = null;
  420. if (this.handler instanceof OpenLayers.Handler.RegularPolygon) {
  421. // box
  422. if (this.handler.irregular === true) {
  423. filter = new OpenLayers.Filter.Spatial({
  424. type: OpenLayers.Filter.Spatial.BBOX,
  425. property: geometryAttribute.name,
  426. value: geometry.getBounds()}
  427. );
  428. } else {
  429. filter = new OpenLayers.Filter.Spatial({
  430. type: OpenLayers.Filter.Spatial.INTERSECTS,
  431. property: geometryAttribute.name,
  432. value: geometry}
  433. );
  434. }
  435. } else if (this.handler instanceof OpenLayers.Handler.Polygon) {
  436. filter = new OpenLayers.Filter.Spatial({
  437. type: OpenLayers.Filter.Spatial.INTERSECTS,
  438. property: geometryAttribute.name,
  439. value: geometry}
  440. );
  441. } else if (this.handler instanceof OpenLayers.Handler.Path) {
  442. // if source layer is point based, use DWITHIN instead
  443. if (geometryAttribute.type.indexOf('Point') >= 0) {
  444. filter = new OpenLayers.Filter.Spatial({
  445. type: OpenLayers.Filter.Spatial.DWITHIN,
  446. property: geometryAttribute.name,
  447. distance: this.map.getExtent().getWidth()*0.01 ,
  448. distanceUnits: this.map.getUnits(),
  449. value: geometry}
  450. );
  451. } else {
  452. filter = new OpenLayers.Filter.Spatial({
  453. type: OpenLayers.Filter.Spatial.INTERSECTS,
  454. property: geometryAttribute.name,
  455. value: geometry}
  456. );
  457. }
  458. } else if (this.handler instanceof OpenLayers.Handler.Click) {
  459. if (geometryAttribute.type.indexOf('Polygon') >= 0) {
  460. filter = new OpenLayers.Filter.Spatial({
  461. type: OpenLayers.Filter.Spatial.INTERSECTS,
  462. property: geometryAttribute.name,
  463. value: geometry}
  464. );
  465. } else {
  466. filter = new OpenLayers.Filter.Spatial({
  467. type: OpenLayers.Filter.Spatial.DWITHIN,
  468. property: geometryAttribute.name,
  469. distance: this.map.getExtent().getWidth()*0.01 ,
  470. distanceUnits: this.map.getUnits(),
  471. value: geometry}
  472. );
  473. }
  474. }
  475. return filter;
  476. },
  477. /**
  478. * Method: select
  479. * When the handler is done, use SLD_BODY on the selection layer to
  480. * display the selection in the map.
  481. *
  482. * Parameters:
  483. * geometry - {Object} or {<OpenLayers.Geometry>}
  484. */
  485. select: function(geometry) {
  486. this._queue = function() {
  487. for (var i=0, len=this.layers.length; i<len; i++) {
  488. var layer = this.layers[i];
  489. var geometryAttributes = this.getGeometryAttributes(layer);
  490. var filters = [];
  491. for (var j=0, lenj=geometryAttributes.length; j<lenj; j++) {
  492. var geometryAttribute = geometryAttributes[j];
  493. if (geometryAttribute !== null) {
  494. // from the click handler we will not get an actual
  495. // geometry so transform
  496. if (!(geometry instanceof OpenLayers.Geometry)) {
  497. var point = this.map.getLonLatFromPixel(
  498. geometry.xy);
  499. geometry = new OpenLayers.Geometry.Point(
  500. point.lon, point.lat);
  501. }
  502. var filter = this.createFilter(geometryAttribute,
  503. geometry);
  504. if (filter !== null) {
  505. filters.push(filter);
  506. }
  507. }
  508. }
  509. var selectionLayer = this.createSelectionLayer(layer);
  510. this.events.triggerEvent("selected", {
  511. layer: layer,
  512. filters: filters
  513. });
  514. var sld = this.createSLD(layer, filters, geometryAttributes);
  515. selectionLayer.mergeNewParams({SLD_BODY: sld});
  516. delete this._queue;
  517. }
  518. };
  519. this.applySelection();
  520. },
  521. /**
  522. * Method: applySelection
  523. * Checks if all required wfs data is cached, and applies the selection
  524. */
  525. applySelection: function() {
  526. var canApply = true;
  527. for (var i=0, len=this.layers.length; i<len; i++) {
  528. if(!this.wfsCache[this.layers[i].id]) {
  529. canApply = false;
  530. break;
  531. }
  532. }
  533. canApply && this._queue.call(this);
  534. },
  535. CLASS_NAME: "OpenLayers.Control.SLDSelect"
  536. });