ol-stac 1.5.1 → 1.7.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.
- package/README.md +2 -1
- package/http.d.ts +35 -0
- package/http.d.ts.map +1 -0
- package/http.js +101 -0
- package/http.js.map +1 -0
- package/layer/STAC.d.ts +245 -23
- package/layer/STAC.d.ts.map +1 -1
- package/layer/STAC.js +428 -104
- package/layer/STAC.js.map +1 -1
- package/layer/type.d.ts +88 -0
- package/layer/type.d.ts.map +1 -0
- package/layer/type.js +83 -0
- package/layer/type.js.map +1 -0
- package/package.json +3 -2
- package/proj.d.ts +2 -2
- package/proj.d.ts.map +1 -1
- package/proj.js +51 -5
- package/proj.js.map +1 -1
- package/shim/ogcTileUtil.d.ts +11 -0
- package/shim/ogcTileUtil.d.ts.map +1 -0
- package/shim/ogcTileUtil.js +20 -0
- package/shim/ogcTileUtil.js.map +1 -0
- package/shim/proj4.d.ts +9 -0
- package/shim/proj4.d.ts.map +1 -0
- package/shim/proj4.js +28 -0
- package/shim/proj4.js.map +1 -0
- package/source/GeoZarr.d.ts +578 -0
- package/source/GeoZarr.d.ts.map +1 -0
- package/source/GeoZarr.js +1930 -0
- package/source/GeoZarr.js.map +1 -0
- package/source/type.d.ts +20 -3
- package/source/type.d.ts.map +1 -1
- package/source/type.js +19 -2
- package/source/type.js.map +1 -1
- package/util.d.ts +149 -9
- package/util.d.ts.map +1 -1
- package/util.js +852 -44
- package/util.js.map +1 -1
package/layer/STAC.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module ol/layer/STAC
|
|
3
3
|
*/
|
|
4
|
+
import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
|
|
4
5
|
import { isEmpty } from 'ol/extent.js';
|
|
5
6
|
import GeoJSON from 'ol/format/GeoJSON.js';
|
|
6
7
|
import WMTSCapabilities from 'ol/format/WMTSCapabilities.js';
|
|
@@ -18,16 +19,17 @@ import VectorSource from 'ol/source/Vector.js';
|
|
|
18
19
|
import VectorTileSource from 'ol/source/VectorTile.js';
|
|
19
20
|
import WMTS, { optionsFromCapabilities } from 'ol/source/WMTS.js';
|
|
20
21
|
import XYZ from 'ol/source/XYZ.js';
|
|
21
|
-
import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
|
|
22
22
|
import * as pmtiles from 'pmtiles';
|
|
23
23
|
import create, { Asset } from 'stac-js';
|
|
24
24
|
import { fixGeoJson, toGeoJSON, unionBoundingBox } from 'stac-js/src/geo.js';
|
|
25
|
-
import { geojsonMediaType, geotiffMediaTypes, wozMediaTypes, } from 'stac-js/src/mediatypes.js';
|
|
25
|
+
import { geojsonMediaType, geotiffMediaTypes, wozMediaTypes, zarrMediaTypes, } from 'stac-js/src/mediatypes.js';
|
|
26
26
|
import { isObject } from 'stac-js/src/utils.js';
|
|
27
27
|
import ErrorEvent from '../events/ErrorEvent.js';
|
|
28
|
+
import { createImageLoadFunction, createTileLoadFunction } from '../http.js';
|
|
28
29
|
import { getProjection } from '../proj.js';
|
|
29
30
|
import SourceType from '../source/type.js';
|
|
30
|
-
import { LABEL_EXTENSION, defaultBoundsStyle, defaultCollectionStyle, getBoundsStyle,
|
|
31
|
+
import { LABEL_EXTENSION, defaultBoundsStyle, defaultCollectionStyle, exceedsDisplayLimit, getBoundsStyle, getDisplayPixels, getGeoTiffSourceInfoFromAsset, getGeoTiffStyleFromAsset, getGeoZarrSourceOptionsFromAsset, getGeoZarrStyleFromAsset, getSpecificWebMapUrl, getWebMapLinks, isScalar, toContinuousBBox, toOlExtent, } from '../util.js';
|
|
32
|
+
import LayerType from './type.js';
|
|
31
33
|
/**
|
|
32
34
|
* @typedef {import("ol/extent.js").Extent} Extent
|
|
33
35
|
*/
|
|
@@ -55,6 +57,18 @@ import { LABEL_EXTENSION, defaultBoundsStyle, defaultCollectionStyle, getBoundsS
|
|
|
55
57
|
/**
|
|
56
58
|
* @typedef {import('../source/type.js').SourceOptions} SourceOptions
|
|
57
59
|
*/
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {import('./type.js').LayerOptions} LayerOptions
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {import('../http.js').GetHeadersFn} GetHeadersFn
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {import('../http.js').OnErrorFn} OnErrorFn
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* @typedef {function((import("ol/Image.js").default|import("ol/Tile.js").default), string): void} LoadFunction
|
|
71
|
+
*/
|
|
58
72
|
/**
|
|
59
73
|
* @typedef {Object} Options
|
|
60
74
|
* @property {string} [url] The STAC URL. Any of `url` and `data` must be provided.
|
|
@@ -75,8 +89,15 @@ import { LABEL_EXTENSION, defaultBoundsStyle, defaultCollectionStyle, getBoundsS
|
|
|
75
89
|
* Optional function that can be used to configure the underlying sources. The function can do any additional work
|
|
76
90
|
* and return the completed options or a promise for the same. The function will be called with the current source options
|
|
77
91
|
* and the STAC Asset or Link.
|
|
78
|
-
* This can be useful for
|
|
79
|
-
*
|
|
92
|
+
* This can be useful for advanced per-source customization such as signed URLs.
|
|
93
|
+
* To add credentials via query parameters, use the `getRequestUrl` option instead;
|
|
94
|
+
* to send credentials via HTTP headers, use the `getRequestHeaders` option instead.
|
|
95
|
+
* @property {function(LayerType, LayerOptions, (Asset|Link)):(LayerOptions|Promise<LayerOptions>)} [getLayerOptions]
|
|
96
|
+
* Optional function that can be used to configure the individual layers that are created for the assets and links.
|
|
97
|
+
* The function can do any additional (asynchronous) work and return the completed options or a promise for the same.
|
|
98
|
+
* The function will be called with the layer type, the current layer options and the STAC Asset or Link.
|
|
99
|
+
* This can be useful to customize the layers, e.g. to apply a style to a GeoTIFF or GeoZarr layer that is
|
|
100
|
+
* loaded from the STAC metadata.
|
|
80
101
|
* @property {boolean} [displayFootprint=true] Allows to hide the footprints (bounding box/geometry) of the STAC object
|
|
81
102
|
* by default.
|
|
82
103
|
* @property {boolean} [displayGeoTiffByDefault=false] Allow to choose non-cloud-optimized GeoTiffs as default image to show,
|
|
@@ -85,8 +106,16 @@ import { LABEL_EXTENSION, defaultBoundsStyle, defaultCollectionStyle, getBoundsS
|
|
|
85
106
|
* i.e. assets with any of the roles `thumbnail`, `overview`, or a link with relation type `preview`.
|
|
86
107
|
* The previews are usually not covering the full extents and as such may be placed incorrectly on the map.
|
|
87
108
|
* For performance reasons, it is recommended to enable this option if you pass in STAC API Items instead of `displayOverview`.
|
|
88
|
-
* @property {boolean} [displayOverview=true] Allow to display COGs
|
|
109
|
+
* @property {boolean} [displayOverview=true] Allow to display COGs, Zarr and, if `displayGeoTiffByDefault` is enabled, GeoTiffs,
|
|
89
110
|
* usually an asset with role `overview` or `visual`.
|
|
111
|
+
* Zarr assets other than Web-Optimized Zarr are only displayed if the STAC metadata declares what to render
|
|
112
|
+
* (see the datacube extension) and the store is within the `maxDisplayPixels` limit.
|
|
113
|
+
* @property {number} [maxDisplayPixels=16777216] The maximum number of pixels the coarsest resolution level
|
|
114
|
+
* of a GeoTIFF or Zarr asset may have to be displayed client-side, as displaying the full extent of an asset
|
|
115
|
+
* loads every tile of that level. Files without (sufficient) overviews can easily exceed this limit.
|
|
116
|
+
* Larger assets are not chosen for the default visualization, and selecting one explicitly through `assets`
|
|
117
|
+
* reports an error through the `error` event (or renders through the tile server if `buildTileUrlTemplate`
|
|
118
|
+
* and `useTileLayerAsFallback` are set). Set to `Infinity` to display assets of any size.
|
|
90
119
|
* @property {string|boolean|Array<Link|string>} [displayWebMapLink=false] Allow to display a layer
|
|
91
120
|
* based on the information provided through the web map links extension.
|
|
92
121
|
* If an array of links or link ids (property `id` in a Link Object) is provided, all corresponding layers will be shown.
|
|
@@ -94,13 +123,19 @@ import { LABEL_EXTENSION, defaultBoundsStyle, defaultCollectionStyle, getBoundsS
|
|
|
94
123
|
* it lets this library choose a web map link to show, but only if no other data is shown.
|
|
95
124
|
* To disable the functionality set this to `false`.
|
|
96
125
|
* @property {import("ol/layer/WebGLTile.js").Style|null} [style=null] The style for GeoTIFF and GeoZarr layers (WebGLTileLayer style).
|
|
126
|
+
* @property {Array<import("ol/color.js").Color|string>|null} [defaultColormap=null] The colors of the colormap
|
|
127
|
+
* that is used for continuous single-band data when neither the STAC metadata nor the `style` option define
|
|
128
|
+
* a coloring. The colors are evenly distributed over the value range of the data (e.g. from the STAC
|
|
129
|
+
* `statistics`). If not set, the data is stretched to grayscale.
|
|
97
130
|
* @property {Style} [boundsStyle] The style for the overall bounds / footprint.
|
|
98
131
|
* @property {Style} [collectionStyle] The style for individual children in a list of STAC Items or Collections.
|
|
99
132
|
* @property {null|string} [crossOrigin] For thumbnails: The `crossOrigin` attribute for loaded images / tiles.
|
|
100
133
|
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
|
|
101
|
-
* @property {function((Asset|Link)):Promise<string>|string|null} [buildTileUrlTemplate=null] A function that generates a URL template for a tile server (XYZ),
|
|
134
|
+
* @property {function((Asset|Link)):Promise<string|null>|string|null} [buildTileUrlTemplate=null] A function that generates a URL template for a tile server (XYZ),
|
|
102
135
|
* which will be used instead of the client-side GeoTIFF rendering (except if `useTileLayerAsFallback` is `true`).
|
|
103
136
|
* The function provided can return a promise (i.e. be async) or a string.
|
|
137
|
+
* The function can return `null` to not pass the given asset or link to the tile server,
|
|
138
|
+
* e.g. to filter by media type or protocol. In this case client-side rendering is used instead.
|
|
104
139
|
* @property {boolean} [useTileLayerAsFallback=false] Uses the given URL template only when the client-side GeoTIFF rendering fails.
|
|
105
140
|
* @property {number} [opacity=1] Opacity (0, 1).
|
|
106
141
|
* @property {boolean} [visible=true] Visibility.
|
|
@@ -124,6 +159,29 @@ import { LABEL_EXTENSION, defaultBoundsStyle, defaultCollectionStyle, getBoundsS
|
|
|
124
159
|
* @property {function(string,string):(*)} [httpRequestFn=null] Sets a custom function to make HTTP requests with.
|
|
125
160
|
* The first parameter is the URL to request and the output is a promise that resolves with the response body.
|
|
126
161
|
* The second parameter is the return type, either `json` (default) or `text`.
|
|
162
|
+
* The STAC Asset or Link the request is made for is passed as third parameter, if available.
|
|
163
|
+
* @property {Object<string, string>|function((Asset|Link|STACObject|null), string):(Object<string, string>|null)} [getRequestHeaders=null]
|
|
164
|
+
* The HTTP headers (e.g. for authentication) to send with the requests made by this layer,
|
|
165
|
+
* either as a plain object or as a function that returns the headers (or `null` for none) and
|
|
166
|
+
* is called with the STAC Asset or Link that is shown (if available) and the URL that is requested.
|
|
167
|
+
* Use a function to restrict the headers to specific hosts, as tile server URLs and asset URLs
|
|
168
|
+
* may point to hosts that should not receive the credentials.
|
|
169
|
+
* The headers are attached to requests made by the default `httpRequestFn`, to GeoTIFF, GeoZarr
|
|
170
|
+
* and PMTiles requests, and via image/tile load functions (through the Fetch API and object URLs)
|
|
171
|
+
* to preview images and XYZ, TileJSON, WMS and WMTS tiles.
|
|
172
|
+
* @property {function((Asset|Link|STACObject|null), string, boolean):(string|null)} [getRequestUrl=null]
|
|
173
|
+
* Rewrites a URL before a request is made or a source is created, e.g. to append query
|
|
174
|
+
* parameters for authentication (signed URLs, API keys). The function is called with the
|
|
175
|
+
* STAC Asset or Link that is shown (if available), the URL, and whether the URL is a
|
|
176
|
+
* tile URL template. It returns the new URL or `null` to keep the URL unchanged.
|
|
177
|
+
* The rewrite is applied to the initial source URL before `getSourceOptions` is called;
|
|
178
|
+
* templates discovered in fetched documents (TileJSON manifests, WMTS capabilities) are
|
|
179
|
+
* rewritten afterwards and are not passed to `getSourceOptions`.
|
|
180
|
+
* For tiled sources the tile URL template is rewritten, not the individual tile URLs.
|
|
181
|
+
* URL templates originate from XYZ web map links, TileJSON manifests, WMTS links
|
|
182
|
+
* (`uriTemplate`) and capabilities, and `buildTileUrlTemplate`. The returned URL must keep
|
|
183
|
+
* the template placeholders such as `{z}` unchanged, i.e. they must not be percent-encoded
|
|
184
|
+
* (e.g. through URL normalization).
|
|
127
185
|
*/
|
|
128
186
|
/**
|
|
129
187
|
* @classdesc
|
|
@@ -159,6 +217,21 @@ class STACLayer extends LayerGroup {
|
|
|
159
217
|
* @private
|
|
160
218
|
*/
|
|
161
219
|
this.getSourceOptions_ = options.getSourceOptions;
|
|
220
|
+
/**
|
|
221
|
+
* @type {function(LayerType, LayerOptions, (Asset|Link)):(LayerOptions|Promise<LayerOptions>)}
|
|
222
|
+
* @private
|
|
223
|
+
*/
|
|
224
|
+
this.getLayerOptions_ = options.getLayerOptions;
|
|
225
|
+
/**
|
|
226
|
+
* @type {Object<string, string>|function((Asset|Link|STACObject|null), string):(Object<string, string>|null)|null}
|
|
227
|
+
* @private
|
|
228
|
+
*/
|
|
229
|
+
this.getRequestHeaders_ = options.getRequestHeaders || null;
|
|
230
|
+
/**
|
|
231
|
+
* @type {function((Asset|Link|STACObject|null), string, boolean):(string|null)|null}
|
|
232
|
+
* @private
|
|
233
|
+
*/
|
|
234
|
+
this.getRequestUrl_ = options.getRequestUrl || null;
|
|
162
235
|
/**
|
|
163
236
|
* @type {Array<STAC>|null}
|
|
164
237
|
* @private
|
|
@@ -209,7 +282,12 @@ class STACLayer extends LayerGroup {
|
|
|
209
282
|
*/
|
|
210
283
|
this.displayWebMapLink_ = options.displayWebMapLink || false;
|
|
211
284
|
/**
|
|
212
|
-
* @type {
|
|
285
|
+
* @type {number|undefined}
|
|
286
|
+
* @private
|
|
287
|
+
*/
|
|
288
|
+
this.maxDisplayPixels_ = options.maxDisplayPixels;
|
|
289
|
+
/**
|
|
290
|
+
* @type {function((Asset|Link)):Promise<string|null>|string|null}
|
|
213
291
|
* @private
|
|
214
292
|
*/
|
|
215
293
|
this.buildTileUrlTemplate_ = options.buildTileUrlTemplate || null;
|
|
@@ -223,6 +301,11 @@ class STACLayer extends LayerGroup {
|
|
|
223
301
|
* @private
|
|
224
302
|
*/
|
|
225
303
|
this.style_ = options.style || null;
|
|
304
|
+
/**
|
|
305
|
+
* @type {Array<import("ol/color.js").Color|string>|null}
|
|
306
|
+
* @private
|
|
307
|
+
*/
|
|
308
|
+
this.defaultColormap_ = options.defaultColormap || null;
|
|
226
309
|
/**
|
|
227
310
|
* @type {Style}
|
|
228
311
|
* @private
|
|
@@ -268,19 +351,71 @@ class STACLayer extends LayerGroup {
|
|
|
268
351
|
if (!options.url) {
|
|
269
352
|
throw new Error('Either url or data must be provided');
|
|
270
353
|
}
|
|
271
|
-
this.fetch_(options.url)
|
|
354
|
+
this.fetch_(this.getRequestUrlFor_(options.url))
|
|
272
355
|
.then((data) => this.configure_(data, options.url, options.children, options.assets, options.bands))
|
|
273
356
|
.catch((error) => this.handleError_(error));
|
|
274
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Rewrites the given URL based on the `getRequestUrl` option.
|
|
360
|
+
*
|
|
361
|
+
* @param {string} url The URL that is requested.
|
|
362
|
+
* @param {Asset|Link|STACObject|null} [ref] The STAC Asset or Link that is shown, if available.
|
|
363
|
+
* @param {boolean} [isTemplate] Whether the URL is a tile URL template with placeholders such as `{z}`.
|
|
364
|
+
* @return {string} The rewritten URL, or the given URL if it is not rewritten.
|
|
365
|
+
*/
|
|
366
|
+
getRequestUrlFor_(url, ref = null, isTemplate = false) {
|
|
367
|
+
if (typeof this.getRequestUrl_ === 'function' && typeof url === 'string') {
|
|
368
|
+
const newUrl = this.getRequestUrl_(ref, url, isTemplate);
|
|
369
|
+
if (typeof newUrl === 'string' && newUrl.length > 0) {
|
|
370
|
+
return newUrl;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return url;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Returns the HTTP headers to send for the given URL, based on the
|
|
377
|
+
* `getRequestHeaders` option.
|
|
378
|
+
*
|
|
379
|
+
* @param {string} url The URL that is requested.
|
|
380
|
+
* @param {Asset|Link|STACObject|null} [ref] The STAC Asset or Link that is shown, if available.
|
|
381
|
+
* @return {Object<string, string>|null} The headers, or `null` if there are none.
|
|
382
|
+
*/
|
|
383
|
+
getRequestHeadersFor_(url, ref = null) {
|
|
384
|
+
let headers = this.getRequestHeaders_;
|
|
385
|
+
if (typeof headers === 'function') {
|
|
386
|
+
headers = headers(ref, url);
|
|
387
|
+
}
|
|
388
|
+
if (isObject(headers) && Object.keys(headers).length > 0) {
|
|
389
|
+
return /** @type {Object<string, string>} */ (headers);
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Creates a load function for images or tiles that attaches the headers
|
|
395
|
+
* from the `getRequestHeaders` option and reports errors through the
|
|
396
|
+
* layer's error event, or `undefined` if no headers are configured.
|
|
397
|
+
*
|
|
398
|
+
* @param {function(GetHeadersFn, OnErrorFn=):LoadFunction} factory `createImageLoadFunction` or `createTileLoadFunction`.
|
|
399
|
+
* @param {Asset|Link|STACObject|null} ref The STAC Asset or Link that is shown, if available.
|
|
400
|
+
* @return {LoadFunction|undefined} The load function.
|
|
401
|
+
*/
|
|
402
|
+
createLoadFunction_(factory, ref) {
|
|
403
|
+
if (!this.getRequestHeaders_) {
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
return factory((url) => this.getRequestHeadersFor_(url, ref), (error) => this.handleError_(error));
|
|
407
|
+
}
|
|
275
408
|
/**
|
|
276
409
|
* Default function make HTTP requests with.
|
|
277
410
|
*
|
|
278
411
|
* @param {string} url The URL to request and the output is a promise that resolves with the response body.
|
|
279
412
|
* @param {string} responseType The return type, either `json` (default) or `text`.
|
|
413
|
+
* @param {Asset|Link|STACObject|null} [ref] The STAC Asset or Link the request is made for, if available.
|
|
280
414
|
* @return {Promise<*>} The (parsed) response body.
|
|
281
415
|
*/
|
|
282
|
-
async fetch_(url, responseType = 'json') {
|
|
283
|
-
const
|
|
416
|
+
async fetch_(url, responseType = 'json', ref = null) {
|
|
417
|
+
const headers = this.getRequestHeadersFor_(url, ref);
|
|
418
|
+
const response = await fetch(url, headers ? { headers } : undefined);
|
|
284
419
|
if (!response.ok) {
|
|
285
420
|
throw new Error(`Unexpected response from ${url}: ${response.status}`);
|
|
286
421
|
}
|
|
@@ -359,7 +494,14 @@ class STACLayer extends LayerGroup {
|
|
|
359
494
|
}
|
|
360
495
|
this.set('stac', stac);
|
|
361
496
|
this.bands_ = bands;
|
|
362
|
-
|
|
497
|
+
try {
|
|
498
|
+
this.boundsLayer_ = this.addFootprint_();
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
// Don't fail the whole layer if the footprint can't be shown,
|
|
502
|
+
// the map can still zoom to the bounding box of the STAC entity
|
|
503
|
+
this.handleError_(error);
|
|
504
|
+
}
|
|
363
505
|
const updateBoundsStyle = () => {
|
|
364
506
|
if (this.boundsLayer_) {
|
|
365
507
|
this.boundsLayer_.setStyle(getBoundsStyle(this.boundsStyle_, this));
|
|
@@ -478,18 +620,21 @@ class STACLayer extends LayerGroup {
|
|
|
478
620
|
* @type {import("ol/source/ImageStatic.js").Options}
|
|
479
621
|
*/
|
|
480
622
|
let options = {
|
|
481
|
-
url: image.getAbsoluteUrl(),
|
|
623
|
+
url: this.getRequestUrlFor_(image.getAbsoluteUrl(), image),
|
|
482
624
|
projection,
|
|
483
625
|
imageExtent: toOlExtent(bbox, projection),
|
|
484
626
|
crossOrigin: this.crossOrigin_,
|
|
485
627
|
};
|
|
628
|
+
const imageLoadFunction = this.createLoadFunction_(createImageLoadFunction, image);
|
|
629
|
+
if (imageLoadFunction) {
|
|
630
|
+
options.imageLoadFunction = imageLoadFunction;
|
|
631
|
+
}
|
|
486
632
|
if (this.getSourceOptions_) {
|
|
487
633
|
// @ts-ignore
|
|
488
634
|
options = await this.getSourceOptions_(SourceType.ImageStatic, options, image);
|
|
489
635
|
}
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
});
|
|
636
|
+
const layerOptions = await this.updateLayerOptions_(LayerType.Image, { source: new StaticImage(options) }, image);
|
|
637
|
+
const layer = new ImageLayer(layerOptions);
|
|
493
638
|
this.addLayer_(layer, image);
|
|
494
639
|
return layer;
|
|
495
640
|
}
|
|
@@ -503,16 +648,21 @@ class STACLayer extends LayerGroup {
|
|
|
503
648
|
*/
|
|
504
649
|
async addLayerForLink(link) {
|
|
505
650
|
// Replace any occurances of {s} if possible, otherwise return
|
|
506
|
-
|
|
651
|
+
let url = getSpecificWebMapUrl(link);
|
|
507
652
|
if (!url) {
|
|
508
653
|
return;
|
|
509
654
|
}
|
|
655
|
+
url = this.getRequestUrlFor_(url, link, link.rel === 'xyz');
|
|
510
656
|
const options = {
|
|
511
657
|
attributions: link.getMetadata('attribution') ||
|
|
512
658
|
this.getData().getMetadata('attribution'),
|
|
513
659
|
crossOrigin: this.crossOrigin_,
|
|
514
660
|
url,
|
|
515
661
|
};
|
|
662
|
+
const tileLoadFunction = this.createLoadFunction_(createTileLoadFunction, link);
|
|
663
|
+
if (tileLoadFunction && link.rel !== 'pmtiles') {
|
|
664
|
+
options.tileLoadFunction = tileLoadFunction;
|
|
665
|
+
}
|
|
516
666
|
const updateOptions = async (type, options) => {
|
|
517
667
|
if (this.getSourceOptions_) {
|
|
518
668
|
options = await this.getSourceOptions_(type, options, link);
|
|
@@ -521,28 +671,82 @@ class STACLayer extends LayerGroup {
|
|
|
521
671
|
};
|
|
522
672
|
const sources = [];
|
|
523
673
|
switch (link.rel) {
|
|
524
|
-
case 'pmtiles':
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
let
|
|
528
|
-
|
|
674
|
+
case 'pmtiles': {
|
|
675
|
+
const snapshot = JSON.stringify(options);
|
|
676
|
+
/** @type {*} */
|
|
677
|
+
let pmOptions = await updateOptions(SourceType.PMTiles, options);
|
|
678
|
+
// Whether getSourceOptions reacted to SourceType.PMTiles,
|
|
679
|
+
// see the backward compatibility handling below
|
|
680
|
+
const handled = JSON.stringify(pmOptions) !== snapshot;
|
|
681
|
+
const headers = this.getRequestHeadersFor_(pmOptions.url, link);
|
|
682
|
+
const withHeaders = (url) => headers && typeof url === 'string'
|
|
683
|
+
? new pmtiles.FetchSource(url, new Headers(headers))
|
|
684
|
+
: url;
|
|
685
|
+
let pmtilesHeader;
|
|
686
|
+
try {
|
|
687
|
+
const p = new pmtiles.PMTiles(withHeaders(pmOptions.url));
|
|
688
|
+
pmtilesHeader = await p.getHeader();
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
this.handleError_(error);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
let type;
|
|
695
|
+
switch (pmtilesHeader.tileType) {
|
|
529
696
|
case pmtiles.TileType.Mvt:
|
|
530
|
-
|
|
697
|
+
type = SourceType.PMTilesVector;
|
|
531
698
|
break;
|
|
532
699
|
case pmtiles.TileType.Avif:
|
|
533
700
|
case pmtiles.TileType.Jpeg:
|
|
534
701
|
case pmtiles.TileType.Png:
|
|
535
702
|
case pmtiles.TileType.Webp:
|
|
536
|
-
|
|
703
|
+
type = SourceType.PMTilesRaster;
|
|
537
704
|
break;
|
|
538
705
|
default:
|
|
539
706
|
return; // Unsupported
|
|
540
707
|
}
|
|
708
|
+
if (!handled) {
|
|
709
|
+
// Backward compatibility for getSourceOptions callbacks that don't
|
|
710
|
+
// handle SourceType.PMTiles yet: call them with the deprecated
|
|
711
|
+
// type-specific source type once the tile type is known.
|
|
712
|
+
// As before v1.6.0, these rewrites don't apply to the tile type
|
|
713
|
+
// sniff above. TODO: Remove in 2.0.0
|
|
714
|
+
pmOptions = await updateOptions(type, pmOptions);
|
|
715
|
+
}
|
|
716
|
+
pmOptions.url = withHeaders(pmOptions.url);
|
|
717
|
+
const source = type === SourceType.PMTilesVector
|
|
718
|
+
? new PMTilesVectorSource(pmOptions)
|
|
719
|
+
: new PMTilesRasterSource(pmOptions);
|
|
541
720
|
sources.push(source);
|
|
542
721
|
break;
|
|
543
|
-
|
|
544
|
-
|
|
722
|
+
}
|
|
723
|
+
case 'tilejson': {
|
|
724
|
+
/** @type {*} */
|
|
725
|
+
const tjOptions = await updateOptions(SourceType.TileJSON, options);
|
|
726
|
+
if (tjOptions.jsonp || tjOptions.tileJSON) {
|
|
727
|
+
// Let the source load the manifest itself (e.g. via JSONP)
|
|
728
|
+
sources.push(new TileJSON(tjOptions));
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
// Load the manifest through the request function so that
|
|
732
|
+
// credentials are attached
|
|
733
|
+
let tileJSON;
|
|
734
|
+
try {
|
|
735
|
+
tileJSON = await this.fetch_(tjOptions.url, 'json', link);
|
|
736
|
+
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
this.handleError_(error);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (isObject(tileJSON) && Array.isArray(tileJSON.tiles)) {
|
|
742
|
+
// The tile templates from the manifest must be rewritten as well
|
|
743
|
+
tileJSON.tiles = tileJSON.tiles.map((template) => this.getRequestUrlFor_(template, link, true));
|
|
744
|
+
}
|
|
745
|
+
delete tjOptions.url;
|
|
746
|
+
tjOptions.tileJSON = tileJSON;
|
|
747
|
+
sources.push(new TileJSON(tjOptions));
|
|
545
748
|
break;
|
|
749
|
+
}
|
|
546
750
|
case 'wms':
|
|
547
751
|
if (!Array.isArray(link['wms:layers'])) {
|
|
548
752
|
break;
|
|
@@ -569,7 +773,7 @@ class STACLayer extends LayerGroup {
|
|
|
569
773
|
}
|
|
570
774
|
break;
|
|
571
775
|
case 'wmts':
|
|
572
|
-
const wmtsCapabilities = await this.getWmtsCapabilities_(url, link['wmts:encoding']);
|
|
776
|
+
const wmtsCapabilities = await this.getWmtsCapabilities_(url, link, link['wmts:encoding']);
|
|
573
777
|
if (!wmtsCapabilities) {
|
|
574
778
|
return;
|
|
575
779
|
}
|
|
@@ -580,7 +784,7 @@ class STACLayer extends LayerGroup {
|
|
|
580
784
|
else if (typeof link['wmts:layer'] === 'string') {
|
|
581
785
|
layers = [link['wmts:layer']];
|
|
582
786
|
}
|
|
583
|
-
for (const layer of layers) {
|
|
787
|
+
layers: for (const layer of layers) {
|
|
584
788
|
let wmtsOptions = Object.assign({}, options, {
|
|
585
789
|
layer,
|
|
586
790
|
requestEncoding: link['wmts:encoding'] === 'rest' ? 'REST' : 'KVP',
|
|
@@ -593,6 +797,10 @@ class STACLayer extends LayerGroup {
|
|
|
593
797
|
if (opts === null) {
|
|
594
798
|
continue;
|
|
595
799
|
}
|
|
800
|
+
if (wmtsOptions.tileLoadFunction) {
|
|
801
|
+
// Not passed through by optionsFromCapabilities
|
|
802
|
+
opts.tileLoadFunction = wmtsOptions.tileLoadFunction;
|
|
803
|
+
}
|
|
596
804
|
if (typeof link.uriTemplate === 'string') {
|
|
597
805
|
let uriTemplate = link.uriTemplate;
|
|
598
806
|
const vars = isObject(link.variables) ? link.variables : {};
|
|
@@ -612,11 +820,15 @@ class STACLayer extends LayerGroup {
|
|
|
612
820
|
uriTemplate = uriTemplate.replaceAll(`{${key}}`, String(value));
|
|
613
821
|
}
|
|
614
822
|
else {
|
|
615
|
-
|
|
823
|
+
// We don't know which value to use, so we can't visualize the layer
|
|
824
|
+
continue layers;
|
|
616
825
|
}
|
|
617
826
|
}
|
|
618
827
|
delete opts.urls;
|
|
619
|
-
opts.url = uriTemplate;
|
|
828
|
+
opts.url = this.getRequestUrlFor_(uriTemplate, link, true);
|
|
829
|
+
}
|
|
830
|
+
else if (Array.isArray(opts.urls)) {
|
|
831
|
+
opts.urls = opts.urls.map((u) => this.getRequestUrlFor_(u, link, opts.requestEncoding === 'REST'));
|
|
620
832
|
}
|
|
621
833
|
sources.push(new WMTS(opts));
|
|
622
834
|
}
|
|
@@ -627,34 +839,42 @@ class STACLayer extends LayerGroup {
|
|
|
627
839
|
default:
|
|
628
840
|
return;
|
|
629
841
|
}
|
|
630
|
-
return sources.map((source) => {
|
|
842
|
+
return await Promise.all(sources.map(async (source) => {
|
|
631
843
|
let layer;
|
|
632
844
|
if (source instanceof VectorTileSource) {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
declutter: true,
|
|
636
|
-
});
|
|
845
|
+
const layerOptions = await this.updateLayerOptions_(LayerType.VectorTile, { source, declutter: true }, link);
|
|
846
|
+
layer = new VectorTileLayer(layerOptions);
|
|
637
847
|
}
|
|
638
848
|
else if (source instanceof PMTilesRasterSource) {
|
|
639
|
-
|
|
849
|
+
const layerOptions = await this.updateLayerOptions_(LayerType.WebGLTile, { source }, link);
|
|
850
|
+
layer = new WebGLTileLayer(layerOptions);
|
|
640
851
|
}
|
|
641
852
|
else {
|
|
642
|
-
|
|
853
|
+
const layerOptions = await this.updateLayerOptions_(LayerType.Tile, { source }, link);
|
|
854
|
+
layer = new TileLayer(layerOptions);
|
|
643
855
|
}
|
|
644
856
|
this.addLayer_(layer, link);
|
|
645
857
|
return layer;
|
|
646
|
-
});
|
|
858
|
+
}));
|
|
647
859
|
}
|
|
648
860
|
/**
|
|
649
861
|
* @param {Asset} [asset] A STAC Asset
|
|
862
|
+
* @param {boolean} [autoDisplay] Whether the asset was chosen automatically
|
|
863
|
+
* (not explicitly requested): skip it silently instead of reporting an
|
|
864
|
+
* error when it can't be displayed within the configured limits.
|
|
650
865
|
* @return {Promise<Layer|undefined>} Resolves with a Layer or undefined when complete.
|
|
651
866
|
* @private
|
|
652
867
|
*/
|
|
653
|
-
async addGeoTiff_(asset) {
|
|
868
|
+
async addGeoTiff_(asset, autoDisplay = false) {
|
|
654
869
|
if (this.buildTileUrlTemplate_ && !this.useTileLayerAsFallback_) {
|
|
655
|
-
|
|
870
|
+
const layer = await this.addTileLayerForImagery_(asset);
|
|
871
|
+
// If no tile server URL was provided for the asset, continue with client-side rendering
|
|
872
|
+
if (layer) {
|
|
873
|
+
return layer;
|
|
874
|
+
}
|
|
656
875
|
}
|
|
657
876
|
const sourceInfo = getGeoTiffSourceInfoFromAsset(asset, this.bands_);
|
|
877
|
+
sourceInfo.url = this.getRequestUrlFor_(sourceInfo.url, asset);
|
|
658
878
|
/**
|
|
659
879
|
* @type {import("ol/source/GeoTIFF.js").Options}
|
|
660
880
|
*/
|
|
@@ -667,10 +887,14 @@ class STACLayer extends LayerGroup {
|
|
|
667
887
|
if (projection) {
|
|
668
888
|
options.projection = projection;
|
|
669
889
|
}
|
|
670
|
-
const
|
|
671
|
-
if (
|
|
890
|
+
const metadataStyle = getGeoTiffStyleFromAsset(asset, sourceInfo, this.defaultColormap_);
|
|
891
|
+
if (metadataStyle) {
|
|
672
892
|
options.normalize = false;
|
|
673
893
|
}
|
|
894
|
+
const headers = this.getRequestHeadersFor_(sourceInfo.url, asset);
|
|
895
|
+
if (headers) {
|
|
896
|
+
options.sourceOptions = { headers };
|
|
897
|
+
}
|
|
674
898
|
if (this.getSourceOptions_) {
|
|
675
899
|
// @ts-ignore
|
|
676
900
|
options = await this.getSourceOptions_(SourceType.GeoTIFF, options, asset);
|
|
@@ -690,34 +914,51 @@ class STACLayer extends LayerGroup {
|
|
|
690
914
|
});
|
|
691
915
|
try {
|
|
692
916
|
await status;
|
|
693
|
-
|
|
917
|
+
if (this.checkDisplayLimit_(source, asset, autoDisplay)) {
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* @type {import("ol/layer/WebGLTile.js").Options}
|
|
922
|
+
*/
|
|
923
|
+
let layerOptions = { source };
|
|
694
924
|
if (this.style_) {
|
|
695
925
|
layerOptions.style = this.style_;
|
|
696
926
|
}
|
|
697
|
-
else if (
|
|
698
|
-
layerOptions.style =
|
|
927
|
+
else if (metadataStyle) {
|
|
928
|
+
layerOptions.style = metadataStyle;
|
|
699
929
|
}
|
|
930
|
+
layerOptions = await this.updateLayerOptions_(LayerType.WebGLTile, layerOptions, asset);
|
|
700
931
|
const layer = new WebGLTileLayer(layerOptions);
|
|
701
932
|
this.addLayer_(layer, asset);
|
|
702
933
|
return layer;
|
|
703
934
|
}
|
|
704
935
|
catch (error) {
|
|
705
936
|
if (this.useTileLayerAsFallback_) {
|
|
706
|
-
|
|
937
|
+
const layer = await this.addTileLayerForImagery_(asset);
|
|
938
|
+
if (layer) {
|
|
939
|
+
return layer;
|
|
940
|
+
}
|
|
707
941
|
}
|
|
708
942
|
this.handleError_(error);
|
|
709
943
|
}
|
|
710
944
|
}
|
|
711
945
|
/**
|
|
712
946
|
* @param {Asset|Link} [data] A STAC Asset or Link
|
|
713
|
-
* @return {Promise<TileLayer>} Resolves with a TileLayer
|
|
947
|
+
* @return {Promise<TileLayer|undefined>} Resolves with a TileLayer, or undefined if no tile server URL was provided.
|
|
714
948
|
* @private
|
|
715
949
|
*/
|
|
716
950
|
async addTileLayerForImagery_(data) {
|
|
951
|
+
if (typeof this.buildTileUrlTemplate_ !== 'function') {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
717
954
|
let url = this.buildTileUrlTemplate_(data);
|
|
718
955
|
if (url instanceof Promise) {
|
|
719
956
|
url = await url;
|
|
720
957
|
}
|
|
958
|
+
if (!url) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
url = this.getRequestUrlFor_(url, data, true);
|
|
721
962
|
/**
|
|
722
963
|
* @type {import("ol/source/XYZ.js").Options}
|
|
723
964
|
*/
|
|
@@ -725,15 +966,33 @@ class STACLayer extends LayerGroup {
|
|
|
725
966
|
crossOrigin: this.crossOrigin_,
|
|
726
967
|
url,
|
|
727
968
|
};
|
|
969
|
+
const tileLoadFunction = this.createLoadFunction_(createTileLoadFunction, data);
|
|
970
|
+
if (tileLoadFunction) {
|
|
971
|
+
options.tileLoadFunction = tileLoadFunction;
|
|
972
|
+
}
|
|
728
973
|
if (this.getSourceOptions_) {
|
|
729
974
|
options = await this.getSourceOptions_(SourceType.XYZ, options, data);
|
|
730
975
|
}
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
});
|
|
976
|
+
const layerOptions = await this.updateLayerOptions_(LayerType.Tile, { source: new XYZ(options) }, data);
|
|
977
|
+
const layer = new TileLayer(layerOptions);
|
|
734
978
|
this.addLayer_(layer, data);
|
|
735
979
|
return layer;
|
|
736
980
|
}
|
|
981
|
+
/**
|
|
982
|
+
* Passes the layer options through the `getLayerOptions` function, if given.
|
|
983
|
+
*
|
|
984
|
+
* @param {LayerType} type The type of the layer that is going to be created.
|
|
985
|
+
* @param {LayerOptions} options The layer options.
|
|
986
|
+
* @param {Asset|Link} reference The STAC Asset or Link the layer is created for.
|
|
987
|
+
* @return {Promise<*>} The updated layer options.
|
|
988
|
+
* @private
|
|
989
|
+
*/
|
|
990
|
+
async updateLayerOptions_(type, options, reference) {
|
|
991
|
+
if (this.getLayerOptions_) {
|
|
992
|
+
options = await this.getLayerOptions_(type, options, reference);
|
|
993
|
+
}
|
|
994
|
+
return options;
|
|
995
|
+
}
|
|
737
996
|
/**
|
|
738
997
|
* @param {Layer|LayerGroup} [layer] A Layer to add to the LayerGroup
|
|
739
998
|
* @param {STACObject} [data] The STAC object, can be any class exposed by stac-js
|
|
@@ -766,7 +1025,7 @@ class STACLayer extends LayerGroup {
|
|
|
766
1025
|
geojson = data.toGeoJSON(fixAntimeridian);
|
|
767
1026
|
}
|
|
768
1027
|
if (geojson) {
|
|
769
|
-
const layer = this.
|
|
1028
|
+
const layer = new VectorLayer(this.getGeoJsonLayerOptions_(geojson, getBoundsStyle(this.boundsStyle_, this), this.displayFootprint_));
|
|
770
1029
|
layer.set('bounds', true);
|
|
771
1030
|
layer.on('change', () => this.setMap_(layer.getMapInternal()));
|
|
772
1031
|
this.addLayer_(layer, data, 1);
|
|
@@ -781,8 +1040,9 @@ class STACLayer extends LayerGroup {
|
|
|
781
1040
|
*/
|
|
782
1041
|
async addGeoJson_(asset) {
|
|
783
1042
|
try {
|
|
784
|
-
const geojson = await this.fetch_(asset.getAbsoluteUrl());
|
|
785
|
-
const
|
|
1043
|
+
const geojson = await this.fetch_(this.getRequestUrlFor_(asset.getAbsoluteUrl(), asset), 'json', asset);
|
|
1044
|
+
const layerOptions = await this.updateLayerOptions_(LayerType.Vector, this.getGeoJsonLayerOptions_(geojson), asset);
|
|
1045
|
+
const layer = new VectorLayer(layerOptions);
|
|
786
1046
|
this.addLayer_(layer, asset);
|
|
787
1047
|
return layer;
|
|
788
1048
|
}
|
|
@@ -791,15 +1051,15 @@ class STACLayer extends LayerGroup {
|
|
|
791
1051
|
}
|
|
792
1052
|
}
|
|
793
1053
|
/**
|
|
794
|
-
* Creates a GeoJSON vector layer from the given GeoJSON object.
|
|
1054
|
+
* Creates the options for a GeoJSON vector layer from the given GeoJSON object.
|
|
795
1055
|
*
|
|
796
1056
|
* @param {GeoJSON} [geojson] The GeoJSON object.
|
|
797
1057
|
* @param {Style} [style] The style for the layer.
|
|
798
1058
|
* @param {boolean} [visible] Whether the layer is visible.
|
|
799
|
-
* @return {
|
|
1059
|
+
* @return {import("ol/layer/Vector.js").Options} The vector layer options.
|
|
800
1060
|
* @private
|
|
801
1061
|
*/
|
|
802
|
-
|
|
1062
|
+
getGeoJsonLayerOptions_(geojson, style = null, visible = true) {
|
|
803
1063
|
const format = new GeoJSON();
|
|
804
1064
|
const source = new VectorSource({
|
|
805
1065
|
format,
|
|
@@ -814,7 +1074,7 @@ class STACLayer extends LayerGroup {
|
|
|
814
1074
|
if (!style) {
|
|
815
1075
|
style = defaultCollectionStyle;
|
|
816
1076
|
}
|
|
817
|
-
return
|
|
1077
|
+
return { source, style, visible };
|
|
818
1078
|
}
|
|
819
1079
|
/**
|
|
820
1080
|
* Adds GeoJSON labels and GeoTIFF source imagery to the map based on the label extension.
|
|
@@ -849,7 +1109,7 @@ class STACLayer extends LayerGroup {
|
|
|
849
1109
|
if (labelAsset && sourceLinks.length > 0) {
|
|
850
1110
|
const promises = sourceLinks.map(async (link) => {
|
|
851
1111
|
try {
|
|
852
|
-
const response = await this.fetch_(link.getAbsoluteUrl());
|
|
1112
|
+
const response = await this.fetch_(this.getRequestUrlFor_(link.getAbsoluteUrl(), link), 'json', link);
|
|
853
1113
|
const stac = create(response);
|
|
854
1114
|
return stac;
|
|
855
1115
|
}
|
|
@@ -869,17 +1129,78 @@ class STACLayer extends LayerGroup {
|
|
|
869
1129
|
this.handleError_(error);
|
|
870
1130
|
}
|
|
871
1131
|
}
|
|
872
|
-
|
|
1132
|
+
/**
|
|
1133
|
+
* Checks the `maxDisplayPixels` limit for the given source.
|
|
1134
|
+
* Returns `true` when the layer must not be added: automatically chosen
|
|
1135
|
+
* assets are limited silently, for explicitly requested assets an error
|
|
1136
|
+
* is thrown so that callers can fall back or report it.
|
|
1137
|
+
* @param {import('ol/source/Tile.js').default} source The configured (ready) source.
|
|
1138
|
+
* @param {Asset} asset The asset the source was created for.
|
|
1139
|
+
* @param {boolean} autoDisplay Whether the asset was chosen automatically.
|
|
1140
|
+
* @return {boolean} `true` if the asset must not be displayed.
|
|
1141
|
+
* @private
|
|
1142
|
+
*/
|
|
1143
|
+
checkDisplayLimit_(source, asset, autoDisplay) {
|
|
1144
|
+
if (!exceedsDisplayLimit(source, this.maxDisplayPixels_)) {
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
if (!autoDisplay) {
|
|
1148
|
+
const megapixels = Math.ceil(getDisplayPixels(source) / 1048576);
|
|
1149
|
+
const error = new Error(`Asset ${asset.getKey()} is too large to display safely` +
|
|
1150
|
+
` (~${megapixels} megapixels at the coarsest resolution);` +
|
|
1151
|
+
` set the maxDisplayPixels option to display it anyway`);
|
|
1152
|
+
// Allows applications to detect the error without matching the message
|
|
1153
|
+
error.name = 'DisplayLimitError';
|
|
1154
|
+
throw error;
|
|
1155
|
+
}
|
|
1156
|
+
return true;
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Adds a layer for a GeoZarr asset.
|
|
1160
|
+
* @param {Asset} asset The Zarr asset to show.
|
|
1161
|
+
* @param {boolean} [autoDisplay] Whether the asset was chosen automatically
|
|
1162
|
+
* (not explicitly requested): skip it silently instead of reporting an
|
|
1163
|
+
* error when it doesn't declare what to render or can't be displayed
|
|
1164
|
+
* within the configured limits.
|
|
1165
|
+
* @return {Promise<Layer|undefined>} The layer, if one was added.
|
|
1166
|
+
* @private
|
|
1167
|
+
*/
|
|
1168
|
+
async addGeoZarr_(asset, autoDisplay = false) {
|
|
873
1169
|
if (this.buildTileUrlTemplate_ && !this.useTileLayerAsFallback_) {
|
|
874
|
-
|
|
1170
|
+
const layer = await this.addTileLayerForImagery_(asset);
|
|
1171
|
+
// If no tile server URL was provided for the asset, continue with client-side rendering
|
|
1172
|
+
if (layer) {
|
|
1173
|
+
return layer;
|
|
1174
|
+
}
|
|
875
1175
|
}
|
|
876
1176
|
let options = getGeoZarrSourceOptionsFromAsset(asset, this.bands_);
|
|
1177
|
+
// Web-Optimized Zarr describes its bands in the store metadata,
|
|
1178
|
+
// everything else must declare what to render in the STAC metadata
|
|
1179
|
+
// (or provide it through getSourceOptions for explicitly selected assets)
|
|
1180
|
+
const declaresRendering = () => asset.isType(wozMediaTypes) ||
|
|
1181
|
+
Boolean(options.variable) ||
|
|
1182
|
+
Boolean(options.bands && options.bands.length > 0);
|
|
1183
|
+
if (autoDisplay && !declaresRendering()) {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
options.url = this.getRequestUrlFor_(options.url, asset);
|
|
1187
|
+
const projection = await getProjection(asset);
|
|
1188
|
+
if (projection) {
|
|
1189
|
+
options.projection = projection;
|
|
1190
|
+
}
|
|
1191
|
+
const headers = this.getRequestHeadersFor_(options.url, asset);
|
|
1192
|
+
if (headers) {
|
|
1193
|
+
options.storeOptions = { headers };
|
|
1194
|
+
}
|
|
877
1195
|
if (this.getSourceOptions_) {
|
|
878
1196
|
// @ts-ignore
|
|
879
1197
|
options = await this.getSourceOptions_(SourceType.GeoZarr, options, asset);
|
|
880
1198
|
}
|
|
881
1199
|
try {
|
|
882
|
-
|
|
1200
|
+
if (!declaresRendering()) {
|
|
1201
|
+
throw new Error(`Asset ${asset.getKey()} declares neither bands nor a datacube variable to render`);
|
|
1202
|
+
}
|
|
1203
|
+
const GeoZarr = (await import('../source/GeoZarr.js')).default;
|
|
883
1204
|
const source = new GeoZarr(options);
|
|
884
1205
|
await new Promise((resolve, reject) => {
|
|
885
1206
|
source.on('change', () => {
|
|
@@ -891,17 +1212,33 @@ class STACLayer extends LayerGroup {
|
|
|
891
1212
|
}
|
|
892
1213
|
});
|
|
893
1214
|
});
|
|
894
|
-
|
|
1215
|
+
if (this.checkDisplayLimit_(source, asset, autoDisplay)) {
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* @type {import("ol/layer/WebGLTile.js").Options}
|
|
1220
|
+
*/
|
|
1221
|
+
let layerOptions = { source };
|
|
895
1222
|
if (this.style_) {
|
|
896
1223
|
layerOptions.style = this.style_;
|
|
897
1224
|
}
|
|
1225
|
+
else {
|
|
1226
|
+
const style = getGeoZarrStyleFromAsset(asset, options, this.defaultColormap_);
|
|
1227
|
+
if (style) {
|
|
1228
|
+
layerOptions.style = style;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
layerOptions = await this.updateLayerOptions_(LayerType.WebGLTile, layerOptions, asset);
|
|
898
1232
|
const layer = new WebGLTileLayer(layerOptions);
|
|
899
1233
|
this.addLayer_(layer, asset);
|
|
900
1234
|
return layer;
|
|
901
1235
|
}
|
|
902
1236
|
catch (error) {
|
|
903
1237
|
if (this.useTileLayerAsFallback_) {
|
|
904
|
-
|
|
1238
|
+
const layer = await this.addTileLayerForImagery_(asset);
|
|
1239
|
+
if (layer) {
|
|
1240
|
+
return layer;
|
|
1241
|
+
}
|
|
905
1242
|
}
|
|
906
1243
|
this.handleError_(error);
|
|
907
1244
|
}
|
|
@@ -946,7 +1283,7 @@ class STACLayer extends LayerGroup {
|
|
|
946
1283
|
if (ref.isType(geotiffMediaTypes)) {
|
|
947
1284
|
return await this.addGeoTiff_(ref);
|
|
948
1285
|
}
|
|
949
|
-
if (ref.isType(
|
|
1286
|
+
if (ref.isType(zarrMediaTypes)) {
|
|
950
1287
|
return await this.addGeoZarr_(ref);
|
|
951
1288
|
}
|
|
952
1289
|
if (ref.canBrowserDisplayImage()) {
|
|
@@ -978,14 +1315,13 @@ class STACLayer extends LayerGroup {
|
|
|
978
1315
|
// Find a GeoTiff asset that we can visualize
|
|
979
1316
|
const geotiff = data.getDefaultGeoFile('geotiff', true, !this.displayGeoTiffByDefault_);
|
|
980
1317
|
if (geotiff) {
|
|
981
|
-
layer = await this.addGeoTiff_(geotiff);
|
|
1318
|
+
layer = await this.addGeoTiff_(geotiff, true);
|
|
982
1319
|
}
|
|
983
1320
|
}
|
|
984
1321
|
if (this.displayOverview_ && !layer) {
|
|
985
|
-
|
|
986
|
-
const geozarr = data.getDefaultGeoFile('geozarr', true, true);
|
|
1322
|
+
const geozarr = data.getDefaultGeoFile('geozarr', true, false);
|
|
987
1323
|
if (geozarr) {
|
|
988
|
-
layer = await this.addGeoZarr_(geozarr);
|
|
1324
|
+
layer = await this.addGeoZarr_(geozarr, true);
|
|
989
1325
|
}
|
|
990
1326
|
}
|
|
991
1327
|
// Show web map links if available
|
|
@@ -1023,40 +1359,7 @@ class STACLayer extends LayerGroup {
|
|
|
1023
1359
|
* @api
|
|
1024
1360
|
*/
|
|
1025
1361
|
getWebMapLinks() {
|
|
1026
|
-
|
|
1027
|
-
return [];
|
|
1028
|
-
}
|
|
1029
|
-
const data = this.getData();
|
|
1030
|
-
if (!data || data.isAsset) {
|
|
1031
|
-
return [];
|
|
1032
|
-
}
|
|
1033
|
-
let types = ['xyz', 'tilejson', 'pmtiles', 'wmts', 'wms']; // This also defines the priority
|
|
1034
|
-
if (typeof this.displayWebMapLink_ === 'string') {
|
|
1035
|
-
types = [this.displayWebMapLink_];
|
|
1036
|
-
}
|
|
1037
|
-
let mapLinks = data.getLinksWithRels(types);
|
|
1038
|
-
if (Array.isArray(this.displayWebMapLink_)) {
|
|
1039
|
-
mapLinks = this.displayWebMapLink_
|
|
1040
|
-
.map((link) => {
|
|
1041
|
-
if (typeof link === 'string') {
|
|
1042
|
-
const match = mapLinks.find((candidate) => candidate.id === link);
|
|
1043
|
-
if (match) {
|
|
1044
|
-
return match;
|
|
1045
|
-
}
|
|
1046
|
-
return null;
|
|
1047
|
-
}
|
|
1048
|
-
return link;
|
|
1049
|
-
})
|
|
1050
|
-
.filter((link) => !!link);
|
|
1051
|
-
}
|
|
1052
|
-
else {
|
|
1053
|
-
mapLinks.sort((a, b) => {
|
|
1054
|
-
const prioA = types.indexOf(a.rel);
|
|
1055
|
-
const prioB = types.indexOf(b.rel);
|
|
1056
|
-
return prioA - prioB;
|
|
1057
|
-
});
|
|
1058
|
-
}
|
|
1059
|
-
return mapLinks;
|
|
1362
|
+
return getWebMapLinks(this.getData(), this.displayWebMapLink_);
|
|
1060
1363
|
}
|
|
1061
1364
|
/**
|
|
1062
1365
|
* Set the style for GeoTIFF and GeoZarr layers (WebGLTileLayer style).
|
|
@@ -1071,6 +1374,26 @@ class STACLayer extends LayerGroup {
|
|
|
1071
1374
|
}
|
|
1072
1375
|
}
|
|
1073
1376
|
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Set the colors of the colormap that is used for continuous single-band
|
|
1379
|
+
* data when neither the STAC metadata nor the `style` option define a
|
|
1380
|
+
* coloring. The colors are evenly distributed over the value range of the
|
|
1381
|
+
* data (e.g. from the STAC `statistics`). Set to `null` to stretch the
|
|
1382
|
+
* data to grayscale instead.
|
|
1383
|
+
* @param {Array<import("ol/color.js").Color|string>|null} colormap The colors of the colormap.
|
|
1384
|
+
* @return {Promise} Resolves once the layers are updated.
|
|
1385
|
+
* @api
|
|
1386
|
+
*/
|
|
1387
|
+
async setDefaultColormap(colormap) {
|
|
1388
|
+
if (colormap === this.defaultColormap_) {
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
this.defaultColormap_ = colormap || null;
|
|
1392
|
+
// The layers are recreated, as switching between a styled and an
|
|
1393
|
+
// unstyled visualization also changes how the sources are configured
|
|
1394
|
+
// (e.g. the normalization of GeoTIFF sources)
|
|
1395
|
+
await this.updateLayers();
|
|
1396
|
+
}
|
|
1074
1397
|
/**
|
|
1075
1398
|
* Update the assets to be rendered.
|
|
1076
1399
|
* @param {Array<string|Asset>|null} assets The assets to show.
|
|
@@ -1244,18 +1567,19 @@ class STACLayer extends LayerGroup {
|
|
|
1244
1567
|
/**
|
|
1245
1568
|
* Gets the WMTS capabilities from the given web-map-links URL.
|
|
1246
1569
|
* @param {string} url Base URL for the WMTS
|
|
1570
|
+
* @param {Link} link The web map link the request is made for.
|
|
1247
1571
|
* @param {string} [encoding] The request encoding, either `kvp` (default) or `rest`.
|
|
1248
1572
|
* @return {Promise<Object|null>} Resolves with the WMTS Capabilities object
|
|
1249
1573
|
* @private
|
|
1250
1574
|
*/
|
|
1251
|
-
async getWmtsCapabilities_(url, encoding = 'kvp') {
|
|
1575
|
+
async getWmtsCapabilities_(url, link, encoding = 'kvp') {
|
|
1252
1576
|
try {
|
|
1253
1577
|
const urlObj = new URL(url);
|
|
1254
1578
|
if (encoding !== 'rest') {
|
|
1255
1579
|
urlObj.searchParams.set('service', 'wmts');
|
|
1256
1580
|
urlObj.searchParams.set('request', 'GetCapabilities');
|
|
1257
1581
|
}
|
|
1258
|
-
const response = await this.fetch_(urlObj.toString(), 'text');
|
|
1582
|
+
const response = await this.fetch_(urlObj.toString(), 'text', link);
|
|
1259
1583
|
return new WMTSCapabilities().read(response);
|
|
1260
1584
|
}
|
|
1261
1585
|
catch (_) {
|