itowns
Version:
A JS/WebGL framework for 3D geospatial data visualization
157 lines (151 loc) • 5.44 kB
JavaScript
import { Hierarchy } from 'copc';
import PointCloudNode from "./PointCloudNode.js";
function buildVoxelKey(depth, x, y, z) {
return `${depth}-${x}-${y}-${z}`;
}
/**
* @extends PointCloudNode
*
* @property {boolean} isCopcNode - Used to checkout whether this
* node is a CopcNode. Default is `true`. You should not change
* this, as it is used internally for optimisation.
* @property {number} entryOffset - Offset from the beginning of the file of
* the node entry
* @property {number} entryLength - Size of the node entry
* @property {CopcLayer} layer - COPC layer the node belongs to.
* @property {number} depth - Depth within the octree
* @property {number} x - X position within the octree
* @property {number} y - Y position within the octree
* @property {number} z - Z position within the octree
* @property {string} voxelKey - The id of the node, constituted of the four
* components: `depth-x-y-z`.
*/
class CopcNode extends PointCloudNode {
/**
* Constructs a new instance of a COPC Octree node
*
* @param {number} depth - Depth within the octree
* @param {number} x - X position within the octree
* @param {number} y - Y position within the octree
* @param {number} z - Z position with the octree
* @param {number} entryOffset - Offset from the beginning of the file to
* the node entry
* @param {number} entryLength - Size of the node entry
* @param {CopcLayer} layer - Parent COPC layer
* @param {number} [numPoints=0] - Number of points given by this entry
*/
constructor(depth, x, y, z, entryOffset, entryLength, layer) {
let numPoints = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 0;
super(numPoints, layer);
this.isCopcNode = true;
this.entryOffset = entryOffset;
this.entryLength = entryLength;
this.depth = depth;
this.x = x;
this.y = y;
this.z = z;
this.voxelKey = buildVoxelKey(depth, x, y, z);
}
get octreeIsLoaded() {
return this.numPoints >= 0;
}
get id() {
return `${this.depth}${this.x}${this.y}${this.z}`;
}
/**
* @param {number} offset
* @param {number} size
*/
async _fetch(offset, size) {
return this.layer.source.fetcher(this.layer.source.url, {
...this.layer.source.networkOptions,
headers: {
...this.layer.source.networkOptions.headers,
range: `bytes=${offset}-${offset + size - 1}`
}
});
}
async loadOctree() {
// Load hierarchy
const buffer = await this._fetch(this.entryOffset, this.entryLength);
const hierarchy = await Hierarchy.parse(new Uint8Array(buffer));
// Update current node entry from loaded subtree
const node = hierarchy.nodes[this.voxelKey];
if (!node) {
return Promise.reject('[CopcNode]: Ill-formed data, entry not found in hierarchy.');
}
this.numPoints = node.pointCount;
this.entryOffset = node.pointDataOffset;
this.entryLength = node.pointDataLength;
// Load subtree entries
const stack = [];
stack.push(this);
while (stack.length) {
const node = stack.shift();
const depth = node.depth + 1;
const x = node.x * 2;
const y = node.y * 2;
const z = node.z * 2;
node.findAndCreateChild(depth, x, y, z, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y, z, hierarchy, stack);
node.findAndCreateChild(depth, x, y + 1, z, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y + 1, z, hierarchy, stack);
node.findAndCreateChild(depth, x, y, z + 1, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y, z + 1, hierarchy, stack);
node.findAndCreateChild(depth, x, y + 1, z + 1, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y + 1, z + 1, hierarchy, stack);
}
}
/**
* Create a CopcNode from the provided subtree and add it as child
* of the current node.
* @param {number} depth - Child node depth in the octree
* @param {number} x - Child node x position in the octree
* @param {number} y - Child node y position in the octree
* @param {number} z - Child node z position in the octree
* @param {Hierarchy.Subtree} hierarchy - Octree's subtree
* @param {CopcNode[]} stack - Stack of node candidates for traversal
*/
findAndCreateChild(depth, x, y, z, hierarchy, stack) {
const voxelKey = buildVoxelKey(depth, x, y, z);
let pointCount;
let offset;
let byteSize;
const node = hierarchy.nodes[voxelKey];
if (node) {
pointCount = node.pointCount;
offset = node.pointDataOffset;
byteSize = node.pointDataLength;
} else {
const page = hierarchy.pages[voxelKey];
if (!page) {
return;
}
pointCount = -1;
offset = page.pageOffset;
byteSize = page.pageLength;
}
const child = new CopcNode(depth, x, y, z, offset, byteSize, this.layer, pointCount);
this.add(child);
stack.push(child);
}
/**
* Load the COPC Buffer geometry for this node.
* @returns {Promise<THREE.BufferGeometry>}
*/
async load() {
if (!this.octreeIsLoaded) {
await this.loadOctree();
}
const buffer = await this._fetch(this.entryOffset, this.entryLength);
const geometry = await this.layer.source.parser(buffer, {
in: {
...this.layer.source,
pointCount: this.numPoints
},
out: this.layer
});
return geometry;
}
}
export default CopcNode;