Skip to content

Commit

Permalink
feat: introducing workers for LAS parser
Browse files Browse the repository at this point in the history
  • Loading branch information
Desplandis committed Jul 8, 2024
1 parent 9abdeed commit 0505297
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 17 deletions.
67 changes: 51 additions & 16 deletions src/Parser/LASParser.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import * as THREE from 'three';
import LASLoader from 'Loader/LASLoader';
import { spawn, Thread, Transfer } from 'threads';

const lasLoader = new LASLoader();
let _lazPerf;
let _thread;

function workerInstance() {
return new Worker(
/* webpackChunkName: "itowns_lasparser" */
new URL('../Worker/LASLoaderWorker.js', import.meta.url),
{ type: 'module' },
);
}

async function loader() {
if (_thread) { return _thread; }
_thread = await spawn(workerInstance());
if (_lazPerf) { _thread.lazPerf(_lazPerf); }
return _thread;
}

function buildBufferGeometry(attributes) {
const geometry = new THREE.BufferGeometry();
Expand Down Expand Up @@ -53,9 +69,18 @@ export default {
if (!path) {
throw new Error('Path to laz-perf is mandatory');
}
lasLoader.lazPerf = path;
_lazPerf = path;
},

/**
* Terminate all worker instances.
* @returns {Promise<void>}
*/
terminate() {
const currentThread = _thread;
_thread = undefined;
return Thread.terminate(currentThread);
},

/**
* Parses a chunk of a LAS or LAZ (LASZip) and returns the corresponding
Expand All @@ -79,12 +104,18 @@ export default {
* @return {Promise<THREE.BufferGeometry>} A promise resolving with a
* `THREE.BufferGeometry`.
*/
parseChunk(data, options = {}) {
return lasLoader.parseChunk(data, options.in).then((parsedData) => {
const geometry = buildBufferGeometry(parsedData.attributes);
geometry.computeBoundingBox();
return geometry;
async parseChunk(data, options = {}) {
const lasLoader = await loader();
const parsedData = await lasLoader.parseChunk(Transfer(data), {
pointCount: options.in.pointCount,
header: options.in.header,
eb: options.eb,
colorDepth: options.in.colorDepth,
});

const geometry = buildBufferGeometry(parsedData.attributes);
geometry.computeBoundingBox();
return geometry;
},

/**
Expand All @@ -101,17 +132,21 @@ export default {
* @return {Promise} A promise resolving with a `THREE.BufferGeometry`. The
* header of the file is contained in `userData`.
*/
parse(data, options = {}) {
async parse(data, options = {}) {
if (options.out?.skip) {
console.warn("Warning: options 'skip' not supported anymore");
}
return lasLoader.parseFile(data, {
colorDepth: options.in?.colorDepth,
}).then((parsedData) => {
const geometry = buildBufferGeometry(parsedData.attributes);
geometry.userData.header = parsedData.header;
geometry.computeBoundingBox();
return geometry;

const input = options.in;

const lasLoader = await loader();
const parsedData = await lasLoader.parseFile(Transfer(data), {
colorDepth: input?.colorDepth,
});

const geometry = buildBufferGeometry(parsedData.attributes);
geometry.userData.header = parsedData.header;
geometry.computeBoundingBox();
return geometry;
},
};
26 changes: 26 additions & 0 deletions src/Worker/LASLoaderWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { expose, Transfer } from 'threads/worker';
import LASLoader from 'Loader/LASLoader';

const loader = new LASLoader();

function transferable(attributes) {
return Object.values(attributes)
.filter(ArrayBuffer.isView)
.map(a => a.buffer);
}

expose({
lazPerf(path) {
loader.lazPerf = path;
},

async parseChunk(data, options) {
const result = await loader.parseChunk(data, options);
return Transfer(result, transferable(result.attributes));
},

async parseFile(data, options) {
const result = await loader.parseFile(data, options);
return Transfer(result, transferable(result.attributes));
},
});
5 changes: 4 additions & 1 deletion test/unit/entwine.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Coordinates from 'Core/Geographic/Coordinates';
import EntwinePointTileSource from 'Source/EntwinePointTileSource';
import EntwinePointTileLayer from 'Layer/EntwinePointTileLayer';
import EntwinePointTileNode from 'Core/EntwinePointTileNode';
import LASParser from 'Parser/LASParser';
import sinon from 'sinon';
import Fetcher from 'Provider/Fetcher';
import Renderer from './bootstrap';
Expand Down Expand Up @@ -33,14 +34,16 @@ describe('Entwine Point Tile', function () {
.callsFake(() => Promise.resolve(new ArrayBuffer()));
// currently no test on data fetched...

LASParser.enableLazPerf('./examples/libs/laz-perf');
source = new EntwinePointTileSource({
url: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/pointclouds/entwine',
});
});

after(function () {
after(async function () {
stubFetcherJson.restore();
stubFetcherArrayBuf.restore();
await LASParser.terminate();
});

it('loads the EPT structure', (done) => {
Expand Down
4 changes: 4 additions & 0 deletions test/unit/lasparser.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,9 @@ describe('LASParser', function () {
assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.y + origin.y, header.max[1], epsilon));
assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.z + origin.z, header.max[2], epsilon));
});

afterEach(async function () {
await LASParser.terminate();
});
});
});
3 changes: 3 additions & 0 deletions webpack.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ module.exports = () => {
itowns_potree2worker: {
import: './src/Worker/Potree2Worker.js',
},
itowns_lasworker: {
import: './src/Worker/LASLoaderWorker.js',
},
},
devtool: 'source-map',
output: {
Expand Down

0 comments on commit 0505297

Please sign in to comment.