Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi ranges #94

Merged
merged 26 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6cea5e6
First implementation of a multi-range block requesting
constantinius May 16, 2019
094060c
Using LRU cache for fetched blocks
constantinius May 16, 2019
2a3ae31
Adding functions to deal with various HTTP related functionality: par…
constantinius May 16, 2019
7396cb5
Merge branch 'master' into multi-ranges
constantinius Mar 16, 2020
3a49429
Fixing block size
constantinius Mar 17, 2020
2c2e519
Merge branch 'master' into multi-ranges
constantinius Feb 15, 2021
3f129bb
Re-structuring sources to own modules/classes
constantinius Feb 16, 2021
0d7a01d
Fixing byte range parser
constantinius Feb 17, 2021
d984d7c
Fixing fetch source and blocked source semantics
constantinius Feb 17, 2021
967b725
Adding nginx Dockerfile/config to test multipart
constantinius Feb 17, 2021
241acca
Updating package-lock
constantinius Feb 17, 2021
1aba1a0
Fixing import in test spec
constantinius Feb 17, 2021
4a3a996
Fixing `fetch` of base source to await all sub fetches
constantinius Feb 17, 2021
d2c53c9
Using isomorphic-fetch in the test spec
constantinius Feb 17, 2021
3c0066c
Split remote source functionality in source/client
constantinius Feb 18, 2021
9b21445
Fixing typos
constantinius Feb 18, 2021
026404f
Small fixes in clients
constantinius Feb 18, 2021
4d1c9f8
Properly propagating the AbortSignal
constantinius Feb 18, 2021
11a26d9
Using custom error type for aborting requests
constantinius Feb 18, 2021
70fd823
Adding documentation for aborting via signal
constantinius Feb 18, 2021
baaa1e6
Missing check for optional signal
constantinius Feb 18, 2021
07ed101
Adding error handling on block requests
constantinius Feb 19, 2021
dd3b36f
In BlockedSource retry blocks if they were
constantinius Feb 22, 2021
dd72ff1
Merge branch 'master' into multi-ranges
constantinius Feb 22, 2021
cb7985b
Merge branch 'master' into multi-ranges
constantinius Mar 24, 2021
80687f3
Updating package-lock
constantinius Mar 24, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,23 @@ const multiTiff = await fromUrls(
);
```

### AbortController Support

Geotiff.js supports the use of [`AbortController`s](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). Calls to `getRasters`, `readRGB` and `getTileOrStrip` will throw an `Error` with name `AbortSignal` similar to the browser's `fetch` behavior.

```javascript
const abortController = new AbortController();
const { signal } = abortController;
abortController.abort();
try {
const data = await tiff.readRasters({ signal });
} catch(e) {
if (err.name === 'AbortError') {
// do stuff
}
}
```

### Writing GeoTIFFs (Beta Version)
You can create a binary representation of a GeoTIFF using `writeArrayBuffer`.
This function returns an ArrayBuffer which you can then save as a .tif file.
Expand Down
17,176 changes: 15,008 additions & 2,168 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
"browsers": "defaults"
},
"dependencies": {
"content-type-parser": "^1.0.2",
"lru-cache": "^6.0.0",
"pako": "^1.0.11",
"parse-headers": "^2.0.2",
"threads": "^1.3.1",
"txml": "^3.1.2"
},
Expand All @@ -40,14 +43,17 @@
"eslint-plugin-react": "^7.19.0",
"express": "^4.17.1",
"finalhandler": "^1.1.2",
"fs-extra": "^7.0.1",
"isomorphic-fetch": "^2.2.1",
"jsdoc": "^3.6.4",
"jshint-stylish": "^2.2.1",
"mocha": "^7.1.0",
"node-abort-controller": "^1.1.0",
"npm-run-all": "^4.1.5",
"parcel-bundler": "^1.12.4",
"parcel-plugin-bundle-visualiser": "^1.2.0",
"rimraf": "^3.0.2",
"send-ranges": "^3.0.0",
"serve-static": "^1.14.1"
},
"scripts": {
Expand Down
55 changes: 39 additions & 16 deletions src/geotiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import GeoTIFFImage from './geotiffimage';
import DataView64 from './dataview64';
import DataSlice from './dataslice';
import Pool from './pool';
import { makeRemoteSource, makeBufferSource, makeFileSource, makeFileReaderSource } from './source';

import { makeRemoteSource } from './source/remote';
import { makeBufferSource } from './source/arraybuffer';
import { makeFileReaderSource } from './source/filereader';
import { makeFileSource } from './source/file';

import { fieldTypes, fieldTagNames, arrayFields, geoKeyNames } from './globals';
import { writeGeotiff } from './geotiffwriter';
import * as globals from './globals';
Expand Down Expand Up @@ -184,6 +189,8 @@ class GeoTIFFBase {
* @param {Number} [options.height] The desired height of the output. When the width is not the
* same as the images, resampling will be performed.
* @param {String} [options.resampleMethod='nearest'] The desired resampling method.
* @param {AbortSignal} [options.signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @param {Number|Number[]} [options.fillValue] The value to use for parts of the image
* outside of the images extent. When multiple
* samples are requested, an array of fill values
Expand Down Expand Up @@ -314,9 +321,13 @@ class GeoTIFF extends GeoTIFFBase {
async getSlice(offset, size) {
const fallbackSize = this.bigTiff ? 4048 : 1024;
return new DataSlice(
await this.source.fetch(
offset, typeof size !== 'undefined' ? size : fallbackSize,
), offset, this.littleEndian, this.bigTiff,
(await this.source.fetch([{
offset,
length: typeof size !== 'undefined' ? size : fallbackSize,
}]))[0],
offset,
this.littleEndian,
this.bigTiff,
);
}

Expand Down Expand Up @@ -514,9 +525,11 @@ class GeoTIFF extends GeoTIFFBase {
*
* @param {source~Source} source The source of data to parse from.
* @param {object} options Additional options.
* @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
*/
static async fromSource(source, options) {
const headerData = await source.fetch(0, 1024);
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, 0);
Expand Down Expand Up @@ -648,20 +661,24 @@ export { MultiGeoTIFF };
* @param {string} url The URL to access the image from
* @param {object} [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 = {}) {
return GeoTIFF.fromSource(makeRemoteSource(url, options));
export async function fromUrl(url, options = {}, signal) {
return GeoTIFF.fromSource(makeRemoteSource(url, options), 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) {
return GeoTIFF.fromSource(makeBufferSource(arrayBuffer));
export async function fromArrayBuffer(arrayBuffer, signal) {
return GeoTIFF.fromSource(makeBufferSource(arrayBuffer), signal);
}

/**
Expand All @@ -672,10 +689,12 @@ export async function fromArrayBuffer(arrayBuffer) {
* 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) {
return GeoTIFF.fromSource(makeFileSource(path));
export async function fromFile(path, signal) {
return GeoTIFF.fromSource(makeFileSource(path), signal);
}

/**
Expand All @@ -684,10 +703,12 @@ export async function fromFile(path) {
* [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) {
return GeoTIFF.fromSource(makeFileReaderSource(blob));
export async function fromBlob(blob, signal) {
return GeoTIFF.fromSource(makeFileReaderSource(blob), signal);
}

/**
Expand All @@ -697,10 +718,12 @@ export async function fromBlob(blob) {
* @param {object} [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 = {}) {
const mainFile = await GeoTIFF.fromSource(makeRemoteSource(mainUrl, options));
export async function fromUrls(mainUrl, overviewUrls = [], options = {}, signal) {
const mainFile = await GeoTIFF.fromSource(makeRemoteSource(mainUrl, options), signal);
const overviewFiles = await Promise.all(
overviewUrls.map((url) => GeoTIFF.fromSource(makeRemoteSource(url, options))),
);
Expand Down
44 changes: 29 additions & 15 deletions src/geotiffimage.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,11 @@ class GeoTIFFImage {
* @param {Number} y the tile y-offset (0 for stripped images)
* @param {Number} sample the sample to get for separated samples
* @param {Pool|AbstractDecoder} poolOrDecoder the decoder or decoder pool
* @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @returns {Promise.<ArrayBuffer>}
*/
async getTileOrStrip(x, y, sample, poolOrDecoder) {
async getTileOrStrip(x, y, sample, poolOrDecoder, signal) {
const numTilesPerRow = Math.ceil(this.getWidth() / this.getTileWidth());
const numTilesPerCol = Math.ceil(this.getHeight() / this.getTileHeight());
let index;
Expand All @@ -257,7 +259,7 @@ class GeoTIFFImage {
offset = this.fileDirectory.StripOffsets[index];
byteCount = this.fileDirectory.StripByteCounts[index];
}
const slice = await this.source.fetch(offset, byteCount);
const slice = (await this.source.fetch([{ offset, length: byteCount }], signal))[0];

// either use the provided pool or decoder to decode the data
let request;
Expand All @@ -277,10 +279,15 @@ class GeoTIFFImage {
* @param {Array} samples The selected samples (0-based indices)
* @param {TypedArray[]|TypedArray} valueArrays The array(s) to write into
* @param {Boolean} interleave Whether or not to write in an interleaved manner
* @param {Pool} pool The decoder pool
* @param {Pool|AbstractDecoder} poolOrDecoder the decoder or decoder pool
* @param {number} width the width of window to be read into
* @param {number} height the height of window to be read into
* @param {number} resampleMethod the resampling method to be used when interpolating
* @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @returns {Promise<TypedArray[]>|Promise<TypedArray>}
*/
async _readRaster(imageWindow, samples, valueArrays, interleave, poolOrDecoder, width, height, resampleMethod) {
async _readRaster(imageWindow, samples, valueArrays, interleave, poolOrDecoder, width, height, resampleMethod, signal) {
const tileWidth = this.getTileWidth();
const tileHeight = this.getTileHeight();

Expand Down Expand Up @@ -320,7 +327,7 @@ class GeoTIFFImage {
if (this.planarConfiguration === 2) {
bytesPerPixel = this.getSampleByteSize(sample);
}
const promise = this.getTileOrStrip(xTile, yTile, sample, poolOrDecoder);
const promise = this.getTileOrStrip(xTile, yTile, sample, poolOrDecoder, signal);
promises.push(promise);
promise.then((tile) => {
const buffer = tile.data;
Expand Down Expand Up @@ -416,11 +423,13 @@ class GeoTIFFImage {
* outside of the images extent. When
* multiple samples are requested, an
* array of fill values can be passed.
* @param {AbortSignal} [options.signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @returns {Promise.<(TypedArray|TypedArray[])>} the decoded arrays as a promise
*/
async readRasters({
window: wnd, samples = [], interleave, pool = null,
width, height, resampleMethod, fillValue,
width, height, resampleMethod, fillValue, signal,
} = {}) {
const imageWindow = wnd || [0, 0, this.getWidth(), this.getHeight()];

Expand Down Expand Up @@ -469,7 +478,7 @@ class GeoTIFFImage {
const poolOrDecoder = pool || getDecoder(this.fileDirectory);

const result = await this._readRaster(
imageWindow, samples, valueArrays, interleave, poolOrDecoder, width, height, resampleMethod,
imageWindow, samples, valueArrays, interleave, poolOrDecoder, width, height, resampleMethod, signal,
);
return result;
}
Expand All @@ -484,16 +493,18 @@ class GeoTIFFImage {
*
* @param {Object} [options] optional parameters
* @param {Array} [options.window=whole image] the subset to read data from.
* @param {Number} [pool=null] The optional decoder pool to use.
* @param {number} [width] The desired width of the output. When the width is no the
* same as the images, resampling will be performed.
* @param {number} [height] The desired height of the output. When the width is no the
* same as the images, resampling will be performed.
* @param {string} [resampleMethod='nearest'] The desired resampling method.
* @param {bool} [enableAlpha=false] Enable reading alpha channel if present.
* @param {Number} [options.pool=null] The optional decoder pool to use.
* @param {number} [options.width] The desired width of the output. When the width is no the
* same as the images, resampling will be performed.
* @param {number} [options.height] The desired height of the output. When the width is no the
* same as the images, resampling will be performed.
* @param {string} [options.resampleMethod='nearest'] The desired resampling method.
* @param {bool} [options.enableAlpha=false] Enable reading alpha channel if present.
* @param {AbortSignal} [options.signal] An AbortSignal that may be signalled if the request is
* to be aborted
* @returns {Promise.<TypedArray|TypedArray[]>} the RGB array as a Promise
*/
async readRGB({ window, pool = null, width, height, resampleMethod, enableAlpha = false } = {}) {
async readRGB({ window, pool = null, width, height, resampleMethod, enableAlpha = false, signal } = {}) {
const imageWindow = window || [0, 0, this.getWidth(), this.getHeight()];

// check parameters
Expand All @@ -518,6 +529,8 @@ class GeoTIFFImage {
pool,
width,
height,
resampleMethod,
signal,
});
}

Expand Down Expand Up @@ -547,6 +560,7 @@ class GeoTIFFImage {
width,
height,
resampleMethod,
signal,
};
const { fileDirectory } = this;
const raster = await this.readRasters(subOptions);
Expand Down
Loading