UNPKG

@loaders.gl/shapefile

Version:

Loader for the Shapefile Format

165 lines (164 loc) 5.63 kB
// loaders.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors export class BinaryChunkReader { offset; arrayBuffers; ended; maxRewindBytes; constructor(options) { const { maxRewindBytes = 0 } = options || {}; /** current global offset into current array buffer*/ this.offset = 0; /** current buffer from iterator */ this.arrayBuffers = []; this.ended = false; /** bytes behind offset to hold on to */ this.maxRewindBytes = maxRewindBytes; } /** * @param arrayBuffer */ write(arrayBuffer) { this.arrayBuffers.push(arrayBuffer); } end() { this.arrayBuffers = []; this.ended = true; } /** * Has enough bytes available in array buffers * * @param bytes Number of bytes * @return boolean */ hasAvailableBytes(bytes) { let bytesAvailable = -this.offset; for (const arrayBuffer of this.arrayBuffers) { bytesAvailable += arrayBuffer.byteLength; if (bytesAvailable >= bytes) { return true; } } return false; } /** * Find offsets of byte ranges within this.arrayBuffers * * @param bytes Byte length to read * @return Arrays with byte ranges pointing to this.arrayBuffers, Output type is nested array, e.g. [ [0, [1, 2]], ...] */ findBufferOffsets(bytes) { let offset = -this.offset; const selectedBuffers = []; for (let i = 0; i < this.arrayBuffers.length; i++) { const buf = this.arrayBuffers[i]; // Current buffer isn't long enough to reach global offset if (offset + buf.byteLength <= 0) { offset += buf.byteLength; // eslint-disable-next-line no-continue continue; } // Find start/end offsets for this buffer // When offset < 0, need to skip over Math.abs(offset) bytes // When offset > 0, implies bytes in previous buffer, start at 0 const start = offset <= 0 ? Math.abs(offset) : 0; let end; // Length of requested bytes is contained in current buffer if (start + bytes <= buf.byteLength) { end = start + bytes; selectedBuffers.push([i, [start, end]]); return selectedBuffers; } // Will need to look into next buffer end = buf.byteLength; selectedBuffers.push([i, [start, end]]); // Need to read fewer bytes in next iter bytes -= buf.byteLength - start; offset += buf.byteLength; } // Should only finish loop if exhausted all arrays return null; } /** * Get the required number of bytes from the iterator * * @param bytes Number of bytes * @return DataView with data */ getDataView(bytes) { const bufferOffsets = this.findBufferOffsets(bytes); // return `null` if not enough data, except if end() already called, in // which case throw an error. if (!bufferOffsets && this.ended) { throw new Error('binary data exhausted'); } if (!bufferOffsets) { return null; } // If only one arrayBuffer needed, return DataView directly if (bufferOffsets.length === 1) { const [bufferIndex, [start, end]] = bufferOffsets[0]; const arrayBuffer = this.arrayBuffers[bufferIndex]; const view = new DataView(arrayBuffer, start, end - start); this.offset += bytes; this.disposeBuffers(); return view; } // Concatenate portions of multiple ArrayBuffers const view = new DataView(this._combineArrayBuffers(bufferOffsets)); this.offset += bytes; this.disposeBuffers(); return view; } /** * Dispose of old array buffers */ disposeBuffers() { while (this.arrayBuffers.length > 0 && this.offset - this.maxRewindBytes >= this.arrayBuffers[0].byteLength) { this.offset -= this.arrayBuffers[0].byteLength; this.arrayBuffers.shift(); } } /** * Copy multiple ArrayBuffers into one contiguous ArrayBuffer * * In contrast to concatenateArrayBuffers, this only copies the necessary * portions of the source arrays, rather than first copying the entire arrays * then taking a part of them. * * @param bufferOffsets List of internal array offsets * @return New contiguous ArrayBuffer */ _combineArrayBuffers(bufferOffsets) { let byteLength = 0; for (const bufferOffset of bufferOffsets) { const [start, end] = bufferOffset[1]; byteLength += end - start; } const result = new Uint8Array(byteLength); // Copy the subarrays let resultOffset = 0; for (const bufferOffset of bufferOffsets) { const [bufferIndex, [start, end]] = bufferOffset; const sourceArray = new Uint8Array(this.arrayBuffers[bufferIndex]); result.set(sourceArray.subarray(start, end), resultOffset); resultOffset += end - start; } return result.buffer; } /** * @param bytes */ skip(bytes) { this.offset += bytes; } /** * @param bytes */ rewind(bytes) { // TODO - only works if offset is already set this.offset -= bytes; } }