UNPKG

@loaders.gl/shapefile

Version:

Loader for the Shapefile Format

195 lines (169 loc) 5.36 kB
// loaders.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors export type BinaryChunkReaderOptions = { maxRewindBytes: number; }; export class BinaryChunkReader { offset: number; arrayBuffers: ArrayBuffer[]; ended: boolean; maxRewindBytes: number; constructor(options?: BinaryChunkReaderOptions) { 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: ArrayBuffer): void { this.arrayBuffers.push(arrayBuffer); } end(): void { this.arrayBuffers = []; this.ended = true; } /** * Has enough bytes available in array buffers * * @param bytes Number of bytes * @return boolean */ hasAvailableBytes(bytes: number): boolean { 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: number): any[] | null { let offset = -this.offset; const selectedBuffers: any = []; 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: number; // 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: number): DataView | null { 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(): void { 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: any[]): ArrayBufferLike { let byteLength: number = 0; for (const bufferOffset of bufferOffsets) { const [start, end] = bufferOffset[1]; byteLength += end - start; } const result = new Uint8Array(byteLength); // Copy the subarrays let resultOffset: number = 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: number): void { this.offset += bytes; } /** * @param bytes */ rewind(bytes: number): void { // TODO - only works if offset is already set this.offset -= bytes; } }