ol-stac 0.0.0

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.
Files changed (107) hide show
  1. package/.eslintignore +2 -0
  2. package/.github/workflows/deploy.yml +67 -0
  3. package/.github/workflows/publish.yml +32 -0
  4. package/.github/workflows/release.yml +21 -0
  5. package/.github/workflows/test.yml +85 -0
  6. package/CHANGELOG.md +14 -0
  7. package/DEVELOPING.md +104 -0
  8. package/LICENSE.md +201 -0
  9. package/README.md +43 -0
  10. package/config/jsdoc/api/conf.json +38 -0
  11. package/config/jsdoc/api/index.md +1 -0
  12. package/config/jsdoc/api/readme.md +65 -0
  13. package/config/jsdoc/api/template/README.md +3 -0
  14. package/config/jsdoc/api/template/publish.js +743 -0
  15. package/config/jsdoc/api/template/static/scripts/linenumber.js +11 -0
  16. package/config/jsdoc/api/template/static/scripts/main.js +305 -0
  17. package/config/jsdoc/api/template/static/scripts/prettify/Apache-License-2.0.txt +202 -0
  18. package/config/jsdoc/api/template/static/scripts/prettify/lang-css.js +2 -0
  19. package/config/jsdoc/api/template/static/scripts/prettify/prettify.js +28 -0
  20. package/config/jsdoc/api/template/static/styles/carbon.css +65 -0
  21. package/config/jsdoc/api/template/static/styles/jaguar.css +512 -0
  22. package/config/jsdoc/api/template/static/styles/prettify-jsdoc.css +111 -0
  23. package/config/jsdoc/api/template/static/styles/prettify-tomorrow.css +132 -0
  24. package/config/jsdoc/api/template/static/theme +1 -0
  25. package/config/jsdoc/api/template/tmpl/container.tmpl +205 -0
  26. package/config/jsdoc/api/template/tmpl/details.tmpl +84 -0
  27. package/config/jsdoc/api/template/tmpl/example.tmpl +2 -0
  28. package/config/jsdoc/api/template/tmpl/examples.tmpl +16 -0
  29. package/config/jsdoc/api/template/tmpl/exceptions.tmpl +30 -0
  30. package/config/jsdoc/api/template/tmpl/layout.tmpl +92 -0
  31. package/config/jsdoc/api/template/tmpl/mainpage.tmpl +14 -0
  32. package/config/jsdoc/api/template/tmpl/members.tmpl +38 -0
  33. package/config/jsdoc/api/template/tmpl/method.tmpl +132 -0
  34. package/config/jsdoc/api/template/tmpl/navigation.tmpl +59 -0
  35. package/config/jsdoc/api/template/tmpl/observables.tmpl +39 -0
  36. package/config/jsdoc/api/template/tmpl/params.tmpl +109 -0
  37. package/config/jsdoc/api/template/tmpl/properties.tmpl +85 -0
  38. package/config/jsdoc/api/template/tmpl/returns.tmpl +72 -0
  39. package/config/jsdoc/api/template/tmpl/source.tmpl +8 -0
  40. package/config/jsdoc/api/template/tmpl/stability.tmpl +7 -0
  41. package/config/jsdoc/api/template/tmpl/tutorial.tmpl +19 -0
  42. package/config/jsdoc/api/template/tmpl/type.tmpl +7 -0
  43. package/config/jsdoc/info/conf.json +22 -0
  44. package/config/jsdoc/info/publish.js +175 -0
  45. package/config/jsdoc/package.json +4 -0
  46. package/config/jsdoc/plugins/api.cjs +181 -0
  47. package/config/jsdoc/plugins/default-export.cjs +35 -0
  48. package/config/jsdoc/plugins/define-plugin.cjs +33 -0
  49. package/config/jsdoc/plugins/events.cjs +39 -0
  50. package/config/jsdoc/plugins/inline-options.cjs +102 -0
  51. package/config/jsdoc/plugins/markdown.cjs +119 -0
  52. package/config/jsdoc/plugins/observable.cjs +69 -0
  53. package/config/jsdoc/plugins/virtual-plugin.cjs +16 -0
  54. package/config/rollup-full-build.js +24 -0
  55. package/config/tsconfig-build.json +25 -0
  56. package/examples/.eslintrc +29 -0
  57. package/examples/index.html +100 -0
  58. package/examples/index.js +109 -0
  59. package/examples/planetary-computer.html +14 -0
  60. package/examples/planetary-computer.js +53 -0
  61. package/examples/resources/Jugl.js +15 -0
  62. package/examples/resources/common.js +111 -0
  63. package/examples/stac-collection-webmaplinks.html +9 -0
  64. package/examples/stac-collection-webmaplinks.js +55 -0
  65. package/examples/stac-item-from-object.html +9 -0
  66. package/examples/stac-item-from-object.js +102 -0
  67. package/examples/stac-item-tileserver.html +9 -0
  68. package/examples/stac-item-tileserver.js +34 -0
  69. package/examples/stac-item.html +9 -0
  70. package/examples/stac-item.js +45 -0
  71. package/examples/stac-itemcollection.html +11 -0
  72. package/examples/stac-itemcollection.js +39 -0
  73. package/examples/templates/example.html +206 -0
  74. package/examples/templates/readme.md +3 -0
  75. package/examples/webpack/config.mjs +69 -0
  76. package/examples/webpack/example-builder.js +410 -0
  77. package/package.json +107 -0
  78. package/site/build.js +26 -0
  79. package/site/layouts/default.hbs +78 -0
  80. package/site/src/doc/faq.md +8 -0
  81. package/site/src/doc/index.md +22 -0
  82. package/site/src/doc/quickstart.md +121 -0
  83. package/site/src/doc/tutorials/index.md +8 -0
  84. package/site/src/download/index.hbs +62 -0
  85. package/site/src/favicon.ico +0 -0
  86. package/site/src/index.hbs +71 -0
  87. package/site/src/theme/index.css +26 -0
  88. package/site/src/theme/site.css +531 -0
  89. package/src/ol/events/ErrorEvent.js +25 -0
  90. package/src/ol/layer/STAC.js +780 -0
  91. package/src/ol/layer/stacUtil.js +169 -0
  92. package/src/ol/source/GeoTIFF2.js +47 -0
  93. package/tasks/.eslintrc +5 -0
  94. package/tasks/build-website.sh +90 -0
  95. package/tasks/create-release.js +107 -0
  96. package/tasks/generate-index.js +125 -0
  97. package/tasks/generate-info.js +189 -0
  98. package/tasks/get-latest-release.js +36 -0
  99. package/tasks/newest-tag.js +52 -0
  100. package/tasks/prepare-package.js +46 -0
  101. package/tasks/publish.sh +90 -0
  102. package/test/README.md +56 -0
  103. package/test/browser/.eslintrc +14 -0
  104. package/test/browser/karma.config.cjs +94 -0
  105. package/test/browser/spec/ol/layer/STAC.test.js +18 -0
  106. package/test/browser/test-extensions.js +419 -0
  107. package/tsconfig.json +62 -0
