UNPKG

gis-tools-ts

Version:

A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.

741 lines 31.7 kB
import { ArithmeticDecoder, IntegerCompressor, LASWavePacket13, LASpoint10, LASpoint14, LASrgba, LASrgbaNir, LAZPoint10v1Reader, LAZPoint10v2Reader, LAZPoint14v3Reader, LAZbyte10v1Reader, LAZbyte10v2Reader, LAZbyte14v3Reader, LAZgpstime11v1Reader, LAZgpstime11v2Reader, LAZrgb12v1Reader, LAZrgb12v2Reader, LAZrgb14v3Reader, LAZrgbNir14v3Reader, LAZwavepacket13v1Reader, LAZwavepacket14v3Reader, modifyPoint14RawInput, } from './laz'; import { LAZCompressor, LAZHeaderItemType } from './types'; import { Transformer, buildParamsFromGeoKeys, parseGeotiffRawGeoKeys, toReader } from '../..'; import { getPointFormat0, getPointFormat1, getPointFormat10, getPointFormat2, getPointFormat3, getPointFormat4, getPointFormat5, getPointFormat6, getPointFormat7, getPointFormat8, getPointFormat9, } from './getPoint'; export * from './getPoint'; export * from './types'; const U32_MAX = 4294967295; /** * # LAS Reader * * ## Description * Reads LAS data. Supports up to the LAS 1.4 specification. * [See specification](https://www.asprs.org/wp-content/uploads/2010/12/LAS_1_4_r13.pdf) * Implements the {@link FeatureIterator} interface * * Data is stored like so: *``` * | PUBLIC HEADER BLOCK | * | VARIABLE LENGTH RECORDS | * | POINT DATA RECORDS | * ``` * * ## Usage * ```ts * import { LASReader } from 'gis-tools-ts'; * import { FileReader } from 'gis-tools-ts/file'; * // or use the MMapReader if using Bun: * // import { MMapReader } from 'gis-tools-ts/mmap'; * * const reader = new LASReader(new FileReader('./data.las')); * * // read the features * for (const feature of reader) { * console.log(feature); * } * ``` * * ## Links * - https://www.usgs.gov/ngp-standards-and-specifications/lidar-base-specification-online * - https://www.asprs.org/wp-content/uploads/2010/12/LAS_1_4_r13.pdf * - https://liblas.org/development/index.html * - https://downloads.rapidlasso.de/doc/LAZ_Specification_1.4_R1.pdf * - https://github.com/PDAL/PDAL * - https://github.com/libLAS/libLAS (deprecated for PDAL) * - https://github.com/LASzip */ export class LASReader { dontTransform; reader; header; variableLengthRecords = {}; wkt; GeoKeyDirectory; transformer = new Transformer(); #decoder = new TextDecoder(); /** * @param input - The LAS input data from a reader/buffer * @param definitions - an array of projection definitions for the transformer if needed * @param epsgCodes - a record of EPSG codes to use for the transformer if needed * @param gridStores - an array of grid readers if needed * @param dontTransform - if you set to true, the source projection is kept */ constructor(input, definitions = [], epsgCodes = {}, gridStores = [], dontTransform = false) { this.dontTransform = dontTransform; this.reader = toReader(input); this.header = this.#parseHeader(); this.#parseVariableLengthRecords(); // set definitions, espgCodes, and gridStores for (const proj of definitions) this.transformer.insertDefinition(proj); for (const [key, value] of Object.entries(epsgCodes)) this.transformer.insertEPSGCode(key, value); for (const { key, reader } of gridStores) this.transformer.addGridFromReader(key, reader); // try WTK this.wkt = this.#buildWKT(); // they try GeoTiff this.GeoKeyDirectory = this.#buildGeoKeyDirectory(); } /** * Get the number of points stored * @returns - the number of points */ get length() { return this.header.numPoints; } /** * Reads a point in at index * @param index - The index of the point to read * @returns - The parsed point */ getPoint(index) { const { reader, header } = this; const { offsetToPoints, pointDataFormatID: format, pointDataRecordLength } = header; const offset = offsetToPoints + index * pointDataRecordLength; const item = reader.slice(offset, offset + pointDataRecordLength); let point; if (format === 0) point = getPointFormat0(item, header); else if (format === 1) point = getPointFormat1(item, header); else if (format === 2) point = getPointFormat2(item, header); else if (format === 3) point = getPointFormat3(item, header); else if (format === 4) point = getPointFormat4(item, header); else if (format === 5) point = getPointFormat5(item, header); else if (format === 6) point = getPointFormat6(item, header); else if (format === 7) point = getPointFormat7(item, header); else if (format === 8) point = getPointFormat8(item, header); else if (format === 9) point = getPointFormat9(item, header); else if (format === 10) point = getPointFormat10(item, header); else throw new Error(`Unknown Point Data Format ID: ${format}`); if (!this.dontTransform) point = this.transformer.forward(point); return point; } /** * Generator to iterate over each WGS84 lon-lat point * @yields {VectorFeature} */ async *[Symbol.asyncIterator]() { const numPoints = this.length; if (numPoints === 0) return; for (let i = 0; i < numPoints; i++) { const coordinates = this.getPoint(i); yield { type: 'VectorFeature', geometry: { type: 'Point', coordinates, is3D: true, }, properties: {}, }; } } /** * Reads the Public Header Block * @returns - The public header */ #parseHeader() { const { reader } = this; const header = { signature: reader.parseString(0, 4), sourceID: reader.getUint16(4, true), encoding: reader.getUint16(6, true), projectID1: reader.getUint32(8, true), projectID2: reader.getUint16(12, true), projectID3: reader.getUint16(14, true), projectID4: reader.parseString(16, 8), majorVersion: reader.getUint8(24), minorVersion: reader.getUint8(25), systemIdentifier: reader.parseString(26, 32), generatingSoftware: reader.parseString(58, 32), fileCreationDay: reader.getUint16(90, true), fileCreationYear: reader.getUint16(92, true), headerSize: reader.getUint16(94, true), offsetToPoints: reader.getUint32(96, true), numVariableLengthRecords: reader.getUint32(100, true), pointDataFormatID: reader.getUint8(104), pointDataRecordLength: reader.getUint16(105, true), numPoints: reader.getUint32(107, true), numPointsByReturn: [ reader.getUint32(111, true), reader.getUint32(115, true), reader.getUint32(119, true), reader.getUint32(123, true), reader.getUint32(127, true), ], xScaleFactor: reader.getFloat64(131, true), yScaleFactor: reader.getFloat64(139, true), zScaleFactor: reader.getFloat64(147, true), xOffset: reader.getFloat64(155, true), yOffset: reader.getFloat64(163, true), zOffset: reader.getFloat64(171, true), maxX: reader.getFloat64(179, true), minX: reader.getFloat64(187, true), maxY: reader.getFloat64(195, true), minY: reader.getFloat64(203, true), maxZ: reader.getFloat64(211, true), minZ: reader.getFloat64(219, true), waveformDataPacketOffset: 0, extendedVariableLengthRecordOffset: 0, extendedVariableLengthSize: 0, }; // 1.4 or later supports larger headers if (header.headerSize > 227) header.waveformDataPacketOffset = Number(reader.getBigUint64(227, true)); if (header.headerSize > 235) header.extendedVariableLengthRecordOffset = reader.getUint32(235, true); if (header.headerSize > 239) header.extendedVariableLengthSize = Number(reader.getBigUint64(239, true)); // re-adjust numPoints and numPointsByReturn if header includes modern numPoints variable if (header.headerSize > 247) header.numPoints = reader.getUint32(247, true); // set new numPointsByReturn if header includes if (header.headerSize > 251) { let curOffset = 251; header.numPointsByReturn = []; for (let i = 0; i < 15; i++) { header.numPointsByReturn.push(Number(reader.getBigUint64(curOffset, true))); curOffset += 8; } } return header; } /** * The Public Header Block is followed by one or more Variable Length Records (There is one * mandatory Variable Length Record, GeoKeyDirectoryTag). The number of Variable Length * Records is specified in the "Number of Variable Length Records" field in the Public Header Block. * The Variable Length Records must be accessed sequentially since the size of each variable length * record is contained in the Variable Length Record Header. Each Variable Length Record Header * is 54 bytes in length. * * Each record is as follows: * - Reserved unsigned short 2 bytes * - User ID char[16] 16 bytes * - Record ID unsigned short 2 bytes * - Record Length After Header unsigned short 2 bytes * - Description char[32] 32 bytes * - optional data: variable size */ #parseVariableLengthRecords() { const { reader, header } = this; const { headerSize, numVariableLengthRecords } = header; let position = headerSize; for (let i = 0; i < numVariableLengthRecords; ++i) { const recordLength = reader.getUint16(position + 20, true); const record = { reserved: reader.getUint16(position, true), userID: reader.parseString(position + 2, 16), recordID: reader.getUint16(position + 18, true), recordLength, description: reader.parseString(position + 22, 32), data: recordLength > 0 ? reader.slice(position + 54, position + 54 + recordLength) : undefined, }; position += 54 + recordLength; this.variableLengthRecords[record.recordID] = record; } } /** * WKT Parsing * * For definition of WKT, we refer to Open Geospatial Consortium (OGC) specification “OpenGIS * coordinate transformation service implementation specification” revision 1.00 released 12 * January 2001, section 7 (coordinate transformation services spec). This specification may be * found at www.opengeospatial.org/standards/ct. As there are a few dialects of WKT, please note * that LAS is not using the “ESRI WKT” dialect, which does not include TOWGS84 and authority * nodes. * - OGC MATH TRANSFORM WKT RECORD (2111) * - OGC COORDINATE SYSTEM WKT (2112) * * NOTE: It is required to use WKT if the point type is 6-10 * @returns - the WKT string if it exists */ #buildWKT() { const { header, variableLengthRecords } = this; // 4th bit of global encoding must be set if ((header.encoding & (1 << 3)) !== 0) return; // OGC MATH TRANSFORM WKT RECORD: const wktMathOGC = variableLengthRecords[2111]?.data; // OGC COORDINATE SYSTEM WKT: const wktCoordSystemData = variableLengthRecords[2112]?.data; if (wktMathOGC === undefined && wktCoordSystemData === undefined) return; const wktCoordSystem = this.#decoder.decode(wktMathOGC ?? wktCoordSystemData); this.transformer.setSource(wktCoordSystem); return wktCoordSystem; } /** * userID of "LASF_Projection" will contain at least 3 records: * - GeoKeyDirectoryTag (34735) * - GeoDoubleParamsTag (34736) * - GeoASCIIParamsTag (34737) * * Only the `GeoKeyDirectoryTag` record is required. This parses the `GeoKeyDirectoryTag`. * This record contains the key values that define the coordinate system. A complete description * can be found in the GeoTIFF format specification. Here is a summary from a programmatic point * of view for someone interested in implementation. * * The `GeoKeyDirectoryTag` is defined as just an array of unsigned short values. But, * programmatically, the data can be seen as something like this: * @returns - The parsed GeoKeyDirectory */ #buildGeoKeyDirectory() { const { variableLengthRecords } = this; // GeoKeyDirectoryTag const geokeyRecord = variableLengthRecords[34735]?.data; if (geokeyRecord === undefined) return; const rawGeoKeys = new Uint16Array(geokeyRecord.buffer, geokeyRecord.byteOffset); // GeoDoubleParamsTag const doubleRecord = variableLengthRecords[34736]?.data; const GeoDoubleParams = doubleRecord !== undefined ? [...new Float64Array(doubleRecord.buffer, doubleRecord.byteOffset)] : undefined; // GeoAsciiParamsTag const asciiRecord = variableLengthRecords[34737]?.data; const GeoAsciiParams = asciiRecord !== undefined ? this.#decoder.decode(asciiRecord) : undefined; const gkd = parseGeotiffRawGeoKeys(rawGeoKeys, { GeoKeyDirectory: this.GeoKeyDirectory, GeoDoubleParams, GeoAsciiParams, }); const gkdParams = buildParamsFromGeoKeys(gkd); if (gkdParams !== undefined) this.transformer.setSource(gkdParams); return gkd; } } /** * # LASzip Reader * * ## Description * Reads LAS zipped data. Supports LAS 1.4 specification although missing some support. * [See specification](https://downloads.rapidlasso.de/doc/LAZ_Specification_1.4_R1.pdf) * Implements the {@link FeatureIterator} interface * * Data is stored like so: *``` * | PUBLIC HEADER BLOCK | * | VARIABLE LENGTH RECORDS | * | POINT DATA RECORDS | * | Extended Variable Length Records (EVLRs) | * | Field Chunk table start position (EOF) | * ``` * * ## Usage * ```ts * import { LASZipReader } from 'gis-tools-ts'; * import { FileReader } from 'gis-tools-ts/file'; * // or use the MMapReader if using Bun: * // import { MMapReader } from 'gis-tools-ts/mmap'; * * const reader = new LASZipReader(new FileReader('./data.laz')); * * // read the features * for (const feature of reader) { * console.log(feature); * } * ``` * * ## Links * - https://www.usgs.gov/ngp-standards-and-specifications/lidar-base-specification-online * - https://www.asprs.org/wp-content/uploads/2010/12/LAS_1_4_r13.pdf * - https://liblas.org/development/index.html * - https://downloads.rapidlasso.de/doc/LAZ_Specification_1.4_R1.pdf * - https://github.com/PDAL/PDAL * - https://github.com/libLAS/libLAS (deprecated for PDAL) * - https://github.com/LASzip */ export class LASZipReader extends LASReader { lazHeader; #dec; decompressSelective = 0xffffffff; // all // Is true if this file uses layered compression for LAS 1.4. layeredLas14Compression = false; // number of points per chunk // #numReaders = 0; #chunkSize = U32_MAX; // U32 #chunkCount = 0; // U32 #currChunk = 0; // U32 #numberChunks = 0; // U32 #tabledChunks = 0; // U32 #chunkTotals = []; // U32 #chunkStarts = []; // I64 #pointStart = 0; // I64 // #pointSize = 0; // U32 // #seekPoint = 0; // #readersRaw: any[] = []; #readers = []; // #readersCompressed: any[] = []; /** * @param input - The LAZ input data from a reader/buffer * @param definitions - an array of projection definitions for the transformer if needed * @param epsgCodes - a record of EPSG codes to use for the transformer if needed * @param gridStores - an array of grid readers if needed * @param dontTransform - if you set to true, the source projection is kept */ constructor(input, definitions = [], epsgCodes = {}, gridStores, dontTransform = false) { // setup header variables and VLRs super(input, definitions, epsgCodes, gridStores, dontTransform); this.lazHeader = this.#buildLaz(); this.#parseExtendedVariableLengthRecords(); // prep decoder if (this.lazHeader.coder !== 0) throw Error(`Unsupported decoder: ${this.lazHeader.coder}`); // setup other decoding variables this.layeredLas14Compression = this.lazHeader.compressor === LAZCompressor.LAYERED_AND_CHUNKED; if (this.lazHeader.compressor !== LAZCompressor.POINTWISE) { this.#chunkCount = this.lazHeader.chunkSize; if (this.lazHeader.chunkSize !== 0) this.#chunkSize = this.lazHeader.chunkSize; this.#numberChunks = U32_MAX; } } /** * Generator to iterate over each WGS84 lon-lat point * @yields {VectorFeature} */ async *[Symbol.asyncIterator]() { const numPoints = this.length; if (numPoints === 0) return; // set pos this.reader.seek(this.header.offsetToPoints); // init all readers for (let i = 0; i < numPoints; i++) { const coordinates = this.#readPoint(); yield { type: 'VectorFeature', geometry: { type: 'Point', coordinates, is3D: true, }, properties: {}, }; } } /** * If the LAZ variable length record is present, build a LAZ parser * @returns - the LAZ header guide for decompressing the point data */ #buildLaz() { const lazData = this.variableLengthRecords['22204']?.data; if (lazData === undefined || this.header.pointDataFormatID < 127) throw Error('LAZ data, but LAZ record not found.'); return this.#parseLAZHeader(lazData); } /** * Parses the LAZ header * @param rawHeader - The raw header data * @returns - The parsed header */ #parseLAZHeader(rawHeader) { const header = { compressor: rawHeader.getUint16(0, true), coder: rawHeader.getUint16(2, true), versionMajor: rawHeader.getUint8(4), versionMinor: rawHeader.getUint8(5), versionRevision: rawHeader.getUint16(6, true), options: rawHeader.getUint32(8, true), chunkSize: rawHeader.getUint32(12, true), numSpecialEvlrs: Number(rawHeader.getBigInt64(16, true)), offsetSpecialEvlrs: Number(rawHeader.getBigInt64(24, true)), numItems: rawHeader.getUint16(32, true), items: [], }; // add decoder if compressor is not NONE const isCompressed = header.compressor !== LAZCompressor.NONE; if (isCompressed) this.#dec = new ArithmeticDecoder(this.reader); // Parse items for (let i = 0; i < header.numItems; i++) { header.items.push({ type: rawHeader.getUint16(34 + i * 6, true), size: rawHeader.getUint16(36 + i * 6, true), version: rawHeader.getUint16(38 + i * 6, true), }); } return header; } /** * @param item - the item to build readers for * @returns - the compression reader for the item */ #getPointCompressedReader(item) { const { type, size, version } = item; if (type === LAZHeaderItemType.POINT10) { if (version === 1) return new LAZPoint10v1Reader(this.#dec); else if (version === 2) return new LAZPoint10v2Reader(this.#dec); } else if (type === LAZHeaderItemType.GPSTIME11) { if (version === 1) return new LAZgpstime11v1Reader(this.#dec); else if (version === 2) return new LAZgpstime11v2Reader(this.#dec); } else if (type === LAZHeaderItemType.RGB12) { if (version === 1) return new LAZrgb12v1Reader(this.#dec); else if (version === 2) return new LAZrgb12v2Reader(this.#dec); } else if (type === LAZHeaderItemType.WAVEPACKET13) { if (version === 1) return new LAZwavepacket13v1Reader(this.#dec); } else if (type === LAZHeaderItemType.BYTE) { if (version === 1) return new LAZbyte10v1Reader(this.#dec, size); else if (version === 2) return new LAZbyte10v2Reader(this.#dec, size); } else if (type === LAZHeaderItemType.POINT14) { if (version === 3 || version === 4) return new LAZPoint14v3Reader(this.#dec); // else if (version === 4) return new LAZPoint14v4Reader(this.#dec!); } else if (type === LAZHeaderItemType.RGB14) { if (version === 3 || version === 4) return new LAZrgb14v3Reader(this.#dec); // else if (version === 4) return new LAZrgb14v4Reader(this.#dec!); } else if (type === LAZHeaderItemType.RGBNIR14) { if (version === 3 || version === 4) return new LAZrgbNir14v3Reader(this.#dec); // else if (version === 4) return new LAZrgbNir14v4Reader(this.#dec!); } else if (type === LAZHeaderItemType.WAVEPACKET14) { if (version === 3 || version === 4) return new LAZwavepacket14v3Reader(this.#dec); // else if (version === 4) return new LAZwavepacket14v4Reader(this.#dec!); } else if (type === LAZHeaderItemType.BYTE14) { if (version === 3 || version === 4) return new LAZbyte14v3Reader(this.#dec, size); // else if (version === 4) return new LAZbyte14v4Reader(this.#dec!, size); } throw Error(`Unsupported compressed point type: ${type} & version: ${version}`); } /** * The Compressed Data Block can be followed by any number of EVLRs, which are identical to the * LAS 1.4 specification. The EVLR is similar to a VLR, but can carry a larger payload, as the Record * Length After Header field is 8 bytes instead of 2 bytes. The number of EVLRs is specified in the * Number of Extended Variable Length Records field in the Public Header Block. The start of the * first EVLR is at the file offset indicated by the Start of first Extended Variable Length Record in * the Public Header Block. * * The Extended Variable Length Records must be accessed sequentially, since the size of each * variable length record is contained in the Extended Variable Length Record Header. Each Extended * Variable Length Record Header (i.e. without the optional payload data) is 60 bytes in length. */ #parseExtendedVariableLengthRecords() { const { reader, lazHeader } = this; const { offsetSpecialEvlrs, numSpecialEvlrs } = lazHeader; let position = offsetSpecialEvlrs; for (let i = 0; i < numSpecialEvlrs; ++i) { const recordLength = Number(reader.getBigUint64(position + 20, true)); const record = { reserved: reader.getUint16(position, true), userID: reader.parseString(position + 2, 16), recordID: reader.getUint16(position + 18, true), recordLength, description: reader.parseString(position + 28, 32), data: recordLength > 0 ? reader.slice(position + 60, position + 60 + recordLength) : undefined, }; position += 60 + recordLength; this.variableLengthRecords[record.recordID] = record; } } /** @returns - the next point */ #readPoint() { const { compressor } = this.lazHeader; const pointData = []; const context = { value: 0 }; // no decoder means it wasn't compressed, just pull the point const uncompressed = this.#setNextChunk(); this.#chunkCount++; if (uncompressed || compressor === LAZCompressor.NONE) { this.#firstChunkRead(context, pointData); } else this.#pointwiseCompressRead(pointData, context); let point = this.#toVectorPoint(pointData, this.header); if (!this.dontTransform) point = this.transformer.forward(point); return point; } /** * If we are at the end of the chunk, we need to set up the next chunk. * @returns - true if we are starting a new chunk, false otherwise */ #setNextChunk() { if (this.#chunkCount === this.#chunkSize) { if (this.#pointStart !== 0) this.#currChunk++; this.#initDec(); if (this.#currChunk === this.#tabledChunks) { // no or incomplete chunk table? this.#chunkStarts[this.#tabledChunks] = this.#pointStart; // needs fixing this.#tabledChunks++; } else if (this.#chunkTotals.length > 0) { // variable sized chunks? this.#chunkSize = this.#chunkTotals[this.#currChunk + 1] - this.#chunkTotals[this.#currChunk]; } this.#chunkCount = 0; return true; } return false; } /** * @param context - current context * @param pointData - where to store the decompressed data */ #firstChunkRead(context, pointData) { const { items } = this.lazHeader; let i; this.#readers = []; // first read in the raw data for (i = 0; i < items.length; i++) { const { type, size } = items[i]; this.#readers.push(this.#getPointCompressedReader(items[i])); let rawData = this.reader.seekSlice(size); if (type === LAZHeaderItemType.POINT14) rawData = modifyPoint14RawInput(rawData); pointData.push({ type, rawData }); } // now choose how we initialize if (this.layeredLas14Compression) { // set decoder and grap count size this.#dec.init(false); const _count = this.reader.getUint32(undefined); // chunk sizes then init for (i = 0; i < items.length; i++) this.#readers[i].chunkSizes(this.reader); for (i = 0; i < items.length; i++) this.#readers[i].init(pointData[i].rawData, context); } else { // initialize readers then init decoder for (i = 0; i < items.length; i++) this.#readers[i].init(pointData[i].rawData, context); this.#dec.init(); } } /** * @param pointData - where to store the decompressed data * @param context - the context */ #pointwiseCompressRead(pointData, context) { const { items } = this.lazHeader; for (let i = 0; i < items.length; i++) { const { type, size } = items[i]; let rawData = new DataView(new ArrayBuffer(size)); if (type === LAZHeaderItemType.POINT14) rawData = modifyPoint14RawInput(rawData); this.#readers[i].read(rawData, context); pointData.push({ type, rawData }); } } /** * @param pointData - a collection of raw data with it's type * @param header - the las header data * @returns a vector point */ #toVectorPoint(pointData, header) { const res = { x: 0, y: 0, z: 0, m: {} }; for (const { type, rawData } of pointData) { if (type === LAZHeaderItemType.POINT10) LASpoint10.rawToVectorPoint(rawData, header, res); else if (type === LAZHeaderItemType.GPSTIME11) res.m.gpsTime = rawData.getFloat64(0, true); else if (type === LAZHeaderItemType.RGB12 || type === LAZHeaderItemType.RGB14) LASrgba.rawToVectorPoint(rawData, res); else if (type === LAZHeaderItemType.WAVEPACKET13 || type === LAZHeaderItemType.WAVEPACKET14) LASWavePacket13.rawToVectorPoint(rawData, res); else if (type === LAZHeaderItemType.POINT14) LASpoint14.rawToVectorPoint(rawData, header, res); else if (type === LAZHeaderItemType.RGBNIR14) LASrgbaNir.rawToVectorPoint(rawData, res); } return res; } /** Initialize decoder */ #initDec() { // maybe read chunk table (only if chunking enabled) if (this.#numberChunks === U32_MAX) { this.#readChunkTable(); this.#currChunk = 0; if (this.#chunkTotals.length > 0) this.#chunkSize = this.#chunkTotals[1]; } this.#pointStart = this.reader.tell(); } /** If chunking is enabled, read the chunk table */ #readChunkTable() { const { reader } = this; let chunkTableStartPosition = Number(reader.getBigInt64(undefined, true)); // this is where the chunks start const chunksStart = reader.tell(); // I64 if (chunkTableStartPosition === -1) { // the compressor was writing to a non-seekable stream and wrote the chunk table start at the end // read the last 8 bytes chunkTableStartPosition = Number(reader.getBigInt64(reader.byteLength - 8, true)); } // read the chunk table // move to where the chunk table starts reader.seek(chunkTableStartPosition); // fail if the version is wrong const version = reader.getUint32(undefined, true); if (version !== 0) throw new Error('Bad version number. Aborting.'); // build the chunk table this.#numberChunks = reader.getUint32(undefined, true); this.#chunkTotals = []; // set chunk start and totals if (this.#chunkSize === U32_MAX) { this.#chunkTotals = new Array(this.#numberChunks + 1); this.#chunkTotals[0] = 0; } else this.#chunkStarts[0] = chunksStart; this.#tabledChunks = 1; if (this.#numberChunks > 0) { let i; this.#dec.init(); const ic = new IntegerCompressor(this.#dec, 32, 2); ic.initDecompressor(); for (i = 1; i <= this.#numberChunks; i++) { if (this.#chunkSize === U32_MAX) this.#chunkTotals[i] = ic.decompress(i > 1 ? this.#chunkTotals[i - 1] : 0, { value: 0 }); this.#chunkStarts[i] = ic.decompress(i > 1 ? this.#chunkStarts[i - 1] : 0, { value: 1 }); this.#tabledChunks++; } for (i = 1; i <= this.#numberChunks; i++) { if (this.#chunkSize === U32_MAX) this.#chunkTotals[i] += this.#chunkTotals[i - 1]; this.#chunkStarts[i] += this.#chunkStarts[i - 1]; } } reader.seek(chunksStart); } } //# sourceMappingURL=index.js.map