SLDSelect.js 21 KB

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