1// laslaz.js 2// LAS/LAZ loading 3// 4 5//var common = require("./common"), 6// Promise = require("bluebird"); 7 8(function(scope) { 9 "use strict"; 10 11 var pointFormatReaders = { 12 0: function(dv) { 13 return { 14 "position": [ dv.getInt32(0, true), dv.getInt32(4, true), dv.getInt32(8, true)], 15 "intensity": dv.getUint16(12, true), 16 "classification": dv.getUint8(16, true) 17 }; 18 }, 19 1: function(dv) { 20 return { 21 "position": [ dv.getInt32(0, true), dv.getInt32(4, true), dv.getInt32(8, true)], 22 "intensity": dv.getUint16(12, true), 23 "classification": dv.getUint8(16, true) 24 }; 25 }, 26 2: function(dv) { 27 return { 28 "position": [ dv.getInt32(0, true), dv.getInt32(4, true), dv.getInt32(8, true)], 29 "intensity": dv.getUint16(12, true), 30 "classification": dv.getUint8(16, true), 31 "color": [dv.getUint16(20, true), dv.getUint16(22, true), dv.getUint16(24, true)] 32 }; 33 }, 34 3: function(dv) { 35 return { 36 "position": [ dv.getInt32(0, true), dv.getInt32(4, true), dv.getInt32(8, true)], 37 "intensity": dv.getUint16(12, true), 38 "classification": dv.getUint8(16, true), 39 "color": [dv.getUint16(28, true), dv.getUint16(30, true), dv.getUint16(32, true)] 40 }; 41 } 42 }; 43 44 function readAs(buf, Type, offset, count) { 45 count = (count === undefined || count === 0 ? 1 : count); 46 var sub = buf.slice(offset, offset + Type.BYTES_PER_ELEMENT * count); 47 48 var r = new Type(sub); 49 if (count === undefined || count === 1) 50 return r[0]; 51 52 var ret = []; 53 for (var i = 0 ; i < count ; i ++) { 54 ret.push(r[i]); 55 } 56 57 return ret; 58 } 59 60 function parseLASHeader(arraybuffer) { 61 var o = {}; 62 63 o.pointsOffset = readAs(arraybuffer, Uint32Array, 32*3); 64 o.pointsFormatId = readAs(arraybuffer, Uint8Array, 32*3+8); 65 o.pointsStructSize = readAs(arraybuffer, Uint16Array, 32*3+8+1); 66 o.pointsCount = readAs(arraybuffer, Uint32Array, 32*3 + 11); 67 68 69 var start = 32*3 + 35; 70 o.scale = readAs(arraybuffer, Float64Array, start, 3); start += 24; // 8*3 71 o.offset = readAs(arraybuffer, Float64Array, start, 3); start += 24; 72 73 74 75 var bounds = readAs(arraybuffer, Float64Array, start, 6); start += 48; // 8*6; 76 o.maxs = [bounds[0], bounds[2], bounds[4]]; 77 o.mins = [bounds[1], bounds[3], bounds[5]]; 78 79 return o; 80 } 81 82 var msgIndex = 0; 83 var waitHandlers = {}; 84 85 // This method is scope-wide since the nacl module uses this fuction to notify 86 // us of events 87 scope.handleMessage = function(message_event) { 88 var msg = message_event.data; 89 var resolver = waitHandlers[msg.id]; 90 delete waitHandlers[msg.id]; 91 92 // call the callback in a separate context, make sure we've cleaned our 93 // state out before the callback is invoked since it may queue more doExchanges 94 setTimeout(function() { 95 if (msg.error) 96 return resolver.reject(new Error(msg.message || "Unknown Error")); 97 98 if (msg.hasOwnProperty('count') && msg.hasOwnProperty('hasMoreData')) { 99 return resolver.resolve({ 100 buffer: msg.result, 101 count: msg.count, 102 hasMoreData: msg.hasMoreData}); 103 } 104 105 resolver.resolve(msg.result); 106 }, 0); 107 }; 108 109 var doDataExchange = function(cmd, callback) { 110 cmd.id = msgIndex.toString(); 111 msgIndex ++; 112 113 var resolver = Promise.defer(); 114 waitHandlers[cmd.id] = resolver; 115 116 nacl_module.postMessage(cmd); 117 118 return resolver.promise.cancellable(); 119 }; 120 121 // LAS Loader 122 // Loads uncompressed files 123 // 124 var LASLoader = function(arraybuffer) { 125 this.arraybuffer = arraybuffer; 126 }; 127 128 LASLoader.prototype.open = function() { 129 // nothing needs to be done to open this file 130 // 131 this.readOffset = 0; 132 return new Promise(function(res, rej) { 133 setTimeout(res, 0); 134 }); 135 }; 136 137 LASLoader.prototype.getHeader = function() { 138 var o = this; 139 140 return new Promise(function(res, rej) { 141 setTimeout(function() { 142 o.header = parseLASHeader(o.arraybuffer); 143 res(o.header); 144 }, 0); 145 }); 146 }; 147 148 LASLoader.prototype.readData = function(count, offset, skip) { 149 var o = this; 150 151 return new Promise(function(res, rej) { 152 setTimeout(function() { 153 if (!o.header) 154 return rej(new Error("Cannot start reading data till a header request is issued")); 155 156 var start; 157 if (skip <= 1) { 158 count = Math.min(count, o.header.pointsCount - o.readOffset); 159 start = o.header.pointsOffset + o.readOffset * o.header.pointsStructSize; 160 var end = start + count * o.header.pointsStructSize; 161 res({ 162 buffer: o.arraybuffer.slice(start, end), 163 count: count, 164 hasMoreData: o.readOffset + count < o.header.pointsCount}); 165 o.readOffset += count; 166 } 167 else { 168 var pointsToRead = Math.min(count * skip, o.header.pointsCount - o.readOffset);
169 var bufferSize = Math.ceil(pointsToRead / skip); 170 var pointsRead = 0; 171 172 var buf = new Uint8Array(bufferSize * o.header.pointsStructSize); 173 for (var i = 0 ; i < pointsToRead ; i ++) { 174 if (i % skip === 0) { 175 start = o.header.pointsOffset + o.readOffset * o.header.pointsStructSize; 176 var src = new Uint8Array(o.arraybuffer, start, o.header.pointsStructSize); 177 178 buf.set(src, pointsRead * o.header.pointsStructSize); 179 pointsRead ++; 180 } 181 182 o.readOffset ++; 183 } 184 185 res({ 186 buffer: buf.buffer, 187 count: pointsRead, 188 hasMoreData: o.readOffset < o.header.pointsCount 189 }); 190 } 191 }, 0); 192 }); 193 }; 194 195 LASLoader.prototype.close = function() { 196 var o = this; 197 return new Promise(function(res, rej) { 198 o.arraybuffer = null; 199 setTimeout(res, 0); 200 }); 201 }; 202 203 // LAZ Loader 204 // Uses NaCL module to load LAZ files 205 // 206 var LAZLoader = function(arraybuffer) { 207 this.arraybuffer = arraybuffer; 208 209 let workerPath = Potree.scriptPath + "/workers/LASLAZWorker.js"; 210 this.ww = Potree.workerPool.getWorker(workerPath); 211 212 this.nextCB = null; 213 var o = this; 214 215 this.ww.onmessage = function(e) { 216 if (o.nextCB !== null) { 217 o.nextCB(e.data); 218 o.nextCB = null; 219 } 220 }; 221 222 this.dorr = function(req, cb) { 223 o.nextCB = cb; 224 o.ww.postMessage(req); 225 }; 226 }; 227 228 LAZLoader.prototype.open = function() { 229 230 // nothing needs to be done to open this file 231 // 232 var o = this; 233 return new Promise(function(res, rej) { 234 o.dorr({type:"open", arraybuffer: o.arraybuffer}, function(r) { 235 if (r.status !== 1) 236 return rej(new Error("Failed to open file")); 237 238 res(true); 239 }); 240 }); 241 }; 242 243 LAZLoader.prototype.getHeader = function() { 244 var o = this; 245 246 return new Promise(function(res, rej) { 247 o.dorr({type:'header'}, function(r) { 248 if (r.status !== 1) 249 return rej(new Error("Failed to get header")); 250 251 res(r.header); 252 }); 253 }); 254 }; 255 256 LAZLoader.prototype.readData = function(count, offset, skip) { 257 var o = this; 258 259 return new Promise(function(res, rej) { 260 o.dorr({type:'read', count: count, offset: offset, skip: skip}, function(r) { 261 if (r.status !== 1) 262 return rej(new Error("Failed to read data")); 263 res({ 264 buffer: r.buffer, 265 count: r.count, 266 hasMoreData: r.hasMoreData 267 }); 268 }); 269 }); 270 }; 271 272 LAZLoader.prototype.close = function() { 273 var o = this; 274 275 return new Promise(function(res, rej) { 276 o.dorr({type:'close'}, function(r) { 277 let workerPath = Potree.scriptPath + "/workers/LASLAZWorker.js"; 278 Potree.workerPool.returnWorker(workerPath, o.ww); 279 280 if (r.status !== 1) 281 return rej(new Error("Failed to close file")); 282 283 res(true); 284 }); 285 }); 286 }; 287 288 // A single consistent interface for loading LAS/LAZ files 289 var LASFile = function(arraybuffer) { 290 this.arraybuffer = arraybuffer; 291 292 this.determineVersion(); 293 if (this.version > 12) 294 throw new Error("Only file versions <= 1.2 are supported at this time"); 295 296 this.determineFormat(); 297 if (pointFormatReaders[this.formatId] === undefined) 298 throw new Error("The point format ID is not supported"); 299 300 this.loader = this.isCompressed ? 301 new LAZLoader(this.arraybuffer) : 302 new LASLoader(this.arraybuffer); 303 }; 304 305 LASFile.prototype.determineFormat = function() { 306 var formatId = readAs(this.arraybuffer, Uint8Array, 32*3+8); 307 var bit_7 = (formatId & 0x80) >> 7; 308 var bit_6 = (formatId & 0x40) >> 6; 309 310 if (bit_7 === 1 && bit_6 === 1) 311 throw new Error("Old style compression not supported"); 312 313 this.formatId = formatId & 0x3f; 314 this.isCompressed = (bit_7 === 1 || bit_6 === 1); 315 }; 316 317 LASFile.prototype.determineVersion = function() { 318 var ver = new Int8Array(this.arraybuffer, 24, 2); 319 this.version = ver[0] * 10 + ver[1]; 320 this.versionAsString = ver[0] + "." + ver[1]; 321 }; 322 323 LASFile.prototype.open = function() { 324 return this.loader.open(); 325 }; 326 327 LASFile.prototype.getHeader = function() { 328 return this.loader.getHeader(); 329 }; 330 331 LASFile.prototype.readData = function(count, start, skip) { 332 return this.loader.readData(count, start, skip); 333 }; 334 335 LASFile.prototype.close = function() { 336 return this.loader.close(); 337 }; 338 339 // Decodes LAS records into points 340 // 341 var LASDecoder = function(buffer, pointFormatID, pointSize, pointsCount, scale, offset, mins, maxs) { 342 this.arrayb = buffer; 343 this.decoder = pointFormatReaders[pointFormatID]; 344 this.pointsCount = pointsCount; 345 this.pointSize = pointSize; 346 this.scale = scale; 347 this.offset = offset; 348 this.mins = mins; 349 this.maxs = maxs; 350 }; 351 352 LASDecoder.prototype.getPoint = function(index) { 353 if (index < 0 || index >= this.pointsCount) 354 throw new Error("Point index out of range"); 355 356 var dv = new DataView(this.arrayb, index * this.pointSize, this.pointSize); 357 return this.decoder(dv); 358 }; 359 360 // NACL Module support 361 // Called by the common.js module. 362 // 363 //window.startNaCl = function(name, tc, config, width, height) { 364 // // check browser support for nacl 365 // // 366 // if(!common.browserSupportsNaCl()) { 367 // return $.event.trigger({ 368 // type: "plasio.nacl.error", 369 // message: "NaCl support is not available" 370 // }); 371 // } 372 373 // navigator.webkitPersistentStorage.requestQuota(2048 * 2048, function(bytes) {
374 // common.updateStatus( 375 // 'Allocated ' + bytes + ' bytes of persistant storage.'); 376 // common.attachDefaultListeners(); 377 // common.createNaClModule(name, tc, config, width, height); 378 // }, 379 // function(e) { 380 // $.event.trigger({ 381 // type: "plasio.nacl.error", 382 // message: "Could not allocate persistant storage" 383 // }); 384 // }); 385 386 // $(document).on("plasio.nacl.available", function() { 387 // scope.LASModuleWasLoaded = true; 388 // }); 389 //}; 390 391 scope.LAZLoader = LAZLoader; 392 scope.LASLoader = LASLoader; 393 scope.LASFile = LASFile; 394 scope.LASDecoder = LASDecoder; 395 scope.LASModuleWasLoaded = false; 396//})(module.exports); 397})(this); 398
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.