gis-tools-ts
Version:
A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.
1,082 lines • 73.6 kB
JavaScript
import { BufferReader } from '../../index.js';
import { ArithmeticDecoder, ArithmeticModel, IntegerCompressor, LASWavePacket13, LASZIP_DECOMPRESS_SELECTIVE_ALL, LASZIP_DECOMPRESS_SELECTIVE_BYTE0, LASZIP_DECOMPRESS_SELECTIVE_CLASSIFICATION, LASZIP_DECOMPRESS_SELECTIVE_FLAGS, LASZIP_DECOMPRESS_SELECTIVE_GPS_TIME, LASZIP_DECOMPRESS_SELECTIVE_INTENSITY, LASZIP_DECOMPRESS_SELECTIVE_NIR, LASZIP_DECOMPRESS_SELECTIVE_POINT_SOURCE, LASZIP_DECOMPRESS_SELECTIVE_RGB, LASZIP_DECOMPRESS_SELECTIVE_SCAN_ANGLE, LASZIP_DECOMPRESS_SELECTIVE_USER_DATA, LASZIP_DECOMPRESS_SELECTIVE_WAVEPACKET, LASZIP_DECOMPRESS_SELECTIVE_Z, LASpoint14, LASrgba, LASrgbaNir, StreamingMedian5, number_return_level_8ctx, number_return_map_6ctx, } from './index.js';
import { U64I64F64, i16Quantize, i8Clamp, u32ZeroBit0, u8Clamp, u8Fold } from '../util.js';
const LASZIP_GPSTIME_MULTI = 500;
const LASZIP_GPSTIME_MULTI_MINUS = -10;
const LASZIP_GPSTIME_MULTI_CODE_FULL = 511; // LASZIP_GPSTIME_MULTI - LASZIP_GPSTIME_MULTI_MINUS + 1;
const LASZIP_GPSTIME_MULTI_TOTAL = 515; // LASZIP_GPSTIME_MULTI - LASZIP_GPSTIME_MULTI_MINUS + 5;
/** LAS Point 1.4 context */
export class LAScontextPOINT14 {
unused = false;
lastItem = new DataView(new ArrayBuffer(128)); // U8[128]
lastIntensity = [0, 0, 0, 0, 0, 0, 0, 0]; // U16[8]
lastXDiffMedian5 = new Array(12).fill(undefined);
lastYDiffMedian5 = new Array(12).fill(undefined);
lastZ = [0, 0, 0, 0, 0, 0, 0, 0]; // I32[8]
mChangedValues = new Array(8).fill(undefined);
mScannerChannel;
mNumberOfReturns = new Array(16).fill(undefined);
mReturnNumberGpsSame;
mReturnNumber = new Array(16).fill(undefined);
icdX;
icdY;
icZ;
mClassification = new Array(64).fill(undefined);
mFlags = new Array(64).fill(undefined);
mUserData = new Array(64).fill(undefined);
icIntensity;
icScanAngle;
icPointSourceID;
// GPS time stuff
last = 0; // U32
next = 0; // U32
lastGpstime = [
new U64I64F64(0, 'u64'),
new U64I64F64(0, 'u64'),
new U64I64F64(0, 'u64'),
new U64I64F64(0, 'u64'),
];
lastGpstimeDiff = [0, 0, 0, 0]; // I32[4]
multiExtremeCounter = [0, 0, 0, 0]; // I32[4]
mGpstimeMulti;
mGpstime0diff;
icGpstime;
/** Setup class */
constructor() {
for (let i = 0; i < 12; i++) {
this.lastXDiffMedian5[i] = new StreamingMedian5();
this.lastYDiffMedian5[i] = new StreamingMedian5();
}
}
}
/** LAS RGB 1.4 context */
export class LAScontextRGB14 {
unused = false;
lastItem = new DataView(new ArrayBuffer(3 * 2)); // U16[3]
// models
mByteUsed;
mRgbDiff0;
mRgbDiff1;
mRgbDiff2;
mRgbDiff3;
mRgbDiff4;
mRgbDiff5;
}
/** LAS RGB & NIR 1.4 context */
export class LAScontextRGBNIR14 {
unused = false;
lastItem = new DataView(new ArrayBuffer(4 * 2)); // U16[4]
// models
mRgbBytesUsed;
mRgbDiff0;
mRgbDiff1;
mRgbDiff2;
mRgbDiff3;
mRgbDiff4;
mRgbDiff5;
mNirBytesUsed;
mNirDiff0;
mNirDiff1;
}
/** LAS WAVEPACKET 1.4 context */
export class LAScontextWAVEPACKET14 {
unused = false;
lastItem = new DataView(new ArrayBuffer(29)); // U8[29]
lastDiff32 = 0; // I32
symLastOffsetDiff = 0; // U32
// models
mPacketIndex;
mOffsetDiff = new Array(4).fill(undefined);
icOffsetDiff;
icPacketSize;
icReturnPoint;
icXyz;
}
/** LAS BYTE 1.4 context */
export class LAScontextBYTE14 {
unused = false;
lastItem = new DataView(new ArrayBuffer(1)); // U8[1] (or more as needed)
// models
mBytes = [undefined];
}
/** Parse LAZ Point 1.4v3 */
export class LAZPoint14v3Reader {
dec;
decompressSelective;
/* streams */
instreamChannelReturnsXY;
instreamZ;
instreamClassification;
instreamFlags;
instreamIntensity;
instreamScanAngle;
instreamUserData;
instreamPointSource;
instreamGpsTime;
/* decoders */
decChannelReturnsXY;
decZ;
decClassification;
decFlags;
decIntensity;
decScanAngle;
decUserData;
decPointSource;
decGpsTime;
/* Point structure */
requestedZ;
requestedClassification;
requestedFlags;
requestedIntensity;
requestedScanAngle;
requestedUserData;
requestedPointSource;
requestedGpsTime;
/* zero numBytes and init booleans */
numBytesChannelReturnsXY = 0;
numBytesZ = 0;
numBytesClassification = 0;
numBytesFlags = 0;
numBytesIntensity = 0;
numBytesScanAngle = 0;
numBytesUserData = 0;
numBytesPointSource = 0;
numBytesGpsTime = 0;
changedZ = false;
changedClassification = false;
changedFlags = false;
changedIntensity = false;
changedScanAngle = false;
changedUserData = false;
changedPointSource = false;
changedGpsTime = false;
bytes;
numBytesAllocated = 0;
currentContext = 0;
contexts = [
new LAScontextPOINT14(),
new LAScontextPOINT14(),
new LAScontextPOINT14(),
new LAScontextPOINT14(),
];
/**
* @param dec - the arithmetic decoder
* @param decompressSelective - which fields to decompress
*/
constructor(dec, decompressSelective = LASZIP_DECOMPRESS_SELECTIVE_ALL) {
this.dec = dec;
this.decompressSelective = decompressSelective;
/* mark the four scanner channel contexts as uninitialized */
for (const context of this.contexts)
context.mChangedValues[0] = undefined;
this.requestedZ = (decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_Z) !== 0;
this.requestedClassification =
(decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_CLASSIFICATION) !== 0;
this.requestedFlags = (decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_FLAGS) !== 0;
this.requestedIntensity = (decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_INTENSITY) !== 0;
this.requestedScanAngle = (decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_SCAN_ANGLE) !== 0;
this.requestedUserData = (decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_USER_DATA) !== 0;
this.requestedPointSource =
(decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_POINT_SOURCE) !== 0;
this.requestedGpsTime = (decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_GPS_TIME) !== 0;
}
/**
* Read in chunk sizes
* @param reader - the full data store
*/
chunkSizes(reader) {
this.numBytesChannelReturnsXY = reader.getUint32(undefined, true);
this.numBytesZ = reader.getUint32(undefined, true);
this.numBytesClassification = reader.getUint32(undefined, true);
this.numBytesFlags = reader.getUint32(undefined, true);
this.numBytesIntensity = reader.getUint32(undefined, true);
this.numBytesScanAngle = reader.getUint32(undefined, true);
this.numBytesUserData = reader.getUint32(undefined, true);
this.numBytesPointSource = reader.getUint32(undefined, true);
this.numBytesGpsTime = reader.getUint32(undefined, true);
}
/**
* @param item - the current item to be read into
* @param context - the current context
*/
init(item, context) {
/* for layered compression 'dec' only hands over the stream */
const { reader } = this.dec;
const itemPoint = new LASpoint14(item);
/* how many bytes do we need to read */
let numBytes = this.numBytesChannelReturnsXY;
if (this.requestedZ)
numBytes += this.numBytesZ;
if (this.requestedClassification)
numBytes += this.numBytesClassification;
if (this.requestedFlags)
numBytes += this.numBytesFlags;
if (this.requestedIntensity)
numBytes += this.numBytesIntensity;
if (this.requestedScanAngle)
numBytes += this.numBytesScanAngle;
if (this.requestedUserData)
numBytes += this.numBytesUserData;
if (this.requestedPointSource)
numBytes += this.numBytesPointSource;
if (this.requestedGpsTime)
numBytes += this.numBytesGpsTime;
/* make sure the buffer is sufficiently large */
if (numBytes > this.numBytesAllocated) {
this.bytes = new DataView(new ArrayBuffer(numBytes));
this.numBytesAllocated = numBytes;
}
/* load the requested bytes and init the corresponding instreams and decoders */
numBytes = 0;
this.bytes = reader.seekSlice(this.numBytesChannelReturnsXY);
this.instreamChannelReturnsXY = new BufferReader(this.bytes.buffer);
this.decChannelReturnsXY = new ArithmeticDecoder(this.instreamChannelReturnsXY);
this.decChannelReturnsXY.init();
numBytes += this.numBytesChannelReturnsXY;
if (this.requestedZ) {
if (this.numBytesZ !== 0) {
this.bytes = reader.seekSlice(this.numBytesZ);
this.instreamZ = new BufferReader(this.bytes.buffer);
this.decZ = new ArithmeticDecoder(this.instreamZ);
this.decZ.init();
numBytes += this.numBytesZ;
this.changedZ = true;
}
else {
this.instreamZ = undefined;
this.changedZ = false;
}
}
else {
if (this.numBytesZ !== 0) {
// skip numBytesZ
reader.seek(reader.tell() + this.numBytesZ);
}
this.changedZ = false;
}
if (this.requestedClassification) {
if (this.numBytesClassification !== 0) {
this.bytes = reader.seekSlice(this.numBytesClassification);
this.instreamClassification = new BufferReader(this.bytes.buffer);
this.decClassification = new ArithmeticDecoder(this.instreamClassification);
this.decClassification.init();
numBytes += this.numBytesClassification;
this.changedClassification = true;
}
else {
this.instreamClassification = undefined;
this.changedClassification = false;
}
}
else {
if (this.numBytesClassification !== 0) {
reader.seek(reader.tell() + this.numBytesClassification);
}
this.changedClassification = false;
}
if (this.requestedFlags) {
if (this.numBytesFlags !== 0) {
this.bytes = reader.seekSlice(this.numBytesFlags);
this.instreamFlags = new BufferReader(this.bytes.buffer);
this.decFlags = new ArithmeticDecoder(this.instreamFlags);
this.decFlags.init();
numBytes += this.numBytesFlags;
this.changedFlags = true;
}
else {
this.instreamFlags = undefined;
this.changedFlags = false;
}
}
else {
if (this.numBytesFlags !== 0) {
reader.seek(reader.tell() + this.numBytesFlags);
}
this.changedFlags = false;
}
if (this.requestedIntensity) {
if (this.numBytesIntensity !== 0) {
this.bytes = reader.seekSlice(this.numBytesIntensity);
this.instreamIntensity = new BufferReader(this.bytes.buffer);
this.decIntensity = new ArithmeticDecoder(this.instreamIntensity);
this.decIntensity.init();
numBytes += this.numBytesIntensity;
this.changedIntensity = true;
}
else {
this.instreamIntensity = undefined;
this.changedIntensity = false;
}
}
else {
if (this.numBytesIntensity !== 0) {
reader.seek(reader.tell() + this.numBytesIntensity);
}
this.changedIntensity = false;
}
if (this.requestedScanAngle) {
if (this.numBytesScanAngle !== 0) {
this.bytes = reader.seekSlice(this.numBytesScanAngle);
this.instreamScanAngle = new BufferReader(this.bytes.buffer);
this.decScanAngle = new ArithmeticDecoder(this.instreamScanAngle);
this.decScanAngle.init();
numBytes += this.numBytesScanAngle;
this.changedScanAngle = true;
}
else {
this.instreamScanAngle = undefined;
this.changedScanAngle = false;
}
}
else {
if (this.numBytesScanAngle !== 0) {
reader.seek(reader.tell() + this.numBytesScanAngle);
}
this.changedScanAngle = false;
}
if (this.requestedUserData) {
if (this.numBytesUserData !== 0) {
this.bytes = reader.seekSlice(this.numBytesUserData);
this.instreamUserData = new BufferReader(this.bytes.buffer);
this.decUserData = new ArithmeticDecoder(this.instreamUserData);
this.decUserData.init();
numBytes += this.numBytesUserData;
this.changedUserData = true;
}
else {
this.instreamUserData = undefined;
this.changedUserData = false;
}
}
else {
if (this.numBytesUserData !== 0) {
reader.seek(reader.tell() + this.numBytesUserData);
}
this.changedUserData = false;
}
if (this.requestedPointSource) {
if (this.numBytesPointSource !== 0) {
this.bytes = reader.seekSlice(this.numBytesPointSource);
this.instreamPointSource = new BufferReader(this.bytes.buffer);
this.decPointSource = new ArithmeticDecoder(this.instreamPointSource);
this.decPointSource.init();
numBytes += this.numBytesPointSource;
this.changedPointSource = true;
}
else {
this.instreamPointSource = undefined;
this.changedPointSource = false;
}
}
else {
if (this.numBytesPointSource !== 0) {
reader.seek(reader.tell() + this.numBytesPointSource);
}
this.changedPointSource = false;
}
if (this.requestedGpsTime) {
if (this.numBytesGpsTime !== 0) {
this.bytes = reader.seekSlice(this.numBytesGpsTime);
this.instreamGpsTime = new BufferReader(this.bytes.buffer);
this.decGpsTime = new ArithmeticDecoder(this.instreamGpsTime);
this.decGpsTime.init();
numBytes += this.numBytesGpsTime;
this.changedGpsTime = true;
}
else {
this.instreamGpsTime = undefined;
this.changedGpsTime = false;
}
}
else {
if (this.numBytesGpsTime !== 0) {
reader.seek(reader.tell() + this.numBytesGpsTime);
}
this.changedGpsTime = false;
}
/* mark the four scanner channel contexts as unused */
for (let c = 0; c < 4; c++)
this.contexts[c].unused = true;
/* set scanner channel as current context */
this.currentContext = itemPoint.scannerChannel;
context.value = this.currentContext; // the POINT14 reader sets context for all other items
/* create and init models and decompressors */
this.#createAndInitModelsAndDecompressors(this.currentContext, itemPoint);
}
/**
* @param item - the current item to be read into
* @param context - the current context
*/
read(item, context) {
const { contexts } = this;
// get last
let lastItem = new LASpoint14(contexts[this.currentContext].lastItem);
////////////////////////////////////////
// decompress returns_XY layer
////////////////////////////////////////
// create single (3) / first (1) / last (2) / intermediate (0) context from last point return
let lpr = lastItem.returnNumber === 1 ? 1 : 0; // (I32) first?
lpr += lastItem.returnNumber >= lastItem.numberOfReturns ? 2 : 0; // last?
// add info whether the GPS time changed in the last return to the context
lpr += lastItem.gpsTimeChange !== 0 ? 4 : 0;
// decompress which values have changed with last point return context
const changedValues = this.decChannelReturnsXY.decodeSymbol(contexts[this.currentContext].mChangedValues[lpr]); // I32
// if scanner channel has changed
if ((changedValues & (1 << 6)) !== 0) {
// U32
const diff = this.decChannelReturnsXY.decodeSymbol(contexts[this.currentContext].mScannerChannel); // curr = last + (sym + 1)
// U32
const scannerChannel = (this.currentContext + diff + 1) % 4;
// maybe create and init entropy models and integer compressors
if (contexts[scannerChannel].unused) {
// create and init entropy models and integer decompressors
this.#createAndInitModelsAndDecompressors(scannerChannel, new LASpoint14(contexts[this.currentContext].lastItem));
}
// switch context to current scanner channel
this.currentContext = scannerChannel;
context.value = this.currentContext; // the POINT14 reader sets context for all other items
// get last for new context
lastItem = new LASpoint14(contexts[this.currentContext].lastItem);
lastItem.scannerChannel = scannerChannel;
}
// determine changed attributes
const pointSourceChange = (changedValues & (1 << 5)) !== 0 ? true : false;
const gpsTimeChange = (changedValues & (1 << 4)) !== 0 ? true : false;
const scanAngleChange = (changedValues & (1 << 3)) !== 0 ? true : false;
// get last return counts
const lastN = lastItem.numberOfReturns; // U32
const lastR = lastItem.returnNumber; // U32
// if number of returns is different we decompress it
let n; // U32
if ((changedValues & (1 << 2)) !== 0) {
if (contexts[this.currentContext].mNumberOfReturns[lastN] === undefined) {
contexts[this.currentContext].mNumberOfReturns[lastN] = new ArithmeticModel(16);
contexts[this.currentContext].mNumberOfReturns[lastN].init();
}
n = this.decChannelReturnsXY.decodeSymbol(contexts[this.currentContext].mNumberOfReturns[lastN]);
lastItem.numberOfReturns = n;
}
else {
n = lastN;
}
// how is the return number different
let r; // U32
if ((changedValues & 3) === 0) {
// same return number
r = lastR;
}
else if ((changedValues & 3) === 1) {
// return number plus 1 mod 16
r = (lastR + 1) % 16;
lastItem.returnNumber = r;
}
else if ((changedValues & 3) === 2) {
// return number minus 1 mod 16
r = (lastR + 15) % 16;
lastItem.returnNumber = r;
}
else {
// the return number difference is bigger than +1 / -1 so we decompress how it is different
if (gpsTimeChange) {
// if the GPS time has changed
if (contexts[this.currentContext].mReturnNumber[lastR] === undefined) {
contexts[this.currentContext].mReturnNumber[lastR] = new ArithmeticModel(16);
contexts[this.currentContext].mReturnNumber[lastR].init();
}
r = this.decChannelReturnsXY.decodeSymbol(contexts[this.currentContext].mReturnNumber[lastR]);
} // if the GPS time has not changed
else {
// I32
const sym = this.decChannelReturnsXY.decodeSymbol(contexts[this.currentContext].mReturnNumberGpsSame);
r = (lastR + (sym + 2)) % 16;
}
lastItem.returnNumber = r;
}
// set legacy return counts and number of returns
if (n > 7) {
if (r > 6) {
if (r >= n) {
lastItem.legacyReturnNumber = 7;
}
else {
lastItem.legacyReturnNumber = 6;
}
}
else {
lastItem.legacyReturnNumber = r;
}
lastItem.legacyNumberOfReturns = 7;
}
else {
lastItem.legacyReturnNumber = r;
lastItem.legacyNumberOfReturns = n;
}
// get return map m and return level l context for current point
const m = number_return_map_6ctx[n][r]; // U32
const l = number_return_level_8ctx[n][r]; // U32
// create single (3) / first (1) / last (2) / intermediate (0) return context for current point
let cpr = r === 1 ? 2 : 0; // (I32) first ?
cpr += r >= n ? 1 : 0; // last ?
let kBits; // U32
let median, diff; // I32
const decIndex = (m << 1) | (gpsTimeChange ? 1 : 0);
// decompress X coordinate
median = contexts[this.currentContext].lastXDiffMedian5[decIndex].get();
diff = contexts[this.currentContext].icdX.decompress(median, { value: n === 1 ? 1 : 0 });
lastItem.x += diff;
contexts[this.currentContext].lastXDiffMedian5[decIndex].add(diff);
// decompress Y coordinate
median = contexts[this.currentContext].lastYDiffMedian5[decIndex].get();
kBits = contexts[this.currentContext].icdX.getK();
diff = contexts[this.currentContext].icdY.decompress(median, {
value: (n === 1 ? 1 : 0) + (kBits < 20 ? u32ZeroBit0(kBits) : 20),
});
lastItem.y += diff;
contexts[this.currentContext].lastYDiffMedian5[decIndex].add(diff);
////////////////////////////////////////
// decompress Z layer (if changed and requested)
////////////////////////////////////////
if (this.changedZ) {
// if the Z coordinate should be decompressed and changes within this chunk
kBits =
(contexts[this.currentContext].icdX.getK() + contexts[this.currentContext].icdY.getK()) /
2;
lastItem.z = contexts[this.currentContext].icZ.decompress(contexts[this.currentContext].lastZ[l], {
value: (n === 1 ? 1 : 0) + (kBits < 18 ? u32ZeroBit0(kBits) : 18),
});
contexts[this.currentContext].lastZ[l] = lastItem.z;
}
////////////////////////////////////////
// decompress classifications layer (if changed and requested)
////////////////////////////////////////
if (this.changedClassification) {
// if the classification should be decompressed and changes within this chunk
const lastClassification = lastItem.classification; // U32
const ccc = ((lastClassification & 0x1f) << 1) + (cpr === 3 ? 1 : 0); // I32
if (contexts[this.currentContext].mClassification[ccc] === undefined) {
contexts[this.currentContext].mClassification[ccc] = new ArithmeticModel(256);
contexts[this.currentContext].mClassification[ccc].init();
}
lastItem.classification = this.decClassification.decodeSymbol(contexts[this.currentContext].mClassification[ccc]);
// update the legacy copy
if (lastItem.classification < 32) {
lastItem.legacyClassification = lastItem.classification;
}
else {
lastItem.legacyClassification = 0;
}
}
////////////////////////////////////////
// decompress flags layer (if changed and requested)
////////////////////////////////////////
if (this.changedFlags) {
// if the flags should be decompressed and change within this chunk
// U32
const lastFlags = (lastItem.edgeOfFlightLine << 5) |
(lastItem.scanDirectionFlag << 4) |
lastItem.classificationFlags;
if (contexts[this.currentContext].mFlags[lastFlags] === undefined) {
contexts[this.currentContext].mFlags[lastFlags] = new ArithmeticModel(64);
contexts[this.currentContext].mFlags[lastFlags].init();
}
const flags = this.decFlags.decodeSymbol(contexts[this.currentContext].mFlags[lastFlags]); // U32
lastItem.edgeOfFlightLine = (flags & (1 << 5)) !== 0 ? 1 : 0;
lastItem.scanDirectionFlag = (flags & (1 << 4)) !== 0 ? 1 : 0;
lastItem.classificationFlags = flags & 0x0f;
// legacy copies
lastItem.legacyFlags = flags & 0x07;
}
////////////////////////////////////////
// decompress intensity layer (if changed and requested)
////////////////////////////////////////
if (this.changedIntensity) {
// if the intensity should be decompressed and changes within this chunk
// U16
const intensity = contexts[this.currentContext].icIntensity.decompress(contexts[this.currentContext].lastIntensity[(cpr << 1) | (gpsTimeChange ? 1 : 0)], { value: cpr });
contexts[this.currentContext].lastIntensity[(cpr << 1) | (gpsTimeChange ? 1 : 0)] = intensity;
lastItem.intensity = intensity;
}
////////////////////////////////////////
// decompress scanAngle layer (if changed and requested)
////////////////////////////////////////
if (this.changedScanAngle) {
// if the scan angle should be decompressed and changes within this chunk
if (scanAngleChange) {
// if the scan angle has actually changed
lastItem.scanAngle = contexts[this.currentContext].icScanAngle.decompress(lastItem.scanAngle, { value: gpsTimeChange ? 1 : 0 }); // if the GPS time has changed
lastItem.legacyScanAngleRank = i8Clamp(i16Quantize(0.006 * lastItem.scanAngle));
}
}
////////////////////////////////////////
// decompress userData layer (if changed and requested)
////////////////////////////////////////
if (this.changedUserData) {
const index = Math.trunc(lastItem.userData / 4);
// if the user data should be decompressed and changes within this chunk
if (contexts[this.currentContext].mUserData[index] === undefined) {
contexts[this.currentContext].mUserData[index] = new ArithmeticModel(256);
contexts[this.currentContext].mUserData[index].init();
}
lastItem.userData = this.decUserData.decodeSymbol(contexts[this.currentContext].mUserData[index]);
}
////////////////////////////////////////
// decompress point_source layer (if changed and requested)
////////////////////////////////////////
if (this.changedPointSource) {
// if the point source ID should be decompressed and changes within this chunk
if (pointSourceChange) {
// if the point source ID has actually changed
lastItem.pointSourceID = contexts[this.currentContext].icPointSourceID.decompress(lastItem.pointSourceID);
}
}
////////////////////////////////////////
// decompress gpsTime layer (if changed and requested)
////////////////////////////////////////
if (this.changedGpsTime) {
// if the GPS time should be decompressed and changes within this chunk
if (gpsTimeChange) {
// if the GPS time has actually changed
this.#readGpsTime();
lastItem.gpsTime =
contexts[this.currentContext].lastGpstime[contexts[this.currentContext].last].f64;
}
}
// copy the last item
lastItem.copyTo(item, 45);
// remember if the last point had a gpsTimeChange
lastItem.gpsTimeChange = gpsTimeChange ? 1 : 0;
}
/** Read the GPS Time */
#readGpsTime() {
const { contexts, currentContext } = this;
let multi; // I32
if (contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last] === 0) {
// if the last integer difference was zero
multi = this.decGpsTime.decodeSymbol(contexts[currentContext].mGpstime0diff);
if (multi === 0) {
// the difference can be represented with 32 bits
contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last] = contexts[currentContext].icGpstime.decompress(0, { value: 0 });
contexts[currentContext].lastGpstime[contexts[currentContext].last].i64 += BigInt(contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last]);
contexts[currentContext].multiExtremeCounter[contexts[currentContext].last] = 0;
}
else if (multi === 1) {
// the difference is huge
contexts[currentContext].next = (contexts[currentContext].next + 1) & 3;
contexts[currentContext].lastGpstime[contexts[currentContext].next].u64 = contexts[currentContext].icGpstime.decompress(Number(contexts[currentContext].lastGpstime[contexts[currentContext].last].u64 >> 32n), { value: 8 });
contexts[currentContext].lastGpstime[contexts[currentContext].next].u64 =
contexts[currentContext].lastGpstime[contexts[currentContext].next].u64 << 32n;
const inputINT = this.decGpsTime.readInt();
contexts[currentContext].lastGpstime[contexts[currentContext].next].u64 |= BigInt(inputINT);
contexts[currentContext].last = contexts[currentContext].next;
contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last] = 0;
contexts[currentContext].multiExtremeCounter[contexts[currentContext].last] = 0;
} // we switch to another sequence
else {
contexts[currentContext].last = (contexts[currentContext].last + multi - 1) & 3;
this.#readGpsTime();
}
}
else {
multi = this.decGpsTime.decodeSymbol(contexts[currentContext].mGpstimeMulti);
if (multi === 1) {
contexts[currentContext].lastGpstime[contexts[currentContext].last].i64 += BigInt(contexts[currentContext].icGpstime.decompress(contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last], { value: 1 }));
contexts[currentContext].multiExtremeCounter[contexts[currentContext].last] = 0;
}
else if (multi < LASZIP_GPSTIME_MULTI_CODE_FULL) {
let gpstimeDiff; // I32
if (multi === 0) {
gpstimeDiff = contexts[currentContext].icGpstime.decompress(0, { value: 7 });
contexts[currentContext].multiExtremeCounter[contexts[currentContext].last]++;
if (contexts[currentContext].multiExtremeCounter[contexts[currentContext].last] > 3) {
contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last] = gpstimeDiff;
contexts[currentContext].multiExtremeCounter[contexts[currentContext].last] = 0;
}
}
else if (multi < LASZIP_GPSTIME_MULTI) {
if (multi < 10)
gpstimeDiff = contexts[currentContext].icGpstime.decompress(multi * contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last], { value: 2 });
else
gpstimeDiff = contexts[currentContext].icGpstime.decompress(multi * contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last], { value: 3 });
}
else if (multi === LASZIP_GPSTIME_MULTI) {
gpstimeDiff = contexts[currentContext].icGpstime.decompress(LASZIP_GPSTIME_MULTI *
contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last], { value: 4 });
contexts[currentContext].multiExtremeCounter[contexts[currentContext].last]++;
if (contexts[currentContext].multiExtremeCounter[contexts[currentContext].last] > 3) {
contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last] = gpstimeDiff;
contexts[currentContext].multiExtremeCounter[contexts[currentContext].last] = 0;
}
}
else {
multi = LASZIP_GPSTIME_MULTI - multi;
if (multi > LASZIP_GPSTIME_MULTI_MINUS) {
gpstimeDiff = contexts[currentContext].icGpstime.decompress(multi * contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last], { value: 5 });
}
else {
gpstimeDiff = contexts[currentContext].icGpstime.decompress(LASZIP_GPSTIME_MULTI_MINUS *
contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last], { value: 6 });
contexts[currentContext].multiExtremeCounter[contexts[currentContext].last]++;
if (contexts[currentContext].multiExtremeCounter[contexts[currentContext].last] > 3) {
contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last] = gpstimeDiff;
contexts[currentContext].multiExtremeCounter[contexts[currentContext].last] = 0;
}
}
}
contexts[currentContext].lastGpstime[contexts[currentContext].last].i64 +=
BigInt(gpstimeDiff);
}
else if (multi === LASZIP_GPSTIME_MULTI_CODE_FULL) {
contexts[currentContext].next = (contexts[currentContext].next + 1) & 3;
contexts[currentContext].lastGpstime[contexts[currentContext].next].u64 = contexts[currentContext].icGpstime.decompress(Number(contexts[currentContext].lastGpstime[contexts[currentContext].last].u64 >> 32n), { value: 8 });
contexts[currentContext].lastGpstime[contexts[currentContext].next].u64 =
contexts[currentContext].lastGpstime[contexts[currentContext].next].u64 << 32n;
contexts[currentContext].lastGpstime[contexts[currentContext].next].u64 |= BigInt(this.decGpsTime.readInt());
contexts[currentContext].last = contexts[currentContext].next;
contexts[currentContext].lastGpstimeDiff[contexts[currentContext].last] = 0;
contexts[currentContext].multiExtremeCounter[contexts[currentContext].last] = 0;
}
else if (multi >= LASZIP_GPSTIME_MULTI_CODE_FULL) {
contexts[currentContext].last =
(contexts[currentContext].last + multi - LASZIP_GPSTIME_MULTI_CODE_FULL) & 3;
this.#readGpsTime();
}
}
}
/**
* @param context - the current context
* @param item - the current item to be read from
*/
#createAndInitModelsAndDecompressors(context, item) {
const { contexts } = this;
let i;
/* should only be called when context is unused */
// assert(contexts[context].unused);
/* first create all entropy models and integer decompressors (if needed) */
if (contexts[context].mChangedValues[0] === undefined) {
/* for the channel_returns_XY layer */
for (i = 0; i < 8; i++) {
contexts[context].mChangedValues[i] = new ArithmeticModel(128);
}
contexts[context].mScannerChannel = new ArithmeticModel(3);
for (i = 0; i < 16; i++) {
contexts[context].mNumberOfReturns[i] = undefined;
contexts[context].mReturnNumber[i] = undefined;
}
contexts[context].mReturnNumberGpsSame = new ArithmeticModel(13);
contexts[context].icdX = new IntegerCompressor(this.decChannelReturnsXY, 32, 2); // 32 bits, 2 context
contexts[context].icdY = new IntegerCompressor(this.decChannelReturnsXY, 32, 22); // 32 bits, 22 contexts
/* for the Z layer */
contexts[context].icZ = new IntegerCompressor(this.decZ, 32, 20); // 32 bits, 20 contexts
/* for the classification layer, flags layer, and userData layer */
for (i = 0; i < 64; i++) {
contexts[context].mClassification[i] = undefined;
contexts[context].mFlags[i] = undefined;
contexts[context].mUserData[i] = undefined;
}
/* for the intensity layer */
contexts[context].icIntensity = new IntegerCompressor(this.decIntensity, 16, 4);
/* for the scanAngle layer */
contexts[context].icScanAngle = new IntegerCompressor(this.decScanAngle, 16, 2);
/* for the pointSourceID layer */
contexts[context].icPointSourceID = new IntegerCompressor(this.decPointSource, 16);
/* for the gpsTime layer */
contexts[context].mGpstimeMulti = new ArithmeticModel(LASZIP_GPSTIME_MULTI_TOTAL);
contexts[context].mGpstime0diff = new ArithmeticModel(5);
contexts[context].icGpstime = new IntegerCompressor(this.decGpsTime, 32, 9); // 32 bits, 9 contexts
}
/* for the channel_returns_XY layer */
for (i = 0; i < 8; i++)
contexts[context].mChangedValues[i].init();
contexts[context].mScannerChannel.init();
for (i = 0; i < 16; i++) {
if (contexts[context].mNumberOfReturns[i] !== undefined)
contexts[context].mNumberOfReturns[i].init();
if (contexts[context].mReturnNumber[i] !== undefined)
contexts[context].mReturnNumber[i].init();
}
contexts[context].mReturnNumberGpsSame.init();
contexts[context].icdX.initDecompressor();
contexts[context].icdY.initDecompressor();
for (i = 0; i < 12; i++) {
contexts[context].lastXDiffMedian5[i].init();
contexts[context].lastYDiffMedian5[i].init();
}
/* for the Z layer */
contexts[context].icZ.initDecompressor();
for (i = 0; i < 8; i++) {
contexts[context].lastZ[i] = item.z;
}
/* for the classification layer, flags layer, and userData layer */
for (i = 0; i < 64; i++) {
if (contexts[context].mClassification[i] !== undefined)
contexts[context].mClassification[i].init();
if (contexts[context].mFlags[i] !== undefined)
contexts[context].mFlags[i].init();
if (contexts[context].mUserData[i] !== undefined)
contexts[context].mUserData[i].init();
}
/* for the intensity layer */
this.contexts[context].icIntensity.initDecompressor();
for (i = 0; i < 8; i++) {
contexts[context].lastIntensity[i] = item.intensity;
}
/* for the scanAngle layer */
this.contexts[context].icScanAngle.initDecompressor();
/* for the pointSourceID layer */
this.contexts[context].icPointSourceID.initDecompressor();
/* for the gpsTime layer */
contexts[context].mGpstimeMulti.init();
contexts[context].mGpstime0diff.init();
this.contexts[context].icGpstime.initDecompressor();
contexts[context].last = 0;
contexts[context].next = 0;
contexts[context].lastGpstimeDiff[0] = 0;
contexts[context].lastGpstimeDiff[1] = 0;
contexts[context].lastGpstimeDiff[2] = 0;
contexts[context].lastGpstimeDiff[3] = 0;
contexts[context].multiExtremeCounter[0] = 0;
contexts[context].multiExtremeCounter[1] = 0;
contexts[context].multiExtremeCounter[2] = 0;
contexts[context].multiExtremeCounter[3] = 0;
contexts[context].lastGpstime[0].f64 = item.gpsTime;
contexts[context].lastGpstime[1].u64 = 0;
contexts[context].lastGpstime[2].u64 = 0;
contexts[context].lastGpstime[3].u64 = 0;
/* init current context from last item */
item.copyTo(contexts[context].lastItem, 45);
new LASpoint14(contexts[context].lastItem).gpsTimeChange = 0;
contexts[context].unused = false;
}
}
/** Parse LAZ RGB 1.4v3 */
export class LAZrgb14v3Reader {
dec;
decompressSelective;
instreamRGB;
decRGB;
changedRGB = false;
numBytesRGB = 0; // U32
requestedRGB;
bytes;
numBytesAllocated = 0; // U32
currentContext = 0; // U32
contexts = [
new LAScontextRGB14(),
new LAScontextRGB14(),
new LAScontextRGB14(),
new LAScontextRGB14(),
];
/**
* @param dec - the arithmetic decoder
* @param decompressSelective - which fields to decompress
*/
constructor(dec, decompressSelective = LASZIP_DECOMPRESS_SELECTIVE_ALL) {
this.dec = dec;
this.decompressSelective = decompressSelective;
this.requestedRGB = Boolean(decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_RGB);
/* mark the four scanner channel contexts as uninitialized */
for (let c = 0; c < 4; c++)
this.contexts[c].mByteUsed = undefined;
}
/**
* @param item - the current item
* @param context - the current context
*/
init(item, context) {
const { contexts } = this;
const { reader } = this.dec;
/* make sure the buffer is sufficiently large */
if (this.numBytesRGB > this.numBytesAllocated) {
this.numBytesAllocated = this.numBytesRGB;
}
/* load the requested bytes and init the corresponding instreams an decoders */
if (this.requestedRGB) {
if (this.numBytesRGB !== 0) {
this.bytes = reader.seekSlice(this.numBytesRGB);
this.instreamRGB = new BufferReader(this.bytes.buffer);
this.decRGB = new ArithmeticDecoder(this.instreamRGB);
this.decRGB.init();
this.changedRGB = true;
}
else {
this.instreamRGB = undefined;
this.changedRGB = false;
}
}
else {
if (this.numBytesRGB !== 0) {
reader.seek(reader.tell() + this.numBytesRGB);
}
this.changedRGB = false;
}
/* mark the four scanner channel contexts as unused */
for (let c = 0; c < 4; c++)
contexts[c].unused = true;
/* set scanner channel as current context */
this.currentContext = context.value; // all other items use context set by POINT14 reader
/* create and init models and decompressors */
this.#createAndInitModelsAndDecompressors(this.currentContext, new LASrgba(item));
}
/**
* @param item - the current item
* @param context - the current context
*/
read(item, context) {
const { contexts } = this;
const itemRGB = new LASrgba(item);
// get last
let lastItem = new LASrgba(contexts[this.currentContext].lastItem);
// check for context switch
if (this.currentContext !== context.value) {
this.currentContext = context.value; // all other items use context set by POINT14 reader
if (contexts[this.currentContext].unused) {
this.#createAndInitModelsAndDecompressors(this.currentContext, lastItem);
lastItem = new LASrgba(contexts[this.currentContext].lastItem);
}
}
// decompress
if (this.changedRGB) {
let corr; // U8
let diff = 0; // I32
const sym = this.decRGB.decodeSymbol(contexts[this.currentContext].mByteUsed); // U32
if ((sym & (1 << 0)) !== 0) {
corr = this.decRGB.decodeSymbol(contexts[this.currentContext].mRgbDiff0);
itemRGB.r = u8Fold(corr + (lastItem.r & 255));
}
else {
itemRGB.r = lastItem.r & 0xff;
}
if ((sym & (1 << 1)) !== 0) {
corr = this.decRGB.decodeSymbol(contexts[this.currentContext].mRgbDiff1);
itemRGB.r |= u8Fold(corr + (lastItem.r >> 8)) << 8;
}
else {
itemRGB.r |= lastItem.r & 0xff00;
}
if ((sym & (1 << 6)) !== 0) {
diff = (itemRGB.r & 0x00ff) - (lastItem.r & 0x00ff);
if ((sym & (1 << 2)) !== 0) {
corr = this.decRGB.decodeSymbol(contexts[this.currentContext].mRgbDiff2);
itemRGB.g = u8Fold(corr + u8Clamp(diff + (lastItem.g & 255)));
}
else {
itemRGB.g = lastItem.g & 0xff;
}
if ((sym & (1 << 4)) !== 0) {
corr = this.decRGB.decodeSymbol(contexts[this.currentContext].mRgbDiff4);
diff = Math.trunc((diff + ((itemRGB.g & 0x00ff) - (lastItem.g & 0x00ff))) / 2);
itemRGB.b = u8Fold(corr + u8Clamp(diff + (lastItem.b & 255)));
}
else {
itemRGB.b = lastItem.b & 0xff;
}
diff = (itemRGB.r >> 8) - (lastItem.r >> 8);
if ((sym & (1 << 3)) !== 0) {
corr = this.decRGB.decodeSymbol(contexts[this.currentContext].mRgbDiff3);
itemRGB.g |= u8Fold(corr + u8Clamp(diff + (lastItem.g >> 8))) << 8;
}
else {
itemRGB.g |= lastItem.g & 0xff00;
}
if ((sym & (1 << 5)) !== 0) {
corr = this.decRGB.decodeSymbol(contexts[this.currentContext].mRgbDiff5);
diff = Math.trunc((diff + ((itemRGB.g >> 8) - (lastItem.g >> 8))) / 2);
itemRGB.b |= u8Fold(corr + u8Clamp(diff + (lastItem.b >> 8))) << 8;
}
else {
itemRGB.b |= lastItem.b & 0xff00;
}
}
else {
itemRGB.g = itemRGB.r;
itemRGB.b = itemRGB.r;
}
itemRGB.copyTo(lastItem.data, 6);
}
else {
itemRGB.copyFrom(lastItem.data, 6);
}
}
/**
* Read in the chunk size
* @param reader - the data block to read from
*/
chunkSizes(reader) {
/* read bytes per layer */
this.numBytesRGB = reader.getUint32(undefined, true);
}
/**
* @param context - the current context
* @param item - the current item to be read from
*/
#createAndInitModelsAndDecompressors(context, item) {
const { contexts } = this;
/* first create all entropy models (if needed) */
if (contexts[context].mByteUsed === undefined) {
contexts[context].mByteUsed = new ArithmeticModel(128);
contexts[context].mRgbDiff0 = new ArithmeticModel(256);
contexts[context].mRgbDiff1 = new ArithmeticModel(256);
contexts[context].mRgbDiff2 = new ArithmeticModel(256);
contexts[context].mRgbDiff3 = new ArithmeticModel(256);
contexts[context].mRgbDiff4 = new ArithmeticModel(256);
contexts[context].mRgbDiff5 = new ArithmeticModel(256);
}
/* then init entropy models */
contexts[context].mByteUsed.init();
contexts[context].mRgbDiff0.init();
contexts[context].mRgbDiff1.init();
contexts[context].mRgbDiff2.init();
contexts[context].mRgbDiff3.init();
contexts[context].mRgbDiff4.init();
contexts[context].mRgbDiff5.init();
/* init current context from item */
item.copyTo(contexts[context].lastItem, 6);
contexts[context].unused = false;
}
}
/** Parse LAZ RGB NIR 1.4v3 */
export class LAZrgbNir14v3Reader {
dec;
decompressSelective;
instreamRGB;
instreamNIR;
decRGB;
decNIR;
changedRGB = false;
changedNIR = false;
numBytesRGB = 0; // U32
numBytesNIR = 0; // U32
requestedRGB;
requestedNIR;
bytes;
numBytesAllocated = 0; // U32
currentContext = 0; // U32
contexts = [
new LAScontextRGBNIR14(),
new LAScontextRGBNIR14(),
new LAScontextRGBNIR14(),
new LAScontextRGBNIR14(),
];
/**
* @param dec - the arithmetic decoder
* @param decompressSelective - the decompress selective
*/
constructor(dec, decompressSelective = LASZIP_DECOMPRESS_SELECTIVE_ALL) {
this.dec = dec;
this.decompressSelective = decompressSelective;
this.requestedRGB = Boolean(decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_RGB);
this.requestedNIR = Boolean(decompressSelective & LASZIP_DECOMPRESS_SELECTIVE_NIR);
/* mark the four scanner channel contexts as uninitialized */
for (let c = 0; c < 4; c++) {
this.contexts[c].mRgbBytesUsed = undefined;
this.contexts[c].mNirBytesUsed = undefined;
}
}
/** @param reader - the data block to read from */
chunkSizes(reader) {
/* read bytes per layer */
this.numBytesRGB = reader.getUint32(undefined, true);
this.numBytesNIR = reader.getUint32(undefined, true);
}
/**
* @pa