WPSProcess.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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/SingleFile.js
  7. */
  8. /**
  9. * @requires OpenLayers/Geometry.js
  10. * @requires OpenLayers/Feature/Vector.js
  11. * @requires OpenLayers/Format/WKT.js
  12. * @requires OpenLayers/Format/GeoJSON.js
  13. * @requires OpenLayers/Format/WPSExecute.js
  14. * @requires OpenLayers/Request.js
  15. */
  16. /**
  17. * Class: OpenLayers.WPSProcess
  18. * Representation of a WPS process. Usually instances of
  19. * <OpenLayers.WPSProcess> are created by calling 'getProcess' on an
  20. * <OpenLayers.WPSClient> instance.
  21. *
  22. * Currently <OpenLayers.WPSProcess> supports processes that have geometries
  23. * or features as output, using WKT or GeoJSON as output format. It also
  24. * supports chaining of processes by using the <output> method to create a
  25. * handle that is used as process input instead of a static value.
  26. */
  27. OpenLayers.WPSProcess = OpenLayers.Class({
  28. /**
  29. * Property: client
  30. * {<OpenLayers.WPSClient>} The client that manages this process.
  31. */
  32. client: null,
  33. /**
  34. * Property: server
  35. * {String} Local client identifier for this process's server.
  36. */
  37. server: null,
  38. /**
  39. * Property: identifier
  40. * {String} Process identifier known to the server.
  41. */
  42. identifier: null,
  43. /**
  44. * Property: description
  45. * {Object} DescribeProcess response for this process.
  46. */
  47. description: null,
  48. /**
  49. * APIProperty: localWPS
  50. * {String} Service endpoint for locally chained WPS processes. Default is
  51. * 'http://geoserver/wps'.
  52. */
  53. localWPS: 'http://geoserver/wps',
  54. /**
  55. * Property: formats
  56. * {Object} OpenLayers.Format instances keyed by mimetype.
  57. */
  58. formats: null,
  59. /**
  60. * Property: chained
  61. * {Integer} Number of chained processes for pending execute requests that
  62. * don't have a full configuration yet.
  63. */
  64. chained: 0,
  65. /**
  66. * Property: executeCallbacks
  67. * {Array} Callbacks waiting to be executed until all chained processes
  68. * are configured;
  69. */
  70. executeCallbacks: null,
  71. /**
  72. * Constructor: OpenLayers.WPSProcess
  73. *
  74. * Parameters:
  75. * options - {Object} Object whose properties will be set on the instance.
  76. *
  77. * Avaliable options:
  78. * client - {<OpenLayers.WPSClient>} Mandatory. Client that manages this
  79. * process.
  80. * server - {String} Mandatory. Local client identifier of this process's
  81. * server.
  82. * identifier - {String} Mandatory. Process identifier known to the server.
  83. */
  84. initialize: function(options) {
  85. OpenLayers.Util.extend(this, options);
  86. this.executeCallbacks = [];
  87. this.formats = {
  88. 'application/wkt': new OpenLayers.Format.WKT(),
  89. 'application/json': new OpenLayers.Format.GeoJSON()
  90. };
  91. },
  92. /**
  93. * Method: describe
  94. * Makes the client issue a DescribeProcess request asynchronously.
  95. *
  96. * Parameters:
  97. * options - {Object} Configuration for the method call
  98. *
  99. * Available options:
  100. * callback - {Function} Callback to execute when the description is
  101. * available. Will be called with the parsed description as argument.
  102. * Optional.
  103. * scope - {Object} The scope in which the callback will be executed.
  104. * Default is the global object.
  105. */
  106. describe: function(options) {
  107. options = options || {};
  108. if (!this.description) {
  109. this.client.describeProcess(this.server, this.identifier, function(description) {
  110. if (!this.description) {
  111. this.parseDescription(description);
  112. }
  113. if (options.callback) {
  114. options.callback.call(options.scope, this.description);
  115. }
  116. }, this);
  117. } else if (options.callback) {
  118. var description = this.description;
  119. window.setTimeout(function() {
  120. options.callback.call(options.scope, description);
  121. }, 0);
  122. }
  123. },
  124. /**
  125. * APIMethod: configure
  126. * Configure the process, but do not execute it. Use this for processes
  127. * that are chained as input of a different process by means of the
  128. * <output> method.
  129. *
  130. * Parameters:
  131. * options - {Object}
  132. *
  133. * Returns:
  134. * {<OpenLayers.WPSProcess>} this process.
  135. *
  136. * Available options:
  137. * inputs - {Object} The inputs for the process, keyed by input identifier.
  138. * For spatial data inputs, the value of an input is usually an
  139. * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
  140. * geometries or features.
  141. * callback - {Function} Callback to call when the configuration is
  142. * complete. Optional.
  143. * scope - {Object} Optional scope for the callback.
  144. */
  145. configure: function(options) {
  146. this.describe({
  147. callback: function() {
  148. var description = this.description,
  149. inputs = options.inputs,
  150. input, i, ii;
  151. for (i=0, ii=description.dataInputs.length; i<ii; ++i) {
  152. input = description.dataInputs[i];
  153. this.setInputData(input, inputs[input.identifier]);
  154. }
  155. if (options.callback) {
  156. options.callback.call(options.scope);
  157. }
  158. },
  159. scope: this
  160. });
  161. return this;
  162. },
  163. /**
  164. * APIMethod: execute
  165. * Configures and executes the process
  166. *
  167. * Parameters:
  168. * options - {Object}
  169. *
  170. * Available options:
  171. * inputs - {Object} The inputs for the process, keyed by input identifier.
  172. * For spatial data inputs, the value of an input is usually an
  173. * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
  174. * geometries or features.
  175. * output - {String} The identifier of the output to request and parse.
  176. * Optional. If not provided, the first output will be requested.
  177. * success - {Function} Callback to call when the process is complete.
  178. * This function is called with an outputs object as argument, which
  179. * will have a property with the identifier of the requested output
  180. * (or 'result' if output was not configured). For processes that
  181. * generate spatial output, the value will be an array of
  182. * <OpenLayers.Feature.Vector> instances.
  183. * scope - {Object} Optional scope for the success callback.
  184. */
  185. execute: function(options) {
  186. this.configure({
  187. inputs: options.inputs,
  188. callback: function() {
  189. var me = this;
  190. //TODO For now we only deal with a single output
  191. var outputIndex = this.getOutputIndex(
  192. me.description.processOutputs, options.output
  193. );
  194. me.setResponseForm({outputIndex: outputIndex});
  195. (function callback() {
  196. OpenLayers.Util.removeItem(me.executeCallbacks, callback);
  197. if (me.chained !== 0) {
  198. // need to wait until chained processes have a
  199. // description and configuration - see chainProcess
  200. me.executeCallbacks.push(callback);
  201. return;
  202. }
  203. // all chained processes are added as references now, so
  204. // let's proceed.
  205. OpenLayers.Request.POST({
  206. url: me.client.servers[me.server].url,
  207. data: new OpenLayers.Format.WPSExecute().write(me.description),
  208. success: function(response) {
  209. var output = me.description.processOutputs[outputIndex];
  210. var mimeType = me.findMimeType(
  211. output.complexOutput.supported.formats
  212. );
  213. //TODO For now we assume a spatial output
  214. var features = me.formats[mimeType].read(response.responseText);
  215. if (features instanceof OpenLayers.Feature.Vector) {
  216. features = [features];
  217. }
  218. if (options.success) {
  219. var outputs = {};
  220. outputs[options.output || 'result'] = features;
  221. options.success.call(options.scope, outputs);
  222. }
  223. },
  224. scope: me
  225. });
  226. })();
  227. },
  228. scope: this
  229. });
  230. },
  231. /**
  232. * APIMethod: output
  233. * Chain an output of a configured process (see <configure>) as input to
  234. * another process.
  235. *
  236. * (code)
  237. * intersect = client.getProcess('opengeo', 'JTS:intersection');
  238. * intersect.configure({
  239. * // ...
  240. * });
  241. * buffer = client.getProcess('opengeo', 'JTS:buffer');
  242. * buffer.execute({
  243. * inputs: {
  244. * geom: intersect.output('result'), // <-- here we're chaining
  245. * distance: 1
  246. * },
  247. * // ...
  248. * });
  249. * (end)
  250. *
  251. * Parameters:
  252. * identifier - {String} Identifier of the output that we're chaining. If
  253. * not provided, the first output will be used.
  254. */
  255. output: function(identifier) {
  256. return new OpenLayers.WPSProcess.ChainLink({
  257. process: this,
  258. output: identifier
  259. });
  260. },
  261. /**
  262. * Method: parseDescription
  263. * Parses the DescribeProcess response
  264. *
  265. * Parameters:
  266. * description - {Object}
  267. */
  268. parseDescription: function(description) {
  269. var server = this.client.servers[this.server];
  270. this.description = new OpenLayers.Format.WPSDescribeProcess()
  271. .read(server.processDescription[this.identifier])
  272. .processDescriptions[this.identifier];
  273. },
  274. /**
  275. * Method: setInputData
  276. * Sets the data for a single input
  277. *
  278. * Parameters:
  279. * input - {Object} An entry from the dataInputs array of the process
  280. * description.
  281. * data - {Mixed} For spatial data inputs, this is usually an
  282. * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
  283. * geometries or features.
  284. */
  285. setInputData: function(input, data) {
  286. // clear any previous data
  287. delete input.data;
  288. delete input.reference;
  289. if (data instanceof OpenLayers.WPSProcess.ChainLink) {
  290. ++this.chained;
  291. input.reference = {
  292. method: 'POST',
  293. href: data.process.server === this.server ?
  294. this.localWPS : this.client.servers[data.process.server].url
  295. };
  296. data.process.describe({
  297. callback: function() {
  298. --this.chained;
  299. this.chainProcess(input, data);
  300. },
  301. scope: this
  302. });
  303. } else {
  304. input.data = {};
  305. var complexData = input.complexData;
  306. if (complexData) {
  307. var format = this.findMimeType(complexData.supported.formats);
  308. input.data.complexData = {
  309. mimeType: format,
  310. value: this.formats[format].write(this.toFeatures(data))
  311. };
  312. } else {
  313. input.data.literalData = {
  314. value: data
  315. };
  316. }
  317. }
  318. },
  319. /**
  320. * Method: setResponseForm
  321. * Sets the responseForm property of the <execute> payload.
  322. *
  323. * Parameters:
  324. * options - {Object} See below.
  325. *
  326. * Available options:
  327. * outputIndex - {Integer} The index of the output to use. Optional.
  328. * supportedFormats - {Object} Object with supported mime types as key,
  329. * and true as value for supported types. Optional.
  330. */
  331. setResponseForm: function(options) {
  332. options = options || {};
  333. var output = this.description.processOutputs[options.outputIndex || 0];
  334. this.description.responseForm = {
  335. rawDataOutput: {
  336. identifier: output.identifier,
  337. mimeType: this.findMimeType(output.complexOutput.supported.formats, options.supportedFormats)
  338. }
  339. };
  340. },
  341. /**
  342. * Method: getOutputIndex
  343. * Gets the index of a processOutput by its identifier
  344. *
  345. * Parameters:
  346. * outputs - {Array} The processOutputs array to look at
  347. * identifier - {String} The identifier of the output
  348. *
  349. * Returns
  350. * {Integer} The index of the processOutput with the provided identifier
  351. * in the outputs array.
  352. */
  353. getOutputIndex: function(outputs, identifier) {
  354. var output;
  355. if (identifier) {
  356. for (var i=outputs.length-1; i>=0; --i) {
  357. if (outputs[i].identifier === identifier) {
  358. output = i;
  359. break;
  360. }
  361. }
  362. } else {
  363. output = 0;
  364. }
  365. return output;
  366. },
  367. /**
  368. * Method: chainProcess
  369. * Sets a fully configured chained process as input for this process.
  370. *
  371. * Parameters:
  372. * input - {Object} The dataInput that the chained process provides.
  373. * chainLink - {<OpenLayers.WPSProcess.ChainLink>} The process to chain.
  374. */
  375. chainProcess: function(input, chainLink) {
  376. var output = this.getOutputIndex(
  377. chainLink.process.description.processOutputs, chainLink.output
  378. );
  379. input.reference.mimeType = this.findMimeType(
  380. input.complexData.supported.formats,
  381. chainLink.process.description.processOutputs[output].complexOutput.supported.formats
  382. );
  383. var formats = {};
  384. formats[input.reference.mimeType] = true;
  385. chainLink.process.setResponseForm({
  386. outputIndex: output,
  387. supportedFormats: formats
  388. });
  389. input.reference.body = chainLink.process.description;
  390. while (this.executeCallbacks.length > 0) {
  391. this.executeCallbacks[0]();
  392. }
  393. },
  394. /**
  395. * Method: toFeatures
  396. * Converts spatial input into features so it can be processed by
  397. * <OpenLayers.Format> instances.
  398. *
  399. * Parameters:
  400. * source - {Mixed} An <OpenLayers.Geometry>, an
  401. * <OpenLayers.Feature.Vector>, or an array of geometries or features
  402. *
  403. * Returns:
  404. * {Array(<OpenLayers.Feature.Vector>)}
  405. */
  406. toFeatures: function(source) {
  407. var isArray = OpenLayers.Util.isArray(source);
  408. if (!isArray) {
  409. source = [source];
  410. }
  411. var target = new Array(source.length),
  412. current;
  413. for (var i=0, ii=source.length; i<ii; ++i) {
  414. current = source[i];
  415. target[i] = current instanceof OpenLayers.Feature.Vector ?
  416. current : new OpenLayers.Feature.Vector(current);
  417. }
  418. return isArray ? target : target[0];
  419. },
  420. /**
  421. * Method: findMimeType
  422. * Finds a supported mime type.
  423. *
  424. * Parameters:
  425. * sourceFormats - {Object} An object literal with mime types as key and
  426. * true as value for supported formats.
  427. * targetFormats - {Object} Like <sourceFormats>, but optional to check for
  428. * supported mime types on a different target than this process.
  429. * Default is to check against this process's supported formats.
  430. *
  431. * Returns:
  432. * {String} A supported mime type.
  433. */
  434. findMimeType: function(sourceFormats, targetFormats) {
  435. targetFormats = targetFormats || this.formats;
  436. for (var f in sourceFormats) {
  437. if (f in targetFormats) {
  438. return f;
  439. }
  440. }
  441. },
  442. CLASS_NAME: "OpenLayers.WPSProcess"
  443. });
  444. /**
  445. * Class: OpenLayers.WPSProcess.ChainLink
  446. * Type for chaining processes.
  447. */
  448. OpenLayers.WPSProcess.ChainLink = OpenLayers.Class({
  449. /**
  450. * Property: process
  451. * {<OpenLayers.WPSProcess>} The process to chain
  452. */
  453. process: null,
  454. /**
  455. * Property: output
  456. * {String} The output identifier of the output we are going to use as
  457. * input for another process.
  458. */
  459. output: null,
  460. /**
  461. * Constructor: OpenLayers.WPSProcess.ChainLink
  462. *
  463. * Parameters:
  464. * options - {Object} Properties to set on the instance.
  465. */
  466. initialize: function(options) {
  467. OpenLayers.Util.extend(this, options);
  468. },
  469. CLASS_NAME: "OpenLayers.WPSProcess.ChainLink"
  470. });