geotiff
Version:
GeoTIFF image decoding in JavaScript
739 lines • 31.2 kB
JavaScript
/** @module geotiff */
import GeoTIFFImage from './geotiffimage.js';
import DataView64 from './dataview64.js';
import DataSlice from './dataslice.js';
import Pool from './pool.js';
import { makeRemoteSource, makeCustomSource } from './source/remote.js';
import { makeBufferSource } from './source/arraybuffer.js';
import { makeFileReaderSource } from './source/filereader.js';
import { makeFileSource } from './source/file.js';
import { BaseClient, BaseResponse } from './source/client/base.js';
import { ImageFileDirectoryParser } from './imagefiledirectory.js';
import { fieldTypes, getFieldTypeSize, registerTag } from './globals.js';
import { writeGeotiff } from './geotiffwriter.js';
import * as globals from './globals.js';
import * as rgb from './rgb.js';
import { getDecoder, addDecoder } from './compression/index.js';
import { setLogger } from './logging.js';
/** @import { BaseSource } from './source/basesource.js' */
export { globals };
export { registerTag };
export { rgb };
export { default as BaseDecoder } from './compression/basedecoder.js';
export { getDecoder, addDecoder };
export { setLogger };
export { ImageFileDirectory } from './imagefiledirectory.js';
/**
* @typedef {Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array | Float32Array | Float64Array}
* TypedArray
*/
/**
* @typedef {{ height:number, width: number }} Dimensions
*/
/**
* The autogenerated docs are a little confusing here. The effective type is:
*
* `TypedArray & { height: number; width: number}`
* @typedef {TypedArray & Dimensions} TypedArrayWithDimensions
*/
/**
* The autogenerated docs are a little confusing here. The effective type is:
*
* `TypedArray[] & { height: number; width: number}`
* @typedef {TypedArray[] & Dimensions} TypedArrayArrayWithDimensions
*/
/**
* @typedef {Object} GeotiffWriterMetadata
* @property {number | number[]} [ImageWidth]
* @property {number | number[]} [ImageLength]
* @property {number} [width]
* @property {number} [height]
* @property {number | number[]} [BitsPerSample]
* @property {number | number[]} [Compression]
* @property {number | number[]} [PlanarConfiguration]
* @property {number | number[]} [ExtraSamples]
* @property {number | number[]} [PhotometricInterpretation]
* @property {number | number[]} [SamplesPerPixel]
* @property {number | number[]} [StripByteCounts]
* @property {number[]} [ModelPixelScale]
* @property {number[]} [ModelTransformation]
* @property {number[]} [ModelTiepoint]
* @property {number[]} [GeoKeyDirectory]
* @property {string} [GeoAsciiParams]
* @property {number[]} [GeoDoubleParams]
* @property {number | number[]} [Orientation]
* @property {number | number[]} [ResolutionUnit]
* @property {number | number[]} [XPosition]
* @property {number | number[]} [YPosition]
* @property {number | number[]} [RowsPerStrip]
* @property {number[]} [SampleFormat]
* @property {number | number[]} [TileWidth]
* @property {number | number[]} [TileLength]
* @property {number[]} [TileOffsets]
* @property {number[]} [TileByteCounts]
* @property {string} [GDAL_NODATA]
* @property {number | number[]} [GeographicTypeGeoKey]
* @property {number | number[]} [ProjectedCSTypeGeoKey]
* @property {string} [GeogCitationGeoKey]
* @property {string} [GTCitationGeoKey]
* @property {number | number[]} [GTModelTypeGeoKey]
* @property {number | number[]} [GTRasterTypeGeoKey]
*/
/**
* The autogenerated docs are a little confusing here. The effective type is:
*
* `(TypedArray | TypedArray[]) & { height: number; width: number}`
* @typedef {TypedArrayWithDimensions | TypedArrayArrayWithDimensions} ReadRasterResult
*/
/**
* @typedef {Object} DecoderWorker
* Use the {@link Pool.bindParameters} method to get a decoder worker for
* a specific compression and its parameters.
*
* @property {(buffer: ArrayBufferLike) => Promise<ArrayBufferLike>} decode
* A function that takes a compressed buffer and returns a promise resolving to the decoded buffer.
*/
/**
* @typedef {Object} ReadRastersOptions
* @property {Array<number>} [window] the subset to read data from in pixels. Whole window if not specified.
* @property {Array<number>} [samples] the selection of samples to read from. Default is all samples.
* All samples if not specified.
* @property {Pool|null} [pool=null] The optional decoder pool to use.
* @property {number} [width] The desired width of the output. When the width is not the
* same as the images, resampling will be performed.
* @property {number} [height] The desired height of the output. When the width is not the
* same as the images, resampling will be performed.
* @property {string} [resampleMethod='nearest'] The desired resampling method.
* @property {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @property {number|number[]} [fillValue] The value to use for parts of the image
* outside of the images extent. When multiple samples are requested and `interleave` is
* `false`, an array of fill values can be passed.
* @property {boolean|true|false} [interleave] whether the data shall be read
* in one single array or separate arrays.
*/
/**
* @typedef {Object} ReadRGBOptions
* @property {Array<number>} [window] the subset to read data from in pixels. Whole window if not specified.
* @property {Pool|null} [pool=null] The optional decoder pool to use.
* @property {number} [width] The desired width of the output. When the width is no the
* same as the images, resampling will be performed.
* @property {number} [height] The desired height of the output. When the width is no the
* same as the images, resampling will be performed.
* @property {string} [resampleMethod='nearest'] The desired resampling method.
* @property {boolean} [enableAlpha=false] Enable reading alpha channel if present.
* @property {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @property {boolean|true|false} [interleave] whether the data shall be read
* in one single array or separate arrays.
*/
/**
* @typedef {Object} BlockedSourceOptions
* @property {number} [blockSize] Block size for a BlockedSource.
* @property {number} [cacheSize=100] The number of blocks to cache.
*/
/**
* @typedef {Object} RemoteSourceOptions
* @property {Record<string, string>} [headers={}] Additional headers to add to each request
* @property {number} [maxRanges=0] Maximum number of ranges to request in a single HTTP request. 0 means no multi-range requests.
* @property {boolean} [allowFullFile=false] Whether to allow full file responses when requesting ranges
* @property {boolean} [forceXHR=false] When the Fetch API would be used, force using XMLHttpRequest instead.
*/
/**
* @overload
* @param {DataSlice} dataSlice
* @param {0x0002} fieldType
* @param {number} count
* @param {number} offset
* @returns {string}
*/
/**
* @param {DataSlice} dataSlice
* @param {import('./globals.js').FieldType} fieldType
* @param {number} count
* @param {number} offset
* @returns {TypedArray|Array<number>|string}
*/
function getValues(dataSlice, fieldType, count, offset) {
/** @type {TypedArray|Array<number>|null} */
let values = null;
let readMethod = null;
const fieldTypeLength = getFieldTypeSize(fieldType);
switch (fieldType) {
case fieldTypes.BYTE:
case fieldTypes.ASCII:
case fieldTypes.UNDEFINED:
values = new Uint8Array(count);
readMethod = dataSlice.readUint8;
break;
case fieldTypes.SBYTE:
values = new Int8Array(count);
readMethod = dataSlice.readInt8;
break;
case fieldTypes.SHORT:
values = new Uint16Array(count);
readMethod = dataSlice.readUint16;
break;
case fieldTypes.SSHORT:
values = new Int16Array(count);
readMethod = dataSlice.readInt16;
break;
case fieldTypes.LONG:
case fieldTypes.IFD:
values = new Uint32Array(count);
readMethod = dataSlice.readUint32;
break;
case fieldTypes.SLONG:
values = new Int32Array(count);
readMethod = dataSlice.readInt32;
break;
case fieldTypes.LONG8:
case fieldTypes.IFD8:
values = new Array(count);
readMethod = dataSlice.readUint64;
break;
case fieldTypes.SLONG8:
values = new Array(count);
readMethod = dataSlice.readInt64;
break;
case fieldTypes.RATIONAL:
values = new Uint32Array(count * 2);
readMethod = dataSlice.readUint32;
break;
case fieldTypes.SRATIONAL:
values = new Int32Array(count * 2);
readMethod = dataSlice.readInt32;
break;
case fieldTypes.FLOAT:
values = new Float32Array(count);
readMethod = dataSlice.readFloat32;
break;
case fieldTypes.DOUBLE:
values = new Float64Array(count);
readMethod = dataSlice.readFloat64;
break;
default:
// will throw below
}
if (values === null || readMethod === null) {
throw new RangeError(`Invalid field type: ${fieldType}`);
}
// normal fields
if (!(fieldType === fieldTypes.RATIONAL || fieldType === fieldTypes.SRATIONAL)) {
for (let i = 0; i < count; ++i) {
values[i] = readMethod.call(dataSlice, offset + (i * fieldTypeLength));
}
}
else { // RATIONAL or SRATIONAL
for (let i = 0; i < count; i += 2) {
values[i] = readMethod.call(dataSlice, offset + (i * fieldTypeLength));
values[i + 1] = readMethod.call(dataSlice, offset + ((i * fieldTypeLength) + 4));
}
}
if (fieldType === fieldTypes.ASCII) {
return new TextDecoder('utf-8').decode(/** @type {Uint8Array} */ (values));
}
return values;
}
/**
* Error class for cases when an IFD index was requested, that does not exist
* in the file.
*/
class GeoTIFFImageIndexError extends Error {
/**
* @param {number} index
*/
constructor(index) {
super(`No image at index ${index}`);
this.index = index;
}
}
class GeoTIFFBase {
/**
* @param {number} [_index=0] the index of the image to return.
* @returns {Promise<GeoTIFFImage>} the image at the given index
*/
async getImage(_index = 0) {
throw new Error('Not implemented');
}
/**
* @returns {Promise<number>} the number of internal subfile images
*/
async getImageCount() {
throw new Error('Not implemented');
}
/**
* @typedef {Object} ReadRastersWindowOptions
* @property {number} [resX] desired Y resolution (world units per pixel)
* @property {number} [resY] desired X resolution (world units per pixel)
* @property {Array<number>} [bbox] the subset to read data from in
* geographical coordinates. Whole image if not specified.
*/
/**
* (experimental) Reads raster data from the best fitting image. This function uses
* the image with the lowest resolution that is still a higher resolution than the
* requested resolution.
* When specified, the `bbox` option is translated to the `window` option and the
* `resX` and `resY` to `width` and `height` respectively.
* Then, the [readRasters]{@link GeoTIFFImage#readRasters} method of the selected
* image is called and the result returned.
* @see GeoTIFFImage.readRasters
* @param {ReadRastersOptions & ReadRastersWindowOptions} options optional parameters
* @returns {Promise<ReadRasterResult>} the decoded array(s), with `height` and `width`, as a promise
*/
async readRasters(options = {}) {
const { window: imageWindow, width, height } = options;
let { resX, resY, bbox } = options;
const firstImage = await this.getImage();
let usedImage = firstImage;
const imageCount = await this.getImageCount();
const imgBBox = firstImage.getBoundingBox();
if (imageWindow && bbox) {
throw new Error('Both "bbox" and "window" passed.');
}
// if width/height is passed, transform it to resolution
if (width || height) {
// if we have an image window (pixel coordinates), transform it to a BBox
// using the origin/resolution of the first image.
if (imageWindow) {
const [oX, oY] = firstImage.getOrigin();
const [rX, rY] = firstImage.getResolution();
bbox = [
oX + (imageWindow[0] * rX),
oY + (imageWindow[1] * rY),
oX + (imageWindow[2] * rX),
oY + (imageWindow[3] * rY),
];
}
// if we have a bbox (or calculated one)
const usedBBox = bbox || imgBBox;
if (width) {
if (resX) {
throw new Error('Both width and resX passed');
}
resX = (usedBBox[2] - usedBBox[0]) / width;
}
if (height) {
if (resY) {
throw new Error('Both width and resY passed');
}
resY = (usedBBox[3] - usedBBox[1]) / height;
}
}
// if resolution is set or calculated, try to get the image with the worst acceptable resolution
if (resX || resY) {
const allImages = [];
for (let i = 0; i < imageCount; ++i) {
const image = await this.getImage(i);
const subfileType = image.fileDirectory.getValue('SubfileType');
const newSubfileType = image.fileDirectory.getValue('NewSubfileType');
if (i === 0 || subfileType === 2 || (newSubfileType || 0) & 1) {
allImages.push(image);
}
}
allImages.sort((a, b) => a.getWidth() - b.getWidth());
for (let i = 0; i < allImages.length; ++i) {
const image = allImages[i];
const imgResX = (imgBBox[2] - imgBBox[0]) / image.getWidth();
const imgResY = (imgBBox[3] - imgBBox[1]) / image.getHeight();
usedImage = image;
if ((resX && resX > imgResX) || (resY && resY > imgResY)) {
break;
}
}
}
let wnd = imageWindow;
if (bbox) {
const [oX, oY] = firstImage.getOrigin();
const [imageResX, imageResY] = usedImage.getResolution(firstImage);
wnd = [
Math.round((bbox[0] - oX) / imageResX),
Math.round((bbox[1] - oY) / imageResY),
Math.round((bbox[2] - oX) / imageResX),
Math.round((bbox[3] - oY) / imageResY),
];
wnd = [
Math.min(wnd[0], wnd[2]),
Math.min(wnd[1], wnd[3]),
Math.max(wnd[0], wnd[2]),
Math.max(wnd[1], wnd[3]),
];
}
return usedImage.readRasters({ ...options, window: wnd });
}
}
/**
* @typedef {Object} GeoTIFFOptions
* @property {boolean} [cache=false] whether or not decoded tiles shall be cached.
*/
/**
* The abstraction for a whole GeoTIFF file.
*/
class GeoTIFF extends GeoTIFFBase {
/**
* @constructor
* @param {BaseSource} source The datasource to read from.
* @param {boolean} littleEndian Whether the image uses little endian.
* @param {boolean} bigTiff Whether the image uses bigTIFF conventions.
* @param {number} firstIFDOffset The numeric byte-offset from the start of the image
* to the first IFD.
* @param {GeoTIFFOptions} [options] further options.
*/
constructor(source, littleEndian, bigTiff, firstIFDOffset, options = {}) {
super();
this.source = source;
this.parser = new ImageFileDirectoryParser(source, littleEndian, bigTiff, false);
this.littleEndian = littleEndian;
this.bigTiff = bigTiff;
this.firstIFDOffset = firstIFDOffset;
this.cache = options.cache || false;
/** @type {Array<Promise<import('./imagefiledirectory.js').ImageFileDirectory> | undefined>} */
this.ifdRequests = [];
/** @type {Record<string, unknown>|null} */
this.ghostValues = null;
}
/**
* @param {number} offset
* @param {number} [size]
* @returns {Promise<DataSlice>}
*/
async getSlice(offset, size) {
const fallbackSize = this.bigTiff ? 4048 : 1024;
return new DataSlice((await this.source.fetch([{
offset,
length: typeof size !== 'undefined' ? size : fallbackSize,
}]))[0], offset, this.littleEndian, this.bigTiff);
}
/**
* @param {number} index
* @return {Promise<import('./imagefiledirectory.js').ImageFileDirectory>}
*/
async requestIFD(index) {
// see if we already have that IFD index requested.
if (this.ifdRequests[index]) {
// attach to an already requested IFD
return this.ifdRequests[index];
}
else if (index === 0) {
// special case for index 0
this.ifdRequests[index] = this.parser.parseFileDirectoryAt(this.firstIFDOffset);
return this.ifdRequests[index];
}
else if (!this.ifdRequests[index - 1]) {
// if the previous IFD was not yet loaded, load that one first
// this is the recursive call.
try {
this.ifdRequests[index - 1] = this.requestIFD(index - 1);
}
catch (e) {
// if the previous one already was an index error, rethrow
// with the current index
if (e instanceof GeoTIFFImageIndexError) {
throw new GeoTIFFImageIndexError(index);
}
// rethrow anything else
throw e;
}
}
// if the previous IFD was loaded, we can finally fetch the one we are interested in.
// we need to wrap this in an IIFE, otherwise this.ifdRequests[index] would be delayed
this.ifdRequests[index] = (async () => {
const previousPromise = this.ifdRequests[index - 1];
if (!previousPromise) {
throw new Error('Previous IFD request missing');
}
const previousIfd = await previousPromise;
if (previousIfd.nextIFDByteOffset === 0) {
throw new GeoTIFFImageIndexError(index);
}
return this.parser.parseFileDirectoryAt(previousIfd.nextIFDByteOffset);
})();
return this.ifdRequests[index];
}
/**
* Get the n-th internal subfile of an image. By default, the first is returned.
*
* @param {number} [index=0] the index of the image to return.
* @returns {Promise<GeoTIFFImage>} the image at the given index
*/
async getImage(index = 0) {
return new GeoTIFFImage(await this.requestIFD(index), this.littleEndian, this.cache, this.source);
}
/**
* Returns the count of the internal subfiles.
*
* @returns {Promise<number>} the number of internal subfile images
*/
async getImageCount() {
let index = 0;
// loop until we run out of IFDs
let hasNext = true;
while (hasNext) {
try {
await this.requestIFD(index);
++index;
}
catch (e) {
if (e instanceof GeoTIFFImageIndexError) {
hasNext = false;
}
else {
throw e;
}
}
}
return index;
}
/**
* Get the values of the COG ghost area as a parsed map.
* See https://gdal.org/drivers/raster/cog.html#header-ghost-area for reference
* @returns {Promise<Record<string, unknown>|null>} the parsed ghost area or null, if no such area was found
*/
async getGhostValues() {
const offset = this.bigTiff ? 16 : 8;
if (this.ghostValues !== null) {
return this.ghostValues;
}
const detectionString = 'GDAL_STRUCTURAL_METADATA_SIZE=';
const heuristicAreaSize = detectionString.length + 100;
let slice = await this.getSlice(offset, heuristicAreaSize);
if (detectionString === getValues(slice, fieldTypes.ASCII, detectionString.length, offset)) {
const valuesString = getValues(slice, fieldTypes.ASCII, heuristicAreaSize, offset);
const firstLine = valuesString.split('\n')[0];
const metadataSize = Number(firstLine.split('=')[1].split(' ')[0]) + firstLine.length;
if (metadataSize > heuristicAreaSize) {
slice = await this.getSlice(offset, metadataSize);
}
const fullString = getValues(slice, fieldTypes.ASCII, metadataSize, offset);
/** @type {Record<string, unknown>} */
const ghostValues = {};
fullString
.split('\n')
.filter((line) => line.length > 0)
.map((line) => line.split('='))
.forEach(([key, value]) => {
ghostValues[key] = value;
});
this.ghostValues = ghostValues;
}
return this.ghostValues;
}
/**
* Parse a (Geo)TIFF file from the given source.
*
* @param {BaseSource} source The source of data to parse from.
* @param {GeoTIFFOptions} [options] Additional options.
* @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
*/
static async fromSource(source, options, signal) {
const headerData = (await source.fetch([{ offset: 0, length: 1024 }], signal))[0];
const dataView = new DataView64(headerData);
const BOM = dataView.getUint16(0, false);
let littleEndian;
if (BOM === 0x4949) {
littleEndian = true;
}
else if (BOM === 0x4D4D) {
littleEndian = false;
}
else {
throw new TypeError('Invalid byte order value.');
}
const magicNumber = dataView.getUint16(2, littleEndian);
let bigTiff;
if (magicNumber === 42) {
bigTiff = false;
}
else if (magicNumber === 43) {
bigTiff = true;
const offsetByteSize = dataView.getUint16(4, littleEndian);
if (offsetByteSize !== 8) {
throw new Error('Unsupported offset byte-size.');
}
}
else {
throw new TypeError('Invalid magic number.');
}
const firstIFDOffset = bigTiff
? dataView.getUint64(8, littleEndian)
: dataView.getUint32(4, littleEndian);
return new GeoTIFF(source, littleEndian, bigTiff, firstIFDOffset, options);
}
/**
* Closes the underlying file buffer
* N.B. After the GeoTIFF has been completely processed it needs
* to be closed but only if it has been constructed from a file.
*/
close() {
if (typeof this.source.close === 'function') {
return this.source.close();
}
return false;
}
}
export { GeoTIFF };
export default GeoTIFF;
/**
* Wrapper for GeoTIFF files that have external overviews.
* @augments GeoTIFFBase
*/
class MultiGeoTIFF extends GeoTIFFBase {
/**
* Construct a new MultiGeoTIFF from a main and several overview files.
* @param {GeoTIFF} mainFile The main GeoTIFF file.
* @param {GeoTIFF[]} overviewFiles An array of overview files.
*/
constructor(mainFile, overviewFiles) {
super();
this.mainFile = mainFile;
this.overviewFiles = overviewFiles;
this.imageFiles = [mainFile].concat(overviewFiles);
this.fileDirectoriesPerFile = null;
this.fileDirectoriesPerFileParsing = null;
this.imageCount = null;
}
async parseFileDirectoriesPerFile() {
const requests = [this.mainFile.parser.parseFileDirectoryAt(this.mainFile.firstIFDOffset)]
.concat(this.overviewFiles.map((file) => file.parser.parseFileDirectoryAt(file.firstIFDOffset)));
this.fileDirectoriesPerFile = await Promise.all(requests);
return this.fileDirectoriesPerFile;
}
/**
* Get the n-th internal subfile of an image. By default, the first is returned.
*
* @param {number} [index=0] the index of the image to return.
* @returns {Promise<GeoTIFFImage>} the image at the given index
*/
async getImage(index = 0) {
// Initialize this.imageCounts if not yet done
await this.getImageCount();
if (!this.imageCounts) {
throw new Error('Image counts not available');
}
await this.parseFileDirectoriesPerFile();
let visited = 0;
let relativeIndex = 0;
for (let i = 0; i < this.imageFiles.length; i++) {
const imageFile = this.imageFiles[i];
for (let ii = 0; ii < this.imageCounts[i]; ii++) {
if (index === visited) {
return new GeoTIFFImage(await imageFile.requestIFD(relativeIndex), imageFile.littleEndian, imageFile.cache, imageFile.source);
}
visited++;
relativeIndex++;
}
relativeIndex = 0;
}
throw new RangeError('Invalid image index');
}
/**
* Returns the count of the internal subfiles.
*
* @returns {Promise<number>} the number of internal subfile images
*/
async getImageCount() {
if (this.imageCount !== null) {
return this.imageCount;
}
const requests = [this.mainFile.getImageCount()]
.concat(this.overviewFiles.map((file) => file.getImageCount()));
this.imageCounts = await Promise.all(requests);
this.imageCount = this.imageCounts.reduce((count, ifds) => count + ifds, 0);
return this.imageCount;
}
}
export { MultiGeoTIFF };
/**
* Creates a new GeoTIFF from a remote URL.
* @param {string} url The URL to access the image from
* @param {RemoteSourceOptions} [options] Additional options to pass to the source.
* See {@link makeRemoteSource} for details.
* @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @returns {Promise<GeoTIFF>} The resulting GeoTIFF file.
*/
export async function fromUrl(url, options = {}, signal) {
return GeoTIFF.fromSource(makeRemoteSource(url, options), undefined, signal);
}
/**
* Creates a new GeoTIFF from a custom {@link BaseClient}.
* @param {BaseClient} client The client.
* @param {RemoteSourceOptions} [options] Additional options to pass to the source.
* See {@link makeRemoteSource} for details.
* @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @returns {Promise<GeoTIFF>} The resulting GeoTIFF file.
*/
export async function fromCustomClient(client, options = {}, signal) {
return GeoTIFF.fromSource(makeCustomSource(client, options), undefined, signal);
}
/**
* Construct a new GeoTIFF from an
* [ArrayBuffer]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer}.
* @param {ArrayBuffer} arrayBuffer The data to read the file from.
* @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @returns {Promise<GeoTIFF>} The resulting GeoTIFF file.
*/
export async function fromArrayBuffer(arrayBuffer, signal) {
return GeoTIFF.fromSource(makeBufferSource(arrayBuffer), undefined, signal);
}
/**
* Construct a GeoTIFF from a local file path. This uses the node
* [filesystem API]{@link https://nodejs.org/api/fs.html} and is
* not available on browsers.
*
* N.B. After the GeoTIFF has been completely processed it needs
* to be closed but only if it has been constructed from a file.
* @param {string} path The file path to read from.
* @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @returns {Promise<GeoTIFF>} The resulting GeoTIFF file.
*/
export async function fromFile(path, signal) {
return GeoTIFF.fromSource(makeFileSource(path), undefined, signal);
}
/**
* Construct a GeoTIFF from an HTML
* [Blob]{@link https://developer.mozilla.org/en-US/docs/Web/API/Blob} or
* [File]{@link https://developer.mozilla.org/en-US/docs/Web/API/File}
* object.
* @param {Blob|File} blob The Blob or File object to read from.
* @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @returns {Promise<GeoTIFF>} The resulting GeoTIFF file.
*/
export async function fromBlob(blob, signal) {
return GeoTIFF.fromSource(makeFileReaderSource(blob), undefined, signal);
}
/**
* Construct a MultiGeoTIFF from the given URLs.
* @param {string} mainUrl The URL for the main file.
* @param {string[]} overviewUrls An array of URLs for the overview images.
* @param {RemoteSourceOptions} [options] Additional options to pass to the source.
* See [makeRemoteSource]{@link module:source.makeRemoteSource}
* for details.
* @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @returns {Promise<MultiGeoTIFF>} The resulting MultiGeoTIFF file.
*/
export async function fromUrls(mainUrl, overviewUrls = [], options = {}, signal) {
const mainFile = await GeoTIFF.fromSource(makeRemoteSource(mainUrl, options), undefined, signal);
const overviewFiles = await Promise.all(overviewUrls.map((url) => GeoTIFF.fromSource(makeRemoteSource(url, options), undefined, signal)));
return new MultiGeoTIFF(mainFile, overviewFiles);
}
/**
* Main creating function for GeoTIFF files.
* @param {Array<number>|Array<Array<Array<number>>>|TypedArray} values The pixel values to write.
* Can be a flat array of all pixels or a 3-dimensional array of shape `[band][row][column]`.
* @param {GeotiffWriterMetadata} metadata
* @returns {ArrayBuffer}
*/
export function writeArrayBuffer(values, metadata) {
return writeGeotiff(values, metadata);
}
export { Pool };
export { GeoTIFFImage };
export { BaseClient, BaseResponse };
//# sourceMappingURL=geotiff.js.map