Adding support for chaining processes.

This commit is contained in:
ahocevar
2012-08-09 18:28:53 +02:00
parent c64621f510
commit ec9ab8129e
2 changed files with 280 additions and 46 deletions

View File

@@ -1,6 +1,6 @@
OpenLayers.ProxyHost = 'proxy.cgi?url='; OpenLayers.ProxyHost = 'proxy.cgi?url=';
var map, client, process; var map, client, intersect, buffer;
function init() { function init() {
@@ -23,18 +23,27 @@ function init() {
client = new OpenLayers.WPSClient({ client = new OpenLayers.WPSClient({
servers: { servers: {
local: "http://demo.opengeo.org/geoserver/wps" opengeo: 'http://demo.opengeo.org/geoserver/wps'
} }
}); });
// Create a process and execute it // Create a process and configure it
process = client.getProcess("local", "JTS:intersection"); intersect = client.getProcess('opengeo', 'JTS:intersection');
process.execute({ intersect.configure({
// spatial input can be a feature or a geometry or an array of // spatial input can be a feature or a geometry or an array of
// features or geometries // features or geometries
inputs: { inputs: {
a: features, a: features,
b: geometry 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) { success: function(outputs) {
// outputs.result is a feature or an array of features for spatial // outputs.result is a feature or an array of features for spatial

View File

@@ -14,7 +14,7 @@
*/ */
/** /**
* Class: OpenLayers.WPSProces * Class: OpenLayers.WPSProcess
*/ */
OpenLayers.WPSProcess = OpenLayers.Class({ OpenLayers.WPSProcess = OpenLayers.Class({
@@ -23,7 +23,8 @@ OpenLayers.WPSProcess = OpenLayers.Class({
* {<OpenLayers.Events>} * {<OpenLayers.Events>}
* *
* Supported event types: * 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, events: null,
@@ -57,6 +58,20 @@ OpenLayers.WPSProcess = OpenLayers.Class({
*/ */
formats: null, 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 * Constructor: OpenLayers.WPSProcess
* *
@@ -74,6 +89,7 @@ OpenLayers.WPSProcess = OpenLayers.Class({
OpenLayers.Util.extend(this, options); OpenLayers.Util.extend(this, options);
this.events = new OpenLayers.Events(this); this.events = new OpenLayers.Events(this);
this.executeCallbacks = [];
this.formats = { this.formats = {
'application/wkt': new OpenLayers.Format.WKT(), 'application/wkt': new OpenLayers.Format.WKT(),
@@ -86,17 +102,38 @@ OpenLayers.WPSProcess = OpenLayers.Class({
* Issues a DescribeProcess request asynchronously and fires the * Issues a DescribeProcess request asynchronously and fires the
* 'describeprocess' event as soon as the response is available in * 'describeprocess' event as soon as the response is available in
* <description>. * <description>.
*
* 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]; var server = this.client.servers[this.server];
if (this.description !== null) { if (this.description !== null) {
callback();
return; return;
} else if (server.describeProcessResponse[this.identifier] === null) { } else if (server.describeProcessResponse[this.identifier] === null) {
// pending request // pending request
this.events.register('describeprocess', this, callback);
return; return;
} else if (this.identifier in server.describeProcessResponse) { } else if (this.identifier in server.describeProcessResponse) {
// process description already cached on client // process description already cached on client
this.parseDescription(); this.parseDescription();
callback();
return; return;
} }
// set to null so we know a describeFeature request is pending // set to null so we know a describeFeature request is pending
@@ -109,14 +146,21 @@ OpenLayers.WPSProcess = OpenLayers.Class({
REQUEST: 'DescribeProcess', REQUEST: 'DescribeProcess',
IDENTIFIER: this.identifier IDENTIFIER: this.identifier
}, },
success: this.parseDescription, success: function(response) {
this.parseDescription(response);
if (options.callback) {
options.callback.call(options.scope, this.description);
}
},
scope: this scope: this
}); });
}, },
/** /**
* APIMethod: execute * APIMethod: configure
* Executes the process * 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
* <output> method.
* *
* Parameters: * Parameters:
* options - {Object} * options - {Object}
@@ -126,20 +170,18 @@ OpenLayers.WPSProcess = OpenLayers.Class({
* For spatial data inputs, the value of an input is usually an * For spatial data inputs, the value of an input is usually an
* <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
* geometries or features. * geometries or features.
* success - {Function} Callback to call when the process is complete. * callback - {Function} Callback to call when the configuration is
* This function is called with an outputs object as argument, which * complete. Optional.
* will have a 'result' property. For processes that generate spatial * scope - {Object} Optional scope for the callback.
* output, this will either be a single <OpenLayers.Feature.Vector> or
* an array of features.
* scope - {Object} Optional scope for the success callback.
*/ */
execute: function(options) { configure: function(options) {
if (!this.description) { if (!this.description) {
this.events.register('describeprocess', this, function execute() { this.describe({
this.events.unregister('describeprocess', this, execute); callback: function() {
this.execute(options); this.configure(options);
},
scope: this
}); });
this.describe();
return; return;
} }
var description = this.description, var description = this.description,
@@ -149,26 +191,105 @@ OpenLayers.WPSProcess = OpenLayers.Class({
input = description.dataInputs[i]; input = description.dataInputs[i];
this.setInputData(input, inputs[input.identifier]); this.setInputData(input, inputs[input.identifier]);
} }
//TODO For now we only handle responseForm with a single output if (options.callback) {
this.setResponseForm(); options.callback.call(options.scope);
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); * APIMethod: execute
var features = this.formats[mimeType].read(response.responseText); * Configures and executes the process
if (options.success) { *
options.success.call(options.scope, { * Parameters:
result: features * 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
* <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> 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 <OpenLayers.Feature.Vector> 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 scope: this
}); });
}, },
/**
* APIMethod: output
* Chain an output of a configured process (see <configure>) 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 * Method: parseDescription
* Parses the DescribeProcess response
* *
* Parameters: * Parameters:
* response - {Object} * response - {Object}
@@ -197,15 +318,37 @@ OpenLayers.WPSProcess = OpenLayers.Class({
*/ */
setInputData: function(input, data) { setInputData: function(input, data) {
// clear any previous data // clear any previous data
input.data = {}; delete input.data;
if (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; var complexData = input.complexData;
if (complexData) { if (complexData) {
var format = this.findMimeType(complexData); var format = this.findMimeType(complexData.supported.formats);
input.data.complexData = { input.data.complexData = {
mimeType: format, mimeType: format,
value: this.formats[format].write(this.toFeatures(data)) value: this.formats[format].write(this.toFeatures(data))
}; };
} else {
input.data.literalData = {
value: data
};
} }
} }
}, },
@@ -213,17 +356,81 @@ OpenLayers.WPSProcess = OpenLayers.Class({
/** /**
* Method: setResponseForm * Method: setResponseForm
* Sets the responseForm property of the <execute> payload. * Sets the responseForm property of the <execute> 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() { setResponseForm: function(options) {
output = this.description.processOutputs[0]; options = options || {};
output = this.description.processOutputs[options.outputIndex || 0];
this.description.responseForm = { this.description.responseForm = {
rawDataOutput: { rawDataOutput: {
identifier: output.identifier, 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 - {<OpenLayers.WPSProcess.ChainLink} The process to chain.
*/
chainProcess: function(input, chainLink) {
var output = this.getOutputIndex(
chainLink.process.description.processOutputs, chainLink.output
);
input.reference.mimeType = this.findMimeType(
input.complexData.supported.formats,
chainLink.process.description.processOutputs[output].complexOutput.supported.formats
);
var formats = {};
formats[input.reference.mimeType] = true;
chainLink.process.setResponseForm({
outputIndex: output,
supportedFormats: formats
});
input.reference.body = chainLink.process.description;
while (this.executeCallbacks.length > 0) {
this.executeCallbacks[0]();
}
},
/** /**
* Method: toFeatures * Method: toFeatures
* Converts spatial input into features so it can be processed by * Converts spatial input into features so it can be processed by
@@ -256,16 +463,19 @@ OpenLayers.WPSProcess = OpenLayers.Class({
* Finds a supported mime type. * Finds a supported mime type.
* *
* Parameters: * Parameters:
* complex - {Object} A complexData or complexOutput object from the * sourceFormats - {Object} An object literal with mime types as key and
* process description. * true as value for supported formats.
* targetFormats - {Object} Like <sourceFormats>, 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: * Returns:
* {String} A supported mime type. * {String} A supported mime type.
*/ */
findMimeType: function(complex) { findMimeType: function(sourceFormats, targetFormats) {
var formats = complex.supported.formats; targetFormats = targetFormats || this.formats;
for (var f in formats) { for (var f in sourceFormats) {
if (f in this.formats) { if (f in targetFormats) {
return f; return f;
} }
} }
@@ -274,3 +484,18 @@ OpenLayers.WPSProcess = OpenLayers.Class({
CLASS_NAME: "OpenLayers.WPSProcess" 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);
}
});