ol-stac 1.2.0 → 1.3.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/source/GeoTIFF.js DELETED
@@ -1,920 +0,0 @@
1
- /**
2
- * @module ol/source/GeoTIFF
3
- */
4
- import { Pool, fromBlob as tiffFromBlob, fromUrl as tiffFromUrl, fromUrls as tiffFromUrls, globals as geotiffGlobals, } from 'geotiff';
5
- import { error as logError } from 'ol/console.js';
6
- import { applyTransform, getCenter, getIntersection } from 'ol/extent.js';
7
- import { clamp } from 'ol/math.js';
8
- import { fromCode as unitsFromCode } from 'ol/proj/Units.js';
9
- import { Projection, createTransformFromCoordinateTransform, get as getCachedProjection, toUserCoordinate, toUserExtent, } from 'ol/proj.js';
10
- import DataTile from 'ol/source/DataTile.js';
11
- import TileGrid from 'ol/tilegrid/TileGrid.js';
12
- import { apply as applyMatrix, create as createMatrix, makeInverse, multiply as multiplyTransform, } from 'ol/transform.js';
13
- import { loadProjection } from '../util.js';
14
- /**
15
- * Determine if an image type is a mask.
16
- * See https://www.awaresystems.be/imaging/tiff/tifftags/newsubfiletype.html
17
- * @param {GeoTIFFImage} image The image.
18
- * @return {boolean} The image is a mask.
19
- */
20
- function isMask(image) {
21
- const fileDirectory = image.fileDirectory;
22
- const type = fileDirectory.NewSubfileType || 0;
23
- return (type & 4) === 4;
24
- }
25
- /**
26
- * @param {true|false|'auto'} preference The convertToRGB option.
27
- * @param {GeoTIFFImage} image The image.
28
- * @return {boolean} Use the `image.readRGB()` method.
29
- */
30
- function readRGB(preference, image) {
31
- if (!preference) {
32
- return false;
33
- }
34
- if (preference === true) {
35
- return true;
36
- }
37
- if (image.getSamplesPerPixel() !== 3) {
38
- return false;
39
- }
40
- const interpretation = image.fileDirectory.PhotometricInterpretation;
41
- const interpretations = geotiffGlobals.photometricInterpretations;
42
- return (interpretation === interpretations.CMYK ||
43
- interpretation === interpretations.YCbCr ||
44
- interpretation === interpretations.CIELab ||
45
- interpretation === interpretations.ICCLab);
46
- }
47
- /**
48
- * @typedef {Object} SourceInfo
49
- * @property {string} [url] URL for the source GeoTIFF.
50
- * @property {Array<string>} [overviews] List of any overview URLs, only applies if the url parameter is given.
51
- * @property {Blob} [blob] Blob containing the source GeoTIFF. `blob` and `url` are mutually exclusive.
52
- * @property {number} [min=0] The minimum source data value. Rendered values are scaled from 0 to 1 based on
53
- * the configured min and max. If not provided and raster statistics are available, those will be used instead.
54
- * If neither are available, the minimum for the data type will be used. To disable this behavior, set
55
- * the `normalize` option to `false` in the constructor.
56
- * @property {number} [max] The maximum source data value. Rendered values are scaled from 0 to 1 based on
57
- * the configured min and max. If not provided and raster statistics are available, those will be used instead.
58
- * If neither are available, the maximum for the data type will be used. To disable this behavior, set
59
- * the `normalize` option to `false` in the constructor.
60
- * @property {number} [nodata] Values to discard (overriding any nodata values in the metadata).
61
- * When provided, an additional alpha band will be added to the data. Often the GeoTIFF metadata
62
- * will include information about nodata values, so you should only need to set this property if
63
- * you find that it is not already extracted from the metadata.
64
- * @property {Array<number>} [bands] Band numbers to be read from (where the first band is `1`). If not provided, all bands will
65
- * be read. For example, if a GeoTIFF has blue (1), green (2), red (3), and near-infrared (4) bands, and you only need the
66
- * near-infrared band, configure `bands: [4]`.
67
- */
68
- /**
69
- * @typedef {Object} GeoKeys
70
- * @property {number} GTModelTypeGeoKey Model type.
71
- * @property {number} GTRasterTypeGeoKey Raster type.
72
- * @property {number} GeogAngularUnitsGeoKey Angular units.
73
- * @property {number} GeogInvFlatteningGeoKey Inverse flattening.
74
- * @property {number} GeogSemiMajorAxisGeoKey Semi-major axis.
75
- * @property {number} GeographicTypeGeoKey Geographic coordinate system code.
76
- * @property {number} ProjLinearUnitsGeoKey Projected linear unit code.
77
- * @property {number} ProjectedCSTypeGeoKey Projected coordinate system code.
78
- */
79
- /**
80
- * @typedef {import("geotiff").GeoTIFF} GeoTIFF
81
- */
82
- /**
83
- * @typedef {import("geotiff").MultiGeoTIFF} MultiGeoTIFF
84
- */
85
- /**
86
- * @typedef {Object} GDALMetadata
87
- * @property {string} STATISTICS_MINIMUM The minimum value (as a string).
88
- * @property {string} STATISTICS_MAXIMUM The maximum value (as a string).
89
- */
90
- const STATISTICS_MAXIMUM = 'STATISTICS_MAXIMUM';
91
- const STATISTICS_MINIMUM = 'STATISTICS_MINIMUM';
92
- const defaultTileSize = 256;
93
- /**
94
- * @typedef {import("geotiff").GeoTIFFImage} GeoTIFFImage
95
- */
96
- let workerPool;
97
- function getWorkerPool() {
98
- if (!workerPool) {
99
- workerPool = new Pool();
100
- }
101
- return workerPool;
102
- }
103
- /**
104
- * Get the bounding box of an image. If the image does not have an affine transform,
105
- * the pixel bounds are returned.
106
- * @param {GeoTIFFImage} image The image.
107
- * @return {Array<number>} The image bounding box.
108
- */
109
- function getBoundingBox(image) {
110
- try {
111
- return image.getBoundingBox(true);
112
- }
113
- catch (_a) {
114
- return [0, 0, image.getWidth(), image.getHeight()];
115
- }
116
- }
117
- /**
118
- * Get the origin of an image. If the image does not have an affine transform,
119
- * the top-left corner of the pixel bounds is returned.
120
- * @param {GeoTIFFImage} image The image.
121
- * @return {Array<number>} The image origin.
122
- */
123
- function getOrigin(image) {
124
- try {
125
- return image.getOrigin().slice(0, 2);
126
- }
127
- catch (_a) {
128
- return [0, image.getHeight()];
129
- }
130
- }
131
- /**
132
- * Get the resolution of an image. If the image does not have an affine transform,
133
- * the width of the image is compared with the reference image.
134
- * @param {GeoTIFFImage} image The image.
135
- * @param {GeoTIFFImage} referenceImage The reference image.
136
- * @return {Array<number>} The map x and y units per pixel.
137
- */
138
- function getResolutions(image, referenceImage) {
139
- try {
140
- return image.getResolution(referenceImage);
141
- }
142
- catch (_a) {
143
- return [
144
- referenceImage.getWidth() / image.getWidth(),
145
- referenceImage.getHeight() / image.getHeight(),
146
- ];
147
- }
148
- }
149
- /**
150
- * @async
151
- * @param {Object<string, any>} geoKeys Geo keys object.
152
- * @param {string} geoKey The geo key to lookup.
153
- * @param {string} unitKey The unit key to lookup.
154
- * @param {boolean} loadMissingProjection Whether to load missing projections.
155
- * @return {Promise<Projection|null>} The projection.
156
- */
157
- async function getProjectionFromKeys(geoKeys, geoKey, unitKey, loadMissingProjection) {
158
- const value = geoKeys[geoKey];
159
- if (value && value !== 32767) {
160
- const code = 'EPSG:' + value;
161
- let projection = getCachedProjection(code);
162
- if (!projection && loadMissingProjection) {
163
- projection = await loadProjection(code);
164
- }
165
- if (!projection) {
166
- const units = unitsFromCode(geoKeys[unitKey]);
167
- if (units) {
168
- projection = new Projection({
169
- code: code,
170
- units: units,
171
- });
172
- }
173
- }
174
- return projection || null;
175
- }
176
- }
177
- /**
178
- * @async
179
- * @param {GeoTIFFImage} image A GeoTIFF.
180
- * @param {boolean} loadMissingProjection Whether to load missing projections.
181
- * @return {Promise<Projection|null>} The image projection.
182
- */
183
- async function getProjection(image, loadMissingProjection) {
184
- const geoKeys = image.geoKeys;
185
- if (!geoKeys) {
186
- return null;
187
- }
188
- const projection = await getProjectionFromKeys(geoKeys, 'ProjectedCSTypeGeoKey', 'ProjLinearUnitsGeoKey', loadMissingProjection);
189
- if (projection) {
190
- return projection;
191
- }
192
- return await getProjectionFromKeys(geoKeys, 'GeographicTypeGeoKey', 'GeogAngularUnitsGeoKey', loadMissingProjection);
193
- }
194
- /**
195
- * @param {GeoTIFF|MultiGeoTIFF} tiff A GeoTIFF.
196
- * @return {Promise<Array<GeoTIFFImage>>} Resolves to a list of images.
197
- */
198
- function getImagesForTIFF(tiff) {
199
- return tiff.getImageCount().then(function (count) {
200
- const requests = new Array(count);
201
- for (let i = 0; i < count; ++i) {
202
- requests[i] = tiff.getImage(i);
203
- }
204
- return Promise.all(requests);
205
- });
206
- }
207
- /**
208
- * @param {SourceInfo} source The GeoTIFF source.
209
- * @param {Object} options Options for the GeoTIFF source.
210
- * @return {Promise<Array<GeoTIFFImage>>} Resolves to a list of images.
211
- */
212
- function getImagesForSource(source, options) {
213
- let request;
214
- if (source.blob) {
215
- request = tiffFromBlob(source.blob);
216
- }
217
- else if (source.overviews) {
218
- request = tiffFromUrls(source.url, source.overviews, options);
219
- }
220
- else {
221
- request = tiffFromUrl(source.url, options);
222
- }
223
- return request.then(getImagesForTIFF);
224
- }
225
- /**
226
- * @param {number|Array<number>|Array<Array<number>>} expected Expected value.
227
- * @param {number|Array<number>|Array<Array<number>>} got Actual value.
228
- * @param {number} tolerance Accepted tolerance in fraction of expected between expected and got.
229
- * @param {string} message The error message.
230
- * @param {function(Error):void} rejector A function to be called with any error.
231
- */
232
- function assertEqual(expected, got, tolerance, message, rejector) {
233
- if (Array.isArray(expected)) {
234
- const length = expected.length;
235
- if (!Array.isArray(got) || length != got.length) {
236
- const error = new Error(message);
237
- rejector(error);
238
- throw error;
239
- }
240
- for (let i = 0; i < length; ++i) {
241
- assertEqual(expected[i], got[i], tolerance, message, rejector);
242
- }
243
- return;
244
- }
245
- got = /** @type {number} */ (got);
246
- if (Math.abs(expected - got) > tolerance * expected) {
247
- throw new Error(message);
248
- }
249
- }
250
- /**
251
- * @param {Array} array The data array.
252
- * @return {number} The minimum value.
253
- */
254
- function getMinForDataType(array) {
255
- if (array instanceof Int8Array) {
256
- return -128;
257
- }
258
- if (array instanceof Int16Array) {
259
- return -32768;
260
- }
261
- if (array instanceof Int32Array) {
262
- return -2147483648;
263
- }
264
- if (array instanceof Float32Array) {
265
- return 1.2e-38;
266
- }
267
- return 0;
268
- }
269
- /**
270
- * @param {Array} array The data array.
271
- * @return {number} The maximum value.
272
- */
273
- function getMaxForDataType(array) {
274
- if (array instanceof Int8Array) {
275
- return 127;
276
- }
277
- if (array instanceof Uint8Array) {
278
- return 255;
279
- }
280
- if (array instanceof Uint8ClampedArray) {
281
- return 255;
282
- }
283
- if (array instanceof Int16Array) {
284
- return 32767;
285
- }
286
- if (array instanceof Uint16Array) {
287
- return 65535;
288
- }
289
- if (array instanceof Int32Array) {
290
- return 2147483647;
291
- }
292
- if (array instanceof Uint32Array) {
293
- return 4294967295;
294
- }
295
- if (array instanceof Float32Array) {
296
- return 3.4e38;
297
- }
298
- return 255;
299
- }
300
- /**
301
- * @typedef {Object} GeoTIFFSourceOptions
302
- * @property {boolean} [forceXHR=false] Whether to force the usage of the browsers XMLHttpRequest API.
303
- * @property {Object<string, string>} [headers] additional key-value pairs of headers to be passed with each request. Key is the header name, value the header value.
304
- * @property {string} [credentials] How credentials shall be handled. See
305
- * https://developer.mozilla.org/en-US/docs/Web/API/fetch for reference and possible values
306
- * @property {number} [maxRanges] The maximum amount of ranges to request in a single multi-range request.
307
- * By default only a single range is used.
308
- * @property {boolean} [allowFullFile=false] Whether or not a full file is accepted when only a portion is
309
- * requested. Only use this when you know the source image to be small enough to fit in memory.
310
- * @property {number} [blockSize=65536] The block size to use.
311
- * @property {number} [cacheSize=100] The number of blocks that shall be held in a LRU cache.
312
- */
313
- /**
314
- * @typedef {Object} Options
315
- * @property {Array<SourceInfo>} sources List of information about GeoTIFF sources.
316
- * Multiple sources can be combined when their resolution sets are equal after applying a scale.
317
- * The list of sources defines a mapping between input bands as they are read from each GeoTIFF and
318
- * the output bands that are provided by data tiles. To control which bands to read from each GeoTIFF,
319
- * use the {@link import("./GeoTIFF.js").SourceInfo bands} property. If, for example, you specify two
320
- * sources, one with 3 bands and {@link import("./GeoTIFF.js").SourceInfo nodata} configured, and
321
- * another with 1 band, the resulting data tiles will have 5 bands: 3 from the first source, 1 alpha
322
- * band from the first source, and 1 band from the second source.
323
- * @property {GeoTIFFSourceOptions} [sourceOptions] Additional options to be passed to [geotiff.js](https://geotiffjs.github.io/geotiff.js/module-geotiff.html)'s `fromUrl` or `fromUrls` methods.
324
- * @property {true|false|'auto'} [convertToRGB=false] By default, bands from the sources are read as-is. When
325
- * reading GeoTIFFs with the purpose of displaying them as RGB images, setting this to `true` will
326
- * convert other color spaces (YCbCr, CMYK) to RGB. Setting the option to `'auto'` will make it so CMYK, YCbCr,
327
- * CIELab, and ICCLab images will automatically be converted to RGB.
328
- * @property {boolean} [normalize=true] By default, the source data is normalized to values between
329
- * 0 and 1 with scaling factors based on the raster statistics or `min` and `max` properties of each source.
330
- * If instead you want to work with the raw values in a style expression, set this to `false`. Setting this option
331
- * to `false` will make it so any `min` and `max` properties on sources are ignored.
332
- * @property {import("ol/proj.js").ProjectionLike} [projection] Source projection. If not provided, the GeoTIFF metadata
333
- * will be read for projection information.
334
- * @property {boolean} [loadMissingProjection=false] Whether to attempt to load missing projection definitions.
335
- * Uses the configured EPSG lookup function, which can be set with {@link module:ol/proj/proj4.setEPSGLookup}.
336
- * @property {number} [transition=250] Duration of the opacity transition for rendering.
337
- * To disable the opacity transition, pass `transition: 0`.
338
- * @property {boolean} [wrapX=false] Render tiles beyond the tile grid extent.
339
- * @property {boolean} [interpolate=true] Use interpolated values when resampling. By default,
340
- * the linear interpolation is used to resample the data. If false, nearest neighbor is used.
341
- */
342
- /**
343
- * @classdesc
344
- * A source for working with GeoTIFF data.
345
- * **Note for users of the full build**: The `GeoTIFF` source requires the
346
- * [geotiff.js](https://github.com/geotiffjs/geotiff.js) library to be loaded as well.
347
- *
348
- * @api
349
- */
350
- class GeoTIFFSource extends DataTile {
351
- /**
352
- * @param {Options} options Data tile options.
353
- */
354
- constructor(options) {
355
- super({
356
- state: 'loading',
357
- tileGrid: null,
358
- projection: options.projection || null,
359
- transition: options.transition,
360
- interpolate: options.interpolate !== false,
361
- wrapX: options.wrapX,
362
- });
363
- /**
364
- * @type {Array<SourceInfo>}
365
- * @private
366
- */
367
- this.sourceInfo_ = options.sources;
368
- const numSources = this.sourceInfo_.length;
369
- /**
370
- * @type {Object}
371
- * @private
372
- */
373
- this.sourceOptions_ = options.sourceOptions;
374
- /**
375
- * @type {Array<Array<GeoTIFFImage>>}
376
- * @private
377
- */
378
- this.sourceImagery_ = new Array(numSources);
379
- /**
380
- * @type {Array<Array<GeoTIFFImage>>}
381
- * @private
382
- */
383
- this.sourceMasks_ = new Array(numSources);
384
- /**
385
- * @type {Array<number>}
386
- * @private
387
- */
388
- this.resolutionFactors_ = new Array(numSources);
389
- /**
390
- * @type {Array<number>}
391
- * @private
392
- */
393
- this.samplesPerPixel_;
394
- /**
395
- * @type {Array<Array<number>>}
396
- * @private
397
- */
398
- this.nodataValues_;
399
- /**
400
- * @type {Array<Array<GDALMetadata>>}
401
- * @private
402
- */
403
- this.metadata_;
404
- /**
405
- * @type {boolean}
406
- * @private
407
- */
408
- this.normalize_ = options.normalize !== false;
409
- /**
410
- * @type {boolean}
411
- * @private
412
- */
413
- this.addAlpha_ = false;
414
- /**
415
- * @type {Error}
416
- * @private
417
- */
418
- this.error_ = null;
419
- /**
420
- * @type {true|false|'auto'}
421
- * @private
422
- */
423
- this.convertToRGB_ = options.convertToRGB || false;
424
- /**
425
- * @type {boolean}
426
- * @private
427
- */
428
- this.loadMissingProjection_ = options.loadMissingProjection || false;
429
- this.setKey(this.sourceInfo_.map((source) => source.url).join(','));
430
- const self = this;
431
- const requests = new Array(numSources);
432
- for (let i = 0; i < numSources; ++i) {
433
- requests[i] = getImagesForSource(this.sourceInfo_[i], this.sourceOptions_);
434
- }
435
- Promise.all(requests)
436
- .then(function (sources) {
437
- return self.configure_(sources);
438
- })
439
- .catch(function (error) {
440
- logError(error);
441
- self.error_ = error;
442
- self.setState('error');
443
- });
444
- }
445
- /**
446
- * @return {Error} A source loading error. When the source state is `error`, use this function
447
- * to get more information about the error. To debug a faulty configuration, you may want to use
448
- * a listener like
449
- * ```js
450
- * geotiffSource.on('change', () => {
451
- * if (geotiffSource.getState() === 'error') {
452
- * console.error(geotiffSource.getError());
453
- * }
454
- * });
455
- * ```
456
- */
457
- getError() {
458
- return this.error_;
459
- }
460
- /**
461
- * Determine the projection of the images in this GeoTIFF.
462
- * The default implementation looks at the ProjectedCSTypeGeoKey and the GeographicTypeGeoKey
463
- * of each image in turn.
464
- * You can override this method in a subclass to support more projections.
465
- *
466
- * @async
467
- * @param {Array<Array<GeoTIFFImage>>} sources Each source is a list of images
468
- * from a single GeoTIFF.
469
- */
470
- async determineProjection(sources) {
471
- const firstSource = sources[0];
472
- for (let i = firstSource.length - 1; i >= 0; --i) {
473
- const image = firstSource[i];
474
- const projection = await getProjection(image, this.loadMissingProjection_);
475
- if (projection) {
476
- this.projection = projection;
477
- break;
478
- }
479
- }
480
- }
481
- /**
482
- * Determine any transform matrix for the images in this GeoTIFF.
483
- *
484
- * @param {Array<Array<GeoTIFFImage>>} sources Each source is a list of images
485
- * from a single GeoTIFF.
486
- */
487
- determineTransformMatrix(sources) {
488
- const firstSource = sources[0];
489
- for (let i = firstSource.length - 1; i >= 0; --i) {
490
- const image = firstSource[i];
491
- const modelTransformation = image.fileDirectory.ModelTransformation;
492
- if (modelTransformation) {
493
- // eslint-disable-next-line no-unused-vars
494
- const [a, b, c, d, e, f, g, h] = modelTransformation;
495
- const matrix = multiplyTransform(multiplyTransform([
496
- 1 / Math.sqrt(a * a + e * e),
497
- 0,
498
- 0,
499
- -1 / Math.sqrt(b * b + f * f),
500
- d,
501
- h,
502
- ], [a, e, b, f, 0, 0]), [1, 0, 0, 1, -d, -h]);
503
- this.transformMatrix = matrix;
504
- this.addAlpha_ = true;
505
- break;
506
- }
507
- }
508
- }
509
- /**
510
- * Configure the tile grid based on images within the source GeoTIFFs. Each GeoTIFF
511
- * must have the same internal tiled structure.
512
- * @param {Array<Array<GeoTIFFImage>>} sources Each source is a list of images
513
- * from a single GeoTIFF.
514
- * @private
515
- * @async
516
- */
517
- async configure_(sources) {
518
- let extent;
519
- let origin;
520
- let commonRenderTileSizes;
521
- let commonSourceTileSizes;
522
- let resolutions;
523
- const samplesPerPixel = new Array(sources.length);
524
- const nodataValues = new Array(sources.length);
525
- const metadata = new Array(sources.length);
526
- let minZoom = 0;
527
- const sourceCount = sources.length;
528
- for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
529
- const images = [];
530
- const masks = [];
531
- sources[sourceIndex].forEach((item) => {
532
- if (isMask(item)) {
533
- masks.push(item);
534
- }
535
- else {
536
- images.push(item);
537
- }
538
- });
539
- const imageCount = images.length;
540
- if (masks.length > 0 && masks.length !== imageCount) {
541
- throw new Error(`Expected one mask per image found ${masks.length} masks and ${imageCount} images`);
542
- }
543
- let sourceExtent;
544
- let sourceOrigin;
545
- const sourceTileSizes = new Array(imageCount);
546
- const renderTileSizes = new Array(imageCount);
547
- const sourceResolutions = new Array(imageCount);
548
- nodataValues[sourceIndex] = new Array(imageCount);
549
- metadata[sourceIndex] = new Array(imageCount);
550
- for (let imageIndex = 0; imageIndex < imageCount; ++imageIndex) {
551
- const image = images[imageIndex];
552
- const nodataValue = image.getGDALNoData();
553
- metadata[sourceIndex][imageIndex] = image.getGDALMetadata(0);
554
- nodataValues[sourceIndex][imageIndex] = nodataValue;
555
- const wantedSamples = this.sourceInfo_[sourceIndex].bands;
556
- samplesPerPixel[sourceIndex] = wantedSamples
557
- ? wantedSamples.length
558
- : image.getSamplesPerPixel();
559
- const level = imageCount - (imageIndex + 1);
560
- if (!sourceExtent) {
561
- sourceExtent = getBoundingBox(image);
562
- }
563
- if (!sourceOrigin) {
564
- sourceOrigin = getOrigin(image);
565
- }
566
- const imageResolutions = getResolutions(image, images[0]);
567
- sourceResolutions[level] = imageResolutions[0];
568
- const sourceTileSize = [image.getTileWidth(), image.getTileHeight()];
569
- // request larger blocks for untiled layouts
570
- if (sourceTileSize[0] !== sourceTileSize[1] &&
571
- sourceTileSize[1] < defaultTileSize) {
572
- sourceTileSize[0] = defaultTileSize;
573
- sourceTileSize[1] = defaultTileSize;
574
- }
575
- sourceTileSizes[level] = sourceTileSize;
576
- const aspectRatio = imageResolutions[0] / Math.abs(imageResolutions[1]);
577
- renderTileSizes[level] = [
578
- sourceTileSize[0],
579
- sourceTileSize[1] / aspectRatio,
580
- ];
581
- }
582
- if (!extent) {
583
- extent = sourceExtent;
584
- }
585
- else {
586
- getIntersection(extent, sourceExtent, extent);
587
- }
588
- if (!origin) {
589
- origin = sourceOrigin;
590
- }
591
- else {
592
- const message = `Origin mismatch for source ${sourceIndex}, got [${sourceOrigin}] but expected [${origin}]`;
593
- assertEqual(origin, sourceOrigin, 0, message, this.viewRejector);
594
- }
595
- if (!resolutions) {
596
- resolutions = sourceResolutions;
597
- this.resolutionFactors_[sourceIndex] = 1;
598
- }
599
- else {
600
- if (resolutions.length - minZoom > sourceResolutions.length) {
601
- minZoom = resolutions.length - sourceResolutions.length;
602
- }
603
- const resolutionFactor = resolutions[resolutions.length - 1] /
604
- sourceResolutions[sourceResolutions.length - 1];
605
- this.resolutionFactors_[sourceIndex] = resolutionFactor;
606
- const scaledSourceResolutions = sourceResolutions.map((resolution) => (resolution *= resolutionFactor));
607
- const message = `Resolution mismatch for source ${sourceIndex}, got [${scaledSourceResolutions}] but expected [${resolutions}]`;
608
- assertEqual(resolutions.slice(minZoom, resolutions.length), scaledSourceResolutions, 0.02, message, this.viewRejector);
609
- }
610
- if (!commonRenderTileSizes) {
611
- commonRenderTileSizes = renderTileSizes;
612
- }
613
- else {
614
- assertEqual(commonRenderTileSizes.slice(minZoom, commonRenderTileSizes.length), renderTileSizes, 0.01, `Tile size mismatch for source ${sourceIndex}`, this.viewRejector);
615
- }
616
- if (!commonSourceTileSizes) {
617
- commonSourceTileSizes = sourceTileSizes;
618
- }
619
- else {
620
- assertEqual(commonSourceTileSizes.slice(minZoom, commonSourceTileSizes.length), sourceTileSizes, 0, `Tile size mismatch for source ${sourceIndex}`, this.viewRejector);
621
- }
622
- this.sourceImagery_[sourceIndex] = images.reverse();
623
- this.sourceMasks_[sourceIndex] = masks.reverse();
624
- }
625
- for (let i = 0, ii = this.sourceImagery_.length; i < ii; ++i) {
626
- const sourceImagery = this.sourceImagery_[i];
627
- while (sourceImagery.length < resolutions.length) {
628
- sourceImagery.unshift(undefined);
629
- }
630
- }
631
- if (!this.getProjection()) {
632
- await this.determineProjection(sources);
633
- }
634
- this.determineTransformMatrix(sources);
635
- this.samplesPerPixel_ = samplesPerPixel;
636
- this.nodataValues_ = nodataValues;
637
- this.metadata_ = metadata;
638
- // decide if we need to add an alpha band to handle nodata
639
- outer: for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
640
- // option 1: source is configured with a nodata value
641
- if (this.sourceInfo_[sourceIndex].nodata !== undefined) {
642
- this.addAlpha_ = true;
643
- break;
644
- }
645
- if (this.sourceMasks_[sourceIndex].length) {
646
- this.addAlpha_ = true;
647
- break;
648
- }
649
- const values = nodataValues[sourceIndex];
650
- // option 2: check image metadata for limited bands
651
- const bands = this.sourceInfo_[sourceIndex].bands;
652
- if (bands) {
653
- for (let i = 0; i < bands.length; ++i) {
654
- if (values[bands[i] - 1] !== null) {
655
- this.addAlpha_ = true;
656
- break outer;
657
- }
658
- }
659
- continue;
660
- }
661
- // option 3: check image metadata for all bands
662
- for (let imageIndex = 0; imageIndex < values.length; ++imageIndex) {
663
- if (values[imageIndex] !== null) {
664
- this.addAlpha_ = true;
665
- break outer;
666
- }
667
- }
668
- }
669
- let bandCount = this.addAlpha_ ? 1 : 0;
670
- for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
671
- bandCount += samplesPerPixel[sourceIndex];
672
- }
673
- this.bandCount = bandCount;
674
- const tileGrid = new TileGrid({
675
- extent: extent,
676
- minZoom: minZoom,
677
- origin: origin,
678
- resolutions: resolutions,
679
- tileSizes: commonRenderTileSizes,
680
- });
681
- this.tileGrid = tileGrid;
682
- this.setTileSizes(commonSourceTileSizes);
683
- this.setLoader(this.loadTile_.bind(this));
684
- this.setState('ready');
685
- const zoom = 1;
686
- if (resolutions.length === 2) {
687
- resolutions = [resolutions[0], resolutions[1], resolutions[1] / 2];
688
- }
689
- else if (resolutions.length === 1) {
690
- resolutions = [resolutions[0] * 2, resolutions[0], resolutions[0] / 2];
691
- }
692
- let viewExtent = extent;
693
- if (this.transformMatrix) {
694
- const matrix = makeInverse(createMatrix(), this.transformMatrix.slice());
695
- const transformFn = createTransformFromCoordinateTransform((input) => applyMatrix(matrix, input));
696
- viewExtent = applyTransform(extent, transformFn);
697
- }
698
- this.viewResolver({
699
- showFullExtent: true,
700
- projection: this.projection,
701
- resolutions: resolutions,
702
- center: toUserCoordinate(getCenter(viewExtent), this.projection),
703
- extent: toUserExtent(viewExtent, this.projection),
704
- zoom: zoom,
705
- });
706
- }
707
- /**
708
- * @param {number} z The z tile index.
709
- * @param {number} x The x tile index.
710
- * @param {number} y The y tile index.
711
- * @param {import('ol/source/DataTile.js').LoaderOptions} options The loader options.
712
- * @return {Promise} The composed tile data.
713
- * @private
714
- */
715
- loadTile_(z, x, y, options) {
716
- const sourceTileSize = this.getTileSize(z);
717
- const sourceCount = this.sourceImagery_.length;
718
- const requests = new Array(sourceCount * 2);
719
- const nodataValues = this.nodataValues_;
720
- const sourceInfo = this.sourceInfo_;
721
- const pool = getWorkerPool();
722
- for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
723
- const source = sourceInfo[sourceIndex];
724
- const resolutionFactor = this.resolutionFactors_[sourceIndex];
725
- const pixelBounds = [
726
- Math.round(x * (sourceTileSize[0] * resolutionFactor)),
727
- Math.round(y * (sourceTileSize[1] * resolutionFactor)),
728
- Math.round((x + 1) * (sourceTileSize[0] * resolutionFactor)),
729
- Math.round((y + 1) * (sourceTileSize[1] * resolutionFactor)),
730
- ];
731
- const image = this.sourceImagery_[sourceIndex][z];
732
- let samples;
733
- if (source.bands) {
734
- samples = source.bands.map(function (bandNumber) {
735
- return bandNumber - 1;
736
- });
737
- }
738
- /** @type {number|Array<number>} */
739
- let fillValue;
740
- if ('nodata' in source && source.nodata !== null) {
741
- fillValue = source.nodata;
742
- }
743
- else {
744
- if (!samples) {
745
- fillValue = nodataValues[sourceIndex];
746
- }
747
- else {
748
- fillValue = samples.map(function (sampleIndex) {
749
- return nodataValues[sourceIndex][sampleIndex];
750
- });
751
- }
752
- }
753
- const readOptions = {
754
- window: pixelBounds,
755
- width: sourceTileSize[0],
756
- height: sourceTileSize[1],
757
- samples: samples,
758
- fillValue: fillValue,
759
- pool: pool,
760
- interleave: false,
761
- signal: options.signal,
762
- };
763
- if (readRGB(this.convertToRGB_, image)) {
764
- requests[sourceIndex] = image.readRGB(readOptions);
765
- }
766
- else {
767
- requests[sourceIndex] = image.readRasters(readOptions);
768
- }
769
- // requests after `sourceCount` are for mask data (if any)
770
- const maskIndex = sourceCount + sourceIndex;
771
- const mask = this.sourceMasks_[sourceIndex][z];
772
- if (!mask) {
773
- requests[maskIndex] = Promise.resolve(null);
774
- continue;
775
- }
776
- requests[maskIndex] = mask.readRasters({
777
- window: pixelBounds,
778
- width: sourceTileSize[0],
779
- height: sourceTileSize[1],
780
- samples: [0],
781
- pool: pool,
782
- interleave: false,
783
- });
784
- }
785
- return Promise.all(requests)
786
- .then(this.composeTile_.bind(this, sourceTileSize))
787
- .catch(function (error) {
788
- logError(error);
789
- throw error;
790
- });
791
- }
792
- /**
793
- * @param {import("ol/size.js").Size} sourceTileSize The source tile size.
794
- * @param {Array} sourceSamples The source samples.
795
- * @return {import("ol/DataTile.js").Data} The composed tile data.
796
- * @private
797
- */
798
- composeTile_(sourceTileSize, sourceSamples) {
799
- const metadata = this.metadata_;
800
- const sourceInfo = this.sourceInfo_;
801
- const sourceCount = this.sourceImagery_.length;
802
- const bandCount = this.bandCount;
803
- const samplesPerPixel = this.samplesPerPixel_;
804
- const nodataValues = this.nodataValues_;
805
- const normalize = this.normalize_;
806
- const addAlpha = this.addAlpha_;
807
- const pixelCount = sourceTileSize[0] * sourceTileSize[1];
808
- const dataLength = pixelCount * bandCount;
809
- /** @type {Uint8Array|Float32Array} */
810
- let data;
811
- if (normalize) {
812
- data = new Uint8Array(dataLength);
813
- }
814
- else {
815
- data = new Float32Array(dataLength);
816
- }
817
- let dataIndex = 0;
818
- for (let pixelIndex = 0; pixelIndex < pixelCount; ++pixelIndex) {
819
- let transparent = addAlpha;
820
- for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
821
- const source = sourceInfo[sourceIndex];
822
- let min = source.min;
823
- let max = source.max;
824
- let gain, bias;
825
- if (normalize) {
826
- const stats = metadata[sourceIndex][0];
827
- if (min === undefined) {
828
- if (stats && STATISTICS_MINIMUM in stats) {
829
- min = parseFloat(stats[STATISTICS_MINIMUM]);
830
- }
831
- else {
832
- min = getMinForDataType(sourceSamples[sourceIndex][0]);
833
- }
834
- }
835
- if (max === undefined) {
836
- if (stats && STATISTICS_MAXIMUM in stats) {
837
- max = parseFloat(stats[STATISTICS_MAXIMUM]);
838
- }
839
- else {
840
- max = getMaxForDataType(sourceSamples[sourceIndex][0]);
841
- }
842
- }
843
- gain = 255 / (max - min);
844
- bias = -min * gain;
845
- }
846
- for (let sampleIndex = 0; sampleIndex < samplesPerPixel[sourceIndex]; ++sampleIndex) {
847
- const sourceValue = sourceSamples[sourceIndex][sampleIndex][pixelIndex];
848
- let value;
849
- if (normalize) {
850
- value = clamp(gain * sourceValue + bias, 0, 255);
851
- }
852
- else {
853
- value = sourceValue;
854
- }
855
- if (!addAlpha) {
856
- data[dataIndex] = value;
857
- }
858
- else {
859
- let nodata = source.nodata;
860
- if (nodata === undefined) {
861
- let bandIndex;
862
- if (source.bands) {
863
- bandIndex = source.bands[sampleIndex] - 1;
864
- }
865
- else {
866
- bandIndex = sampleIndex;
867
- }
868
- nodata = nodataValues[sourceIndex][bandIndex];
869
- }
870
- const nodataIsNaN = isNaN(nodata);
871
- if ((!nodataIsNaN && sourceValue !== nodata) ||
872
- (nodataIsNaN && !isNaN(sourceValue))) {
873
- transparent = false;
874
- data[dataIndex] = value;
875
- }
876
- }
877
- dataIndex++;
878
- }
879
- if (!transparent) {
880
- const maskIndex = sourceCount + sourceIndex;
881
- const mask = sourceSamples[maskIndex];
882
- if (mask && !mask[0][pixelIndex]) {
883
- transparent = true;
884
- }
885
- }
886
- }
887
- if (addAlpha) {
888
- if (!transparent) {
889
- data[dataIndex] = 255;
890
- }
891
- dataIndex++;
892
- }
893
- }
894
- return data;
895
- }
896
- }
897
- /**
898
- * Get a promise for view properties based on the source. Use the result of this function
899
- * as the `view` option in a map constructor.
900
- *
901
- * const source = new GeoTIFF(options);
902
- *
903
- * const map = new Map({
904
- * target: 'map',
905
- * layers: [
906
- * new TileLayer({
907
- * source: source,
908
- * }),
909
- * ],
910
- * view: source.getView(),
911
- * });
912
- *
913
- * @function
914
- * @return {Promise<import("ol/View.js").ViewOptions>} A promise for view-related properties.
915
- * @api
916
- *
917
- */
918
- GeoTIFFSource.prototype.getView;
919
- export default GeoTIFFSource;
920
- //# sourceMappingURL=GeoTIFF.js.map