Skip to content

Commit 2dc0648

Browse files
committed
Merge remote-tracking branch 'origin/development'. v0.3.3
2 parents b77811a + efaecfa commit 2dc0648

File tree

5 files changed

+201
-83
lines changed

5 files changed

+201
-83
lines changed

README.md

+69-5
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,85 @@ JavaScript client for the openEO API.
44

55
[![Build Status](https://travis-ci.org/Open-EO/openeo-js-client.svg?branch=master)](https://travis-ci.org/Open-EO/openeo-js-client)
66

7-
This client is in **version 0.3.2** and supports **openEO API versions 0.3.0 and 0.3.1**. Legacy versions are available as releases.
7+
This client is in **version 0.3.3** and supports **openEO API versions 0.3.0 and 0.3.1**. Legacy versions are available as releases.
88

99
## Usage
1010
This library can run in a recent browser supporting ECMAScript 2015 or node.js.
1111

1212
To use it in a browser environment simply add the following code to your HTML file:
13-
```
13+
```html
1414
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
1515
<script src="https://cdn.jsdelivr.net/npm/@openeo/js-client/openeo.js"></script>
1616
```
1717

18-
To install it with npm: `npm install @openeo/js-client`
18+
To install it with npm run: `npm install @openeo/js-client`
19+
20+
### Running a job
21+
22+
```js
23+
// Import the library if running in a nodeJS environment
24+
// const { OpenEO } = require('@openeo/js-client');
25+
26+
var obj = new OpenEO();
27+
// Show the client version
28+
console.log("Client Version: " +obj.version());
29+
30+
try {
31+
// Connect to the back-end
32+
var con = await obj.connect("https://earthengine.openeo.org/v0.3", "basic", {username: "group1", password: "test123"});
33+
34+
// Show implemented API version of the back-end
35+
var capabilities = await con.capabilities();
36+
console.log("Server API version: " +capabilities.version());
37+
38+
// List collection names
39+
var collections = await con.listCollections();
40+
console.log("Collections: " +collections.collections.map(c => c.name));
41+
42+
// List process ids
43+
var processes = await con.listProcesses();
44+
console.log("Processes: " + processes.processes.map(p => p.name));
45+
46+
// List supported file types
47+
var fileTypes = await con.listFileTypes();
48+
console.log("Files types: " + Object.keys(fileTypes.formats));
49+
50+
// Check whether synchronous previews are supported
51+
var syncSupport = capabilities.hasFeature("execute");
52+
console.log("Synchronous previews: " + (syncSupport ? "supported" : "NOT supported"));
53+
54+
// Request a preview synchronously for a process graph
55+
if (syncSupport) {
56+
// Derives maximum NDVI measurements over pixel time series of Sentinel 2 imagery
57+
var processGraph = {
58+
"imagery": {
59+
"red": "B4",
60+
"nir": "B8",
61+
"imagery": {
62+
"extent": ["2018-12-01T00:00:00Z","2018-12-31T23:59:59Z"],
63+
"imagery": {
64+
"extent": {"west": 8.265169,"south": 52.453917,"east": 8.42035,"north": 52.576767},
65+
"imagery": {
66+
"process_id": "get_collection",
67+
"name": "COPERNICUS/S2"
68+
},
69+
"process_id": "filter_bbox"
70+
},
71+
"process_id": "filter_daterange"
72+
},
73+
"process_id": "NDVI"
74+
},
75+
"process_id": "max_time"
76+
};
77+
var preview = await con.execute(processGraph, "png");
78+
// This returns a Blob object with data you could further process or show.
79+
}
80+
} catch(e) {
81+
console.log(e);
82+
}
83+
```
84+
1985

20-
Dependencies required to run the openEO JS client:
21-
* [axios](https://github.com/axios/axios)
2286

2387
## Interactive JS Editor
2488

openeo.js

+99-56
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class OpenEO {
1515
var connection = new Connection(url);
1616
return connection.capabilities().then(capabilities => {
1717
// Check whether back-end is accessible and supports a correct version.
18-
if (capabilities.version().startsWith("0.3")) {
18+
if (capabilities.version().startsWith("0.3.")) {
1919
if(authType !== null) {
2020
switch(authType) {
2121
case 'basic':
@@ -35,7 +35,7 @@ class OpenEO {
3535
}
3636

3737
version() {
38-
return "0.3.2";
38+
return "0.3.3";
3939
}
4040
}
4141

@@ -113,22 +113,12 @@ class Connection {
113113
return Promise.reject(new Error("Not implemented yet."));
114114
}
115115

116-
_base64encode(str) {
117-
var buffer;
118-
if (str instanceof Buffer) {
119-
buffer = str;
120-
} else {
121-
buffer = Buffer.from(str.toString(), 'binary');
122-
}
123-
return buffer.toString('base64');
124-
}
125-
126116
authenticateBasic(username, password) {
127117
return this._send({
128118
method: 'get',
129119
responseType: 'json',
130120
url: '/credentials/basic',
131-
headers: {'Authorization': 'Basic ' + this._base64encode(username + ':' + password)} // btoa is JS's ugly name for encodeBase64
121+
headers: {'Authorization': 'Basic ' + Util.base64encode(username + ':' + password)}
132122
}).then(response => {
133123
if (!response.data.user_id) {
134124
throw new Error("No user_id returned.");
@@ -403,41 +393,6 @@ class Connection {
403393
unsubscribe(topic, parameters, callback) {
404394
return this.subscriptionsObject.unsubscribe(topic, parameters, callback);
405395
}
406-
407-
_saveToFileNode(data, filename) {
408-
var fs = require('fs');
409-
return new Promise((resolve, reject) => {
410-
var writeStream = fs.createWriteStream(filename);
411-
writeStream.on('close', (err) => {
412-
if (err) {
413-
return reject(err);
414-
}
415-
resolve();
416-
});
417-
data.pipe(writeStream);
418-
});
419-
}
420-
421-
/* istanbul ignore next */
422-
_saveToFileBrowser(data, filename) {
423-
// based on: https://github.com/kennethjiang/js-file-download/blob/master/file-download.js
424-
var blob = new Blob([data], {type: 'application/octet-stream'});
425-
var blobURL = window.URL.createObjectURL(blob);
426-
var tempLink = document.createElement('a');
427-
tempLink.style.display = 'none';
428-
tempLink.href = blobURL;
429-
tempLink.setAttribute('download', filename);
430-
431-
if (typeof tempLink.download === 'undefined') {
432-
tempLink.setAttribute('target', '_blank');
433-
}
434-
435-
document.body.appendChild(tempLink);
436-
tempLink.click();
437-
document.body.removeChild(tempLink);
438-
window.URL.revokeObjectURL(blobURL);
439-
return Promise.resolve();
440-
}
441396
}
442397

443398

@@ -461,7 +416,7 @@ class Subscriptions {
461416
if(!this.listeners.has(topic)) {
462417
this.listeners.set(topic, new Map());
463418
}
464-
this.listeners.get(topic).set(JSON.stringify(parameters), callback);
419+
this.listeners.get(topic).set(Util.hash(parameters), callback);
465420
}
466421

467422
this._sendSubscription('subscribe', topic, parameters);
@@ -477,7 +432,7 @@ class Subscriptions {
477432

478433
// remove the applicable sub-callback
479434
if(topicListeners instanceof Map) {
480-
topicListeners.delete(JSON.stringify(parameters));
435+
topicListeners.delete(Util.hash(parameters));
481436
} else {
482437
return Promise.reject(new Error("this.listeners must be a Map of Maps"));
483438
}
@@ -539,8 +494,8 @@ class Subscriptions {
539494
var callback;
540495
// we should now have a Map in which to look for the correct listener
541496
if (topicListeners && topicListeners instanceof Map) {
542-
callback = topicListeners.get('{}') // default: without parameters
543-
|| topicListeners.get('{"job_id":"' + json.payload.job_id + '"}');
497+
callback = topicListeners.get(Util.hash({})) // default: without parameters
498+
|| topicListeners.get(Util.hash({job_id: json.payload.job_id}));
544499
// more parameter checks possible
545500
}
546501
// if we now have a function, we can call it with the information
@@ -765,11 +720,11 @@ class File extends BaseEntity {
765720

766721
_saveToFile(data, filename) {
767722
if (isNode) {
768-
return this.connection._saveToFileNode(data, filename);
723+
return Util.saveToFileNode(data, filename);
769724
}
770725
else {
771726
/* istanbul ignore next */
772-
return this.connection._saveToFileBrowser(data, filename);
727+
return Util.saveToFileBrowser(data, filename);
773728
}
774729
}
775730

@@ -901,7 +856,7 @@ class Job extends BaseEntity {
901856
let parsedUrl = url.parse(link);
902857
let targetPath = path.join(targetFolder, path.basename(parsedUrl.pathname));
903858
let p = this.connection.download(link, false)
904-
.then(response => this.connection._saveToFileNode(response.data, targetPath))
859+
.then(response => Util.saveToFileNode(response.data, targetPath))
905860
.then(() => files.push(targetPath));
906861
promises.push(p);
907862
}
@@ -976,9 +931,97 @@ class Service extends BaseEntity {
976931
}
977932
}
978933

934+
class Util {
935+
936+
static base64encode(str) {
937+
if (typeof btoa === 'function') {
938+
// btoa is JS's ugly name for encodeBase64
939+
return btoa(str);
940+
}
941+
else {
942+
var buffer;
943+
if (str instanceof Buffer) {
944+
buffer = str;
945+
} else {
946+
buffer = Buffer.from(str.toString(), 'binary');
947+
}
948+
return buffer.toString('base64');
949+
}
950+
}
951+
952+
// Non-crypthographic / unsafe hashing for objects
953+
static hash(o) {
954+
switch(typeof o) {
955+
case 'boolean':
956+
return Util.hashString("b:" + o.toString());
957+
case 'number':
958+
return Util.hashString("n:" + o.toString());
959+
case 'string':
960+
return Util.hashString("s:" + o);
961+
case 'object':
962+
if (o === null) {
963+
return Util.hashString("n:");
964+
}
965+
else {
966+
return Util.hashString(Object.keys(o).sort().map(k => "o:" + k + ":" + Util.hash(o[k])).join("::"));
967+
}
968+
default:
969+
return Util.hashString(typeof o);
970+
}
971+
}
972+
973+
// See: https://en.wikipedia.org/wiki/Jenkins_hash_function
974+
static hashString(b) {
975+
for(var a=0,c=b.length;c--;) {
976+
a+=b.charCodeAt(c);
977+
a+=a<<10;
978+
a^=a>>6;
979+
}
980+
a+=a<<3;
981+
a^=a>>11;
982+
a+=a<<15;
983+
return ((a&4294967295)>>>0).toString(16);
984+
}
985+
986+
static saveToFileNode(data, filename) {
987+
var fs = require('fs');
988+
return new Promise((resolve, reject) => {
989+
var writeStream = fs.createWriteStream(filename);
990+
writeStream.on('close', (err) => {
991+
if (err) {
992+
return reject(err);
993+
}
994+
resolve();
995+
});
996+
data.pipe(writeStream);
997+
});
998+
}
999+
1000+
/* istanbul ignore next */
1001+
static saveToFileBrowser(data, filename) {
1002+
// based on: https://github.com/kennethjiang/js-file-download/blob/master/file-download.js
1003+
var blob = new Blob([data], {type: 'application/octet-stream'});
1004+
var blobURL = window.URL.createObjectURL(blob);
1005+
var tempLink = document.createElement('a');
1006+
tempLink.style.display = 'none';
1007+
tempLink.href = blobURL;
1008+
tempLink.setAttribute('download', filename);
1009+
1010+
if (typeof tempLink.download === 'undefined') {
1011+
tempLink.setAttribute('target', '_blank');
1012+
}
1013+
1014+
document.body.appendChild(tempLink);
1015+
tempLink.click();
1016+
document.body.removeChild(tempLink);
1017+
window.URL.revokeObjectURL(blobURL);
1018+
return Promise.resolve();
1019+
}
1020+
}
9791021

9801022
let toExport = {
981-
OpenEO: OpenEO
1023+
OpenEO: OpenEO,
1024+
Util: Util
9821025
};
9831026

9841027
// explanation: https://www.matteoagosti.com/blog/2013/02/24/writing-javascript-modules-for-both-browser-and-node/

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openeo/js-client",
3-
"version": "0.3.2",
3+
"version": "0.3.3",
44
"author": "openEO Consortium",
55
"contributors": [
66
{
@@ -22,12 +22,12 @@
2222
},
2323
"main": "openeo.js",
2424
"devDependencies": {
25-
"jest": "^23.5.0",
25+
"jest": "^24.7.1",
2626
"jest-html-reporter": "^2.4.2"
2727
},
2828
"dependencies": {
2929
"axios": "^0.18.0",
30-
"ws": "^6.1.2"
30+
"ws": "^6.2.1"
3131
},
3232
"scripts": {
3333
"test": "jest basic.test.js --env=jsdom",

tests/basic.test.js

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
const { OpenEO } = require('../openeo.js');
1+
const { OpenEO, Util } = require('../openeo.js');
22
const packageInfo = require('../package.json');
33

4-
describe('Basic client tests', () => {
4+
describe('Client Basics', () => {
55
var obj = new OpenEO();
66
test('Check that import worked', () => {
77
expect(obj).not.toBeNull();
@@ -10,4 +10,24 @@ describe('Basic client tests', () => {
1010
test('Check version number', () => {
1111
expect(obj.version()).toBe(packageInfo.version);
1212
});
13+
});
14+
15+
describe('Utils', () => {
16+
test('Base64 encoder', () => {
17+
expect(Util.base64encode(Buffer.from("test"))).toBe("dGVzdA==");
18+
expect(Util.base64encode("test")).toBe("dGVzdA==");
19+
});
20+
test('Base64 encoder', () => {
21+
expect(Util.base64encode(Buffer.from("test"))).toBe("dGVzdA==");
22+
expect(Util.base64encode("test")).toBe("dGVzdA==");
23+
});
24+
test('String hashing', () => {
25+
expect(Util.hashString("a")).toBe("ca2e9442");
26+
});
27+
test('Object hashing', () => {
28+
expect(Util.hash(null)).toBe("40d6be39");
29+
expect(Util.hash(123)).toBe("fd895d50");
30+
expect(Util.hash({a:"b"})).toBe("2a0a2095");
31+
expect(Util.hash([true])).toBe("7f0d24dd");
32+
});
1333
});

0 commit comments

Comments
 (0)