Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions src/loading/binary-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { Box3, BufferAttribute, BufferGeometry, Uint8BufferAttribute, Vector3 }
import { PointAttributeName, PointAttributeType } from '../point-attributes';
import { PointCloudOctreeGeometryNode } from '../point-cloud-octree-geometry-node';
import { handleEmptyBuffer, handleFailedRequest } from '../utils/utils';
import { WorkerPool } from '../utils/worker-pool';
import { WorkerPool, WorkerType } from '../utils/worker-pool';
import { Version } from '../version';
import { GetUrlFn, XhrRequest } from './types';
import { Callback, GetUrlFn, XhrRequest } from './types';

interface AttributeData {
attribute: {
Expand Down Expand Up @@ -37,8 +37,6 @@ interface BinaryLoaderOptions {
xhrRequest: XhrRequest;
}

type Callback = (node: PointCloudOctreeGeometryNode) => void;

export class BinaryLoader {
version: Version;
boundingBox: Box3;
Expand All @@ -48,10 +46,7 @@ export class BinaryLoader {
xhrRequest: XhrRequest;
callbacks: Callback[];

public static readonly WORKER_POOL = new WorkerPool(
32,
require('../workers/binary-decoder.worker.js').default,
);
public static readonly WORKER_POOL = WorkerPool.getInstance();

constructor({
getUrl = s => Promise.resolve(s),
Expand Down Expand Up @@ -111,7 +106,7 @@ export class BinaryLoader {
return;
}

BinaryLoader.WORKER_POOL.getWorker().then(autoTerminatingWorker => {
BinaryLoader.WORKER_POOL.getWorker(WorkerType.BINARY_DECODER_WORKER).then(autoTerminatingWorker => {
const pointAttributes = node.pcoGeometry.pointAttributes;
const numPoints = buffer.byteLength / pointAttributes.byteSize;

Expand All @@ -122,7 +117,7 @@ export class BinaryLoader {
autoTerminatingWorker.worker.onmessage = (e: WorkerResponse) => {
if (this.disposed) {
resolve();
BinaryLoader.WORKER_POOL.releaseWorker(autoTerminatingWorker);
BinaryLoader.WORKER_POOL.releaseWorker(WorkerType.BINARY_DECODER_WORKER, autoTerminatingWorker);
return;
}

Expand All @@ -145,7 +140,7 @@ export class BinaryLoader {

this.callbacks.forEach(callback => callback(node));
resolve();
BinaryLoader.WORKER_POOL.releaseWorker(autoTerminatingWorker);
BinaryLoader.WORKER_POOL.releaseWorker(WorkerType.BINARY_DECODER_WORKER, autoTerminatingWorker);
};

const message = {
Expand Down
66 changes: 66 additions & 0 deletions src/loading/lazlaz/LASFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { WorkerPool } from 'utils/worker-pool';
import { LASLoader } from './LASLoader';
import { LAZLoader } from './LAZLoader';
import { readAs, pointFormatReaders } from './helpers';

export class LASFile {
arraybuffer: ArrayBuffer;
version: number;
versionAsString: string;
formatId: number;
isCompressed: boolean;

loader: LASLoader | LAZLoader;

constructor(arraybuffer: ArrayBuffer, workerPool: WorkerPool) {
this.arraybuffer = arraybuffer;

const ver = new Int8Array(this.arraybuffer, 24, 2);
this.version = ver[0] * 10 + ver[1];
this.versionAsString = ver[0] + '.' + ver[1];
if (this.version > 12) throw new Error('Only file versions <= 1.2 are supported at this time');

const formatId = readAs(this.arraybuffer, Uint8Array, 32 * 3 + 8) as number;
const bit_7 = (formatId & 0x80) >> 7;
const bit_6 = (formatId & 0x40) >> 6;

if (bit_7 === 1 && bit_6 === 1) throw new Error('Old style compression not supported');

this.formatId = formatId & 0x3f;
this.isCompressed = bit_7 === 1 || bit_6 === 1;

if (pointFormatReaders[this.formatId] === undefined)
throw new Error('The point format ID is not supported');

this.loader = this.isCompressed
? new LAZLoader(this.arraybuffer, workerPool)
: new LASLoader(this.arraybuffer);
}

determineFormat() {
const formatId = readAs(this.arraybuffer, Uint8Array, 32 * 3 + 8) as number;
const bit_7 = (formatId & 0x80) >> 7;
const bit_6 = (formatId & 0x40) >> 6;

if (bit_7 === 1 && bit_6 === 1) throw new Error('Old style compression not supported');

this.formatId = formatId & 0x3f;
this.isCompressed = bit_7 === 1 || bit_6 === 1;
}

open() {
return this.loader.open();
}

getHeader() {
return this.loader.getHeader();
}

readData(count: number, start: number, skip: number) {
return this.loader.readData(count, start, skip);
}

close() {
return this.loader.close();
}
}
85 changes: 85 additions & 0 deletions src/loading/lazlaz/LASLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Adapted from Potree.js http://potree.org
* Potree License: https://github.com/potree/potree/blob/1.8.2/LICENSE
*/

import { parseLASHeader } from './helpers';

export class LASLoader {
arraybuffer: ArrayBuffer | null;
header: any;
readOffset: number;

constructor(arraybuffer: ArrayBuffer) {
this.arraybuffer = arraybuffer;
this.readOffset = 0;
}

open() {
// nothing needs to be done to open this file
//
this.readOffset = 0;
return new Promise(function(res) {
setTimeout(res, 0);
});
}

getHeader() {
return new Promise(res => {
this.header = parseLASHeader(this.arraybuffer!);
res(this.header);
});
}

readData(count: number, _offset: number, skip: number) {
return new Promise((res, rej) => {
setTimeout(() => {
if (!this.header)
return rej(new Error('Cannot start reading data till a header request is issued'));

let start;
if (skip <= 1) {
count = Math.min(count, this.header.pointsCount - this.readOffset);
start = this.header.pointsOffset + this.readOffset * this.header.pointsStructSize;
const end = start + count * this.header.pointsStructSize;
res({
buffer: this.arraybuffer!.slice(start, end),
count: count,
hasMoreData: this.readOffset + count < this.header.pointsCount,
});
this.readOffset += count;
} else {
const pointsToRead = Math.min(count * skip, this.header.pointsCount - this.readOffset);
const bufferSize = Math.ceil(pointsToRead / skip);
let pointsRead = 0;

const buf = new Uint8Array(bufferSize * this.header.pointsStructSize);
for (let i = 0; i < pointsToRead; i++) {
if (i % skip === 0) {
start = this.header.pointsOffset + this.readOffset * this.header.pointsStructSize;
const src = new Uint8Array(this.arraybuffer!, start, this.header.pointsStructSize);

buf.set(src, pointsRead * this.header.pointsStructSize);
pointsRead++;
}

this.readOffset++;
}

res({
buffer: buf.buffer,
count: pointsRead,
hasMoreData: this.readOffset < this.header.pointsCount,
});
}
}, 0);
});
}

close() {
return new Promise(res => {
this.arraybuffer = null;
setTimeout(res, 0);
});
}
}
98 changes: 98 additions & 0 deletions src/loading/lazlaz/LAZLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Adapted from Potree.js http://potree.org
* Potree License: https://github.com/potree/potree/blob/1.8.2/LICENSE
*/

import { WorkerPool, WorkerType } from '../../utils/worker-pool';
import { AutoTerminatingWorker } from '../../utils/worker-queue';

export class LAZLoader {
arraybuffer: ArrayBuffer;

workerPool: WorkerPool;
workerType: WorkerType;
worker: AutoTerminatingWorker | null;

nextCB: Function | null;

constructor(arraybuffer: ArrayBuffer, workerPool: WorkerPool) {
this.arraybuffer = arraybuffer;

this.workerPool = workerPool;
this.workerType = WorkerType.LAZ_LOADER_WORKER;
this.worker = null;

this.nextCB = null;
}

init() {
return new Promise<void>(resolve => {
this.workerPool.getWorker(this.workerType).then(autoTerminatingWorker => {
this.worker = autoTerminatingWorker;
this.worker.worker.onmessage = e => {
if (this.nextCB !== null) {
this.nextCB(e.data);
this.nextCB = null;
}
};
resolve();
});
});
}

async dorr(req: any, cb: Function) {
this.nextCB = cb;
if (!this.worker?.worker) {
await this.init();
}

this.worker?.worker.postMessage(req);
}

open() {
return new Promise((res, rej) => {
this.dorr({ type: 'open', arraybuffer: this.arraybuffer }, function(r: any) {
if (r.status !== 1) return rej(new Error('Failed to open file'));

res(true);
});
});
}

getHeader() {
return new Promise((res, rej) => {
this.dorr({ type: 'header' }, function(r: any) {
if (r.status !== 1) return rej(new Error('Failed to get header'));

res(r.header);
});
});
}

readData(count: number, offset: number, skip: number) {
return new Promise((res, rej) => {
this.dorr({ type: 'read', count: count, offset: offset, skip: skip }, function(r: any) {
if (r.status !== 1) return rej(new Error('Failed to read data'));
res({
buffer: r.buffer,
count: r.count,
hasMoreData: r.hasMoreData,
});
});
});
}

close() {
return new Promise((res, rej) => {
this.dorr({ type: 'close' }, (r: any) => {
if (this.worker) {
this.workerPool.releaseWorker(this.workerType, this.worker);
}

if (r.status !== 1) return rej(new Error('Failed to close file'));

res(true);
});
});
}
}
81 changes: 81 additions & 0 deletions src/loading/lazlaz/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
type RelativeIndexableConstrutor =
| Uint8ArrayConstructor
| Uint16ArrayConstructor
| Uint32ArrayConstructor
| Float32ArrayConstructor
| Float64ArrayConstructor;

export const pointFormatReaders: Record<number, Function> = {
0: function(dv: any) {
return {
position: [dv.getInt32(0, true), dv.getInt32(4, true), dv.getInt32(8, true)],
intensity: dv.getUint16(12, true),
classification: dv.getUint8(16, true),
};
},
1: function(dv: any) {
return {
position: [dv.getInt32(0, true), dv.getInt32(4, true), dv.getInt32(8, true)],
intensity: dv.getUint16(12, true),
classification: dv.getUint8(16, true),
};
},
2: function(dv: any) {
return {
position: [dv.getInt32(0, true), dv.getInt32(4, true), dv.getInt32(8, true)],
intensity: dv.getUint16(12, true),
classification: dv.getUint8(16, true),
color: [dv.getUint16(20, true), dv.getUint16(22, true), dv.getUint16(24, true)],
};
},
3: function(dv: any) {
return {
position: [dv.getInt32(0, true), dv.getInt32(4, true), dv.getInt32(8, true)],
intensity: dv.getUint16(12, true),
classification: dv.getUint8(16, true),
color: [dv.getUint16(28, true), dv.getUint16(30, true), dv.getUint16(32, true)],
};
},
};

export function parseLASHeader(arraybuffer: ArrayBuffer) {
const o: any = {};

o.pointsOffset = readAs(arraybuffer, Uint32Array, 32 * 3);
o.pointsFormatId = readAs(arraybuffer, Uint8Array, 32 * 3 + 8);
o.pointsStructSize = readAs(arraybuffer, Uint16Array, 32 * 3 + 8 + 1);
o.pointsCount = readAs(arraybuffer, Uint32Array, 32 * 3 + 11);

let start = 32 * 3 + 35;
o.scale = readAs(arraybuffer, Float64Array, start, 3);
start += 24; // 8*3
o.offset = readAs(arraybuffer, Float64Array, start, 3);
start += 24;

const bounds = readAs(arraybuffer, Float64Array, start, 6) as number[];
start += 48; // 8*6;
o.maxs = [bounds[0], bounds[2], bounds[4]];
o.mins = [bounds[1], bounds[3], bounds[5]];

return o;
}

export function readAs(
buf: ArrayBuffer,
Type: RelativeIndexableConstrutor,
offset: number,
count?: number,
) {
count = count === undefined || count === 0 ? 1 : count;
const sub = buf.slice(offset, offset + Type.BYTES_PER_ELEMENT * count);

const r = new Type(sub);
if (count === undefined || count === 1) return r[0];

const ret = [];
for (let i = 0; i < count; i++) {
ret.push(r[i]);
}

return ret;
}
Loading