UNPKG

@loaders.gl/las

Version:

Framework-independent loader for the LAS and LAZ formats

187 lines (186 loc) 6.63 kB
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; }