Skip to content

Commit 30cd63c

Browse files
committed
GDC draft
1 parent f3f5c9a commit 30cd63c

File tree

5 files changed

+520
-6
lines changed

5 files changed

+520
-6
lines changed

src/connection.js

+52-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const AuthProvider = require('./authprovider');
88
const BasicProvider = require('./basicprovider');
99
const OidcProvider = require('./oidcprovider');
1010

11-
const Capabilities = require('./capabilities');
11+
const GdcCapabilities = require('./gdc/capabilities');
12+
const GdcMigrate = require('./gdc/migrate');
1213
const FileTypes = require('./filetypes');
1314
const UserFile = require('./userfile');
1415
const Job = require('./job');
@@ -18,6 +19,7 @@ const Service = require('./service');
1819
const Builder = require('./builder/builder');
1920
const BuilderNode = require('./builder/node');
2021

22+
2123
const CONFORMANCE_RELS = [
2224
'conformance',
2325
'http://www.opengis.net/def/rel/ogc/1.0/conformance'
@@ -120,7 +122,8 @@ class Connection {
120122
}
121123
}
122124

123-
this.capabilitiesObject = new Capabilities(data);
125+
GdcMigrate.connection = this;
126+
this.capabilitiesObject = new GdcCapabilities(data);
124127
return this.capabilitiesObject;
125128
}
126129

@@ -802,6 +805,45 @@ class Connection {
802805
return await pg.describeUserProcess();
803806
}
804807