@@ -0,0 +1,780 @@
1
+ /**
2
+ * @module ol/layer/STAC
3
+ */
4
+ import ErrorEvent from '../events/ErrorEvent.js';
5
+ import GeoJSON from 'ol/format/GeoJSON.js';
6
+ import GeoTIFF from '../source/GeoTIFF2.js';
7
+ import ImageLayer from 'ol/layer/Image.js';
8
+ import Layer from 'ol/layer/Layer.js';
9
+ import LayerGroup from 'ol/layer/Group.js';
10
+ import StaticImage from 'ol/source/ImageStatic.js';
11
+ import TileJSON from 'ol/source/TileJSON.js';
12
+ import TileLayer from 'ol/layer/Tile.js';
13
+ import VectorLayer from 'ol/layer/Vector.js';
14
+ import VectorSource from 'ol/source/Vector.js';
15
+ import WMS from 'ol/source/TileWMS.js';
16
+ import WMTS, {optionsFromCapabilities} from 'ol/source/WMTS.js';
17
+ import WebGLTileLayer from 'ol/layer/WebGLTile.js';
18
+ import XYZ from 'ol/source/XYZ.js';
19
+ import create, {Asset, STAC} from 'stac-js';
20
+ import {
21
+ defaultBoundsStyle,
22
+ defaultCollectionStyle,
23
+ getBoundsStyle,
24
+ getGeoTiffSourceInfoFromAsset,
25
+ getProjection,
26
+ getSpecificWebMapUrl,
27
+ getWmtsCapabilities,
28
+ } from './stacUtil.js';
29
+ import {toGeoJSON} from 'stac-js/src/geo.js';
30
+ import {transformExtent} from 'ol/proj.js';
31
+
32
+ /**
33
+ * @typedef {import("ol/extent.js").Extent} Extent
34
+ */
35
+ /**
36
+ * @typedef {import("../source/GeoTIFF2.js").Options2} GeoTIFFSourceOptions
37
+ */
38
+ /**
39
+ * @typedef {import("ol/source/ImageStatic.js").Options} ImageStaticSourceOptions
40
+ */
41
+ /**
42
+ * @typedef {import("stac-js").Link} Link
43
+ */
44
+ /**
45
+ * @typedef {import("stac-js").STACObject} STACObject
46
+ */
47
+ /**
48
+ * @typedef {import('ol/style.js').Style} Style
49
+ */
50
+ /**
51
+ * @typedef {import("ol/source/XYZ.js").Options} XYZSourceOptions
52
+ */
53
+
54
+ /**
55
+ * @typedef {Object} Options
56
+ * @property {string} [url] The STAC URL. Any of `url` and `data` must be provided.
57
+ * Can also be used as url for data, if it is absolute and doesn't contain a self link.
58
+ * @property {STAC|Asset|Object} [data] The STAC metadata. Any of `url` and `data` must be provided.
59
+ * `data` take precedence over `url`.
60
+ * @property {Array<string|Asset>|null} [assets=null] The selector for the assets to be rendered,
61
+ * only for STAC Items and Collections.
62
+ * This can be an array of strings corresponding to asset keys or Asset objects.
63
+ * null shows the default asset, an empty array shows no asset.
64
+ * @property {Array<number>} [bands] The (one-based) bands to show.
65
+ * @property {function(GeoTIFFSourceOptions, Asset):(GeoTIFFSourceOptions|Promise<GeoTIFFSourceOptions>)} [getGeoTIFFSourceOptions]
66
+ * Optional function that can be used to configure the underlying GeoTIFF sources. The function can do any additional work
67
+ * and return the completed options or a promise for the same. The function will be called with the current source options
68
+ * and the STAC Asset.
69
+ * @property {function(ImageStaticSourceOptions, (Asset|Link)):(ImageStaticSourceOptions|Promise<ImageStaticSourceOptions>)} [getImageStaticSourceOptions]
70
+ * Optional function that can be used to configure the underlying ImageStatic sources. The function can do any additional work
71
+ * and return the completed options or a promise for the same. The function will be called with the current source options
72
+ * and the STAC Asset or Link.
73
+ * @property {function(XYZSourceOptions, (Asset|Link)):(XYZSourceOptions|Promise<XYZSourceOptions>)} [getXYZSourceOptions]
74
+ * Optional function that can be used to configure the underlying XYZ sources that displays imagery. The function can do any
75
+ * additional work and return the completed options or a promise for the same. The function will be called with the current
76
+ * source options and the STAC Asset or Link.
77
+ * @property {boolean} [displayGeoTiffByDefault=false] Allow to choose non-cloud-optimized GeoTiffs as default image to show,
78
+ * which might not work well for larger files or larger amounts of files.
79
+ * @property {boolean} [displayPreview=false] Allow to display images that a browser can display (e.g. PNG, JPEG),
80
+ * usually assets with role `thumbnail` or the link with relation type `preview`.
81
+ * The previews are usually not covering the full extents and as such may be placed incorrectly on the map.
82
+ * For performance reasons, it is recommended to enable this option if you pass in STAC API Items.
83
+ * @property {boolean} [displayOverview=true] Allow to display COGs and, if `displayGeoTiffByDefault` is enabled, GeoTiffs,
84
+ * usually the assets with role `overview` or `visual`.
85
+ * @property {string|boolean} [displayWebMapLink=false] Allow to display a layer based on the information provided through the
86
+ * web map links extension. It is only used if no other data is shown. You can set a specific type of
87
+ * web map link (`tilejson`, `wms`, `wmts`, `xyz`), let OpenLayers choose (`true`) or disable the functionality (`false`).
88
+ * @property {Style} [boundsStyle] The style for the overall bounds / footprint.
89
+ * @property {Style} [collectionStyle] The style for individual items in a list of STAC Items or Collections.
90
+ * @property {null|string} [crossOrigin] For thumbnails: The `crossOrigin` attribute for loaded images / tiles.
91
+ * See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
92
+ * @property {function(Asset):string|null} [buildTileUrlTemplate=null] A function that generates a URL template for a tile server (XYZ),
93
+ * which will be used instead of the client-side GeoTIFF rendering (except if `useTileLayerAsFallback` is `true`).
94
+ * @property {boolean} [useTileLayerAsFallback=false] Uses the given URL template only when the client-side GeoTIFF rendering fails.
95
+ * @property {number} [opacity=1] Opacity (0, 1).
96
+ * @property {boolean} [visible=true] Visibility.
97
+ * @property {Extent} [extent] The bounding extent for layer rendering. The layer will not be
98
+ * rendered outside of this extent.
99
+ * @property {number} [zIndex] The z-index for layer rendering. At rendering time, the layers
100
+ * will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed
101
+ * for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()`
102
+ * method was used.
103
+ * @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be
104
+ * visible.
105
+ * @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
106
+ * be visible.
107
+ * @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
108
+ * visible.
109
+ * @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
110
+ * be visible.
111
+ */
112
+
113
+ /**
114
+ * @classdesc
115
+ * Renders STAC entities such as STAC Items, Collectons or lists of them as returned by APIs.
116
+ * The layers created by this LayerGroup all have a 'stac' value that can be retrieved using `layer.get('stac')`.
117
+ *
118
+ * @extends LayerGroup
119
+ * @fires sourceready
120
+ * @fires assetsready
121
+ * @fires ErorEvent#event:error
122
+ * @api
123
+ */
124
+ class STACLayer extends LayerGroup {
125
+ /**
126
+ * @param {Options} options Layer options.
127
+ */
128
+ constructor(options) {
129
+ const superOptions = {};
130
+ [
131
+ 'opacity',
132
+ 'visible',
133
+ 'zIndex',
134
+ 'minResolution',
135
+ 'maxResolution',
136
+ 'minZoom',
137
+ 'maxZoom',
138
+ ].forEach((key) => (superOptions[key] = options[key]));
139
+
140
+ super(superOptions);
141
+
142
+ /**
143
+ * @type {function(GeoTIFFSourceOptions, Asset):(GeoTIFFSourceOptions|Promise<GeoTIFFSourceOptions>)}
144
+ * @private
145
+ */
146
+ this.getGeoTIFFSourceOptions_ = options.getGeoTIFFSourceOptions;
147
+
148
+ /**
149
+ * @type {function(ImageStaticSourceOptions, (Asset|Link)):(ImageStaticSourceOptions|Promise<ImageStaticSourceOptions>)}
150
+ * @private
151
+ */
152
+ this.getImageStaticSourceOptions_ = options.getImageStaticSourceOptions;
153
+
154
+ /**
155
+ * @type {function(XYZSourceOptions, (Asset|Link)):(XYZSourceOptions|Promise<XYZSourceOptions>)}
156
+ * @private
157
+ */
158
+ this.getXYZSourceOptions_ = options.getXYZSourceOptions;
159
+
160
+ /**
161
+ * @type {STAC|Asset}
162
+ * @private
163
+ */
164
+ this.data_;
165
+
166
+ /**
167
+ * @type {Array<Asset> | null}
168
+ * @private
169
+ */
170
+ this.assets_ = null;
171
+
172
+ /**
173
+ * @type {Array<number>}
174
+ * @private
175
+ */
176
+ this.bands_ = [];
177
+
178
+ /**
179
+ * @type {string | null}
180
+ * @private
181
+ */
182
+ this.crossOrigin_ = options.crossOrigin || null;
183
+
184
+ /**
185
+ * @type {boolean}
186
+ * @private
187
+ */
188
+ this.displayGeoTiffByDefault_ = Boolean(options.displayGeoTiffByDefault);
189
+
190
+ /**
191
+ * @type {boolean}
192
+ * @private
193
+ */
194
+ this.displayPreview_ = Boolean(options.displayPreview);
195
+
196
+ /**
197
+ * @type {boolean}
198
+ * @private
199
+ */
200
+ this.displayOverview_ = options.displayOverview === false ? false : true;
201
+
202
+ /**
203
+ * @type {string|boolean}
204
+ */
205
+ this.displayWebMapLink_ = options.displayWebMapLink || false;
206
+
207
+ /**
208
+ * @type {function(Asset):string|null}
209
+ * @private
210
+ */
211
+ this.buildTileUrlTemplate_ = options.buildTileUrlTemplate || null;
212
+
213
+ /**
214
+ * @type {boolean}
215
+ * @private
216
+ */
217
+ this.useTileLayerAsFallback_ = options.useTileLayerAsFallback || false;
218
+
219
+ /**
220
+ * @type {Style}
221
+ * @private
222
+ */
223
+ this.boundsStyle_ = options.boundsStyle || defaultBoundsStyle;
224
+
225
+ /**
226
+ * @type {Style}
227
+ * @private
228
+ */
229
+ this.collectionStyle_ = options.collectionStyle || defaultCollectionStyle;
230
+
231
+ /**
232
+ * @type {VectorLayer|null}
233
+ * @private
234
+ */
235
+ this.boundsLayer_ = null;
236
+
237
+ if (options.data) {
238
+ try {
239
+ this.configure_(
240
+ options.data,
241
+ options.url,
242
+ options.assets,
243
+ options.bands
244
+ );
245
+ } catch (error) {
246
+ this.handleError_(error);
247
+ }
248
+ return;
249
+ }
250
+
251
+ if (!options.url) {
252
+ throw new Error('Either url or data must be provided');
253
+ }
254
+
255
+ fetch(options.url)
256
+ .then((response) => response.json())
257
+ .then((data) =>
258
+ this.configure_(data, options.url, options.assets, options.bands)
259
+ )
260
+ .catch((error) => this.handleError_(error));
261
+ }
262
+
263
+ /**
264
+ * Returns the vector layer that visualizes the bounds / footprint.
265
+ * @return {VectorLayer|null} The vector layer for the bounds
266
+ */
267
+ getBoundsLayer() {
268
+ return this.boundsLayer_;
269
+ }
270
+
271
+ /**
272
+ * @private
273
+ * @param {Error} error The error.
274
+ */
275
+ handleError_(error) {
276
+ /**
277
+ * Error event.
278
+ *
279
+ * @event ErorEvent#event:error
280
+ * @type {Object}
281
+ * @property {Error} error - Provides the original error.
282
+ */
283
+ this.dispatchEvent(new ErrorEvent(error));
284
+ }
285
+
286
+ /**
287
+ * @private
288
+ * @param {STAC|Asset|Object} data The STAC data.
289
+ * @param {string} url The url to the data.
290
+ * @param {Array<Asset|string> | null} assets The assets to show.
291
+ * @param {Array<number>} bands The (one-based) bands to show.
292
+ */
293
+ configure_(data, url = null, assets = null, bands = []) {
294
+ if (data instanceof Asset || data instanceof STAC) {
295
+ this.data_ = data;
296
+ } else {
297
+ this.data_ = create(data);
298
+ }
299
+ if (url && url.includes('://')) {
300
+ this.data_.setAbsoluteUrl(url);
301
+ }
302
+ this.bands_ = bands;
303
+
304
+ this.boundsLayer_ = this.addFootprint_();
305
+ const updateBoundsStyle = () => {
306
+ if (this.boundsLayer_) {
307
+ this.boundsLayer_.setStyle(getBoundsStyle(this.boundsStyle_, this));
308
+ }
309
+ };
310
+ this.getLayers().on('add', updateBoundsStyle);
311
+ this.getLayers().on('remove', updateBoundsStyle);
312
+
313
+ this.setAssets(assets)
314
+ .then(() => {
315
+ /**
316
+ * Invoked once all assets are loaded and shown on the map.
317
+ *
318
+ * @event assetsready
319
+ */
320
+ return this.dispatchEvent('assetsready');
321
+ })
322
+ .catch((error) => this.handleError_(error));
323
+
324
+ /**
325
+ * Invoked once the source is ready.
326
+ * If you provide the data inline, the event is likely fired before you can
327
+ * attach a listener to it. So this only really helps if a url is provided.
328
+ *
329
+ * @event sourceready
330
+ */
331
+ this.dispatchEvent('sourceready');
332
+ }
333
+
334
+ /**
335
+ * @private
336
+ * @return {Promise} Resolves when complete.
337
+ */
338
+ async addApiCollection_() {
339
+ const promises = this.getData()
340
+ .getAll()
341
+ .map((obj) => {
342
+ const subgroup = new STACLayer({
343
+ data: obj,
344
+ crossOrigin: this.crossOrigin_,
345
+ boundsStyle: this.collectionStyle_,
346
+ displayGeoTiffByDefault: this.displayGeoTiffByDefault_,
347
+ displayOverview: this.displayOverview_,
348
+ displayPreview: this.displayPreview_,
349
+ });
350
+ this.addLayer_(subgroup);
351
+ return subgroup;
352
+ });
353
+ return await Promise.all(promises);
354
+ }
355
+
356
+ /**
357
+ * @private
358
+ * @return {Promise} Resolves when complete.
359
+ */
360
+ async addStacAssets_() {
361
+ let assets = this.getAssets();
362
+ if (assets === null) {
363
+ assets = [];
364
+ // No specific asset given by the user, visualize the default geotiff
365
+ const geotiff = this.getData().getDefaultGeoTIFF(
366
+ true,
367
+ !this.displayGeoTiffByDefault_
368
+ );
369
+ if (geotiff) {
370
+ assets.push(geotiff);
371
+ } else {
372
+ // This may return Links or Assets
373
+ const thumbnails = this.getData().getThumbnails();
374
+ if (thumbnails.length > 0) {
375
+ assets.push(thumbnails[0]);
376
+ }
377
+ }
378
+ }
379
+
380
+ const promises = assets.map((asset) => this.addImagery_(asset));
381
+ return await Promise.all(promises);
382
+ }
383
+
384
+ /**
385
+ * @private
386
+ * @param {Asset|Link} [ref] A STAC Link or Asset
387
+ * @return {Promise<Layer|undefined>} Resolves with a Layer or undefined when complete.
388
+ */
389
+ async addImagery_(ref) {
390
+ if (!ref) {
391
+ return;
392
+ }
393
+ if (ref.isGeoTIFF()) {
394
+ return await this.addGeoTiff_(ref);
395
+ }
396
+ if (ref.canBrowserDisplayImage()) {
397
+ return await this.addThumbnail_(ref);
398
+ }
399
+ }
400
+
401
+ /**
402
+ * @private
403
+ * @param {Asset|Link} [thumbnail] A STAC Link or Asset
404
+ * @return {Promise<ImageLayer|undefined>} Resolves with am ImageLayer or udnefined when complete.
405
+ */
406
+ async addThumbnail_(thumbnail) {
407
+ if (!this.displayPreview_) {
408
+ return;
409
+ }
410
+ /**
411
+ * @type {ImageStaticSourceOptions}
412
+ */
413
+ let options = {
414
+ url: thumbnail.getAbsoluteUrl(),
415
+ projection: await getProjection(thumbnail, 'EPSG:4326'),
416
+ imageExtent: thumbnail.getContext().getBoundingBox(),
417
+ crossOrigin: this.crossOrigin_,
418
+ };
419
+ if (this.getImageStaticSourceOptions_) {
420
+ options = await this.getImageStaticSourceOptions_(options, thumbnail);
421
+ }
422
+ const layer = new ImageLayer({
423
+ source: new StaticImage(options),
424
+ });
425
+ this.addLayer_(layer, thumbnail);
426
+ return layer;
427
+ }
428
+
429
+ /**
430
+ * Adds a layer for the web map links available in the STAC links.
431
+ * @return {Promise<Array<TileLayer>|undefined>} Resolves with a Layer or undefined when complete.
432
+ */
433
+ async addWebMapLinks_() {
434
+ const links = this.getWebMapLinks();
435
+ if (links.length > 0) {
436
+ return await this.addLayerForLink(links[0]);
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Adds a layer for a link that implements the web-map-links extension.
442
+ * Supports: TileJSON, WMS, WMTS, XYZ
443
+ * @see https://github.com/stac-extensions/web-map-links
444
+ * @param {Link} link A web map link
445
+ * @return {Promise<Array<TileLayer>|undefined>} Resolves with a list of layers or undefined when complete.
446
+ */
447
+ async addLayerForLink(link) {
448
+ // Replace any occurances of {s} if possible, otherwise return
449
+ const url = getSpecificWebMapUrl(link);
450
+ if (!url) {
451
+ return;
452
+ }
453
+
454
+ const options = {
455
+ attributions:
456
+ link.getMetadata('attribution') ||
457
+ this.data_.getMetadata('attribution'),
458
+ crossOrigin: this.crossOrigin_,
459
+ url,
460
+ };
461
+
462
+ const sources = [];
463
+ switch (link.rel) {
464
+ case 'tilejson':
465
+ sources.push(new TileJSON(options));
466
+ break;
467
+ case 'wms':
468
+ if (!Array.isArray(link['wms:layers'])) {
469
+ return;
470
+ }
471
+ const styles = link['wms:styles'] || '';
472
+ for (const layer of link['wms:layers']) {
473
+ const params = Object.assign(
474
+ {
475
+ LAYERS: layer,
476
+ STYLES: styles,
477
+ },
478
+ link['wms:dimensions']
479
+ );
480
+ const wmsOptions = Object.assign({}, options, {params});
481
+ sources.push(new WMS(wmsOptions));
482
+ }
483
+ break;
484
+ case 'wmts':
485
+ const wmtsCapabilities = await getWmtsCapabilities(url);
486
+ if (!wmtsCapabilities) {
487
+ return;
488
+ }
489
+ const layers = Array.isArray(link['wmts:layer'])
490
+ ? link['wmts:layer']
491
+ : [link['wmts:layer']];
492
+ for (const layer of layers) {
493
+ const wmtsOptions = Object.assign({}, options, {layer});
494
+ sources.push(
495
+ new WMTS(optionsFromCapabilities(wmtsCapabilities, wmtsOptions))
496
+ );
497
+ }
498
+ break;
499
+ case 'xyz':
500
+ sources.push(new XYZ(options));
501
+ break;
502
+ default:
503
+ return;
504
+ }
505
+
506
+ return sources.map((source) => {
507
+ const layer = new TileLayer({
508
+ source,
509
+ });
510
+ this.addLayer_(layer, link);
511
+ return layer;
512
+ });
513
+ }
514
+
515
+ /**
516
+ * @private
517
+ * @param {Asset} [asset] A STAC Asset
518
+ * @return {Promise<Layer|undefined>} Resolves with a Layer or undefined when complete.
519
+ */
520
+ async addGeoTiff_(asset) {
521
+ if (!this.displayOverview_) {
522
+ return;
523
+ }
524
+
525
+ if (this.buildTileUrlTemplate_ && !this.useTileLayerAsFallback_) {
526
+ return await this.addTileLayerForImagery_(asset);
527
+ }
528
+
529
+ const sourceInfo = getGeoTiffSourceInfoFromAsset(asset, this.bands_);
530
+
531
+ /**
532
+ * @type {GeoTIFFSourceOptions}
533
+ */
534
+ let options = {
535
+ sources: [sourceInfo],
536
+ };
537
+
538
+ const projection = await getProjection(asset);
539
+ if (projection) {
540
+ options.projection = projection;
541
+ }
542
+
543
+ if (this.getGeoTIFFSourceOptions_) {
544
+ options = await this.getGeoTIFFSourceOptions_(options, asset);
545
+ }
546
+
547
+ const tileserverFallback = async (asset, layer) => {
548
+ if (layer) {
549
+ this.getLayers().remove(layer);
550
+ }
551
+ return await this.addTileLayerForImagery_(asset);
552
+ };
553
+ try {
554
+ const source = new GeoTIFF(options);
555
+ const layer = new WebGLTileLayer({source});
556
+ if (this.useTileLayerAsFallback_) {
557
+ const errorFn = () => tileserverFallback(asset, layer);
558
+ source.on('error', errorFn);
559
+ source.on('tileloaderror', errorFn);
560
+ // see https://github.com/openlayers/openlayers/issues/14926
561
+ source.on('change', () => {
562
+ if (source.getState() === 'error') {
563
+ tileserverFallback(asset, layer);
564
+ }
565
+ });
566
+ layer.on('error', errorFn);
567
+ // Call this to ensure we can load the GeoTIFF, otherwise try fallback
568
+ await source.getView();
569
+ }
570
+ this.addLayer_(layer, asset);
571
+ return layer;
572
+ } catch (error) {
573
+ if (this.useTileLayerAsFallback_) {
574
+ return await tileserverFallback(asset, null);
575
+ }
576
+ this.handleError_(error);
577
+ }
578
+ }
579
+
580
+ /**
581
+ * @private
582
+ * @param {Asset|Link} [data] A STAC Asset or Link
583
+ * @return {Promise<TileLayer>} Resolves with a TileLayer when complete.
584
+ */
585
+ async addTileLayerForImagery_(data) {
586
+ /**
587
+ * @type {XYZSourceOptions}
588
+ */
589
+ let options = {
590
+ crossOrigin: this.crossOrigin_,
591
+ url: this.buildTileUrlTemplate_(data),
592
+ };
593
+ if (this.getXYZSourceOptions_) {
594
+ options = await this.getXYZSourceOptions_(options, data);
595
+ }
596
+ const layer = new TileLayer({
597
+ source: new XYZ(options),
598
+ });
599
+ this.addLayer_(layer, data);
600
+ return layer;
601
+ }
602
+
603
+ /**
604
+ * @param {Layer|LayerGroup} [layer] A Layer to add to the LayerGroup
605
+ * @param {STACObject} [data] The STAC object, can be any class exposed by stac-js
606
+ * @param {number} [zIndex=0] The z-index for the layer
607
+ * @private
608
+ */
609
+ addLayer_(layer, data, zIndex = 0) {
610
+ layer.set('stac', data);
611
+ layer.setZIndex(zIndex);
612
+ this.getLayers().push(layer);
613
+ }
614
+
615
+ /**
616
+ * @private
617
+ * @return {VectorLayer|null} The vector layer showing the geometry/bbox.
618
+ */
619
+ addFootprint_() {
620
+ let geojson = null;
621
+ const data = this.getData();
622
+ if (data.isItemCollection() || data.isCollectionCollection()) {
623
+ geojson = toGeoJSON(data.getBoundingBox());
624
+ } else {
625
+ geojson = data.toGeoJSON();
626
+ }
627
+
628
+ if (geojson) {
629
+ const format = new GeoJSON();
630
+ const source = new VectorSource({
631
+ format,
632
+ loader: (extent, resolution, projection) => {
633
+ const features = format.readFeatures(geojson, {
634
+ featureProjection: projection,
635
+ });
636
+ source.addFeatures(features);
637
+ },
638
+ });
639
+ const vectorLayer = new VectorLayer({
640
+ source,
641
+ style: getBoundsStyle(this.boundsStyle_, this),
642
+ });
643
+ vectorLayer.set('bounds', true);
644
+ this.addLayer_(vectorLayer, data, 1);
645
+ return vectorLayer;
646
+ }
647
+
648
+ return null;
649
+ }
650
+
651
+ /**
652
+ * @private
653
+ */
654
+ async updateLayers_() {
655
+ // Remove old layers
656
+ const oldLayers = this.getLayers();
657
+ for (let i = oldLayers.getLength() - 1; i >= 0; i--) {
658
+ const layer = oldLayers.item(i);
659
+ const stac = layer.get('stac');
660
+ if (stac && (stac.isLink() || stac.isAsset())) {
661
+ oldLayers.removeAt(i);
662
+ }
663
+ }
664
+
665
+ // Add new layers
666
+ const data = this.getData();
667
+ if (data.isItemCollection() || data.isCollectionCollection()) {
668
+ await this.addApiCollection_();
669
+ } else if (data.isItem() || data.isCollection()) {
670
+ await this.addStacAssets_();
671
+ }
672
+ if (this.displayWebMapLink_ && this.hasOnlyBounds()) {
673
+ await this.addWebMapLinks_();
674
+ }
675
+ }
676
+
677
+ /**
678
+ * Indicates whether the LayerGroup shows only the bounds layer (i.e. no imagery/tile layers).
679
+ * @return {boolean} `true` if only the bounds layer is shown, `false` otherwise.
680
+ */
681
+ hasOnlyBounds() {
682
+ const boundsLayer = this.getBoundsLayer();
683
+ const imgLayer = this.getLayersArray().find(
684
+ (layer) => layer !== boundsLayer
685
+ );
686
+ return typeof imgLayer === 'undefined';
687
+ }
688
+
689
+ /**
690
+ * Returns all potential web map links based on the given value for `displayWebMapLink`.
691
+ * @return {Array<Link>} An array of links.
692
+ */
693
+ getWebMapLinks() {
694
+ let types = ['xyz', 'tilejson', 'wmts', 'wms']; // This also defines the priority
695
+ if (typeof this.displayWebMapLink_ === 'string') {
696
+ types = [this.displayWebMapLink_];
697
+ }
698
+ const links = this.data_.getLinksWithRels(types);
699
+ links.sort((a, b) => {
700
+ const prioA = types.indexOf(a.rel);
701
+ const prioB = types.indexOf(b.rel);
702
+ return prioA - prioB;
703
+ });
704
+ return links;
705
+ }
706
+
707
+ /**
708
+ * Update the assets to be rendered.
709
+ * @param {Array<string|Asset>|null} assets The assets to show.
710
+ * @return {Promise} Resolves when all assets are rendered.
711
+ */
712
+ async setAssets(assets) {
713
+ if (Array.isArray(assets)) {
714
+ this.assets_ = assets.map((asset) => {
715
+ if (typeof asset === 'string') {
716
+ return this.getData().getAsset(asset);
717
+ }
718
+ if (!(asset instanceof Asset)) {
719
+ return new Asset(asset);
720
+ }
721
+ return asset;
722
+ });
723
+ } else {
724
+ this.assets_ = null;
725
+ }
726
+ await this.updateLayers_();
727
+ }
728
+
729
+ /**
730
+ * Get the STAC object.
731
+ *
732
+ * @return {STAC|Asset} The STAC object.
733
+ */
734
+ getData() {
735
+ return this.data_;
736
+ }
737
+
738
+ /**
739
+ * Get the STAC assets shown.
740
+ *
741
+ * @return {Array<Asset>} The STAC assets.
742
+ */
743
+ getAssets() {
744
+ return this.assets_;
745
+ }
746
+
747
+ /**
748
+ * Get the extent of the layer.
749
+ *
750
+ * @return {Extent|undefined} The layer extent.
751
+ */
752
+ getExtent() {
753
+ const data = this.getData();
754
+ if (!data) {
755
+ return;
756
+ }
757
+
758
+ const layer = this.getLayers().item(0);
759
+ if (!layer || !(layer instanceof Layer)) {
760
+ return;
761
+ }
762
+
763
+ const map = layer.getMapInternal();
764
+ if (!map) {
765
+ return;
766
+ }
767
+
768
+ const view = map.getView();
769
+ if (!view) {
770
+ return;
771
+ }
772
+
773
+ const bbox = data.getBoundingBox();
774
+ if (bbox) {
775
+ return transformExtent(bbox, 'EPSG:4326', view.getProjection());
776
+ }
777
+ }
778
+ }
779
+
780
+ export default STACLayer;