You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1384 lines
48 KiB
1384 lines
48 KiB
|
|
var CLOUD = CLOUD || {}; |
|
|
|
|
|
(function () { 'use strict'; |
|
|
|
// This is free and unencumbered software released into the public domain. |
|
// See LICENSE.md for more information. |
|
|
|
// |
|
// Utilities |
|
// |
|
|
|
/** |
|
* @param {number} a The number to test. |
|
* @param {number} min The minimum value in the range, inclusive. |
|
* @param {number} max The maximum value in the range, inclusive. |
|
* @return {boolean} True if a >= min and a <= max. |
|
*/ |
|
function inRange(a, min, max) { |
|
return min <= a && a <= max; |
|
} |
|
|
|
/** |
|
* @param {*} o |
|
* @return {Object} |
|
*/ |
|
function ToDictionary(o) { |
|
if (o === undefined) return {}; |
|
if (o === Object(o)) return o; |
|
throw TypeError('Could not convert argument to dictionary'); |
|
} |
|
|
|
/** |
|
* @param {string} string Input string of UTF-16 code units. |
|
* @return {!Array.<number>} Code points. |
|
*/ |
|
function stringToCodePoints(string) { |
|
// https://heycam.github.io/webidl/#dfn-obtain-unicode |
|
|
|
// 1. Let S be the DOMString value. |
|
var s = String(string); |
|
|
|
// 2. Let n be the length of S. |
|
var n = s.length; |
|
|
|
// 3. Initialize i to 0. |
|
var i = 0; |
|
|
|
// 4. Initialize U to be an empty sequence of Unicode characters. |
|
var u = []; |
|
|
|
// 5. While i < n: |
|
while (i < n) { |
|
|
|
// 1. Let c be the code unit in S at index i. |
|
var c = s.charCodeAt(i); |
|
|
|
// 2. Depending on the value of c: |
|
|
|
// c < 0xD800 or c > 0xDFFF |
|
if (c < 0xD800 || c > 0xDFFF) { |
|
// Append to U the Unicode character with code point c. |
|
u.push(c); |
|
} |
|
|
|
// 0xDC00 ≤ c ≤ 0xDFFF |
|
else if (0xDC00 <= c && c <= 0xDFFF) { |
|
// Append to U a U+FFFD REPLACEMENT CHARACTER. |
|
u.push(0xFFFD); |
|
} |
|
|
|
// 0xD800 ≤ c ≤ 0xDBFF |
|
else if (0xD800 <= c && c <= 0xDBFF) { |
|
// 1. If i = n−1, then append to U a U+FFFD REPLACEMENT |
|
// CHARACTER. |
|
if (i === n - 1) { |
|
u.push(0xFFFD); |
|
} |
|
// 2. Otherwise, i < n−1: |
|
else { |
|
// 1. Let d be the code unit in S at index i+1. |
|
var d = string.charCodeAt(i + 1); |
|
|
|
// 2. If 0xDC00 ≤ d ≤ 0xDFFF, then: |
|
if (0xDC00 <= d && d <= 0xDFFF) { |
|
// 1. Let a be c & 0x3FF. |
|
var a = c & 0x3FF; |
|
|
|
// 2. Let b be d & 0x3FF. |
|
var b = d & 0x3FF; |
|
|
|
// 3. Append to U the Unicode character with code point |
|
// 2^16+2^10*a+b. |
|
u.push(0x10000 + (a << 10) + b); |
|
|
|
// 4. Set i to i+1. |
|
i += 1; |
|
} |
|
|
|
// 3. Otherwise, d < 0xDC00 or d > 0xDFFF. Append to U a |
|
// U+FFFD REPLACEMENT CHARACTER. |
|
else { |
|
u.push(0xFFFD); |
|
} |
|
} |
|
} |
|
|
|
// 3. Set i to i+1. |
|
i += 1; |
|
} |
|
|
|
// 6. Return U. |
|
return u; |
|
} |
|
|
|
/** |
|
* @param {!Array.<number>} code_points Array of code points. |
|
* @return {string} string String of UTF-16 code units. |
|
*/ |
|
function codePointsToString(code_points) { |
|
var s = ''; |
|
for (var i = 0; i < code_points.length; ++i) { |
|
var cp = code_points[i]; |
|
if (cp <= 0xFFFF) { |
|
s += String.fromCharCode(cp); |
|
} else { |
|
cp -= 0x10000; |
|
s += String.fromCharCode((cp >> 10) + 0xD800, |
|
(cp & 0x3FF) + 0xDC00); |
|
} |
|
} |
|
return s; |
|
} |
|
|
|
|
|
// |
|
// Implementation of Encoding specification |
|
// https://encoding.spec.whatwg.org/ |
|
// |
|
|
|
// |
|
// 3. Terminology |
|
// |
|
|
|
/** |
|
* End-of-stream is a special token that signifies no more tokens |
|
* are in the stream. |
|
* @const |
|
*/ var end_of_stream = -1; |
|
|
|
/** |
|
* A stream represents an ordered sequence of tokens. |
|
* |
|
* @constructor |
|
* @param {!(Array.<number>|Uint8Array)} tokens Array of tokens that provide the |
|
* stream. |
|
*/ |
|
function Stream(tokens) { |
|
/** @type {!Array.<number>} */ |
|
this.tokens = [].slice.call(tokens); |
|
} |
|
|
|
Stream.prototype = { |
|
/** |
|
* @return {boolean} True if end-of-stream has been hit. |
|
*/ |
|
endOfStream: function() { |
|
return !this.tokens.length; |
|
}, |
|
|
|
/** |
|
* When a token is read from a stream, the first token in the |
|
* stream must be returned and subsequently removed, and |
|
* end-of-stream must be returned otherwise. |
|
* |
|
* @return {number} Get the next token from the stream, or |
|
* end_of_stream. |
|
*/ |
|
read: function() { |
|
if (!this.tokens.length) |
|
return end_of_stream; |
|
return this.tokens.shift(); |
|
}, |
|
|
|
/** |
|
* When one or more tokens are prepended to a stream, those tokens |
|
* must be inserted, in given order, before the first token in the |
|
* stream. |
|
* |
|
* @param {(number|!Array.<number>)} token The token(s) to prepend to the stream. |
|
*/ |
|
prepend: function(token) { |
|
if (Array.isArray(token)) { |
|
var tokens = /**@type {!Array.<number>}*/(token); |
|
while (tokens.length) |
|
this.tokens.unshift(tokens.pop()); |
|
} else { |
|
this.tokens.unshift(token); |
|
} |
|
}, |
|
|
|
/** |
|
* When one or more tokens are pushed to a stream, those tokens |
|
* must be inserted, in given order, after the last token in the |
|
* stream. |
|
* |
|
* @param {(number|!Array.<number>)} token The tokens(s) to prepend to the stream. |
|
*/ |
|
push: function(token) { |
|
if (Array.isArray(token)) { |
|
var tokens = /**@type {!Array.<number>}*/(token); |
|
while (tokens.length) |
|
this.tokens.push(tokens.shift()); |
|
} else { |
|
this.tokens.push(token); |
|
} |
|
} |
|
}; |
|
|
|
// |
|
// 4. Encodings |
|
// |
|
|
|
// 4.1 Encoders and decoders |
|
|
|
/** @const */ |
|
var finished = -1; |
|
|
|
/** |
|
* @param {boolean} fatal If true, decoding errors raise an exception. |
|
* @param {number=} opt_code_point Override the standard fallback code point. |
|
* @return {number} The code point to insert on a decoding error. |
|
*/ |
|
function decoderError(fatal, opt_code_point) { |
|
if (fatal) |
|
throw TypeError('Decoder error'); |
|
return opt_code_point || 0xFFFD; |
|
} |
|
|
|
// |
|
// 7. API |
|
// |
|
|
|
/** @const */ var DEFAULT_ENCODING = 'utf-8'; |
|
|
|
// 7.1 Interface TextDecoder |
|
|
|
/** |
|
* @constructor |
|
* @param {string=} encoding The label of the encoding; |
|
* defaults to 'utf-8'. |
|
* @param {Object=} options |
|
*/ |
|
function TextDecoder$1(encoding, options) { |
|
if (!(this instanceof TextDecoder$1)) { |
|
return new TextDecoder$1(encoding, options); |
|
} |
|
encoding = encoding !== undefined ? String(encoding).toLowerCase() : DEFAULT_ENCODING; |
|
if (encoding !== DEFAULT_ENCODING) { |
|
throw new Error('Encoding not supported. Only utf-8 is supported'); |
|
} |
|
options = ToDictionary(options); |
|
|
|
/** @private @type {boolean} */ |
|
this._streaming = false; |
|
/** @private @type {boolean} */ |
|
this._BOMseen = false; |
|
/** @private @type {?Decoder} */ |
|
this._decoder = null; |
|
/** @private @type {boolean} */ |
|
this._fatal = Boolean(options['fatal']); |
|
/** @private @type {boolean} */ |
|
this._ignoreBOM = Boolean(options['ignoreBOM']); |
|
|
|
Object.defineProperty(this, 'encoding', {value: 'utf-8'}); |
|
Object.defineProperty(this, 'fatal', {value: this._fatal}); |
|
Object.defineProperty(this, 'ignoreBOM', {value: this._ignoreBOM}); |
|
} |
|
|
|
TextDecoder$1.prototype = { |
|
/** |
|
* @param {ArrayBufferView=} input The buffer of bytes to decode. |
|
* @param {Object=} options |
|
* @return {string} The decoded string. |
|
*/ |
|
decode: function decode(input, options) { |
|
var bytes; |
|
if (typeof input === 'object' && input instanceof ArrayBuffer) { |
|
bytes = new Uint8Array(input); |
|
} else if (typeof input === 'object' && 'buffer' in input && |
|
input.buffer instanceof ArrayBuffer) { |
|
bytes = new Uint8Array(input.buffer, |
|
input.byteOffset, |
|
input.byteLength); |
|
} else { |
|
bytes = new Uint8Array(0); |
|
} |
|
|
|
options = ToDictionary(options); |
|
|
|
if (!this._streaming) { |
|
this._decoder = new UTF8Decoder({fatal: this._fatal}); |
|
this._BOMseen = false; |
|
} |
|
this._streaming = Boolean(options['stream']); |
|
|
|
var input_stream = new Stream(bytes); |
|
|
|
var code_points = []; |
|
|
|
/** @type {?(number|!Array.<number>)} */ |
|
var result; |
|
|
|
while (!input_stream.endOfStream()) { |
|
result = this._decoder.handler(input_stream, input_stream.read()); |
|
if (result === finished) |
|
break; |
|
if (result === null) |
|
continue; |
|
if (Array.isArray(result)) |
|
code_points.push.apply(code_points, /**@type {!Array.<number>}*/(result)); |
|
else |
|
code_points.push(result); |
|
} |
|
if (!this._streaming) { |
|
do { |
|
result = this._decoder.handler(input_stream, input_stream.read()); |
|
if (result === finished) |
|
break; |
|
if (result === null) |
|
continue; |
|
if (Array.isArray(result)) |
|
code_points.push.apply(code_points, /**@type {!Array.<number>}*/(result)); |
|
else |
|
code_points.push(result); |
|
} while (!input_stream.endOfStream()); |
|
this._decoder = null; |
|
} |
|
|
|
if (code_points.length) { |
|
// If encoding is one of utf-8, utf-16be, and utf-16le, and |
|
// ignore BOM flag and BOM seen flag are unset, run these |
|
// subsubsteps: |
|
if (['utf-8'].indexOf(this.encoding) !== -1 && |
|
!this._ignoreBOM && !this._BOMseen) { |
|
// If token is U+FEFF, set BOM seen flag. |
|
if (code_points[0] === 0xFEFF) { |
|
this._BOMseen = true; |
|
code_points.shift(); |
|
} else { |
|
// Otherwise, if token is not end-of-stream, set BOM seen |
|
// flag and append token to output. |
|
this._BOMseen = true; |
|
} |
|
} |
|
} |
|
|
|
return codePointsToString(code_points); |
|
} |
|
}; |
|
|
|
// 7.2 Interface TextEncoder |
|
|
|
/** |
|
* @constructor |
|
* @param {string=} encoding The label of the encoding; |
|
* defaults to 'utf-8'. |
|
* @param {Object=} options |
|
*/ |
|
function TextEncoder$1(encoding, options) { |
|
if (!(this instanceof TextEncoder$1)) |
|
return new TextEncoder$1(encoding, options); |
|
encoding = encoding !== undefined ? String(encoding).toLowerCase() : DEFAULT_ENCODING; |
|
if (encoding !== DEFAULT_ENCODING) { |
|
throw new Error('Encoding not supported. Only utf-8 is supported'); |
|
} |
|
options = ToDictionary(options); |
|
|
|
/** @private @type {boolean} */ |
|
this._streaming = false; |
|
/** @private @type {?Encoder} */ |
|
this._encoder = null; |
|
/** @private @type {{fatal: boolean}} */ |
|
this._options = {fatal: Boolean(options['fatal'])}; |
|
|
|
Object.defineProperty(this, 'encoding', {value: 'utf-8'}); |
|
} |
|
|
|
TextEncoder$1.prototype = { |
|
/** |
|
* @param {string=} opt_string The string to encode. |
|
* @param {Object=} options |
|
* @return {Uint8Array} Encoded bytes, as a Uint8Array. |
|
*/ |
|
encode: function encode(opt_string, options) { |
|
opt_string = opt_string ? String(opt_string) : ''; |
|
options = ToDictionary(options); |
|
|
|
// NOTE: This option is nonstandard. None of the encodings |
|
// permitted for encoding (i.e. UTF-8, UTF-16) are stateful, |
|
// so streaming is not necessary. |
|
if (!this._streaming) |
|
this._encoder = new UTF8Encoder(this._options); |
|
this._streaming = Boolean(options['stream']); |
|
|
|
var bytes = []; |
|
var input_stream = new Stream(stringToCodePoints(opt_string)); |
|
/** @type {?(number|!Array.<number>)} */ |
|
var result; |
|
while (!input_stream.endOfStream()) { |
|
result = this._encoder.handler(input_stream, input_stream.read()); |
|
if (result === finished) |
|
break; |
|
if (Array.isArray(result)) |
|
bytes.push.apply(bytes, /**@type {!Array.<number>}*/(result)); |
|
else |
|
bytes.push(result); |
|
} |
|
if (!this._streaming) { |
|
while (true) { |
|
result = this._encoder.handler(input_stream, input_stream.read()); |
|
if (result === finished) |
|
break; |
|
if (Array.isArray(result)) |
|
bytes.push.apply(bytes, /**@type {!Array.<number>}*/(result)); |
|
else |
|
bytes.push(result); |
|
} |
|
this._encoder = null; |
|
} |
|
return new Uint8Array(bytes); |
|
} |
|
}; |
|
|
|
// |
|
// 8. The encoding |
|
// |
|
|
|
// 8.1 utf-8 |
|
|
|
/** |
|
* @constructor |
|
* @implements {Decoder} |
|
* @param {{fatal: boolean}} options |
|
*/ |
|
function UTF8Decoder(options) { |
|
var fatal = options.fatal; |
|
|
|
// utf-8's decoder's has an associated utf-8 code point, utf-8 |
|
// bytes seen, and utf-8 bytes needed (all initially 0), a utf-8 |
|
// lower boundary (initially 0x80), and a utf-8 upper boundary |
|
// (initially 0xBF). |
|
var /** @type {number} */ utf8_code_point = 0, |
|
/** @type {number} */ utf8_bytes_seen = 0, |
|
/** @type {number} */ utf8_bytes_needed = 0, |
|
/** @type {number} */ utf8_lower_boundary = 0x80, |
|
/** @type {number} */ utf8_upper_boundary = 0xBF; |
|
|
|
/** |
|
* @param {Stream} stream The stream of bytes being decoded. |
|
* @param {number} bite The next byte read from the stream. |
|
* @return {?(number|!Array.<number>)} The next code point(s) |
|
* decoded, or null if not enough data exists in the input |
|
* stream to decode a complete code point. |
|
*/ |
|
this.handler = function(stream, bite) { |
|
// 1. If byte is end-of-stream and utf-8 bytes needed is not 0, |
|
// set utf-8 bytes needed to 0 and return error. |
|
if (bite === end_of_stream && utf8_bytes_needed !== 0) { |
|
utf8_bytes_needed = 0; |
|
return decoderError(fatal); |
|
} |
|
|
|
// 2. If byte is end-of-stream, return finished. |
|
if (bite === end_of_stream) |
|
return finished; |
|
|
|
// 3. If utf-8 bytes needed is 0, based on byte: |
|
if (utf8_bytes_needed === 0) { |
|
|
|
// 0x00 to 0x7F |
|
if (inRange(bite, 0x00, 0x7F)) { |
|
// Return a code point whose value is byte. |
|
return bite; |
|
} |
|
|
|
// 0xC2 to 0xDF |
|
if (inRange(bite, 0xC2, 0xDF)) { |
|
// Set utf-8 bytes needed to 1 and utf-8 code point to byte |
|
// − 0xC0. |
|
utf8_bytes_needed = 1; |
|
utf8_code_point = bite - 0xC0; |
|
} |
|
|
|
// 0xE0 to 0xEF |
|
else if (inRange(bite, 0xE0, 0xEF)) { |
|
// 1. If byte is 0xE0, set utf-8 lower boundary to 0xA0. |
|
if (bite === 0xE0) |
|
utf8_lower_boundary = 0xA0; |
|
// 2. If byte is 0xED, set utf-8 upper boundary to 0x9F. |
|
if (bite === 0xED) |
|
utf8_upper_boundary = 0x9F; |
|
// 3. Set utf-8 bytes needed to 2 and utf-8 code point to |
|
// byte − 0xE0. |
|
utf8_bytes_needed = 2; |
|
utf8_code_point = bite - 0xE0; |
|
} |
|
|
|
// 0xF0 to 0xF4 |
|
else if (inRange(bite, 0xF0, 0xF4)) { |
|
// 1. If byte is 0xF0, set utf-8 lower boundary to 0x90. |
|
if (bite === 0xF0) |
|
utf8_lower_boundary = 0x90; |
|
// 2. If byte is 0xF4, set utf-8 upper boundary to 0x8F. |
|
if (bite === 0xF4) |
|
utf8_upper_boundary = 0x8F; |
|
// 3. Set utf-8 bytes needed to 3 and utf-8 code point to |
|
// byte − 0xF0. |
|
utf8_bytes_needed = 3; |
|
utf8_code_point = bite - 0xF0; |
|
} |
|
|
|
// Otherwise |
|
else { |
|
// Return error. |
|
return decoderError(fatal); |
|
} |
|
|
|
// Then (byte is in the range 0xC2 to 0xF4) set utf-8 code |
|
// point to utf-8 code point << (6 × utf-8 bytes needed) and |
|
// return continue. |
|
utf8_code_point = utf8_code_point << (6 * utf8_bytes_needed); |
|
return null; |
|
} |
|
|
|
// 4. If byte is not in the range utf-8 lower boundary to utf-8 |
|
// upper boundary, run these substeps: |
|
if (!inRange(bite, utf8_lower_boundary, utf8_upper_boundary)) { |
|
|
|
// 1. Set utf-8 code point, utf-8 bytes needed, and utf-8 |
|
// bytes seen to 0, set utf-8 lower boundary to 0x80, and set |
|
// utf-8 upper boundary to 0xBF. |
|
utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0; |
|
utf8_lower_boundary = 0x80; |
|
utf8_upper_boundary = 0xBF; |
|
|
|
// 2. Prepend byte to stream. |
|
stream.prepend(bite); |
|
|
|
// 3. Return error. |
|
return decoderError(fatal); |
|
} |
|
|
|
// 5. Set utf-8 lower boundary to 0x80 and utf-8 upper boundary |
|
// to 0xBF. |
|
utf8_lower_boundary = 0x80; |
|
utf8_upper_boundary = 0xBF; |
|
|
|
// 6. Increase utf-8 bytes seen by one and set utf-8 code point |
|
// to utf-8 code point + (byte − 0x80) << (6 × (utf-8 bytes |
|
// needed − utf-8 bytes seen)). |
|
utf8_bytes_seen += 1; |
|
utf8_code_point += (bite - 0x80) << (6 * (utf8_bytes_needed - utf8_bytes_seen)); |
|
|
|
// 7. If utf-8 bytes seen is not equal to utf-8 bytes needed, |
|
// continue. |
|
if (utf8_bytes_seen !== utf8_bytes_needed) |
|
return null; |
|
|
|
// 8. Let code point be utf-8 code point. |
|
var code_point = utf8_code_point; |
|
|
|
// 9. Set utf-8 code point, utf-8 bytes needed, and utf-8 bytes |
|
// seen to 0. |
|
utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0; |
|
|
|
// 10. Return a code point whose value is code point. |
|
return code_point; |
|
}; |
|
} |
|
|
|
/** |
|
* @constructor |
|
* @implements {Encoder} |
|
* @param {{fatal: boolean}} options |
|
*/ |
|
function UTF8Encoder(options) { |
|
var fatal = options.fatal; |
|
/** |
|
* @param {Stream} stream Input stream. |
|
* @param {number} code_point Next code point read from the stream. |
|
* @return {(number|!Array.<number>)} Byte(s) to emit. |
|
*/ |
|
this.handler = function(stream, code_point) { |
|
// 1. If code point is end-of-stream, return finished. |
|
if (code_point === end_of_stream) |
|
return finished; |
|
|
|
// 2. If code point is in the range U+0000 to U+007F, return a |
|
// byte whose value is code point. |
|
if (inRange(code_point, 0x0000, 0x007f)) |
|
return code_point; |
|
|
|
// 3. Set count and offset based on the range code point is in: |
|
var count, offset; |
|
// U+0080 to U+07FF: 1 and 0xC0 |
|
if (inRange(code_point, 0x0080, 0x07FF)) { |
|
count = 1; |
|
offset = 0xC0; |
|
} |
|
// U+0800 to U+FFFF: 2 and 0xE0 |
|
else if (inRange(code_point, 0x0800, 0xFFFF)) { |
|
count = 2; |
|
offset = 0xE0; |
|
} |
|
// U+10000 to U+10FFFF: 3 and 0xF0 |
|
else if (inRange(code_point, 0x10000, 0x10FFFF)) { |
|
count = 3; |
|
offset = 0xF0; |
|
} |
|
|
|
// 4.Let bytes be a byte sequence whose first byte is (code |
|
// point >> (6 × count)) + offset. |
|
var bytes = [(code_point >> (6 * count)) + offset]; |
|
|
|
// 5. Run these substeps while count is greater than 0: |
|
while (count > 0) { |
|
|
|
// 1. Set temp to code point >> (6 × (count − 1)). |
|
var temp = code_point >> (6 * (count - 1)); |
|
|
|
// 2. Append to bytes 0x80 | (temp & 0x3F). |
|
bytes.push(0x80 | (temp & 0x3F)); |
|
|
|
// 3. Decrease count by one. |
|
count -= 1; |
|
} |
|
|
|
// 6. Return bytes bytes, in order. |
|
return bytes; |
|
}; |
|
} |
|
|
|
function getGlobal() { |
|
if (typeof self !== 'undefined') return self; |
|
if (typeof global !== 'undefined') return global; |
|
throw new Error('No global found'); |
|
} |
|
|
|
if (typeof TextDecoder !== 'function') { |
|
getGlobal().TextDecoder = TextDecoder$1; |
|
} |
|
|
|
if (typeof TextEncoder !== 'function') { |
|
getGlobal().TextEncoder = TextEncoder$1; |
|
} |
|
|
|
})(); |
|
class PntsParser{ |
|
constructor(){ |
|
this.utf8Decoder = new TextDecoder('utf-8'); |
|
} |
|
|
|
|
|
parse(buffer) { |
|
|
|
if (!buffer) { |
|
return Promise.reject(buffer); |
|
} |
|
const view = new DataView(buffer); |
|
|
|
let byteOffset = 0; |
|
const pntsHeader = {}; |
|
let batchTable = {}; |
|
let point = {}; |
|
|
|
// Magic type is unsigned char [4] |
|
pntsHeader.magic = this.utf8Decoder.decode(new Uint8Array(buffer, byteOffset, 4)); |
|
byteOffset += 4; |
|
|
|
if (pntsHeader.magic) { |
|
// Version, byteLength, batchTableJSONByteLength, batchTableBinaryByteLength and batchTable types are uint32 |
|
pntsHeader.version = view.getUint32(byteOffset, true); |
|
byteOffset += Uint32Array.BYTES_PER_ELEMENT; |
|
|
|
pntsHeader.byteLength = view.getUint32(byteOffset, true); |
|
byteOffset += Uint32Array.BYTES_PER_ELEMENT; |
|
|
|
pntsHeader.FTJSONLength = view.getUint32(byteOffset, true); |
|
byteOffset += Uint32Array.BYTES_PER_ELEMENT; |
|
|
|
pntsHeader.FTBinaryLength = view.getUint32(byteOffset, true); |
|
byteOffset += Uint32Array.BYTES_PER_ELEMENT; |
|
|
|
pntsHeader.BTJSONLength = view.getUint32(byteOffset, true); |
|
byteOffset += Uint32Array.BYTES_PER_ELEMENT; |
|
|
|
pntsHeader.BTBinaryLength = view.getUint32(byteOffset, true); |
|
byteOffset += Uint32Array.BYTES_PER_ELEMENT; |
|
|
|
// binary table |
|
if (pntsHeader.FTBinaryLength > 0) { |
|
point = this.parseFeatureBinary(buffer, byteOffset, pntsHeader.FTJSONLength); |
|
} |
|
|
|
// batch table |
|
if (pntsHeader.BTJSONLength > 0) { |
|
const sizeBegin = 28 + pntsHeader.FTJSONLength + pntsHeader.FTBinaryLength; |
|
batchTable = BatchTableParser.parse( |
|
buffer.slice(sizeBegin, pntsHeader.BTJSONLength + sizeBegin)); |
|
} |
|
|
|
const pnts = { |
|
point, |
|
batchTable |
|
}; |
|
return Promise.resolve(pnts); |
|
} else { |
|
throw new Error('Invalid pnts file.'); |
|
} |
|
|
|
} |
|
|
|
parseFeatureBinary(array, byteOffset, FTJSONLength) { |
|
// Init geometry |
|
const geometry = new THREE.BufferGeometry(); |
|
// init Array feature binary |
|
const subArrayJson = this.utf8Decoder.decode(new Uint8Array(array, byteOffset, FTJSONLength)); |
|
const parseJSON = JSON.parse(subArrayJson); |
|
let lengthFeature; |
|
if (parseJSON.POINTS_LENGTH) { |
|
lengthFeature = parseJSON.POINTS_LENGTH; |
|
} |
|
if (parseJSON.POSITION) { |
|
const byteOffsetPos = (parseJSON.POSITION.byteOffset + subArrayJson.length + byteOffset); |
|
const positionArray = new Float32Array(array, byteOffsetPos, lengthFeature * 3); |
|
geometry.setAttribute('position', new THREE.BufferAttribute(positionArray, 3)); |
|
} |
|
if (parseJSON.RGB) { |
|
const byteOffsetCol = parseJSON.RGB.byteOffset + subArrayJson.length + byteOffset; |
|
const colorArray = new Uint8Array(array, byteOffsetCol, lengthFeature * 3); |
|
geometry.setAttribute('color', new THREE.BufferAttribute(colorArray, 3, true)); |
|
} |
|
if (parseJSON.POSITION_QUANTIZED) { |
|
throw new Error('For pnts loader, POSITION_QUANTIZED: not yet managed'); |
|
} |
|
if (parseJSON.RGBA) { |
|
throw new Error('For pnts loader, RGBA: not yet managed'); |
|
} |
|
if (parseJSON.RGB565) { |
|
throw new Error('For pnts loader, RGB565: not yet managed'); |
|
} |
|
if (parseJSON.NORMAL) { |
|
//throw new Error('For pnts loader, NORMAL: not yet managed'); |
|
const byteOffsetNor = parseJSON.RGB.byteOffset + subArrayJson.length + byteOffset; |
|
const normalArray = new Float32Array(array, byteOffsetNor, lengthFeature * 3); |
|
geometry.setAttribute( 'normal', new THREE.BufferAttribute(normalArray, 3 )); |
|
} |
|
if (parseJSON.NORMAL_OCT16P) { |
|
throw new Error('For pnts loader, NORMAL_OCT16P: not yet managed'); |
|
} |
|
if (parseJSON.BATCH_ID) { |
|
throw new Error('For pnts loader, BATCH_ID: not yet managed'); |
|
} |
|
|
|
// Add RTC feature |
|
const offset = parseJSON.RTC_CENTER ? |
|
new THREE.Vector3().fromArray(parseJSON.RTC_CENTER) : undefined; |
|
|
|
return { |
|
geometry, |
|
offset, |
|
}; |
|
} |
|
} |
|
|
|
CLOUD.PntsParser = PntsParser; |
|
;(() => { |
|
|
|
let _inverseSceneMatrix = new THREE.Matrix4(); |
|
class ExtendTileset{ |
|
|
|
constructor(tileset, baseURL){ |
|
this.tileset = tileset; |
|
this.baseURL = baseURL; |
|
this.counter = 1; |
|
this.index = {}; |
|
this.recurse(tileset.root, baseURL); |
|
} |
|
|
|
recurse(node, baseURL, parent) { |
|
// compute transform (will become Object3D.matrix when the object is downloaded) |
|
node.transform = node.transform ? (new THREE.Matrix4()).fromArray(node.transform) : |
|
undefined; |
|
|
|
// The only reason to store _worldFromLocalTransform is because of extendTileset where we need the |
|
// transform chain for one node. |
|
node._worldFromLocalTransform = node.transform; |
|
if (parent && parent._worldFromLocalTransform) { |
|
if (node.transform) { |
|
node._worldFromLocalTransform = new THREE.Matrix4().multiplyMatrices( |
|
parent._worldFromLocalTransform, node.transform); |
|
} else { |
|
node._worldFromLocalTransform = parent._worldFromLocalTransform; |
|
} |
|
} |
|
|
|
// getBox only use inverseTileTransform for volume.region so let's not |
|
// compute the inverse matrix each time |
|
// Assumes that node.boundingVolume is defined if node.viewerRequestVolume is undefined |
|
if ((node.viewerRequestVolume && node.viewerRequestVolume.region) || |
|
node.boundingVolume.region) { |
|
if (node._worldFromLocalTransform) { |
|
_inverseSceneMatrix.copy(node._worldFromLocalTransform).invert(); |
|
} else { |
|
_inverseSceneMatrix.identity(); |
|
} |
|
} |
|
|
|
node.viewerRequestVolume = node.viewerRequestVolume ? this.getBox(node.viewerRequestVolume, |
|
_inverseSceneMatrix) : undefined; |
|
node.boundingVolume = this.getBox(node.boundingVolume, _inverseSceneMatrix); |
|
|
|
this.index[this.counter] = node; |
|
node.tileId = this.counter; |
|
node.baseURL = baseURL; |
|
//add |
|
node.points = undefined; |
|
node.b3dm = undefined; |
|
this.counter++; |
|
if (node.children) { |
|
for (const child of node.children) { |
|
this.recurse(child, baseURL, node); |
|
} |
|
} |
|
} |
|
|
|
getBox(volume, inverseTileTransform) { |
|
if (volume.region) { |
|
const region = volume.region; |
|
extent.set( |
|
THREE.Math.radToDeg(region[0]), |
|
THREE.Math.radToDeg(region[2]), |
|
THREE.Math.radToDeg(region[1]), |
|
THREE.Math.radToDeg(region[3])); |
|
const box = new OBB().setFromExtent(extent); |
|
// at this point box.matrix = box.epsg4978_from_local, so |
|
// we transform it in parent_from_local by using parent's epsg4978_from_local |
|
// which from our point of view is epsg4978_from_parent. |
|
// box.matrix = (epsg4978_from_parent ^ -1) * epsg4978_from_local |
|
// = parent_from_epsg4978 * epsg4978_from_local |
|
// = parent_from_local |
|
box.matrix.premultiply(inverseTileTransform); |
|
// update position, rotation and scale |
|
box.matrix.decompose(box.position, box.quaternion, box.scale); |
|
return { |
|
region: box |
|
}; |
|
} else if (volume.box) { |
|
// TODO: only works for axis aligned boxes |
|
const box = volume.box; |
|
// box[0], box[1], box[2] = center of the box |
|
// box[3], box[4], box[5] = x axis direction and half-length |
|
// box[6], box[7], box[8] = y axis direction and half-length |
|
// box[9], box[10], box[11] = z axis direction and half-length |
|
const center = new THREE.Vector3(box[0], box[1], box[2]); |
|
const w = center.x - box[3]; |
|
const e = center.x + box[3]; |
|
const s = center.y - box[7]; |
|
const n = center.y + box[7]; |
|
const b = center.z - box[11]; |
|
const t = center.z + box[11]; |
|
|
|
return { |
|
box: new THREE.Box3(new THREE.Vector3(w, s, b), new THREE.Vector3(e, n, t)) |
|
}; |
|
} else if (volume.sphere) { |
|
const sphere = new THREE.Sphere(new THREE.Vector3(volume.sphere[0], volume.sphere[1], volume |
|
.sphere[2]), |
|
volume.sphere[3]); |
|
return { |
|
sphere |
|
}; |
|
} |
|
} |
|
|
|
} |
|
|
|
CLOUD.ExtendTileset = ExtendTileset; |
|
}) |
|
|
|
class PntLoader { |
|
constructor(maximumScreenSpaceError, unitScale) { |
|
|
|
this.unitScale = unitScale; |
|
this.pntParser = new PntsParser(); |
|
this.utf8Decoder = new TextDecoder('utf-8'); |
|
this.tileIndexMap = {}; |
|
this.rootPntUrl = undefined; |
|
this.maximumScreenSpaceError = maximumScreenSpaceError === undefined ? 5 : maximumScreenSpaceError; |
|
this.loadPromises = []; |
|
this.camera = undefined; |
|
this.globalInverseMatrix = new THREE.Matrix4(); |
|
this.box = null; |
|
|
|
this.loadJsonPromises = []; |
|
this.pntFileList = {}; |
|
|
|
} |
|
|
|
/** |
|
* load tilesset.json in 3dtiles |
|
* |
|
* @param {String} tilesJsonUrl tilesset.json ptah |
|
* @param {Function} callback 回调函数 |
|
*/ |
|
|
|
loadPntTotalJson(tilesJsonUrl, callback) { |
|
let scope = this; |
|
scope.loadPromises = []; |
|
this.rootPntUrl = tilesJsonUrl.slice(0, tilesJsonUrl.lastIndexOf('/') + 1); |
|
|
|
const promiseFin = new Promise(function (resolve, reject) { |
|
// load json |
|
var loader = new THREE.FileLoader(); |
|
loader.load(tilesJsonUrl, function (text) { |
|
let tilesJson = JSON.parse(text); |
|
scope.box = tilesJson.root.boundingVolume.box; |
|
if (callback) { |
|
callback(scope.box); |
|
} |
|
scope.parseTilesJson(tilesJson.root); |
|
|
|
var handlePromise = Promise.all(scope.loadJsonPromises.map(function (promiseItem) { |
|
return promiseItem.catch(function (err) { |
|
return err |
|
}) |
|
})); |
|
|
|
var loadPntTaskManager = new CLOUD.TaskManager(); |
|
handlePromise.then((result) => { |
|
// load pnts |
|
let count = 0; |
|
for (let url in scope.tileIndexMap) { |
|
let tileIndex = scope.tileIndexMap[url]; |
|
const newPrefix = url.slice(0, url.lastIndexOf('/') + 1); |
|
for (let id in tileIndex.index) { |
|
let subUrl = newPrefix + tileIndex.index[id].content.url; |
|
loadPntTaskManager.addTask(count); |
|
scope.pntFileList[count] = {}; |
|
scope.pntFileList[count].jsonUrl = url; |
|
scope.pntFileList[count].pntUrl = subUrl; |
|
scope.pntFileList[count].index = id; |
|
count++; |
|
} |
|
} |
|
|
|
loadPntTaskManager.processTasks(scope.loadPnt.bind(scope), undefined, function (data) { |
|
var urlPointGroupMap = {}; |
|
for (let id in scope.pntFileList) { |
|
var url = scope.pntFileList[id].jsonUrl; |
|
if (!(url in urlPointGroupMap)) { |
|
let pointGroup = new CLOUD.ObjectGroup(CLOUD.ObjectGroupType.EXTRUDEBODYMANAGER, { |
|
pickableType: CLOUD.PICKABLETYPE.Geometry, |
|
globalSpace: true |
|
}); |
|
urlPointGroupMap[url] = pointGroup; |
|
} |
|
|
|
var tileIndex = scope.tileIndexMap[url]; |
|
tileIndex.index[scope.pntFileList[id].index].points = scope.pntFileList[id].result; |
|
urlPointGroupMap[url].add(scope.pntFileList[id].result); |
|
|
|
} |
|
|
|
var pointList = []; |
|
for (var url in urlPointGroupMap) { |
|
pointList.push(urlPointGroupMap[url]); |
|
} |
|
resolve(pointList); |
|
}) |
|
}); |
|
}) |
|
}); |
|
|
|
return promiseFin; |
|
} |
|
|
|
parseTilesJson(context) { |
|
let scope = this; |
|
|
|
if (context.content && context.content.url) { |
|
let url = this.rootPntUrl + context.content.url; |
|
scope.loadTileSetJson(url); |
|
} |
|
|
|
if (context.children) { |
|
for (let child of context.children) { |
|
this.parseTilesJson(child); |
|
} |
|
} |
|
} |
|
|
|
loadTileSetJson(tilesJsonUrl) { |
|
let scope = this; |
|
const promiseFin = new Promise(function (resolve, reject) { |
|
var loader = new THREE.FileLoader(); |
|
loader.setResponseType('json'); |
|
loader.load(tilesJsonUrl, |
|
function (data) { |
|
if (data === null || data === undefined) { |
|
reject(data); |
|
} else { |
|
let tileIndex = new ExtendTileset(data, ''); |
|
scope.tileIndexMap[tilesJsonUrl] = tileIndex; |
|
const rootNode = tileIndex.index['1']; |
|
const newPrefix = tilesJsonUrl.slice(0, tilesJsonUrl.lastIndexOf('/') + 1); |
|
resolve(tileIndex); |
|
} |
|
}, undefined, |
|
function (err) { |
|
reject(data); |
|
console.error(`Load ${tilesJsonUrl} failed, ${err}`); |
|
}); |
|
}); |
|
|
|
scope.loadJsonPromises.push(promiseFin); |
|
return promiseFin; |
|
} |
|
|
|
|
|
|
|
/** |
|
* refresh the display of 3dtiles |
|
* |
|
* @param {CLOUD.Camera} camera camera |
|
* @param {Matrix4} globalInverseMatrix 全局变换矩阵 |
|
*/ |
|
|
|
ProcessTileIndexs(camera, globalInverseMatrix) { |
|
this.camera = camera; |
|
this.globalInverseMatrix = globalInverseMatrix; |
|
for (let url in this.tileIndexMap) { |
|
let tileset = this.tileIndexMap[url]; |
|
const rootNode = tileset.index['1']; |
|
this.ProcessNode(rootNode, url); |
|
} |
|
} |
|
|
|
ProcessNode(node, tilesJsonUrl, parent) { |
|
node.visible = false; |
|
let point = this.tileIndexMap[tilesJsonUrl].index[node.tileId].points; |
|
if (point == undefined) { |
|
return; |
|
} |
|
point.visible = false; |
|
const isVisible = true; //$3dTilesCulling(camera, node, point.matrixWorld); |
|
if (isVisible && this.$3dTilesSubdivisionControl(this.camera, node)) { |
|
point.visible = true; |
|
node.visible = true; |
|
//output tileId for lod test |
|
//console.log(node.tileId + ' '); |
|
} |
|
if (parent != undefined) { |
|
// TODO: ADD |
|
if (parent.refine.toUpperCase() === 'ADD') { |
|
|
|
} else { |
|
parent.visible = false; |
|
let pPoint = this.tileIndexMap[tilesJsonUrl].index[parent.tileId] |
|
pPoint.visible = false; |
|
} |
|
} |
|
if (node.children) { |
|
for (let child of node.children) { |
|
this.ProcessNode(child, tilesJsonUrl, node); |
|
} |
|
} |
|
} |
|
|
|
$3dTilesSubdivisionControl(camera, node) { |
|
const sse = this.computeNodeSSE(camera, node); |
|
return sse > this.maximumScreenSpaceError; |
|
} |
|
|
|
computeNodeSSE(camera, node) { |
|
const boundingVolumeBox = new THREE.Box3(); |
|
const boundingVolumeSphere = new THREE.Sphere(); |
|
let cameraPositionWc = camera.position.clone(); |
|
cameraPositionWc.multiplyScalar(1 / this.unitScale); |
|
cameraPositionWc.applyMatrix4(this.globalInverseMatrix); |
|
|
|
node.distance = 0; |
|
if (node.boundingVolume.region) { |
|
boundingVolumeBox.copy(node.boundingVolume.region.box3D); |
|
boundingVolumeBox.applyMatrix4(node.boundingVolume.region.matrixWorld); |
|
node.distance = boundingVolumeBox.distanceToPoint(cameraPositionWc); |
|
} else if (node.boundingVolume.box) { |
|
// boundingVolume.box is affected by matrixWorld |
|
boundingVolumeBox.copy(node.boundingVolume.box); |
|
if (node.matrixWorld) { |
|
boundingVolumeBox.applyMatrix4(node.matrixWorld); |
|
} |
|
node.distance = boundingVolumeBox.distanceToPoint(cameraPositionWc); |
|
} else if (node.boundingVolume.sphere) { |
|
// boundingVolume.sphere is affected by matrixWorld |
|
boundingVolumeSphere.copy(node.boundingVolume.sphere); |
|
boundingVolumeSphere.applyMatrix4(node.matrixWorld); |
|
// TODO: see https://github.com/iTowns/itowns/issues/800 |
|
node.distance = Math.max(0.0, |
|
boundingVolumeSphere.distanceToPoint(cameraPositionWc)); |
|
} else { |
|
return Infinity; |
|
} |
|
if (node.distance === 0) { |
|
// This test is needed in case geometricError = distance = 0 |
|
return Infinity; |
|
} |
|
|
|
// fix zero |
|
if (node.geometricError - 0 < 0.0001) { |
|
node.geometricError = 1; |
|
} |
|
return camera._preSSE * (node.geometricError / node.distance); |
|
} |
|
|
|
/** |
|
* update Pre Sse |
|
* |
|
* @param {CLOUD.Camera} camera camera |
|
* @param {Number} height the height of canvas |
|
* @param {Number} fov the fov of camera |
|
*/ |
|
|
|
|
|
updatePreSse(camera, height) { |
|
// sse = projected geometric error on screen plane from distance |
|
// We're using an approximation, assuming that the geometric error of all |
|
// objects is perpendicular to the camera view vector (= we always compute |
|
// for worst case). |
|
// |
|
// screen plane object |
|
// | __ |
|
// | / \ |
|
// | geometric{| |
|
// < fov angle . } sse error {| | |
|
// | \__/ |
|
// | |
|
// |<---------------------> |
|
// | distance |
|
// |
|
// geometric_error * screen_width (resp. screen_height) |
|
// = --------------------------------------- |
|
// 2 * distance * tan (horizontal_fov / 2) (resp. vertical_fov) |
|
// |
|
// |
|
// We pre-compute the preSSE (= constant part of the screen space error formula) once here |
|
const fov = camera.fov; |
|
const verticalFOV = THREE.Math.degToRad(fov); |
|
const verticalPreSSE = height / (2.0 * Math.tan(verticalFOV * 0.5)); |
|
|
|
// Note: the preSSE for the horizontal FOV is the same value |
|
// focale = (this.height * 0.5) / Math.tan(verticalFOV * 0.5); |
|
// horizontalFOV = 2 * Math.atan(this.width * 0.5 / focale); |
|
// horizontalPreSSE = this.width / (2.0 * Math.tan(horizontalFOV * 0.5)); (1) |
|
// => replacing horizontalFOV in Math.tan(horizontalFOV * 0.5) |
|
// Math.tan(horizontalFOV * 0.5) = Math.tan(2 * Math.atan(this.width * 0.5 / focale) * 0.5) |
|
// = Math.tan(Math.atan(this.width * 0.5 / focale)) |
|
// = this.width * 0.5 / focale |
|
// => now replacing focale |
|
// = this.width * 0.5 / (this.height * 0.5) / Math.tan(verticalFOV * 0.5) |
|
// = Math.tan(verticalFOV * 0.5) * this.width / this.height |
|
// => back to (1) |
|
// horizontalPreSSE = this.width / (2.0 * Math.tan(verticalFOV * 0.5) * this.width / this.height) |
|
// = this.height / 2.0 * Math.tan(verticalFOV * 0.5) |
|
// = verticalPreSSE |
|
camera._preSSE = verticalPreSSE; |
|
} |
|
|
|
|
|
loadPnt(id, callback) { |
|
let scope = this; |
|
var url = scope.pntFileList[id].pntUrl; |
|
return new Promise(function (resolve, reject) { |
|
var loader = new THREE.FileLoader(); |
|
loader.setResponseType('arraybuffer'); |
|
loader.load(url, |
|
function (pnts) { |
|
if (pnts !== undefined) { |
|
const magic = scope.utf8Decoder.decode(new Uint8Array(pnts, 0, 4)); |
|
if (magic[0] === '{') { |
|
pnts = JSON.parse(scope.utf8Decoder.decode(new Uint8Array(pnts))); |
|
const newPrefix = url.slice(0, url.lastIndexOf('/') + 1); |
|
} else if (magic == 'b3dm') { |
|
console.log('b3dm is load'); |
|
} else if (magic == 'pnts') { |
|
console.log('pnts is load'); |
|
} else { |
|
return Promise.reject(`Unsupported magic code ${magic}`); |
|
} |
|
scope.pntParser.parse(pnts).then((result) => { |
|
const material = new THREE.PointsMaterial({ |
|
size: 0.05, |
|
vertexColors: THREE.VertexColors |
|
}); |
|
|
|
// creation points with geometry and material |
|
const points = new THREE.Points(result.point.geometry, material); |
|
|
|
// Test Normal |
|
// if(url.indexOf("point_1_7.pnts") != -1){ |
|
// var vertexNormalsHelper = new THREE.VertexNormalsHelper(points, 10); |
|
// points.add(vertexNormalsHelper); |
|
// } |
|
|
|
|
|
if (result.point.offset) { |
|
points.position.copy(result.point.offset); |
|
} |
|
//可见度设为false |
|
points.visible = false; |
|
points.name = url; |
|
scope.pntFileList[id].result = points; |
|
//resolve(points); |
|
|
|
callback(); |
|
}); |
|
} |
|
}, undefined, |
|
function (err) { |
|
reject(undefined); |
|
} |
|
); |
|
}); |
|
} |
|
|
|
|
|
loadPntByPromise(url) { |
|
let scope = this; |
|
return new Promise(function (resolve, reject) { |
|
var loader = new THREE.FileLoader(); |
|
loader.setResponseType('arraybuffer'); |
|
loader.load(url, |
|
function (pnts) { |
|
if (pnts !== undefined) { |
|
const magic = scope.utf8Decoder.decode(new Uint8Array(pnts, 0, 4)); |
|
if (magic[0] === '{') { |
|
pnts = JSON.parse(scope.utf8Decoder.decode(new Uint8Array(pnts))); |
|
const newPrefix = url.slice(0, url.lastIndexOf('/') + 1); |
|
} else if (magic == 'b3dm') { |
|
console.log('b3dm is load'); |
|
} else if (magic == 'pnts') { |
|
console.log('pnts is load'); |
|
} else { |
|
return Promise.reject(`Unsupported magic code ${magic}`); |
|
} |
|
scope.pntParser.parse(pnts).then((result) => { |
|
const material = new THREE.PointsMaterial({ |
|
size: 0.05, |
|
vertexColors: THREE.VertexColors |
|
}); |
|
|
|
// creation points with geometry and material |
|
const points = new THREE.Points(result.point.geometry, material); |
|
|
|
// Test Normal |
|
// if(url.indexOf("point_1_7.pnts") != -1){ |
|
// var vertexNormalsHelper = new THREE.VertexNormalsHelper(points, 10); |
|
// points.add(vertexNormalsHelper); |
|
// } |
|
|
|
|
|
if (result.point.offset) { |
|
points.position.copy(result.point.offset); |
|
} |
|
//可见度设为false |
|
points.visible = false; |
|
points.name = url; |
|
resolve(points); |
|
}); |
|
} |
|
}, undefined, |
|
function (err) { |
|
reject(undefined); |
|
} |
|
); |
|
}); |
|
} |
|
|
|
|
|
loadPntTotalJsonByPromise(tilesJsonUrl, callback) { |
|
let scope = this; |
|
scope.loadPromises = []; |
|
this.rootPntUrl = tilesJsonUrl.slice(0, tilesJsonUrl.lastIndexOf('/') + 1); |
|
|
|
const promiseFin = new Promise(function (resolve, reject) { |
|
// load json |
|
var loader = new THREE.FileLoader(); |
|
loader.load(tilesJsonUrl, function (text) { |
|
let tilesJson = JSON.parse(text); |
|
scope.box = tilesJson.root.boundingVolume.box; |
|
if (callback) { |
|
callback(scope.box); |
|
} |
|
scope.parseTilesJson(tilesJson.root); |
|
|
|
var handlePromise = Promise.all(scope.loadJsonPromises.map(function (promiseItem) { |
|
return promiseItem.catch(function (err) { |
|
return err |
|
}) |
|
})); |
|
|
|
handlePromise.then((result) => { |
|
// load pnts |
|
var pntLoadPromises = []; |
|
for (let url in scope.tileIndexMap) { |
|
scope.urlPointGroupMap[url] = {}; |
|
let tileIndex = scope.tileIndexMap[url]; |
|
const newPrefix = url.slice(0, url.lastIndexOf('/') + 1); |
|
for (let id in tileIndex.index) { |
|
let subUrl = newPrefix + tileIndex.index[id].content.url; |
|
pntLoadPromises.push(scope.loadPnt(subUrl).then(result => ({ |
|
url: url, |
|
context: { |
|
index: id, |
|
result |
|
} |
|
}))); |
|
} |
|
} |
|
|
|
let loadHandlePromise = Promise.all(pntLoadPromises.map(function (promiseItem) { |
|
return promiseItem.catch(function (err) { |
|
return err |
|
}) |
|
})); |
|
|
|
loadHandlePromise.then((result) => { |
|
|
|
for (let url in scope.urlPointGroupMap) { |
|
|
|
let pointGroup = new CLOUD.ObjectGroup(CLOUD.ObjectGroupType.EXTRUDEBODYMANAGER, { |
|
pickableType: CLOUD.PICKABLETYPE.Geometry, |
|
globalSpace: true |
|
}); |
|
|
|
scope.urlPointGroupMap[url] = pointGroup; |
|
|
|
} |
|
|
|
|
|
for (const res of result) { |
|
if (res === undefined) { |
|
continue; |
|
} |
|
var tileIndex = scope.tileIndexMap[res.url]; |
|
tileIndex.index[res.context.index].points = res.context.result; |
|
scope.urlPointGroupMap[res.url].add(res.context.result); |
|
} |
|
|
|
|
|
var pointList = []; |
|
for (var url in scope.urlPointGroupMap) { |
|
pointList.push(scope.urlPointGroupMap[url]); |
|
} |
|
resolve(pointList); |
|
}) |
|
|
|
|
|
}).catch(function (reason) { |
|
console.log('promise reject failed reason', reason) |
|
}); |
|
}); |
|
|
|
}); |
|
|
|
|
|
return promiseFin; |
|
} |
|
|
|
} |
|
|
|
CLOUD.PntLoader = PntLoader; |