808+
isOgcProcess(process) {
809+
let nodes = Object.values(process.process_graph);
810+
return Boolean(nodes.find(node => {
811+
let process = this.processes.get(node.process_id);
812+
return Utils.isObject(process) && Boolean(process.ogcapi);
813+
}));
814+
}
815+
816+
async executeOgcProcess(process, abortController = null) {
817+
let openEO = this._normalizeUserProcess(process);
818+
let mode = null;
819+
let p = Object.values(openEO.process.process_graph).find(v => {
820+
let spec = this.processes.get(v.process_id);
821+
if (Array.isArray(spec.jobControlOptions) && spec.jobControlOptions.includes("async-execute")) {
822+
mode = 'async';
823+
}
824+
return Boolean(spec && spec.ogcapi);
825+
});
826+
let requestBody = GdcMigrate.execute(openEO);
827+
let headers = {};
828+
if (mode === 'async') {
829+
headers.Prefer = 'respond-async';
830+
}
831+
console.log(p.process_id, requestBody, headers); // @todo remove
832+
let response = await this._post(`/processes/${p.process_id}/execution`, requestBody, Environment.getResponseType(), abortController, headers);
833+
let syncResult = {
834+
data: response.data,
835+
costs: null,
836+
type: null,
837+
logs: []
838+
};
839+
840+
if (typeof response.headers['content-type'] === 'string') {
841+
syncResult.type = response.headers['content-type'];
842+
}
843+
844+
return syncResult;
845+
}
846+
805847
/**
806848
* Executes a process synchronously and returns the result as the response.
807849
*
@@ -823,6 +865,9 @@ class Connection {
823865
budget: budget
824866
})
825867
);
868+
if (this.isOgcProcess(requestBody.process)) {
869+
return this.executeOgcProcess(process, abortController);
870+
}
826871
let response = await this._post('/result', requestBody, Environment.getResponseType(), abortController);
827872
let syncResult = {
828873
data: response.data,
@@ -1131,16 +1176,18 @@ class Connection {
11311176
* @param {*} body
11321177
* @param {string} responseType - Response type according to axios, defaults to `json`.
11331178
* @param {?AbortController} [abortController=null] - An AbortController object that can be used to cancel the request.
1179+
* @param {Array.<Object.<string, string>>} [headers={}] - Headers
11341180
* @returns {Promise<AxiosResponse>}
11351181
* @throws {Error}
11361182
* @see https://github.com/axios/axios#request-config
11371183
*/
1138-
async _post(path, body, responseType, abortController = null) {
1184+
async _post(path, body, responseType, abortController = null, headers = {}) {
11391185
let options = {
11401186
method: 'post',
1141-
responseType: responseType,
1187+
responseType,
11421188
url: path,
1143-
data: body
1189+
data: body,
1190+
headers
11441191
};
11451192
return await this._send(options, abortController);
11461193
}

src/gdc/capabilities.js

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
const Capabilities = require("../capabilities");
2+
const Utils = require('@openeo/js-commons/src/utils');
3+
const Migrate = require('./migrate');
4+
5+
class GdcCapabilities extends Capabilities {
6+
7+
constructor(data) {
8+
super(data);
9+
Object.assign(this.featureMap, {
10+
describeCoverage: 'get /collections/{}/coverage',
11+
describeCoverageDomainset: 'get /collections/{}/coverage/domainset',
12+
describeCoverageRangetype: 'get /collections/{}/coverage/rangetype',
13+
describeCoverageRangeset: 'get /collections/{}/coverage/rangeset',
14+
describeCoverageMetadata: 'get /collections/{}/coverage/metadata',
15+
executeOgcProcess: 'post /processes/{}/execution',
16+
});
17+
this.checkConformance();
18+
}
19+
20+
getConformanceClasses() {
21+
if(!Array.isArray(this.data.conformsTo)) {
22+
return [];
23+
}
24+
return this.data.conformsTo;
25+
}
26+
27+
hasConformance(uri) {
28+
if(!Array.isArray(this.data.conformsTo)) {
29+
return false;
30+
}
31+
return this.data.conformsTo.includes(uri);
32+
}
33+
34+
_getLink(rel) {
35+
if (!Array.isArray(this.data.links)) {
36+
return null;
37+
}
38+
return this.data.links.find(link => link.rel === rel) || null;
39+
}
40+
41+
checkConformance() {
42+
if (!Array.isArray(this.data.endpoints)) {
43+
this.data.endpoints = [];
44+
}
45+
const isCoverage = this.hasConformance('http://www.opengis.net/spec/ogcapi-coverages-1/0.0/conf/geodata-coverage');
46+
const isFeatures = this.hasConformance('http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core');
47+
if (isCoverage || isFeatures) {
48+
this.data.endpoints.push({
49+
"path": "/collections",
50+
"methods": ["GET"]
51+
});
52+
this.data.endpoints.push({
53+
"path": "/collections/{collection_id}",
54+
"methods": ["GET"]
55+
});
56+
}
57+
// if (isFeatures) {
58+
// this.data.endpoints.push({
59+
// "path": "/collections/{collection_id}/items",
60+
// "methods": ["GET"]
61+
// });
62+
// this.data.endpoints.push({
63+
// "path": "/collections/{collection_id}/items/{item_id}",
64+
// "methods": ["GET"]
65+
// });
66+
// }
67+
if (isCoverage) {
68+
this.data.endpoints.push({
69+
"path": "/collections/{collection_id}/coverage",
70+
"methods": ["GET"]
71+
});
72+
this.data.endpoints.push({
73+
"path": "/collections/{collection_id}/coverage",
74+
"methods": ["GET"]
75+
});
76+
this.data.endpoints.push({
77+
"path": "/collections/{collection_id}/coverage/domainset",
78+
"methods": ["GET"]
79+
});
80+
this.data.endpoints.push({
81+
"path": "/collections/{collection_id}/coverage/rangetype",
82+
"methods": ["GET"]
83+
});
84+
this.data.endpoints.push({
85+
"path": "/collections/{collection_id}/coverage/rangeset",
86+
"methods": ["GET"]
87+
});
88+
this.data.endpoints.push({
89+
"path": "/collections/{collection_id}/coverage/metadata",
90+
"methods": ["GET"]
91+
});
92+
}
93+
const isProcessApi = this.hasConformance('http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/core');
94+
const processDismiss = this.hasConformance('http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/dismiss');
95+
const processJobList = this.hasConformance('http://www.opengis.net/spec/ogcapi-processes-1/1.0/req/job-list');
96+
const processLink = this._getLink('https://www.opengis.net/def/rel/ogc/1.0/processes');
97+
if (isProcessApi || processLink) {
98+
this.data.endpoints.push({
99+
"path": "/processes",
100+
"methods": ["GET"]
101+
});
102+
this.data.endpoints.push({
103+
"path": "/processes/{processId}",
104+
"methods": ["GET"]
105+
});
106+
this.data.endpoints.push({
107+
"path": "/processes/{processId}/execution",
108+
"methods": ["POST"]
109+
});
110+
let jobMethods = ["GET"];
111+
if (processDismiss) { // @todo Is dismiss equivalent to openEO job cancellation or deletion?
112+
jobMethods.push("DELETE");
113+
}
114+
this.data.endpoints.push({
115+
"path": "/jobs/{job_id}",
116+
"methods": jobMethods
117+
});
118+
this.data.endpoints.push({
119+
"path": "/jobs/{job_id}/results",
120+
"methods": ["GET"]
121+
});
122+
}
123+
const jobLink = this._getLink('https://www.opengis.net/def/rel/ogc/1.0/job-list');
124+
if (processJobList || jobLink) {
125+
this.data.endpoints.push({
126+
"path": "/jobs",
127+
"methods": ["GET"]
128+
});
129+
}
130+
this.init();
131+
}
132+
133+
/**
134+
* Initializes the class.
135+
*
136+
* @protected
137+
*/
138+
init() {
139+
if (Array.isArray(this.data.endpoints)) {
140+
super.init();
141+
}
142+
}
143+
144+
/**
145+
* Validates the capabilities.
146+
*
147+
* Throws an error in case of an issue, otherwise just passes.
148+
*
149+
* @protected
150+
* @throws {Error}
151+
*/
152+
validate() {
153+
if(!Utils.isObject(this.data)) {
154+
throw new Error("No capabilities retrieved.");
155+
}
156+
}
157+
158+
/**
159+
* Returns the openEO API version implemented by the back-end.
160+
*
161+
* @returns {string} openEO API version number.F
162+
*/
163+
apiVersion() {
164+
return this.data.api_version;
165+
}
166+
167+
/**
168+
* Returns the GDC API version implemented by the back-end.
169+
*
170+
* @returns {string} GDC API version number.
171+
*/
172+
gdcVersion() {
173+
return this.data.gdc_version || "1.0.0-beta";
174+
}
175+
176+
isEndpoint(response, method, endpoint) {
177+
if (response.config.method !== method) {
178+
return false;
179+
}
180+
if (endpoint.includes('{}')) {
181+
let pattern = '^' + endpoint.replace('{}', '[^/]+') + '$';
182+
let regex = new RegExp(pattern);
183+
return regex.test(response.config.url);
184+
}
185+
return endpoint === response.config.url;
186+
}
187+
188+
/**
189+
* Migrates a response, if required.
190+
*
191+
* @param {AxiosResponse} response
192+
* @protected
193+
* @returns {AxiosResponse}
194+
*/
195+
migrate(response) {
196+
if (this.isEndpoint(response, 'get', '/collections')) {
197+
response.data.collections = response.data.collections.map(collection => Migrate.collection(collection, response));
198+
}
199+
else if (this.isEndpoint(response, 'get', '/collections/{}')) {
200+
response.data = Migrate.collection(response.data, response);
201+
}
202+
else if (this.isEndpoint(response, 'get', '/processes')) {
203+
response.data.processes = response.data.processes.map(process => Migrate.process(process, response));
204+
}
205+
else if (this.isEndpoint(response, 'get', '/jobs')) {
206+
response.data.jobs = response.data.jobs.map(job => Migrate.job(job, response));
207+
}
208+
else if (this.isEndpoint(response, 'get', '/jobs/{}')) {
209+
response.data = Migrate.job(response.data, response);
210+
}
211+
212+
response = Migrate.all(response);
213+
214+
return response;
215+
}
216+
}
217+
218+
219+
module.exports = GdcCapabilities;

0 commit comments

Comments
 (0)