ol 10.2.2-dev.1729019374816 → 10.2.2-dev.1729036924104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ol.d.ts +2 -0
- package/dist/ol.d.ts.map +1 -1
- package/dist/ol.js +1 -1
- package/dist/ol.js.map +1 -1
- package/interaction/Modify.d.ts +46 -9
- package/interaction/Modify.d.ts.map +1 -1
- package/interaction/Modify.js +274 -134
- package/package.json +1 -1
- package/renderer/webgl/TileLayerBase.d.ts +6 -1
- package/renderer/webgl/TileLayerBase.d.ts.map +1 -1
- package/renderer/webgl/TileLayerBase.js +6 -1
- package/source/SentinelHub.d.ts +503 -0
- package/source/SentinelHub.d.ts.map +1 -0
- package/source/SentinelHub.js +641 -0
- package/util.js +1 -1
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module ol/source/SentinelHub
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import DataTileSource from './DataTile.js';
|
|
6
|
+
import {
|
|
7
|
+
equivalent as equivalentProjections,
|
|
8
|
+
get as getProjection,
|
|
9
|
+
} from '../proj.js';
|
|
10
|
+
|
|
11
|
+
const defaultProcessUrl = 'https://services.sentinel-hub.com/api/v1/process';
|
|
12
|
+
|
|
13
|
+
const defaultTokenUrl =
|
|
14
|
+
'https://services.sentinel-hub.com/auth/realms/main/protocol/openid-connect/token';
|
|
15
|
+
|
|
16
|
+
const defaultEvalscriptVersion = '3';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @type {import('../size.js').Size}
|
|
20
|
+
*/
|
|
21
|
+
const defaultTileSize = [512, 512];
|
|
22
|
+
|
|
23
|
+
const maxRetries = 10;
|
|
24
|
+
const baseDelay = 500;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} AuthConfig
|
|
28
|
+
* @property {string} [tokenUrl='https://services.sentinel-hub.com/auth/realms/main/protocol/openid-connect/token'] The URL to get the authentication token.
|
|
29
|
+
* @property {string} clientId The client ID.
|
|
30
|
+
* @property {string} clientSecret The client secret.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {Object} AccessTokenClaims
|
|
35
|
+
* @property {number} exp The expiration time of the token (in seconds).
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Object} Evalscript
|
|
40
|
+
* @property {Setup} setup The setup function.
|
|
41
|
+
* @property {EvaluatePixel} evaluatePixel The function to transform input samples into output values.
|
|
42
|
+
* @property {UpdateOutput} [updateOutput] Optional function to adjust the output bands.
|
|
43
|
+
* @property {UpdateOutputMetadata} [updateOutputMetadata] Optional function to update the output metadata.
|
|
44
|
+
* @property {Collections} [preProcessScenes] Optional function called before processing.
|
|
45
|
+
* @property {string} [version='3'] The Evalscript version.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {function(): SetupResult} Setup
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {function(Sample|Array<Sample>, Scenes, InputMetadata, CustomData, OutputMetadata): OutputValues|Array<number>|void} EvaluatePixel
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {function(Object<string, UpdatedOutputDescription>): void} UpdateOutput
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {function(Scenes, InputMetadata, OutputMetadata): void} UpdateOutputMetadata
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @typedef {Object} SetupResult
|
|
66
|
+
* @property {Array<string>|Array<InputDescription>} input Description of the input data.
|
|
67
|
+
* @property {OutputDescription|Array<OutputDescription>} output Description of the output data.
|
|
68
|
+
* @property {'SIMPLE'|'ORBIT'|'TILE'} [mosaicking='SIMPLE'] Control how samples from input scenes are composed.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @typedef {Object} InputDescription
|
|
73
|
+
* @property {Array<string>} bands Input band identifiers.
|
|
74
|
+
* @property {string|Array<string>} [units] Input band units.
|
|
75
|
+
* @property {Array<string>} [metadata] Properties to include in the input metadata.
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @typedef {Object} OutputDescription
|
|
80
|
+
* @property {string} [id='default'] Output identifier.
|
|
81
|
+
* @property {number} bands Number of output bands.
|
|
82
|
+
* @property {SampleType} [sampleType='AUTO'] Output sample type.
|
|
83
|
+
* @property {number} [nodataValue] Output nodata value.
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @typedef {Object} UpdatedOutputDescription
|
|
88
|
+
* @property {number} bands Number of output bands.
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @typedef {'INT8'|'UINT8'|'INT16'|'UINT16'|'FLOAT32'|'AUTO'} SampleType
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @typedef {Object<string, number>} Sample
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @typedef {Object} Collections
|
|
101
|
+
* @property {string} [from] For 'ORBIT' mosaicking, this will be the start of the search interval.
|
|
102
|
+
* @property {string} [to] For 'ORBIT' mosaicking, this will be the end of the search interval.
|
|
103
|
+
* @property {Scenes} scenes The scenes in the collection.
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @typedef {Object} Scenes
|
|
108
|
+
* @property {Array<Orbit>} [orbit] Information about scenes included in the tile when 'mosaicking' is 'ORBIT'.
|
|
109
|
+
* @property {Array<Tile>} [tiles] Information about scenes included in the tile when 'mosaicking' is 'TILE'.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @typedef {Object} Orbit
|
|
114
|
+
* @property {string} dateFrom The earliest date for all scenes included in the tile.
|
|
115
|
+
* @property {string} dateTo The latest date for scenes included in the tile.
|
|
116
|
+
* @property {Array} tiles Metadata for each tile.
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @typedef {Object} Tile
|
|
121
|
+
* @property {string} date The date of scene used in the tile.
|
|
122
|
+
* @property {number} cloudCoverage The estimated percentage of pixels obscured by clouds in the scene.
|
|
123
|
+
* @property {string} dataPath The path to the data in storage.
|
|
124
|
+
* @property {number} shId The internal identifier for the scene.
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @typedef {Object} InputMetadata
|
|
129
|
+
* @property {string} serviceVersion The version of the service used for processing.
|
|
130
|
+
* @property {number} normalizationFactor The factor used to convert digital number (DN) values to reflectance.
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @typedef {Object<string, unknown>} CustomData
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @typedef {Object} OutputMetadata
|
|
139
|
+
* @property {Object} userData Arbitrary user data.
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @typedef {Object<string, Array<number>>} OutputValues
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @typedef {Object} ProcessRequest
|
|
148
|
+
* @property {ProcessRequestInput} input Input data configuration.
|
|
149
|
+
* @property {string} evalscript The Evalscript used for processing.
|
|
150
|
+
* @property {ProcessRequestOutput} [output] The output configuration.
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @typedef {Object} ProcessRequestInput
|
|
155
|
+
* @property {ProcessRequestInputBounds} bounds The bounding box of the input data.
|
|
156
|
+
* @property {Array<ProcessRequestInputDataItem>} data The intput data.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @typedef {Object} ProcessRequestInputDataItem
|
|
161
|
+
* @property {string} [type] The type of the input data.
|
|
162
|
+
* @property {string} [id] The identifier of the input data.
|
|
163
|
+
* @property {DataFilter} [dataFilter] The filter to apply to the input data.
|
|
164
|
+
* @property {Object<string, unknown>} [processing] The processing to apply to the input data.
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @typedef {Object} DataFilter
|
|
169
|
+
* @property {TimeRange} [timeRange] The data time range.
|
|
170
|
+
* @property {number} [maxCloudCoverage] The maximum cloud coverage (0-100).
|
|
171
|
+
*/
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @typedef {Object} TimeRange
|
|
175
|
+
* @property {string} [from] The start time (inclusive).
|
|
176
|
+
* @property {string} [to] The end time (inclusive).
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @typedef {Object} ProcessRequestInputBounds
|
|
181
|
+
* @property {Array<number>} [bbox] The bounding box of the input data.
|
|
182
|
+
* @property {ProcessRequestInputBoundsProperties} [properties] The properties of the bounding box.
|
|
183
|
+
* @property {import("geojson").Geometry} [geometry] The geometry of the bounding box.
|
|
184
|
+
*/
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @typedef {Object} ProcessRequestInputBoundsProperties
|
|
188
|
+
* @property {string} crs The coordinate reference system of the bounding box.
|
|
189
|
+
*/
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @typedef {Object} ProcessRequestOutput
|
|
193
|
+
* @property {number} [width] Image width in pixels.
|
|
194
|
+
* @property {number} [height] Image height in pixels.
|
|
195
|
+
* @property {number} [resx] Spatial resolution in the x direction.
|
|
196
|
+
* @property {number} [resy] Spatial resolution in the y direction.
|
|
197
|
+
* @property {Array<ProcessRequestOutputResponse>} [responses] Response configuration.
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @typedef {Object} ProcessRequestOutputResponse
|
|
202
|
+
* @property {string} [identifier] Identifier used to connect results to outputs from the setup.
|
|
203
|
+
* @property {ProcessRequestOutputFormat} [format] Response format.
|
|
204
|
+
*/
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @typedef {Object} ProcessRequestOutputFormat
|
|
208
|
+
* @property {string} [type] The output format type.
|
|
209
|
+
*/
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @param {Evalscript} evalscript The object to serialize.
|
|
213
|
+
* @return {string} The serialized Evalscript.
|
|
214
|
+
*/
|
|
215
|
+
function serializeEvalscript(evalscript) {
|
|
216
|
+
const version = evalscript.version || defaultEvalscriptVersion;
|
|
217
|
+
return `//VERSION=${version}
|
|
218
|
+
${serializeFunction('setup', evalscript.setup)}
|
|
219
|
+
${serializeFunction('evaluatePixel', evalscript.evaluatePixel)}
|
|
220
|
+
${serializeFunction('updateOutput', evalscript.updateOutput)}
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get a loaded image given a response.
|
|
226
|
+
*
|
|
227
|
+
* @param {Response} response The response.
|
|
228
|
+
* @return {Promise<HTMLImageElement>} The image.
|
|
229
|
+
*/
|
|
230
|
+
async function imageFromResponse(response) {
|
|
231
|
+
const blob = await response.blob();
|
|
232
|
+
|
|
233
|
+
return new Promise((resolve, reject) => {
|
|
234
|
+
const image = new Image();
|
|
235
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
236
|
+
image.onload = () => {
|
|
237
|
+
URL.revokeObjectURL(blobUrl);
|
|
238
|
+
resolve(image);
|
|
239
|
+
};
|
|
240
|
+
image.onerror = () => {
|
|
241
|
+
URL.revokeObjectURL(blobUrl);
|
|
242
|
+
reject(new Error('Failed to load image'));
|
|
243
|
+
};
|
|
244
|
+
image.src = blobUrl;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @param {number} ms Milliseconds.
|
|
250
|
+
* @return {Promise<void>} A promise that resolves after the given time.
|
|
251
|
+
*/
|
|
252
|
+
function delay(ms) {
|
|
253
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @param {AuthConfig} auth The authentication configuration.
|
|
258
|
+
* @return {Promise<string>} The authentication token.
|
|
259
|
+
*/
|
|
260
|
+
async function getToken(auth) {
|
|
261
|
+
const url = auth.tokenUrl || defaultTokenUrl;
|
|
262
|
+
const body = new URLSearchParams();
|
|
263
|
+
body.append('grant_type', 'client_credentials');
|
|
264
|
+
body.append('client_id', auth.clientId);
|
|
265
|
+
body.append('client_secret', auth.clientSecret);
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @type {RequestInit}
|
|
269
|
+
*/
|
|
270
|
+
const options = {
|
|
271
|
+
method: 'POST',
|
|
272
|
+
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
|
273
|
+
body,
|
|
274
|
+
};
|
|
275
|
+
const response = await fetch(url, options);
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
if (response.status === 401) {
|
|
278
|
+
throw new Error('Bad client id or secret');
|
|
279
|
+
}
|
|
280
|
+
throw new Error('Failed to get token');
|
|
281
|
+
}
|
|
282
|
+
const data = await response.json();
|
|
283
|
+
return data.access_token;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* @param {string} token The access token to parse.
|
|
288
|
+
* @return {AccessTokenClaims} The parsed token claims.
|
|
289
|
+
*/
|
|
290
|
+
export function parseTokenClaims(token) {
|
|
291
|
+
const base64EncodedClaims = token
|
|
292
|
+
.split('.')[1]
|
|
293
|
+
.replace(/-/g, '+')
|
|
294
|
+
.replace(/_/g, '/');
|
|
295
|
+
|
|
296
|
+
const chars = atob(base64EncodedClaims).split('');
|
|
297
|
+
const count = chars.length;
|
|
298
|
+
const uriEncodedChars = new Array(count);
|
|
299
|
+
for (let i = 0; i < count; ++i) {
|
|
300
|
+
const c = chars[i];
|
|
301
|
+
uriEncodedChars[i] = '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return JSON.parse(decodeURIComponent(uriEncodedChars.join('')));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Gets a CRS identifier accepted by Sentinel Hub.
|
|
309
|
+
* See https://docs.sentinel-hub.com/api/latest/api/process/crs/.
|
|
310
|
+
*
|
|
311
|
+
* @param {import("../proj/Projection.js").default} projection The projection.
|
|
312
|
+
* @return {string} The projection identifier accepted by Sentinel Hub.
|
|
313
|
+
*/
|
|
314
|
+
export function getProjectionIdentifier(projection) {
|
|
315
|
+
const ogcId = 'http://www.opengis.net/def/crs/';
|
|
316
|
+
const code = projection.getCode();
|
|
317
|
+
if (code.startsWith(ogcId)) {
|
|
318
|
+
return code;
|
|
319
|
+
}
|
|
320
|
+
if (code.startsWith('EPSG:')) {
|
|
321
|
+
return `${ogcId}EPSG/0/${code.slice(5)}`;
|
|
322
|
+
}
|
|
323
|
+
if (equivalentProjections(projection, getProjection('EPSG:4326'))) {
|
|
324
|
+
return `${ogcId}EPSG/0/4326`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// hope for the best
|
|
328
|
+
return code;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* This is intended to work with named functions, anonymous functions, arrow functions, and object methods.
|
|
333
|
+
* Due to how the Evalscript is executed, these are serialized as function expressions using `var`.
|
|
334
|
+
*
|
|
335
|
+
* @param {string} name The name of the function.
|
|
336
|
+
* @param {Function|undefined} func The function to serialize.
|
|
337
|
+
* @return {string} The serialized function.
|
|
338
|
+
*/
|
|
339
|
+
export function serializeFunction(name, func) {
|
|
340
|
+
if (!func) {
|
|
341
|
+
return '';
|
|
342
|
+
}
|
|
343
|
+
let expression = func.toString();
|
|
344
|
+
if (
|
|
345
|
+
func.name &&
|
|
346
|
+
func.name !== 'function' &&
|
|
347
|
+
expression.match(new RegExp('^' + func.name.replace('$', '\\$') + '\\b'))
|
|
348
|
+
) {
|
|
349
|
+
// assume function came from an object property using method syntax
|
|
350
|
+
expression = 'function ' + expression;
|
|
351
|
+
}
|
|
352
|
+
return `var ${name} = ${expression};`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* @typedef {Object} Options
|
|
357
|
+
* @property {AuthConfig|string} [auth] The authentication configuration with `clientId` and `clientSecret` or an access token.
|
|
358
|
+
* See [Sentinel Hub authentication](https://docs.sentinel-hub.com/api/latest/api/overview/authentication/)
|
|
359
|
+
* for details. If not provided in the constructor, the source will not be rendered until {@link module:ol/source/SentinelHub~SentinelHub#setAuth}
|
|
360
|
+
* is called.
|
|
361
|
+
* @property {Array<ProcessRequestInputDataItem>} [data] The input data configuration. If not provided in the constructor,
|
|
362
|
+
* the source will not be rendered until {@link module:ol/source/SentinelHub~SentinelHub#setData} is called.
|
|
363
|
+
* @property {Evalscript|string} [evalscript] The process applied to the input data. If not provided in the constructor,
|
|
364
|
+
* the source will not be rendered until {@link module:ol/source/SentinelHub~SentinelHub#setEvalscript} is called. See the
|
|
365
|
+
* `setEvalscript` documentation for details on the restrictions when passing process functions.
|
|
366
|
+
* @property {number|import("../size.js").Size} [tileSize=[512, 512]] The pixel width and height of the source tiles.
|
|
367
|
+
* @property {string} [url='https://services.sentinel-hub.com/api/v1/process'] The Sentinel Hub Processing API URL.
|
|
368
|
+
* @property {import("../proj.js").ProjectionLike} [projection] Projection. Default is the view projection.
|
|
369
|
+
* @property {boolean} [attributionsCollapsible=true] Allow the attributions to be collapsed.
|
|
370
|
+
* @property {boolean} [interpolate=true] Use interpolated values when resampling. By default,
|
|
371
|
+
* linear interpolation is used when resampling. Set to false to use the nearest neighbor instead.
|
|
372
|
+
* @property {boolean} [wrapX=true] Wrap the world horizontally.
|
|
373
|
+
* @property {number} [transition] Duration of the opacity transition for rendering.
|
|
374
|
+
* To disable the opacity transition, pass `transition: 0`.
|
|
375
|
+
*/
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* @classdesc
|
|
379
|
+
* A tile source that generates tiles using the Sentinel Hub [Processing API](https://docs.sentinel-hub.com/api/latest/api/process/).
|
|
380
|
+
* All of the constructor options are optional, however the source will not be ready for rendering until the `auth`, `data`,
|
|
381
|
+
* and `evalscript` properties are provided. These can be set after construction with the {@link module:ol/source/SentinelHub~SentinelHub#setAuth},
|
|
382
|
+
* {@link module:ol/source/SentinelHub~SentinelHub#setData}, and {@link module:ol/source/SentinelHub~SentinelHub#setEvalscript}
|
|
383
|
+
* methods.
|
|
384
|
+
*
|
|
385
|
+
* If there are errors while configuring the source or fetching an access token, the `change` event will be fired and the
|
|
386
|
+
* source state will be set to `error`. See the {@link module:ol/source/SentinelHub~SentinelHub#getError} method for
|
|
387
|
+
* details on handling these errors.
|
|
388
|
+
* @api
|
|
389
|
+
*/
|
|
390
|
+
class SentinelHub extends DataTileSource {
|
|
391
|
+
/**
|
|
392
|
+
* @param {Options} [options] Sentinel Hub options.
|
|
393
|
+
*/
|
|
394
|
+
constructor(options) {
|
|
395
|
+
/**
|
|
396
|
+
* @type {Options}
|
|
397
|
+
*/
|
|
398
|
+
const config = options || {};
|
|
399
|
+
|
|
400
|
+
super({
|
|
401
|
+
state: 'loading',
|
|
402
|
+
projection: config.projection,
|
|
403
|
+
attributionsCollapsible: config.attributionsCollapsible,
|
|
404
|
+
interpolate: config.interpolate,
|
|
405
|
+
tileSize: config.tileSize || defaultTileSize,
|
|
406
|
+
wrapX: config.wrapX !== undefined ? config.wrapX : true,
|
|
407
|
+
transition: config.transition,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
this.setLoader((x, y, z) => this.loadTile_(x, y, z, 1));
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @type {Error|null}
|
|
414
|
+
*/
|
|
415
|
+
this.error_ = null;
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* @type {string}
|
|
419
|
+
* @private
|
|
420
|
+
*/
|
|
421
|
+
this.evalscript_ = '';
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* @type {Array<ProcessRequestInputDataItem>|null}
|
|
425
|
+
* @private
|
|
426
|
+
*/
|
|
427
|
+
this.inputData_ = null;
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* @type {string}
|
|
431
|
+
* @private
|
|
432
|
+
*/
|
|
433
|
+
this.processUrl_ = config.url || defaultProcessUrl;
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* @type {string}
|
|
437
|
+
* @private
|
|
438
|
+
*/
|
|
439
|
+
this.token_ = '';
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* @type {ReturnType<typeof setTimeout>}
|
|
443
|
+
* @private
|
|
444
|
+
*/
|
|
445
|
+
this.tokenRenewalId_;
|
|
446
|
+
|
|
447
|
+
if (config.auth) {
|
|
448
|
+
this.setAuth(config.auth);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (config.data) {
|
|
452
|
+
this.setData(config.data);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (config.evalscript) {
|
|
456
|
+
this.setEvalscript(config.evalscript);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Set the authentication configuration for the source (if not provided in the constructor).
|
|
462
|
+
* If an object with `clientId` and `clientSecret` is provided, an access token will be fetched
|
|
463
|
+
* and used with processing requests. Alternatively, an access token can be supplied directly.
|
|
464
|
+
*
|
|
465
|
+
* @param {AuthConfig|string} auth The auth config or access token.
|
|
466
|
+
* @api
|
|
467
|
+
*/
|
|
468
|
+
async setAuth(auth) {
|
|
469
|
+
clearTimeout(this.tokenRenewalId_);
|
|
470
|
+
|
|
471
|
+
if (typeof auth === 'string') {
|
|
472
|
+
this.token_ = auth;
|
|
473
|
+
this.fireWhenReady_();
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* @type {string}
|
|
479
|
+
*/
|
|
480
|
+
let token;
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* @type {AccessTokenClaims}
|
|
484
|
+
*/
|
|
485
|
+
let claims;
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
token = await getToken(auth);
|
|
489
|
+
claims = parseTokenClaims(token);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
this.error_ = error;
|
|
492
|
+
this.setState('error');
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
this.token_ = token;
|
|
496
|
+
|
|
497
|
+
const expiry = claims.exp * 1000;
|
|
498
|
+
const timeout = Math.max(expiry - Date.now() - 60 * 1000, 1);
|
|
499
|
+
this.tokenRenewalId_ = setTimeout(() => this.setAuth(auth), timeout);
|
|
500
|
+
this.fireWhenReady_();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Set or update the input data used.
|
|
505
|
+
*
|
|
506
|
+
* @param {Array<ProcessRequestInputDataItem>} data The input data configuration.
|
|
507
|
+
* @api
|
|
508
|
+
*/
|
|
509
|
+
setData(data) {
|
|
510
|
+
this.inputData_ = data;
|
|
511
|
+
this.fireWhenReady_();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Set or update the Evalscript used to process the data. Either a process object or a string
|
|
516
|
+
* Evalscript can be provided. If a process object is provided, it will be serialized to produce the
|
|
517
|
+
* Evalscript string. Because these functions will be serialized and executed by the Processing API,
|
|
518
|
+
* they cannot refer to other variables or functions that are not provided by the Processing API
|
|
519
|
+
* context.
|
|
520
|
+
*
|
|
521
|
+
* @param {Evalscript|string} evalscript The process to apply to the input data.
|
|
522
|
+
* @api
|
|
523
|
+
*/
|
|
524
|
+
setEvalscript(evalscript) {
|
|
525
|
+
let script;
|
|
526
|
+
if (typeof evalscript === 'string') {
|
|
527
|
+
script = evalscript;
|
|
528
|
+
} else {
|
|
529
|
+
try {
|
|
530
|
+
script = serializeEvalscript(evalscript);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
this.error_ = error;
|
|
533
|
+
this.setState('error');
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
this.evalscript_ = script;
|
|
538
|
+
this.fireWhenReady_();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
fireWhenReady_() {
|
|
542
|
+
if (!this.token_ || !this.evalscript_ || !this.inputData_) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const state = this.getState();
|
|
546
|
+
if (state === 'ready') {
|
|
547
|
+
this.changed();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
this.setState('ready');
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* @param {number} z The z tile index.
|
|
555
|
+
* @param {number} x The x tile index.
|
|
556
|
+
* @param {number} y The y tile index.
|
|
557
|
+
* @param {number} attempt The attempt number (starting with 1). Incremented with retries.
|
|
558
|
+
* @return {Promise<import('../DataTile.js').Data>} The composed tile data.
|
|
559
|
+
* @private
|
|
560
|
+
*/
|
|
561
|
+
async loadTile_(z, x, y, attempt) {
|
|
562
|
+
const tileGrid = this.getTileGrid();
|
|
563
|
+
const extent = tileGrid.getTileCoordExtent([z, x, y]);
|
|
564
|
+
const tileSize = this.getTileSize(z);
|
|
565
|
+
const projection = this.getProjection();
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* @type {ProcessRequest}
|
|
569
|
+
*/
|
|
570
|
+
const body = {
|
|
571
|
+
input: {
|
|
572
|
+
bounds: {
|
|
573
|
+
bbox: extent,
|
|
574
|
+
properties: {crs: getProjectionIdentifier(projection)},
|
|
575
|
+
},
|
|
576
|
+
data: this.inputData_,
|
|
577
|
+
},
|
|
578
|
+
output: {
|
|
579
|
+
width: tileSize[0],
|
|
580
|
+
height: tileSize[1],
|
|
581
|
+
},
|
|
582
|
+
evalscript: this.evalscript_,
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* @type {RequestInit}
|
|
587
|
+
*/
|
|
588
|
+
const options = {
|
|
589
|
+
method: 'POST',
|
|
590
|
+
headers: {
|
|
591
|
+
'Content-Type': 'application/json',
|
|
592
|
+
Authorization: `Bearer ${this.token_}`,
|
|
593
|
+
'Access-Control-Request-Headers': 'Retry-After',
|
|
594
|
+
},
|
|
595
|
+
body: JSON.stringify(body),
|
|
596
|
+
credentials: 'include',
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const response = await fetch(this.processUrl_, options);
|
|
600
|
+
if (!response.ok) {
|
|
601
|
+
if (response.status === 429 && attempt < maxRetries - 1) {
|
|
602
|
+
// The Retry-After header includes unreasonable wait times, instead use exponential backoff.
|
|
603
|
+
const retryAfter = baseDelay * 2 ** attempt;
|
|
604
|
+
await delay(retryAfter);
|
|
605
|
+
return this.loadTile_(x, y, z, attempt + 1);
|
|
606
|
+
}
|
|
607
|
+
throw new Error(`Failed to get tile: ${response.statusText}`);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return imageFromResponse(response);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* When the source state is `error`, use this function to get more information about the error.
|
|
615
|
+
* To debug a faulty configuration, you may want to use a listener like this:
|
|
616
|
+
* ```js
|
|
617
|
+
* source.on('change', () => {
|
|
618
|
+
* if (source.getState() === 'error') {
|
|
619
|
+
* console.error(source.getError());
|
|
620
|
+
* }
|
|
621
|
+
* });
|
|
622
|
+
* ```
|
|
623
|
+
*
|
|
624
|
+
* @return {Error|null} A source loading error.
|
|
625
|
+
* @api
|
|
626
|
+
*/
|
|
627
|
+
getError() {
|
|
628
|
+
return this.error_;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Clean up.
|
|
633
|
+
* @override
|
|
634
|
+
*/
|
|
635
|
+
disposeInternal() {
|
|
636
|
+
clearTimeout(this.tokenRenewalId_);
|
|
637
|
+
super.disposeInternal();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export default SentinelHub;
|
package/util.js
CHANGED