diff --git a/examples/wps-client.js b/examples/wps-client.js index 84971e2013..dee7e5c50d 100644 --- a/examples/wps-client.js +++ b/examples/wps-client.js @@ -1,6 +1,6 @@ OpenLayers.ProxyHost = 'proxy.cgi?url='; -var map, client, process; +var map, client, intersect, buffer; function init() { @@ -23,18 +23,27 @@ function init() { client = new OpenLayers.WPSClient({ servers: { - local: "http://demo.opengeo.org/geoserver/wps" + opengeo: 'http://demo.opengeo.org/geoserver/wps' } }); - // Create a process and execute it - process = client.getProcess("local", "JTS:intersection"); - process.execute({ + // Create a process and configure it + intersect = client.getProcess('opengeo', 'JTS:intersection'); + intersect.configure({ // spatial input can be a feature or a geometry or an array of // features or geometries inputs: { a: features, b: geometry + } + }); + + // Create another process which chains the previous one and execute it + buffer = client.getProcess('opengeo', 'JTS:buffer'); + buffer.execute({ + inputs: { + geom: intersect.output(), + distance: 1 }, success: function(outputs) { // outputs.result is a feature or an array of features for spatial diff --git a/lib/OpenLayers/WPSProcess.js b/lib/OpenLayers/WPSProcess.js index 400a2eb455..7344df5768 100644 --- a/lib/OpenLayers/WPSProcess.js +++ b/lib/OpenLayers/WPSProcess.js @@ -14,7 +14,7 @@ */ /** - * Class: OpenLayers.WPSProces + * Class: OpenLayers.WPSProcess */ OpenLayers.WPSProcess = OpenLayers.Class({ @@ -23,7 +23,8 @@ OpenLayers.WPSProcess = OpenLayers.Class({ * {} * * Supported event types: - * describeprocess - fires when the process description is available + * describeprocess - Fires when the process description is available for + * the first time. */ events: null, @@ -57,6 +58,20 @@ OpenLayers.WPSProcess = OpenLayers.Class({ */ formats: null, + /** + * Property: chained + * {Integer} Number of chained processes for pending execute reqeusts that + * don't have a full configuration yet. + */ + chained: 0, + + /** + * Property: executeCallbacks + * {Array} Callbacks waiting to be executed until all chained processes + * are configured; + */ + executeCallbacks: null, + /** * Constructor: OpenLayers.WPSProcess * @@ -74,6 +89,7 @@ OpenLayers.WPSProcess = OpenLayers.Class({ OpenLayers.Util.extend(this, options); this.events = new OpenLayers.Events(this); + this.executeCallbacks = []; this.formats = { 'application/wkt': new OpenLayers.Format.WKT(), @@ -86,17 +102,38 @@ OpenLayers.WPSProcess = OpenLayers.Class({ * Issues a DescribeProcess request asynchronously and fires the * 'describeprocess' event as soon as the response is available in * . + * + * Parameters: + * options - {Object} Coniguration for the method call + * + * Available options: + * callback - {Function} Callback to execute when the description is + * available. Will be called with the parsed description as argument. + * Optional. + * scope - {Object} The scope in which the callback will be executed. + * Default is the global object. */ - describe: function() { + describe: function(options) { + options = options || {}; + function callback() { + if (options.callback) { + window.setTimeout(function() { + options.callback.call(options.scope, this.description); + }, 0); + } + } var server = this.client.servers[this.server]; if (this.description !== null) { + callback(); return; } else if (server.describeProcessResponse[this.identifier] === null) { // pending request + this.events.register('describeprocess', this, callback); return; } else if (this.identifier in server.describeProcessResponse) { // process description already cached on client this.parseDescription(); + callback(); return; } // set to null so we know a describeFeature request is pending @@ -109,14 +146,21 @@ OpenLayers.WPSProcess = OpenLayers.Class({ REQUEST: 'DescribeProcess', IDENTIFIER: this.identifier }, - success: this.parseDescription, + success: function(response) { + this.parseDescription(response); + if (options.callback) { + options.callback.call(options.scope, this.description); + } + }, scope: this }); }, /** - * APIMethod: execute - * Executes the process + * APIMethod: configure + * Configure the process, but do not execute it. Use this for processes + * that are chained as input of a different process by means of the + * method. * * Parameters: * options - {Object} @@ -126,20 +170,18 @@ OpenLayers.WPSProcess = OpenLayers.Class({ * For spatial data inputs, the value of an input is usually an * , an or an array of * geometries or features. - * success - {Function} Callback to call when the process is complete. - * This function is called with an outputs object as argument, which - * will have a 'result' property. For processes that generate spatial - * output, this will either be a single or - * an array of features. - * scope - {Object} Optional scope for the success callback. + * callback - {Function} Callback to call when the configuration is + * complete. Optional. + * scope - {Object} Optional scope for the callback. */ - execute: function(options) { + configure: function(options) { if (!this.description) { - this.events.register('describeprocess', this, function execute() { - this.events.unregister('describeprocess', this, execute); - this.execute(options); + this.describe({ + callback: function() { + this.configure(options); + }, + scope: this }); - this.describe(); return; } var description = this.description, @@ -149,26 +191,105 @@ OpenLayers.WPSProcess = OpenLayers.Class({ input = description.dataInputs[i]; this.setInputData(input, inputs[input.identifier]); } - //TODO For now we only handle responseForm with a single output - this.setResponseForm(); - OpenLayers.Request.POST({ - url: this.client.servers[this.server].url, - data: new OpenLayers.Format.WPSExecute().write(this.description), - success: function(response) { - var mimeType = this.findMimeType(this.description.processOutputs[0].complexOutput); - var features = this.formats[mimeType].read(response.responseText); - if (options.success) { - options.success.call(options.scope, { - result: features + if (options.callback) { + options.callback.call(options.scope); + } + }, + + /** + * APIMethod: execute + * Configures and executes the process + * + * Parameters: + * options - {Object} + * + * Available options: + * inputs - {Object} The inputs for the process, keyed by input identifier. + * For spatial data inputs, the value of an input is usually an + * , an or an array of + * geometries or features. + * output - {String} The identifier of an output to parse. Optional. If not + * provided, the first output will be parsed. + * success - {Function} Callback to call when the process is complete. + * This function is called with an outputs object as argument, which + * will have a 'result' property. For processes that generate spatial + * output, this will either be a single or + * an array of features. + * scope - {Object} Optional scope for the success callback. + */ + execute: function(options) { + this.configure({ + inputs: options.inputs, + callback: function() { + var me = this; + //TODO For now we only deal with a single output + var output = this.getOutputIndex( + me.description.processOutputs, options.output + ); + me.setResponseForm({outputIndex: output}); + (function callback() { + OpenLayers.Util.removeItem(me.executeCallbacks, callback); + if (me.chained !== 0) { + me.executeCallbacks.push(callback); + return; + } + OpenLayers.Request.POST({ + url: me.client.servers[me.server].url, + data: new OpenLayers.Format.WPSExecute().write(me.description), + success: function(response) { + var mimeType = me.findMimeType( + me.description.processOutputs[output].complexOutput.supported.formats + ); + //TODO For now we assume a spatial output + var features = me.formats[mimeType].read(response.responseText); + if (options.success) { + options.success.call(options.scope, { + result: features + }); + } + }, + scope: me }); - } + })(); }, scope: this }); }, + /** + * APIMethod: output + * Chain an output of a configured process (see ) as input to + * another process. + * + * (code) + * intersect = client.getProcess('opengeo', 'JTS:intersection'); + * intersect.configure({ + * // ... + * }); + * buffer = client.getProcess('opengeo', 'JTS:buffer'); + * buffer.execute({ + * inputs: { + * geom: intersect.output(), // <-- here we're chaining + * distance: 1 + * }, + * // ... + * }); + * (end) + * + * Parameters: + * identifier - {String} Identifier of the output that we're chaining. If + * not provided, the first output will be used. + */ + output: function(identifier) { + return new OpenLayers.WPSProcess.ChainLink({ + process: this, + output: identifier + }); + }, + /** * Method: parseDescription + * Parses the DescribeProcess response * * Parameters: * response - {Object} @@ -197,15 +318,37 @@ OpenLayers.WPSProcess = OpenLayers.Class({ */ setInputData: function(input, data) { // clear any previous data - input.data = {}; - if (data) { + delete input.data; + delete input.reference; + if (data instanceof OpenLayers.WPSProcess.ChainLink) { + ++this.chained; + input.reference = { + method: 'POST', + href: data.process.server === this.server ? + //TODO what about implementations other than GeoServer? + 'http://geoserver/wps' : + this.client.servers[data.process.server].url + }; + data.process.describe({ + callback: function() { + --this.chained; + this.chainProcess(input, data); + }, + scope: this + }); + } else { + input.data = {}; var complexData = input.complexData; if (complexData) { - var format = this.findMimeType(complexData); + var format = this.findMimeType(complexData.supported.formats); input.data.complexData = { mimeType: format, value: this.formats[format].write(this.toFeatures(data)) }; + } else { + input.data.literalData = { + value: data + }; } } }, @@ -213,17 +356,81 @@ OpenLayers.WPSProcess = OpenLayers.Class({ /** * Method: setResponseForm * Sets the responseForm property of the payload. + * + * Parameters: + * options - {Object} See below. + * + * Available options: + * outputIndex - {Integer} The index of the output to use. Optional. + * supportedFormats - {Object} Object with supported mime types as key, + * and true as value for supported types. Optional. */ - setResponseForm: function() { - output = this.description.processOutputs[0]; + setResponseForm: function(options) { + options = options || {}; + output = this.description.processOutputs[options.outputIndex || 0]; this.description.responseForm = { rawDataOutput: { identifier: output.identifier, - mimeType: this.findMimeType(output.complexOutput) + mimeType: this.findMimeType(output.complexOutput.supported.formats, options.supportedFormats) } }; }, + /** + * Method: getOutputIndex + * Gets the index of a processOutput by its identifier + * + * Parameters: + * outputs - {Array} The processOutputs array to look at + * identifier - {String} The identifier of the output + * + * Returns + * {Integer} The index of the processOutput with the provided identifier + * in the outputs array. + */ + getOutputIndex: function(outputs, identifier) { + var output; + if (identifier) { + for (var i=outputs.length-1; i>=0; --i) { + if (outputs[i].identifier === identifier) { + output = i; + break; + } + } + } else { + output = 0; + } + return output; + }, + + /** + * Method: chainProcess + * Sets a fully configured chained process as input for this process. + * + * Parameters: + * input - {Object} The dataInput that the chained process provides. + * chainLink - { 0) { + this.executeCallbacks[0](); + } + }, + /** * Method: toFeatures * Converts spatial input into features so it can be processed by @@ -256,16 +463,19 @@ OpenLayers.WPSProcess = OpenLayers.Class({ * Finds a supported mime type. * * Parameters: - * complex - {Object} A complexData or complexOutput object from the - * process description. + * sourceFormats - {Object} An object literal with mime types as key and + * true as value for supported formats. + * targetFormats - {Object} Like , but optional to check for + * supported mime types on a different target than this process. + * Default is to check against this process's supported formats. * * Returns: * {String} A supported mime type. */ - findMimeType: function(complex) { - var formats = complex.supported.formats; - for (var f in formats) { - if (f in this.formats) { + findMimeType: function(sourceFormats, targetFormats) { + targetFormats = targetFormats || this.formats; + for (var f in sourceFormats) { + if (f in targetFormats) { return f; } } @@ -274,3 +484,18 @@ OpenLayers.WPSProcess = OpenLayers.Class({ CLASS_NAME: "OpenLayers.WPSProcess" }); + +/** + * Class: OpenLayers.WPSProcess.ChainLink + */ +OpenLayers.WPSProcess.ChainLink = OpenLayers.Class({ + + process: null, + + output: null, + + initialize: function(options) { + OpenLayers.Util.extend(this, options); + } + +});