@loaders.gl/las
Version:
Framework-independent loader for the LAS and LAZ formats
187 lines (186 loc) • 6.63 kB
JavaScript
import { getMeshBoundingBox /* , convertMesh */ } from '@loaders.gl/schema';
import { LASFile } from "./laslaz-decoder.js";
import { getLASSchema } from "./get-las-schema.js";
/**
* Parsing of .las file
* @param arrayBuffer
* @param options
* @returns LASHeader
*/
export function parseLAS(arrayBuffer, options) {
return parseLASMesh(arrayBuffer, options);
// This code breaks pointcloud example on the website
// const mesh = parseLASMesh(arrayBuffer, options);
// return convertMesh(mesh, options?.las?.shape || 'mesh') as LASMesh | ArrowTable | ColumnarTable;
}
/**
* Parsing of .las file
* @param arrayBuffer
* @param options
* @returns LASHeader
*/
function parseLASMesh(arrayBuffer, options = {}) {
let pointIndex = 0;
let positions;
let colors;
let intensities;
let classifications;
let originalHeader;
const lasMesh = {
loader: 'las',
loaderData: {},
// shape: 'mesh',
schema: { fields: [], metadata: {} },
header: {
vertexCount: 0,
boundingBox: [
[0, 0, 0],
[0, 0, 0]
]
},
attributes: {},
topology: 'point-list',
mode: 0 // GL.POINTS
};
/* eslint-disable max-statements */
// @ts-ignore Possibly undefined
parseLASChunked(arrayBuffer, options.las?.skip, (decoder = {}, lasHeader) => {
if (!originalHeader) {
originalHeader = lasHeader;
const total = lasHeader.totalToRead;
const PositionsType = options.las?.fp64 ? Float64Array : Float32Array;
positions = new PositionsType(total * 3);
// laslaz-decoder.js `pointFormatReaders`
colors = lasHeader.pointsFormatId >= 2 ? new Uint8Array(total * 4) : null;
intensities = new Uint16Array(total);
classifications = new Uint8Array(total);
lasMesh.loaderData = lasHeader;
lasMesh.attributes = {
POSITION: { value: positions, size: 3 },
// non-gltf attributes, use non-capitalized names for now
intensity: { value: intensities, size: 1 },
classification: { value: classifications, size: 1 }
};
if (colors) {
lasMesh.attributes.COLOR_0 = { value: colors, size: 4 };
}
}
const batchSize = decoder.pointsCount;
const { scale: [scaleX, scaleY, scaleZ], offset: [offsetX, offsetY, offsetZ] } = lasHeader;
const twoByteColor = detectTwoByteColors(decoder, batchSize, options.las?.colorDepth);
for (let i = 0; i < batchSize; i++) {
const { position, color, intensity, classification } = decoder.getPoint(i);
positions[pointIndex * 3] = position[0] * scaleX + offsetX;
positions[pointIndex * 3 + 1] = position[1] * scaleY + offsetY;
positions[pointIndex * 3 + 2] = position[2] * scaleZ + offsetZ;
if (color && colors) {
if (twoByteColor) {
colors[pointIndex * 4] = color[0] / 256;
colors[pointIndex * 4 + 1] = color[1] / 256;
colors[pointIndex * 4 + 2] = color[2] / 256;
}
else {
colors[pointIndex * 4] = color[0];
colors[pointIndex * 4 + 1] = color[1];
colors[pointIndex * 4 + 2] = color[2];
}
colors[pointIndex * 4 + 3] = 255;
}
intensities[pointIndex] = intensity;
classifications[pointIndex] = classification;
pointIndex++;
}
const meshBatch = {
...lasMesh,
header: {
vertexCount: lasHeader.totalRead
},
progress: lasHeader.totalRead / lasHeader.totalToRead
};
options?.onProgress?.(meshBatch);
});
/* eslint-enable max-statements */
lasMesh.header = {
vertexCount: originalHeader.totalToRead,
boundingBox: getMeshBoundingBox(lasMesh?.attributes || {})
};
if (lasMesh) {
lasMesh.schema = getLASSchema(lasMesh.loaderData, lasMesh.attributes);
}
return lasMesh;
}
/**
* parse laz data
* @param rawData
* @param skip
* @param onParseData
* @return parsed point cloud
*/
/* eslint-enable max-statements */
export function parseLASChunked(rawData, skip, onParseData = {}) {
const dataHandler = new LASFile(rawData);
try {
// open data
dataHandler.open();
const header = dataHandler.getHeader();
// start loading
const Unpacker = dataHandler.getUnpacker();
const totalToRead = Math.ceil(header.pointsCount / Math.max(1, skip));
header.totalToRead = totalToRead;
let totalRead = 0;
/* eslint-disable no-constant-condition */
while (true) {
const chunk = dataHandler.readData(1000 * 100, 0, skip);
totalRead += chunk.count;
header.totalRead = totalRead;
header.versionAsString = chunk.versionAsString;
header.isCompressed = chunk.isCompressed;
const unpacker = new Unpacker(chunk.buffer, chunk.count, header);
// surface unpacker and progress via call back
// use unpacker.pointsCount and unpacker.getPoint(i) to handle data in app
onParseData(unpacker, header);
if (!chunk.hasMoreData || totalRead >= totalToRead) {
break;
}
}
}
catch (e) {
throw e;
}
finally {
dataHandler.close();
}
}
/**
* @param decoder
* @param batchSize
* @param colorDepth
* @returns boolean
*/
function detectTwoByteColors(decoder = {}, batchSize, colorDepth) {
let twoByteColor = false;
switch (colorDepth) {
case 8:
twoByteColor = false;
break;
case 16:
twoByteColor = true;
break;
case 'auto':
if (decoder.getPoint(0).color) {
for (let i = 0; i < batchSize; i++) {
const { color } = decoder.getPoint(i);
// eslint-disable-next-line max-depth
if (color[0] > 255 || color[1] > 255 || color[2] > 255) {
twoByteColor = true;
}
}
}
break;
default:
// eslint-disable-next-line
console.warn('las: illegal value for options.las.colorDepth');
break;
}
return twoByteColor;
}