UNPKG

ucsc-xena-geotiff

Version:

GeoTIFF image decoding in JavaScript

80 lines 4.04 kB
"use strict"; 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