@loaders.gl/potree
Version:
potree loaders for large point clouds.
141 lines (140 loc) • 4.76 kB
JavaScript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { load } from '@loaders.gl/core';
import { DataSource, resolvePath } from '@loaders.gl/loader-utils';
import { LASLoader } from '@loaders.gl/las';
import { PotreeHierarchyChunkLoader } from "../potree-hierarchy-chunk-loader.js";
import { PotreeLoader } from "../potree-loader.js";
import { parseVersion } from "../utils/parse-version.js";
/**
* A Potree data source
* @version 1.0 - https://github.com/potree/potree/blob/1.0RC/docs/file_format.md
* @version 1.7 - https://github.com/potree/potree/blob/1.7/docs/potree-file-format.md
* @note Point cloud nodes tile source
*/
export class PotreeNodesSource extends DataSource {
/** Dataset base URL */
baseUrl = '';
/** Input data: string - dataset url, blob - single file data */
data;
/** Input props */
props;
/** Meta information from `cloud.js` */
metadata = null;
/** Root node */
root = null;
/** Is data source ready to use after initial loading */
isReady = false;
initPromise = null;
/**
* @constructor
* @param data - if string - data set path url or path to `cloud.js` metadata file
* - if Blob - single file data
* @param props - data source properties
*/
constructor(data, props) {
super(props);
this.props = props;
this.data = data;
this.makeBaseUrl(this.data);
this.initPromise = this.init();
}
/** Initial data source loading */
async init() {
if (this.initPromise) {
await this.initPromise;
return;
}
this.metadata = await load(`${this.baseUrl}/cloud.js`, PotreeLoader);
await this.loadHierarchy();
this.isReady = true;
}
/** Is data set supported */
isSupported() {
const { minor, major } = parseVersion(this.metadata?.version ?? '');
return (this.isReady &&
major === 1 &&
minor <= 8 &&
typeof this.metadata?.pointAttributes === 'string' &&
['LAS', 'LAZ'].includes(this.metadata?.pointAttributes));
}
/** Get content files extension */
getContentExtension() {
if (!this.isReady) {
return null;
}
switch (this.metadata?.pointAttributes) {
case 'LAS':
return 'las';
case 'LAZ':
return 'laz';
default:
return 'bin';
}
}
/**
* Load octree node content
* @param path array of numbers between 0-7 specifying successive octree divisions.
* @return node content geometry or null if the node doesn't exist
*/
async loadNodeContent(path) {
await this.initPromise;
if (!this.isSupported()) {
return null;
}
const isAvailable = await this.isNodeAvailable(path);
if (isAvailable) {
return load(`${this.baseUrl}/${this.metadata?.octreeDir}/r/r${path.join('')}.${this.getContentExtension()}`, LASLoader);
}
return null;
}
/**
* Check if a node exists in the octree
* @param path array of numbers between 0-7 specifying successive octree divisions
* @returns true - the node does exist, false - the nodes doesn't exist
*/
async isNodeAvailable(path) {
if (this.metadata?.hierarchy) {
return this.metadata.hierarchy.findIndex((item) => item[0] === `r${path.join()}`) !== -1;
}
if (!this.root) {
return false;
}
let currentParent = this.root;
let name = '';
let result = true;
for (const nodeLevel of path) {
const newName = `${name}${nodeLevel}`;
const node = currentParent.children.find((child) => child.name === newName);
if (node) {
currentParent = node;
name = newName;
}
else {
result = false;
break;
}
}
return result;
}
/**
* Load data source hierarchy into tree of available nodes
*/
async loadHierarchy() {
this.root = await load(`${this.baseUrl}/${this.metadata?.octreeDir}/r/r.hrc`, PotreeHierarchyChunkLoader);
}
/**
* Deduce base url from the input url sring
* @param data - data source input data
*/
makeBaseUrl(data) {
this.baseUrl = typeof data === 'string' ? resolvePath(data) : '';
if (this.baseUrl.endsWith('cloud.js')) {
this.baseUrl = this.baseUrl.substring(0, -8);
}
if (this.baseUrl.endsWith('/')) {
this.baseUrl = this.baseUrl.substring(0, -1);
}
}
}