@loaders.gl/potree
Version:
potree loaders for large point clouds.
409 lines (395 loc) • 13.4 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// dist/index.js
var dist_exports = {};
__export(dist_exports, {
PotreeBinLoader: () => PotreeBinLoader,
PotreeHierarchyChunkLoader: () => PotreeHierarchyChunkLoader,
PotreeLoader: () => PotreeLoader,
PotreeSource: () => PotreeSource
});
module.exports = __toCommonJS(dist_exports);
// dist/potree-loader.js
var VERSION = true ? "4.4.2" : "latest";
var PotreeLoader = {
dataType: null,
batchType: null,
name: "potree metadata",
id: "potree",
module: "potree",
version: VERSION,
extensions: ["js"],
mimeTypes: ["application/json"],
testText: (text) => text.indexOf("octreeDir") >= 0,
parse: (data) => JSON.parse(new TextDecoder().decode(data)),
parseTextSync: (text) => JSON.parse(text),
options: {
potree: {}
}
};
// dist/parsers/parse-potree-hierarchy-chunk.js
function parsePotreeHierarchyChunk(arrayBuffer) {
const tileHeaders = parseBinaryChunk(arrayBuffer);
return buildHierarchy(tileHeaders);
}
function parseBinaryChunk(arrayBuffer, byteOffset = 0) {
const dataView = new DataView(arrayBuffer);
const stack = [];
const topTileHeader = {};
byteOffset = decodeRow(dataView, byteOffset, topTileHeader);
stack.push(topTileHeader);
const tileHeaders = [topTileHeader];
while (stack.length > 0) {
const snode = stack.shift();
let mask = 1;
for (let i = 0; i < 8; i++) {
if (snode && (snode.header.childMask & mask) !== 0) {
const tileHeader = {};
byteOffset = decodeRow(dataView, byteOffset, tileHeader);
tileHeader.name = snode.name + i;
stack.push(tileHeader);
tileHeaders.push(tileHeader);
snode.header.childCount++;
}
mask = mask * 2;
}
if (byteOffset === dataView.byteLength) {
break;
}
}
return tileHeaders;
}
function decodeRow(dataView, byteOffset, tileHeader) {
tileHeader.header = tileHeader.header || {};
tileHeader.header.childMask = dataView.getUint8(byteOffset);
tileHeader.header.childCount = 0;
tileHeader.pointCount = dataView.getUint32(byteOffset + 1, true);
tileHeader.name = "";
byteOffset += 5;
return byteOffset;
}
function buildHierarchy(flatNodes, options = {}) {
const DEFAULT_OPTIONS = { spacing: 100 };
options = { ...DEFAULT_OPTIONS, ...options };
const topNode = flatNodes[0];
const nodes = {};
for (const node of flatNodes) {
const { name } = node;
const index = parseInt(name.charAt(name.length - 1), 10);
const parentName = name.substring(0, name.length - 1);
const parentNode = nodes[parentName];
const level = name.length - 1;
node.level = level;
node.hasChildren = Boolean(node.header.childCount);
node.children = [];
node.childrenByIndex = new Array(8).fill(null);
node.spacing = ((options == null ? void 0 : options.spacing) || 0) / Math.pow(2, level);
node.type = "pointcloud";
node.id = node.name;
if (parentNode) {
parentNode.children.push(node);
parentNode.childrenByIndex[index] = node;
}
nodes[name] = node;
}
return topNode;
}
// dist/potree-hierarchy-chunk-loader.js
var VERSION2 = true ? "4.4.2" : "latest";
var PotreeHierarchyChunkLoader = {
dataType: null,
batchType: null,
name: "potree Hierarchy Chunk",
id: "potree-hrc",
module: "potree",
version: VERSION2,
extensions: ["hrc"],
mimeTypes: ["application/octet-stream"],
// binary potree files have no header bytes, no content test function possible
// test: ['...'],
parse: async (arrayBuffer, options) => parsePotreeHierarchyChunk(arrayBuffer),
parseSync: (arrayBuffer, options) => parsePotreeHierarchyChunk(arrayBuffer),
options: {
potree: {}
},
binary: true
};
// dist/parsers/parse-potree-bin.js
function parsePotreeBin(arrayBuffer, byteOffset, options, index) {
return null;
}
// dist/potree-bin-loader.js
var PotreeBinLoader = {
dataType: null,
batchType: null,
name: "potree Binary Point Attributes",
id: "potree",
extensions: ["bin"],
mimeTypes: ["application/octet-stream"],
// Unfortunately binary potree files have no header bytes, no test possible
// test: ['...'],
parseSync,
binary: true,
options: {}
// @ts-ignore
};
function parseSync(arrayBuffer, options) {
const index = {};
const byteOffset = 0;
parsePotreeBin(arrayBuffer, byteOffset, options, index);
return index;
}
// dist/lib/potree-node-source.js
var import_core = require("@loaders.gl/core");
var import_loader_utils = require("@loaders.gl/loader-utils");
var import_las = require("@loaders.gl/las");
// dist/utils/parse-version.js
function parseVersion(version) {
const parts = version.split(".").map(Number);
return { major: parts[0], minor: parts[1] };
}
// dist/lib/potree-node-source.js
var import_proj42 = require("@math.gl/proj4");
// dist/utils/projection-utils.js
var import_proj4 = require("@math.gl/proj4");
var createProjection = (projectionData) => {
if (!projectionData) {
return null;
}
return new import_proj4.Proj4Projection({
from: projectionData,
to: "WGS84"
});
};
// dist/utils/bounding-box-utils.js
var getCartographicOriginFromBoundingBox = (projection, boundingBox) => {
if (!boundingBox) {
return [0, 0, 0];
}
const [minXOriginal, minYOriginal, minZ] = boundingBox[0];
const [maxXOriginal, maxYOriginal, maxZ] = boundingBox[1];
let minX = minXOriginal;
let minY = minYOriginal;
let maxX = maxXOriginal;
let maxY = maxYOriginal;
if (projection) {
[minX, minY] = projection.project([minX, minY]);
[maxX, maxY] = projection.project([maxX, maxY]);
}
return [minX + (maxX - minX) / 2, minY + (maxY - minY) / 2, minZ + (maxZ - minZ) / 2];
};
// dist/lib/potree-node-source.js
var COORDINATE_SYSTEM;
(function(COORDINATE_SYSTEM2) {
COORDINATE_SYSTEM2[COORDINATE_SYSTEM2["DEFAULT"] = -1] = "DEFAULT";
COORDINATE_SYSTEM2[COORDINATE_SYSTEM2["LNGLAT"] = 1] = "LNGLAT";
COORDINATE_SYSTEM2[COORDINATE_SYSTEM2["METER_OFFSETS"] = 2] = "METER_OFFSETS";
COORDINATE_SYSTEM2[COORDINATE_SYSTEM2["LNGLAT_OFFSETS"] = 3] = "LNGLAT_OFFSETS";
COORDINATE_SYSTEM2[COORDINATE_SYSTEM2["CARTESIAN"] = 0] = "CARTESIAN";
})(COORDINATE_SYSTEM || (COORDINATE_SYSTEM = {}));
var PotreeNodesSource = class extends import_loader_utils.DataSource {
/** Dataset base URL */
baseUrl = "";
/** Meta information from `cloud.js` */
metadata = null;
/** Root node */
root = null;
/** Is data source ready to use after initial loading */
isReady = false;
/** local CRS to WGS84 projection */
projection = null;
/** The data set minimum bounding box */
boundingBox;
initPromise = null;
/**
* @constructor
* @param data - if string - data set path url or path to `cloud.js` metadata file
* - if Blob - single file data
* @param options - data source properties
*/
constructor(data, options) {
super(data, options);
this.makeBaseUrl(this.data);
this.initPromise = this.init();
}
/** Initial data source loading */
async init() {
var _a;
if (this.initPromise) {
await this.initPromise;
return;
}
this.metadata = await (0, import_core.load)(`${this.baseUrl}/cloud.js`, PotreeLoader);
this.projection = createProjection((_a = this.metadata) == null ? void 0 : _a.projection);
this.parseBoundingVolume();
await this.loadHierarchy();
this.isReady = true;
}
/** Is data set supported */
isSupported() {
var _a, _b, _c;
const { minor, major } = parseVersion(((_a = this.metadata) == null ? void 0 : _a.version) ?? "");
return this.isReady && major === 1 && minor <= 8 && typeof ((_b = this.metadata) == null ? void 0 : _b.pointAttributes) === "string" && ["LAS", "LAZ"].includes((_c = this.metadata) == null ? void 0 : _c.pointAttributes);
}
/** Get content files extension */
getContentExtension() {
var _a;
if (!this.isReady) {
return null;
}
switch ((_a = this.metadata) == null ? void 0 : _a.pointAttributes) {
case "LAS":
return "las";
case "LAZ":
return "laz";
default:
return "bin";
}
}
/**
* Load octree node content
* @param nodeName name of a node, string of numbers in range 0..7
* @return node content geometry or null if the node doesn't exist
*/
async loadNodeContent(nodeName) {
var _a, _b, _c;
await this.initPromise;
if (!this.isSupported()) {
return null;
}
const isAvailable = await this.isNodeAvailable(nodeName);
if (isAvailable) {
const result = await (0, import_core.load)(`${this.baseUrl}/${(_a = this.metadata) == null ? void 0 : _a.octreeDir}/r/r${nodeName}.${this.getContentExtension()}`, import_las.LASLoader);
if (result) {
result.cartographicOrigin = getCartographicOriginFromBoundingBox(this.projection, (_b = result.header) == null ? void 0 : _b.boundingBox);
const position = result.attributes.POSITION.value;
for (let i = 0; i < (((_c = result.header) == null ? void 0 : _c.vertexCount) ?? 0); i++) {
let vertex = position.slice(i * 3, i * 3 + 2);
if (this.projection) {
vertex = this.projection.project(Array.from(vertex));
}
const offsets = [
vertex[0] - result.cartographicOrigin[0],
vertex[1] - result.cartographicOrigin[1],
position[i * 3 + 2] - result.cartographicOrigin[2]
];
position.set(offsets, i * 3);
}
result.attributes.positions = result.attributes.POSITION;
result.attributes.colors = result.attributes.COLOR_0;
result.attributes.normals = result.attributes.NORMAL;
result.coordinateSystem = COORDINATE_SYSTEM.LNGLAT_OFFSETS;
return result;
}
}
return null;
}
/**
* Check if a node exists in the octree
* @param nodeName name of a node, string of numbers in range 0..7
* @returns true - the node does exist, false - the nodes doesn't exist
*/
async isNodeAvailable(nodeName) {
var _a;
if ((_a = this.metadata) == null ? void 0 : _a.hierarchy) {
return this.metadata.hierarchy.findIndex((item) => item[0] === `r${nodeName}`) !== -1;
}
if (!this.root) {
return false;
}
let currentParent = this.root;
let name = "";
let result = true;
for (const char of nodeName) {
const newName = `${name}${char}`;
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() {
var _a;
this.root = await (0, import_core.load)(`${this.baseUrl}/${(_a = this.metadata) == null ? void 0 : _a.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" ? (0, import_loader_utils.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);
}
}
parseBoundingVolume() {
var _a, _b;
if (((_a = this.metadata) == null ? void 0 : _a.projection) && this.metadata.tightBoundingBox) {
const projection = new import_proj42.Proj4Projection({
from: this.metadata.projection,
to: "WGS84"
});
const { lx, ly, ux, uy } = this.metadata.tightBoundingBox;
const lCoord = [lx, ly];
const wgs84LCood = projection.project(lCoord);
const uCoord = [ux, uy];
const wgs84UCood = projection.project(uCoord);
this.boundingBox = {
...this.metadata.tightBoundingBox,
lx: wgs84LCood[0],
ly: wgs84LCood[1],
ux: wgs84UCood[0],
uy: wgs84UCood[1]
};
} else {
this.boundingBox = (_b = this.metadata) == null ? void 0 : _b.tightBoundingBox;
}
}
};
// dist/potree-source.js
var VERSION3 = "1.7";
var PotreeSource = {
name: "Potree",
id: "potree",
module: "potree",
version: VERSION3,
extensions: ["bin", "las", "laz"],
mimeTypes: ["application/octet-stream"],
type: "potree",
fromUrl: true,
fromBlob: true,
defaultOptions: {
potree: {}
},
testURL: (url) => url.endsWith(".js"),
createDataSource: (url, options) => new PotreeNodesSource(url, options)
// , PotreeNodesSource.defaultOptions)
};
//# sourceMappingURL=index.cjs.map