// laslaz.js // LAS/LAZ loading // //var common = require("./common"), // Promise = require("bluebird"); (function(scope) { "use strict"; var pointFormatReaders = { 0: function(dv) { 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) { 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) { 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) { 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)] }; } }; function readAs(buf, Type, offset, count) { count = (count === undefined || count === 0 ? 1 : count); var sub = buf.slice(offset, offset + Type.BYTES_PER_ELEMENT * count); var r = new Type(sub); if (count === undefined || count === 1) return r[0]; var ret = []; for (var i = 0 ; i < count ; i ++) { ret.push(r[i]); } return ret; } function parseLASHeader(arraybuffer) { var o = {}; 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); var start = 32*3 + 35; o.scale = readAs(arraybuffer, Float64Array, start, 3); start += 24; // 8*3 o.offset = readAs(arraybuffer, Float64Array, start, 3); start += 24; var bounds = readAs(arraybuffer, Float64Array, start, 6); start += 48; // 8*6; o.maxs = [bounds[0], bounds[2], bounds[4]]; o.mins = [bounds[1], bounds[3], bounds[5]]; return o; } var msgIndex = 0; var waitHandlers = {}; // This method is scope-wide since the nacl module uses this fuction to notify // us of events scope.handleMessage = function(message_event) { var msg = message_event.data; var resolver = waitHandlers[msg.id]; delete waitHandlers[msg.id]; // call the callback in a separate context, make sure we've cleaned our // state out before the callback is invoked since it may queue more doExchanges setTimeout(function() { if (msg.error) return resolver.reject(new Error(msg.message || "Unknown Error")); if (msg.hasOwnProperty('count') && msg.hasOwnProperty('hasMoreData')) { return resolver.resolve({ buffer: msg.result, count: msg.count, hasMoreData: msg.hasMoreData}); } resolver.resolve(msg.result); }, 0); }; var doDataExchange = function(cmd, callback) { cmd.id = msgIndex.toString(); msgIndex ++; var resolver = Promise.defer(); waitHandlers[cmd.id] = resolver; nacl_module.postMessage(cmd); return resolver.promise.cancellable(); }; // LAS Loader // Loads uncompressed files // var LASLoader = function(arraybuffer) { this.arraybuffer = arraybuffer; }; LASLoader.prototype.open = function() { // nothing needs to be done to open this file // this.readOffset = 0; return new Promise(function(res, rej) { setTimeout(res, 0); }); }; LASLoader.prototype.getHeader = function() { var o = this; return new Promise(function(res, rej) { setTimeout(function() { o.header = parseLASHeader(o.arraybuffer); res(o.header); }, 0); }); }; LASLoader.prototype.readData = function(count, offset, skip) { var o = this; return new Promise(function(res, rej) { setTimeout(function() { if (!o.header) return rej(new Error("Cannot start reading data till a header request is issued")); var start; if (skip <= 1) { count = Math.min(count, o.header.pointsCount - o.readOffset); start = o.header.pointsOffset + o.readOffset * o.header.pointsStructSize; var end = start + count * o.header.pointsStructSize; res({ buffer: o.arraybuffer.slice(start, end), count: count, hasMoreData: o.readOffset + count < o.header.pointsCount}); o.readOffset += count; } else { var pointsToRead = Math.min(count * skip, o.header.pointsCount - o.readOffset); var bufferSize = Math.ceil(pointsToRead / skip); var pointsRead = 0; var buf = new Uint8Array(bufferSize * o.header.pointsStructSize); for (var i = 0 ; i < pointsToRead ; i ++) { if (i % skip === 0) { start = o.header.pointsOffset + o.readOffset * o.header.pointsStructSize; var src = new Uint8Array(o.arraybuffer, start, o.header.pointsStructSize); buf.set(src, pointsRead * o.header.pointsStructSize); pointsRead ++; } o.readOffset ++; } res({ buffer: buf.buffer, count: pointsRead, hasMoreData: o.readOffset < o.header.pointsCount }); } }, 0); }); }; LASLoader.prototype.close = function() { var o = this; return new Promise(function(res, rej) { o.arraybuffer = null; setTimeout(res, 0); }); }; // LAZ Loader // Uses NaCL module to load LAZ files // var LAZLoader = function(arraybuffer) { this.arraybuffer = arraybuffer; let workerPath = Potree.scriptPath + "/workers/LASLAZWorker.js"; this.ww = Potree.workerPool.getWorker(workerPath); this.nextCB = null; var o = this; this.ww.onmessage = function(e) { if (o.nextCB !== null) { o.nextCB(e.data); o.nextCB = null; } }; this.dorr = function(req, cb) { o.nextCB = cb; o.ww.postMessage(req); }; }; LAZLoader.prototype.open = function() { // nothing needs to be done to open this file // var o = this; return new Promise(function(res, rej) { o.dorr({type:"open", arraybuffer: o.arraybuffer}, function(r) { if (r.status !== 1) return rej(new Error("Failed to open file")); res(true); }); }); }; LAZLoader.prototype.getHeader = function() { var o = this; return new Promise(function(res, rej) { o.dorr({type:'header'}, function(r) { if (r.status !== 1) return rej(new Error("Failed to get header")); res(r.header); }); }); }; LAZLoader.prototype.readData = function(count, offset, skip) { var o = this; return new Promise(function(res, rej) { o.dorr({type:'read', count: count, offset: offset, skip: skip}, function(r) { if (r.status !== 1) return rej(new Error("Failed to read data")); res({ buffer: r.buffer, count: r.count, hasMoreData: r.hasMoreData }); }); }); }; LAZLoader.prototype.close = function() { var o = this; return new Promise(function(res, rej) { o.dorr({type:'close'}, function(r) { let workerPath = Potree.scriptPath + "/workers/LASLAZWorker.js"; Potree.workerPool.returnWorker(workerPath, o.ww); if (r.status !== 1) return rej(new Error("Failed to close file")); res(true); }); }); }; // A single consistent interface for loading LAS/LAZ files var LASFile = function(arraybuffer) { this.arraybuffer = arraybuffer; this.determineVersion(); if (this.version > 12) throw new Error("Only file versions <= 1.2 are supported at this time"); this.determineFormat(); if (pointFormatReaders[this.formatId] === undefined) throw new Error("The point format ID is not supported"); this.loader = this.isCompressed ? new LAZLoader(this.arraybuffer) : new LASLoader(this.arraybuffer); }; LASFile.prototype.determineFormat = function() { var formatId = readAs(this.arraybuffer, Uint8Array, 32*3+8); var bit_7 = (formatId & 0x80) >> 7; var 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); }; LASFile.prototype.determineVersion = function() { var ver = new Int8Array(this.arraybuffer, 24, 2); this.version = ver[0] * 10 + ver[1]; this.versionAsString = ver[0] + "." + ver[1]; }; LASFile.prototype.open = function() { return this.loader.open(); }; LASFile.prototype.getHeader = function() { return this.loader.getHeader(); }; LASFile.prototype.readData = function(count, start, skip) { return this.loader.readData(count, start, skip); }; LASFile.prototype.close = function() { return this.loader.close(); }; // Decodes LAS records into points // var LASDecoder = function(buffer, pointFormatID, pointSize, pointsCount, scale, offset, mins, maxs) { this.arrayb = buffer; this.decoder = pointFormatReaders[pointFormatID]; this.pointsCount = pointsCount; this.pointSize = pointSize; this.scale = scale; this.offset = offset; this.mins = mins; this.maxs = maxs; }; LASDecoder.prototype.getPoint = function(index) { if (index < 0 || index >= this.pointsCount) throw new Error("Point index out of range"); var dv = new DataView(this.arrayb, index * this.pointSize, this.pointSize); return this.decoder(dv); }; // NACL Module support // Called by the common.js module. // //window.startNaCl = function(name, tc, config, width, height) { // // check browser support for nacl // // // if(!common.browserSupportsNaCl()) { // return $.event.trigger({ // type: "plasio.nacl.error", // message: "NaCl support is not available" // }); // } // navigator.webkitPersistentStorage.requestQuota(2048 * 2048, function(bytes) { // common.updateStatus( // 'Allocated ' + bytes + ' bytes of persistant storage.'); // common.attachDefaultListeners(); // common.createNaClModule(name, tc, config, width, height); // }, // function(e) { // $.event.trigger({ // type: "plasio.nacl.error", // message: "Could not allocate persistant storage" // }); // }); // $(document).on("plasio.nacl.available", function() { // scope.LASModuleWasLoaded = true; // }); //}; scope.LAZLoader = LAZLoader; scope.LASLoader = LASLoader; scope.LASFile = LASFile; scope.LASDecoder = LASDecoder; scope.LASModuleWasLoaded = false; //})(module.exports); })(this);