ucsc-xena-geotiff
Version:
GeoTIFF image decoding in JavaScript
80 lines • 4.04 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BlockedSource = void 0;
const quick_lru_1 = __importDefault(require("quick-lru"));
const operators_1 = require("rxjs/operators");
const rxjs_1 = require("rxjs");
const node_abort_controller_1 = __importDefault(require("node-abort-controller")); // should be noop on browser
const basesource_js_1 = require("./basesource.js");
const utils_js_1 = require("../utils.js");
const range = (s, e) => new Array(e - s).fill(0).map((x, i) => s + i);
function blockedSource(blockSize, cacheSize, source) {
const cache = new quick_lru_1.default({ maxSize: cacheSize }); // block cache
const queue = new Map(); // outstanding block requests
const toCache = id => block => cache.set(id, block);
const responseData = resp => resp[0].data;
const sliceForBlock = id => ({ offset: id * blockSize, length: blockSize });
const cleanUp = (ac, id) => () => {
ac.abort(); // If unsubed, we need to abort. On complete or error, this is a noop.
queue.delete(id);
};
// Dispatch fetch of one block. Add block to cache when complete. Save the
// fetch in queue, reference count it, and abort it if the count goes to
// zero (i.e. all requests for this block are cancelled).
const fetchBlock = id => {
const ac = new node_abort_controller_1.default();
const request = (0, rxjs_1.from)(source.fetch([sliceForBlock(id)], ac.signal))
.pipe((0, operators_1.map)(responseData), (0, operators_1.tap)(toCache(id)), (0, operators_1.finalize)(cleanUp(ac, id)), (0, operators_1.share)());
queue.set(id, request);
return request;
};
const cachedOrQueued = block => cache.has(block) ? (0, rxjs_1.of)(cache.get(block)) :
queue.has(block) ? queue.get(block) :
fetchBlock(block);
// compute block indices for a slice
const toBlocks = ({ offset, length }) => range(~~(offset / blockSize), ~~((offset + length - 1) / blockSize) + 1);
// concatenate a list of buffers
const concat = blocks => {
const result = new Uint8Array(blocks.length * blockSize);
blocks.forEach((b, i) => result.set(new Uint8Array(b), i * blockSize));
return result;
};
// build slice from contiguous blocks
const copySlice = slice => blocks => {
const start = slice.offset % blockSize;
return concat(blocks).slice(start, start + slice.length).buffer;
};
const errObs = signal => (0, rxjs_1.fromEvent)(signal, 'abort')
.pipe((0, operators_1.map)(() => { throw new utils_js_1.AbortError(); }));
// If signal was passed in, listen for abort & throw AbortError.
// take(1) is so errObs won't hold the merge open after fetchObs is complete.
const setSignal = (signal, fetchObs) => signal ? (0, rxjs_1.merge)(fetchObs, errObs(signal)).pipe((0, operators_1.take)(1)) : fetchObs;
const fetchSlice = slice => (0, rxjs_1.zip)(toBlocks(slice).map(cachedOrQueued)).pipe((0, operators_1.map)(copySlice(slice)));
return (slices, signal) => {
const result = (0, rxjs_1.zip)(slices.map(fetchSlice));
// handle abort signal, and return as a promise
return (0, rxjs_1.lastValueFrom)(setSignal(signal, result));
};
}
class BlockedSource extends basesource_js_1.BaseSource {
/**
*
* @param {BaseSource} source The underlying source that shall be blocked and cached
* @param {object} options
* @param {number} [options.blockSize]
* @param {number} [options.cacheSize]
*/
constructor(source, { blockSize = 65536, cacheSize = 100 } = {}) {
super();
this.source = source;
this.fetch = blockedSource(blockSize, cacheSize, source);
}
get fileSize() {
return this.source.fileSize;
}
}
exports.BlockedSource = BlockedSource;
//# sourceMappingURL=blockedsource2.js.map