From b84b2c262e9ebc591ab7c48d490620ce2915a857 Mon Sep 17 00:00:00 2001 From: Matthias Mohr Date: Thu, 5 Aug 2021 19:46:51 +0200 Subject: [PATCH] Experimental process namespace support https://github.com/Open-EO/openeo-api/pull/348 --- CHANGELOG.md | 4 ++ openeo.d.ts | 66 +++++++++++++++++++--------- src/builder/builder.js | 60 ++++++++++++++++++------- src/builder/formula.js | 1 + src/builder/node.js | 15 +++++-- src/builder/tapdigit.js | 15 +++++-- src/connection.js | 46 +++++++++++++++----- src/typedefs.js | 1 + tests/builder.namespaces.test.js | 75 ++++++++++++++++++++++++++++++++ tests/earthengine.test.js | 6 +-- tests/eodc.test.js | 2 +- tests/vito.test.js | 49 +++++++++++++++++++++ 12 files changed, 282 insertions(+), 58 deletions(-) create mode 100644 tests/builder.namespaces.test.js create mode 100644 tests/vito.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b027351..2cb347a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Experimental support for process namespaces, see [API#348](https://github.com/Open-EO/openeo-api/pull/348). + ### Fixed - Return a better error message if issues with reading batch job results occur in `Job.getResultsAsStac` diff --git a/openeo.d.ts b/openeo.d.ts index 0444dc21..e4d7a939 100644 --- a/openeo.d.ts +++ b/openeo.d.ts @@ -1346,12 +1346,13 @@ declare module OpenEO { * Operators: - (subtract), + (add), / (divide), * (multiply), ^ (power) * * It supports all mathematical functions (i.e. expects a number and returns a number) the back-end implements, e.g. `sqrt(x)`. + * For namespaced processes, use for example `process#namespace(x)` - EXPERIMENTAL! * * Only available if a builder is specified in the constructor: * You can refer to output from processes with a leading `#`, e.g. `#loadco1` if the node to refer to has the key `loadco1`. * * Only available if a parent node is set via `setNode()`: - * Parameters can be accessed simply by name. + * Parameters can be accessed simply by name. * If the first parameter is a (labeled) array, the value for a specific index or label can be accessed by typing the numeric index or textual label with a `$` in front, for example `$B1` for the label `B1` or `$0` for the first element in the array. Numeric labels are not supported. * You can access subsequent parameters by adding additional `$` at the beginning, e.g. `$$0` to access the first element of an array in the second parameter, `$$$0` for the same in the third parameter etc. * @@ -1430,8 +1431,9 @@ declare module OpenEO { * @param {string} processId * @param {object.} [processArgs={}] * @param {?string} [processDescription=null] + * @param {?string} [processNamespace=null] */ - constructor(parent: Builder, processId: string, processArgs?: any, processDescription?: string | null); + constructor(parent: Builder, processId: string, processArgs?: any, processDescription?: string | null, processNamespace?: string | null); /** * The parent builder. * @type {Builder} @@ -1448,6 +1450,11 @@ declare module OpenEO { * @type {string} */ id: string; + /** + * The namespace of the process - EXPERIMENTAL! + * @type {string} + */ + namespace: string; /** * The arguments for the process. * @type {object.} @@ -1508,7 +1515,7 @@ declare module OpenEO { * * @protected * @param {?BuilderNode} [parentNode=null] - * @param {?string} parentParameter + * @param {?string} [parentParameter=null] * @returns {BuilderNode} */ protected createBuilder(parentNode?: BuilderNode | null, parentParameter?: string | null): BuilderNode; @@ -1608,7 +1615,7 @@ declare module OpenEO { * * @async * @static - * @param {?string} version + * @param {?string} [version=null] * @returns {Promise} * @throws {Error} */ @@ -1637,10 +1644,17 @@ declare module OpenEO { */ constructor(processes: Array | Processes, parent?: Builder | null, id?: string); /** - * List of all process specifications. + * List of all non-namespaced process specifications. * @type {Array.} */ processes: Array; + /** + * Namespaced process specifications. EXPERIMENTAL! + * @type {Object.>} + */ + namespacedProcesses: { + [x: string]: Array; + }; /** * The parent builder. * @type {?Builder} @@ -1670,9 +1684,10 @@ declare module OpenEO { * Adds a process specification to the builder so that it can be used to create a process graph. * * @param {Process} process - Process specification compliant to openEO API + * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL! * @throws {Error} */ - addProcessSpec(process: Process): void; + addProcessSpec(process: Process, namespace?: string | null): void; /** * Sets the parent for this Builder. * @@ -1705,12 +1720,13 @@ declare module OpenEO { */ addParameter(parameter: any, root?: boolean): void; /** - * Returns the process specification for the given process identifier. + * Returns the process specification for the given process identifier and namespace. * - * @param {string} id + * @param {string} id - Process identifier + * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL! * @returns {Process} */ - spec(id: string): Process; + spec(id: string, namespace?: string | null): Process; /** * Adds a mathematical formula to the process. * @@ -1723,18 +1739,19 @@ declare module OpenEO { */ math(formula: string): BuilderNode; /** - * Checks whether a process with the given id is supported by the back-end. + * Checks whether a process with the given id and namespace is supported by the back-end. * - * @param {string} processId - The id of the process to call. + * @param {string} processId - The id of the process. + * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL! * @returns {boolean} */ - supports(processId: string): boolean; + supports(processId: string, namespace?: string | null): boolean; /** * Adds another process call to the process chain. * - * @param {string} processId - The id of the process to call. - * @param {object.|Array} args - The arguments as key-value pairs or as array. For objects, they keys must be the parameter names and the values must be the arguments. For arrays, arguments must be specified in the same order as in the corresponding process. - * @param {?string} description - An optional description for the process call. + * @param {string} processId - The id of the process to call. To access a namespaced process, use the `process@namespace` notation. + * @param {object.|Array} [args={}] - The arguments as key-value pairs or as array. For objects, they keys must be the parameter names and the values must be the arguments. For arrays, arguments must be specified in the same order as in the corresponding process. + * @param {?string} [description=null] - An optional description for the process call. * @returns {BuilderNode} */ process(processId: string, args?: any | any[], description?: string | null): BuilderNode; @@ -1872,27 +1889,32 @@ declare module OpenEO { */ listCollectionItems(collectionId: string, spatialExtent?: Array | null, temporalExtent?: Array | null, limit?: number | null): AsyncGenerator; /** - * List all processes available on the back-end. + * List processes available on the back-end. + * + * Requests pre-defined processes by default. + * Set the namespace parameter to request processes from a specific namespace. * - * Data is cached in memory. + * Pre-defined processes are cached in memory. * * @async + * @param {?string} [namespace=null] - Namespace of the processes (default to `null`, i.e. pre-defined processes). EXPERIMENTAL! * @returns {Promise} - A response compatible to the API specification. * @throws {Error} */ - listProcesses(): Promise; + listProcesses(namespace?: string | null): Promise; /** * Get information about a single process. * * @async * @param {string} processId - Collection ID to request further metadata for. + * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL! * @returns {Promise} - A single process as object, or `null` if none is found. * @throws {Error} * @see Connection#listProcesses */ - describeProcess(processId: string): Promise; + describeProcess(processId: string, namespace?: string | null): Promise; /** - * Returns an object to simply build user-defined processes. + * Returns an object to simply build user-defined processes based upon pre-defined processes. * * @async * @param {string} id - A name for the process. @@ -2572,6 +2594,10 @@ declare module OpenEO { export type Processes = { processes: Array; links: Array; + /** + * EXPERIMENTAL! + */ + namespaces: Array | null; }; /** * An openEO processing chain. diff --git a/src/builder/builder.js b/src/builder/builder.js index 039cc804..6d771681 100644 --- a/src/builder/builder.js +++ b/src/builder/builder.js @@ -83,7 +83,7 @@ class Builder { * * @async * @static - * @param {?string} version + * @param {?string} [version=null] * @returns {Promise} * @throws {Error} */ @@ -123,10 +123,15 @@ class Builder { */ constructor(processes, parent = null, id = undefined) { /** - * List of all process specifications. + * List of all non-namespaced process specifications. * @type {Array.} */ this.processes = []; + /** + * Namespaced process specifications. EXPERIMENTAL! + * @type {object.>} + */ + this.namespacedProcesses = {}; /** * The parent builder. * @type {?Builder} @@ -176,13 +181,20 @@ class Builder { * Adds a process specification to the builder so that it can be used to create a process graph. * * @param {Process} process - Process specification compliant to openEO API + * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL! * @throws {Error} */ - addProcessSpec(process) { + addProcessSpec(process, namespace = null) { if (!Utils.isObject(process)) { throw new Error("Process '" + process.id + "' must be an object."); } - if (typeof this[process.id] === 'undefined') { + if (namespace && namespace !== 'backend' && namespace !== 'user') { + if (!this.namespacedProcesses[namespace]) { + this.namespacedProcesses[namespace] = []; + } + this.namespacedProcesses[namespace].push(process); + } + else if (typeof this[process.id] === 'undefined') { this.processes.push(process); /** * Implicitly calls the process with the given name on the back-end by adding it to the process. @@ -281,13 +293,22 @@ class Builder { } /** - * Returns the process specification for the given process identifier. + * Returns the process specification for the given process identifier and namespace. * - * @param {string} id + * @param {string} id - Process identifier + * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL! * @returns {Process} */ - spec(id) { - return this.processes.find(process => process.id === id); + spec(id, namespace = null) { + if (namespace && namespace !== 'backend' && namespace !== 'user') { + if (!this.namespacedProcesses[namespace]) { + return; + } + return this.namespacedProcesses[namespace].find(process => process.id === id); + } + else { + return this.processes.find(process => process.id === id); + } } /** @@ -308,25 +329,32 @@ class Builder { } /** - * Checks whether a process with the given id is supported by the back-end. + * Checks whether a process with the given id and namespace is supported by the back-end. * - * @param {string} processId - The id of the process to call. + * @param {string} processId - The id of the process. + * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL! * @returns {boolean} */ - supports(processId) { - return Utils.isObject(this.spec(processId)); + supports(processId, namespace = null) { + return Utils.isObject(this.spec(processId, namespace)); } /** * Adds another process call to the process chain. * - * @param {string} processId - The id of the process to call. - * @param {object.|Array} args - The arguments as key-value pairs or as array. For objects, they keys must be the parameter names and the values must be the arguments. For arrays, arguments must be specified in the same order as in the corresponding process. - * @param {?string} description - An optional description for the process call. + * @param {string} processId - The id of the process to call. To access a namespaced process, use the `process@namespace` notation. + * @param {object.|Array} [args={}] - The arguments as key-value pairs or as array. For objects, they keys must be the parameter names and the values must be the arguments. For arrays, arguments must be specified in the same order as in the corresponding process. + * @param {?string} [description=null] - An optional description for the process call.a * @returns {BuilderNode} */ process(processId, args = {}, description = null) { - let node = new BuilderNode(this, processId, args, description); + let namespace = null; + if (processId.includes('@')) { + let rest; + [processId, ...rest] = processId.split('@'); + namespace = rest.join('@'); + } + let node = new BuilderNode(this, processId, args, description, namespace); this.nodes[node.id] = node; return node; } diff --git a/src/builder/formula.js b/src/builder/formula.js index 9165cd0b..a4815bdc 100644 --- a/src/builder/formula.js +++ b/src/builder/formula.js @@ -8,6 +8,7 @@ const BuilderNode = require('./node'); * Operators: - (subtract), + (add), / (divide), * (multiply), ^ (power) * * It supports all mathematical functions (i.e. expects a number and returns a number) the back-end implements, e.g. `sqrt(x)`. + * For namespaced processes, use for example `process#namespace(x)` - EXPERIMENTAL! * * Only available if a builder is specified in the constructor: * You can refer to output from processes with a leading `#`, e.g. `#loadco1` if the node to refer to has the key `loadco1`. diff --git a/src/builder/node.js b/src/builder/node.js index 0db7f3c7..f6f2c7de 100644 --- a/src/builder/node.js +++ b/src/builder/node.js @@ -13,8 +13,9 @@ class BuilderNode { * @param {string} processId * @param {object.} [processArgs={}] * @param {?string} [processDescription=null] + * @param {?string} [processNamespace=null] */ - constructor(parent, processId, processArgs = {}, processDescription = null) { + constructor(parent, processId, processArgs = {}, processDescription = null, processNamespace = null) { /** * The parent builder. * @type {Builder} @@ -26,7 +27,7 @@ class BuilderNode { * @type {Process} * @readonly */ - this.spec = this.parent.spec(processId); + this.spec = this.parent.spec(processId, processNamespace); if (!Utils.isObject(this.spec)) { throw new Error("Process doesn't exist: " + processId); } @@ -36,6 +37,11 @@ class BuilderNode { * @type {string} */ this.id = parent.generateId(processId); + /** + * The namespace of the process - EXPERIMENTAL! + * @type {string} + */ + this.namespace = processNamespace; /** * The arguments for the process. * @type {object.} @@ -176,7 +182,7 @@ class BuilderNode { * * @protected * @param {?BuilderNode} [parentNode=null] - * @param {?string} parentParameter + * @param {?string} [parentParameter=null] * @returns {BuilderNode} */ createBuilder(parentNode = null, parentParameter = null) { @@ -228,6 +234,9 @@ class BuilderNode { process_id: this.spec.id, arguments: {} }; + if (this.namespace) { + obj.namespace = this.namespace; + } for(let name in this.arguments) { if (typeof this.arguments[name] !== 'undefined') { obj.arguments[name] = this.exportArgument(this.arguments[name], name); diff --git a/src/builder/tapdigit.js b/src/builder/tapdigit.js index 74ddcb29..5da0ec76 100644 --- a/src/builder/tapdigit.js +++ b/src/builder/tapdigit.js @@ -119,8 +119,12 @@ TapDigit.Lexer = function () { return (ch === '_') || (ch === '#') || (ch === '$') || isLetter(ch); } - function isIdentifierPart(ch) { - return (ch === '_') || isLetter(ch) || isDecimalDigit(ch); + function isAdditionalNamespaceChar(ch) { + return (ch === '-') || (ch === '.') || (ch === '~') || (ch === '@'); + } + + function isIdentifierPart(ch, ns = false) { + return (ch === '_') || isLetter(ch) || isDecimalDigit(ch) || (ns && isAdditionalNamespaceChar(ch)); } function scanIdentifier() { @@ -130,6 +134,7 @@ TapDigit.Lexer = function () { } let id = getNextChar(); + let ns = false; while (true) { let ch = peekNextChar(); // If the first character is a $, it is allowed that more $ follow directly after @@ -138,7 +143,10 @@ TapDigit.Lexer = function () { startCh = ''; // Stop allowing $ once the first non-$ has been found } // else: allowed } - else if (!isIdentifierPart(ch)) { + else if (ch === '@') { + ns = true; + } + else if (!isIdentifierPart(ch, ns)) { break; } id += getNextChar(); @@ -357,6 +365,7 @@ TapDigit.Parser = function () { if (matchOp(token, '(')) { lexer.next(); expr = parseExpression(); + console.log(expr); token = lexer.next(); if (!matchOp(token, ')')) { throw new SyntaxError('Expecting )'); diff --git a/src/connection.js b/src/connection.js index fe7109d9..e25e5294 100644 --- a/src/connection.js +++ b/src/connection.js @@ -208,20 +208,35 @@ class Connection { } /** - * List all processes available on the back-end. + * List processes available on the back-end. * - * Data is cached in memory. + * Requests pre-defined processes by default. + * Set the namespace parameter to request processes from a specific namespace. + * + * Pre-defined processes are cached in memory. * * @async + * @param {?string} [namespace=null] - Namespace of the processes (default to `null`, i.e. pre-defined processes). EXPERIMENTAL! * @returns {Promise} - A response compatible to the API specification. * @throws {Error} */ - async listProcesses() { - if (this.processes === null) { - let response = await this._get('/processes'); + async listProcesses(namespace = null) { + let isPredefined = (!namespace || namespace === 'backend'); + + // Load pre-defined processes from cache + if (isPredefined && this.processes !== null) { + return this.processes; + } + + let path = isPredefined ? '/processes' : `/processes/${namespace}`; + let response = await this._get(path); + + // Store pre-defined processes in cache + if (isPredefined) { this.processes = response.data; } - return this.processes; + + return response.data; } /** @@ -229,20 +244,27 @@ class Connection { * * @async * @param {string} processId - Collection ID to request further metadata for. + * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL! * @returns {Promise} - A single process as object, or `null` if none is found. * @throws {Error} * @see Connection#listProcesses */ - async describeProcess(processId) { - let response = await this.listProcesses(); - if (Array.isArray(response.processes)) { - return response.processes.find(process => Utils.isObject(process) && process.id === processId) || null; + async describeProcess(processId, namespace = null) { + if (!namespace || namespace === 'backend') { + let response = await this.listProcesses(); + if (Array.isArray(response.processes)) { + return response.processes.find(process => Utils.isObject(process) && process.id === processId) || null; + } + return null; + } + else { + let response = await this._get(`/processes/${namespace}/${processId}`); + return response.data; } - return null; } /** - * Returns an object to simply build user-defined processes. + * Returns an object to simply build user-defined processes based upon pre-defined processes. * * @async * @param {string} id - A name for the process. diff --git a/src/typedefs.js b/src/typedefs.js index 0d544b8e..3465c81f 100644 --- a/src/typedefs.js +++ b/src/typedefs.js @@ -175,6 +175,7 @@ * @type {object} * @property {Array.} processes * @property {Array.} links + * @property {?Array.} namespaces EXPERIMENTAL! */ /** diff --git a/tests/builder.namespaces.test.js b/tests/builder.namespaces.test.js new file mode 100644 index 00000000..10c685a8 --- /dev/null +++ b/tests/builder.namespaces.test.js @@ -0,0 +1,75 @@ +// @ts-nocheck +describe('Process Graph Builder (EVI)', () => { + + const { Builder, Formula } = require('../src/openeo'); + + test('Namespaces processes in builder', async () => { + const builder = await Builder.fromVersion('1.1.0'); + + // Add namespaced processes + builder.addProcessSpec({id: 'msi'}, "vito"); + builder.addProcessSpec({id: 'ndvi'}, "@m.mohr"); + + // Create process (graph) + builder.process("msi@vito"); + builder.process("ndvi@@m.mohr"); + + let pg = builder.toJSON(); + + // Check result + expect(pg).toEqual({ + "process_graph": { + "msi1": { + process_id: 'msi', + namespace: 'vito', + arguments: {} + }, + "ndvi1": { + process_id: 'ndvi', + namespace: '@m.mohr', + arguments: {} + } + } + }); + + }); + test('Namespaces processes in Formula', async () => { + const builder = await Builder.fromVersion('1.1.0'); + + builder.addProcessSpec({ + "id": "hello_world", + "parameters": [], + "returns": { + "description": "The numerical value of Pi.", + "schema": { + "type": "number" + } + } + }, "a-b"); + + builder.math("hello_world@a-b() + 1"); + + let pg = builder.toJSON(); + + // Check result + expect(pg).toEqual({ + process_graph: { + hellow1: { + process_id: 'hello_world', + namespace: 'a-b', + arguments: {} + }, + add1: { + process_id: 'add', + arguments: { + x: { + from_node: "hellow1" + }, + y: 1 + } + } + } + }); + + }); +}); \ No newline at end of file diff --git a/tests/earthengine.test.js b/tests/earthengine.test.js index d5863bb6..d9b46889 100644 --- a/tests/earthengine.test.js +++ b/tests/earthengine.test.js @@ -9,7 +9,7 @@ jest.setTimeout(timeout); // Give Google some time to process data // Generate random int const random = (min, max) => Math.floor(Math.random() * (max - min)) + min; -describe('With earth-engine-driver', () => { +describe('GEE back-end', () => { const { TESTBACKEND, STAC_MIGRATE_VERSION } = require('./config.js'); const TESTBACKENDDIRECT = TESTBACKEND + '/v1.0'; const TESTUSERNAME = `group${random(20,29)}`; @@ -17,9 +17,9 @@ describe('With earth-engine-driver', () => { const FREE_PLAN = {"name":"free","description":"Earth Engine is free for research, education, and nonprofit use. For commercial applications, Google offers paid commercial licenses. Please contact earthengine-commercial@google.com for details.","paid":false}; - const TESTCAPABILITIES = {"api_version":"1.0.0","backend_version":"1.0.0-beta.3","stac_version":"1.0.0-beta.2","production":false,"id":"openeo-earthengine-driver","title":"Google Earth Engine Proxy for openEO","description":"This is the Google Earth Engine Driver for openEO.\n\nGoogle Earth Engine is a planetary-scale platform for Earth science data & analysis. It is powered by Google's cloud infrastructure and combines a multi-petabyte catalog of satellite imagery and geospatial datasets with planetary-scale analysis capabilities. Google makes it available for scientists, researchers, and developers to detect changes, map trends, and quantify differences on the Earth's surface. Google Earth Engine is free for research, education, and nonprofit use.","endpoints":[{"path":"/","methods":["GET"]},{"path":"/conformance","methods":["GET"]},{"path":"/service_types","methods":["GET"]},{"path":"/file_formats","methods":["GET"]},{"path":"/collections","methods":["GET"]},{"path":"/collections/{collection_id}","methods":["GET"]},{"path":"/processes","methods":["GET"]},{"path":"/files","methods":["GET"]},{"path":"/files/{path}","methods":["GET","PUT","DELETE"]},{"path":"/result","methods":["POST"]},{"path":"/jobs","methods":["POST","GET"]},{"path":"/jobs/{job_id}","methods":["GET","PATCH","DELETE"]},{"path":"/jobs/{job_id}/logs","methods":["GET"]},{"path":"/jobs/{job_id}/results","methods":["GET","POST"]},{"path":"/services","methods":["GET","POST"]},{"path":"/services/{service_id}","methods":["GET","PATCH","DELETE"]},{"path":"/services/{service_id}/logs","methods":["GET"]},{"path":"/credentials/basic","methods":["GET"]},{"path":"/me","methods":["GET"]},{"path":"/validation","methods":["POST"]},{"path":"/process_graphs","methods":["GET"]},{"path":"/process_graphs/{process_graph_id}","methods":["GET","PUT","DELETE"]}],"billing":{"currency":"USD","default_plan":"free","plans":[FREE_PLAN]},"links":[{"rel":"about","href":"https://earthengine.google.com/","title":"Google Earth Engine Homepage"},{"rel":"terms-of-service","href":"https://earthengine.google.com/terms/","type":"text/html","title":"Google Earth Engine Terms of Service"},{"rel":"privacy-policy","href":"https://policies.google.com/privacy","type":"text/html","title":"Google Privacy Policy"},{"rel":"related","href":"https://github.com/Open-EO/openeo-earthengine-driver","title":"GitHub repository"},{"rel":"version-history","href":TESTBACKEND+"/.well-known/openeo","type":"application/json","title":"Supported API versions"},{"rel":"data","href":TESTBACKENDDIRECT+"/collections","type":"application/json","title":"Datasets"},{"rel":"conformance","href":TESTBACKENDDIRECT+"/conformance","type":"application/json","title":"OGC Conformance classes"}]}; + const TESTCAPABILITIES = {"api_version":"1.0.1","backend_version":"1.0.0-beta.4","stac_version":"1.0.0","type":"Catalog","production":false,"id":"openeo-earthengine-driver","title":"Google Earth Engine Proxy for openEO","description":"This is the Google Earth Engine Driver for openEO.\n\nGoogle Earth Engine is a planetary-scale platform for Earth science data & analysis. It is powered by Google's cloud infrastructure and combines a multi-petabyte catalog of satellite imagery and geospatial datasets with planetary-scale analysis capabilities. Google makes it available for scientists, researchers, and developers to detect changes, map trends, and quantify differences on the Earth's surface. Google Earth Engine is free for research, education, and nonprofit use.","endpoints":[{"path":"/","methods":["GET"]},{"path":"/conformance","methods":["GET"]},{"path":"/service_types","methods":["GET"]},{"path":"/file_formats","methods":["GET"]},{"path":"/collections","methods":["GET"]},{"path":"/collections/{collection_id}","methods":["GET"]},{"path":"/processes","methods":["GET"]},{"path":"/files","methods":["GET"]},{"path":"/files/{path}","methods":["GET","PUT","DELETE"]},{"path":"/result","methods":["POST"]},{"path":"/jobs","methods":["POST","GET"]},{"path":"/jobs/{job_id}","methods":["GET","PATCH","DELETE"]},{"path":"/jobs/{job_id}/logs","methods":["GET"]},{"path":"/jobs/{job_id}/results","methods":["GET","POST"]},{"path":"/services","methods":["GET","POST"]},{"path":"/services/{service_id}","methods":["GET","PATCH","DELETE"]},{"path":"/services/{service_id}/logs","methods":["GET"]},{"path":"/credentials/basic","methods":["GET"]},{"path":"/me","methods":["GET"]},{"path":"/validation","methods":["POST"]},{"path":"/process_graphs","methods":["GET"]},{"path":"/process_graphs/{process_graph_id}","methods":["GET","PUT","DELETE"]}],"billing":{"currency":"USD","default_plan":"free","plans":[FREE_PLAN]},"links":[{"rel":"about","href":"https://earthengine.google.com/","title":"Google Earth Engine Homepage"},{"rel":"terms-of-service","href":"https://earthengine.google.com/terms/","type":"text/html","title":"Google Earth Engine Terms of Service"},{"rel":"privacy-policy","href":"https://policies.google.com/privacy","type":"text/html","title":"Google Privacy Policy"},{"rel":"related","href":"https://github.com/Open-EO/openeo-earthengine-driver","title":"GitHub repository"},{"rel":"version-history","href":TESTBACKEND+"/.well-known/openeo","type":"application/json","title":"Supported API versions"},{"rel":"data","href":TESTBACKENDDIRECT+"/collections","type":"application/json","title":"Datasets"},{"rel":"conformance","href":TESTBACKENDDIRECT+"/conformance","type":"application/json","title":"OGC Conformance classes"}]}; - const TESTCOLLECTION = {"stac_version":"1.0.0-beta.2","id":"AAFC/ACI","title":"Canada AAFC Annual Crop Inventory","gee:type":"image_collection","description":"Starting in 2009, the Earth Observation Team of the Science and Technology\nBranch (STB) at Agriculture and Agri-Food Canada (AAFC) began the process\nof generating annual crop type digital maps. Focusing on the Prairie\nProvinces in 2009 and 2010, a Decision Tree (DT) based methodology was\napplied using optical (Landsat-5, AWiFS, DMC) and radar (Radarsat-2) based\nsatellite images. Beginning with the 2011 growing season, this activity has\nbeen extended to other provinces in support of a national crop inventory.\nTo date this approach can consistently deliver a crop inventory that meets\nthe overall target accuracy of at least 85% at a final spatial resolution of\n30m (56m in 2009 and 2010).\n","license":"proprietary","links":[],"keywords":["aafc","canada","crop","landcover"],"providers":[{"name":"Agriculture and Agri-Food Canada","roles":["producer","licensor"],"url":"https://open.canada.ca/data/en/dataset/ba2645d5-4458-414d-b196-6303ac06c1c9"},{"name":"Google Earth Engine","roles":["host"],"url":"https://developers.google.com/earth-engine/datasets/catalog/AAFC_ACI"}],"extent":{"spatial":{"bbox":[[-135.17,36.83,-51.24,62.25]]},"temporal":{"interval":[["2009-01-01T00:00:00Z",null]]}},"summaries":{},"sci:citation":"Agriculture and Agri-Food Canada Annual Crop Inventory. {YEAR}","stac_extensions":["collection-assets"],"cube:dimensions":{"x":{"type":"spatial","axis":"x","extent":[-135.17,-51.24]},"y":{"type":"spatial","axis":"y","extent":[36.83,62.25]},"t":{"type":"temporal","extent":["2009-01-01T00:00:00Z",null]},"bands":{"type":"bands","values":["landcover"]}},"assets":{}}; + const TESTCOLLECTION = {"stac_version":"1.0.0","id":"AAFC/ACI","title":"Canada AAFC Annual Crop Inventory","gee:type":"image_collection","description":"Starting in 2009, the Earth Observation Team of the Science and Technology\nBranch (STB) at Agriculture and Agri-Food Canada (AAFC) began the process\nof generating annual crop type digital maps. Focusing on the Prairie\nProvinces in 2009 and 2010, a Decision Tree (DT) based methodology was\napplied using optical (Landsat-5, AWiFS, DMC) and radar (Radarsat-2) based\nsatellite images. Beginning with the 2011 growing season, this activity has\nbeen extended to other provinces in support of a national crop inventory.\nTo date this approach can consistently deliver a crop inventory that meets\nthe overall target accuracy of at least 85% at a final spatial resolution of\n30m (56m in 2009 and 2010).\n","license":"proprietary","links":[],"keywords":["aafc","canada","crop","landcover"],"providers":[{"name":"Agriculture and Agri-Food Canada","roles":["producer","licensor"],"url":"https://open.canada.ca/data/en/dataset/ba2645d5-4458-414d-b196-6303ac06c1c9"},{"name":"Google Earth Engine","roles":["host"],"url":"https://developers.google.com/earth-engine/datasets/catalog/AAFC_ACI"}],"extent":{"spatial":{"bbox":[[-135.17,36.83,-51.24,62.25]]},"temporal":{"interval":[["2009-01-01T00:00:00Z",null]]}},"summaries":{},"sci:citation":"Agriculture and Agri-Food Canada Annual Crop Inventory. {YEAR}","stac_extensions":["collection-assets"],"cube:dimensions":{"x":{"type":"spatial","axis":"x","extent":[-135.17,-51.24]},"y":{"type":"spatial","axis":"y","extent":[36.83,62.25]},"t":{"type":"temporal","extent":["2009-01-01T00:00:00Z",null]},"bands":{"type":"bands","values":["landcover"]}},"assets":{}}; const TESTPROCESS = {"id":"min","summary":"Minimum value","description":"Computes the smallest value of an array of numbers, which is is equal to the last element of a sorted (i.e., ordered) version the array.\n\nAn array without non-`null` elements resolves always with `null`.","categories":["math","reducer"],"parameters":[{"name":"data","description":"An array of numbers.","schema":{"type":"array","items":{"type":["number","null"]}}}],"returns":{"description":"The minimum value.","schema":{"type":["number","null"]}},"examples":[{"arguments":{"data":[1,0,3,2]},"returns":0},{"arguments":{"data":[5,2.5,null,-0.7]},"returns":-0.7},{"arguments":{"data":[]},"returns":null}],"links":[{"rel":"about","href":"http://mathworld.wolfram.com/Minimum.html","title":"Minimum explained by Wolfram MathWorld"}]}; diff --git a/tests/eodc.test.js b/tests/eodc.test.js index f9547ba3..f680d38f 100644 --- a/tests/eodc.test.js +++ b/tests/eodc.test.js @@ -2,7 +2,7 @@ const { OpenEO, Connection, Capabilities } = require('../src/openeo'); const { Utils } = require('@openeo/js-commons'); -describe('With eodc-driver', () => { +describe('EODC back-end', () => { const TESTBACKEND = 'https://openeo.eodc.eu'; const TESTCOLLECTION = 'boa_landsat_8'; diff --git a/tests/vito.test.js b/tests/vito.test.js new file mode 100644 index 00000000..817d21bd --- /dev/null +++ b/tests/vito.test.js @@ -0,0 +1,49 @@ +// @ts-nocheck +const { OpenEO, Connection, Capabilities } = require('../src/openeo'); +const { Utils } = require('@openeo/js-commons'); + +describe('VITO back-end', () => { + const TESTBACKEND = 'https://openeo.vito.be'; + + describe('Request processes from namespace', () => { + + let con; + test('Connect', async () => { + con = await OpenEO.connect(TESTBACKEND); + expect(con instanceof Connection).toBeTruthy(); + let cap = con.capabilities(); + expect(cap instanceof Capabilities).toBeTruthy(); + }); + + // Not implemented yet by VITO +/* test('Check process namespace list', async () => { + let p = await con.listProcesses(); + expect(Array.isArray(p.namespaces)).toBeTruthy(); + expect(p.namespaces).toContain("vito"); + }); */ + + test('Check pre-defined process list does not contain "MSI"', async () => { + let p = await con.listProcesses(); + expect(Array.isArray(p.processes)).toBeTruthy(); + let msi = p.processes.find(process => process.id === 'MSI'); + expect(msi).toBeUndefined(); + }); + + test('Request processes from namespace "vito"', async () => { + let p = await con.listProcesses("vito"); + expect(Array.isArray(p.processes)).toBeTruthy(); + let msi = p.processes.find(process => process.id === 'MSI'); + expect(Utils.isObject(msi)).toBeTruthy(); + expect(msi.description).toBeDefined(); + }); + + test('Request process "MSI" from namespace "vito"', async () => { + let msi = await con.describeProcess("MSI", "vito"); + expect(Utils.isObject(msi)).toBeTruthy(); + expect(msi.id).toBe("MSI"); + expect(msi.description).toBeDefined(); + expect(msi.process_graph).toBeDefined(); + }); + + }); +});