UNPKG

@loaders.gl/potree

Version:

potree loaders for large point clouds.

216 lines 8.31 kB
// 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"; import { Proj4Projection } from '@math.gl/proj4'; import { createProjection } from "../utils/projection-utils.js"; import { getCartographicOriginFromBoundingBox } from "../utils/bounding-box-utils.js"; // https://github.com/visgl/deck.gl/blob/9548f43cba2234a1f4877b6b17f6c88eb35b2e08/modules/core/src/lib/constants.js#L27 // Describes the format of positions export var COORDINATE_SYSTEM; (function (COORDINATE_SYSTEM) { /** * `LNGLAT` if rendering into a geospatial viewport, `CARTESIAN` otherwise */ COORDINATE_SYSTEM[COORDINATE_SYSTEM["DEFAULT"] = -1] = "DEFAULT"; /** * Positions are interpreted as [lng, lat, elevation] * lng lat are degrees, elevation is meters. distances as meters. */ COORDINATE_SYSTEM[COORDINATE_SYSTEM["LNGLAT"] = 1] = "LNGLAT"; /** * Positions are interpreted as meter offsets, distances as meters */ COORDINATE_SYSTEM[COORDINATE_SYSTEM["METER_OFFSETS"] = 2] = "METER_OFFSETS"; /** * Positions are interpreted as lng lat offsets: [deltaLng, deltaLat, elevation] * deltaLng, deltaLat are delta degrees, elevation is meters. * distances as meters. */ COORDINATE_SYSTEM[COORDINATE_SYSTEM["LNGLAT_OFFSETS"] = 3] = "LNGLAT_OFFSETS"; /** * Non-geospatial */ COORDINATE_SYSTEM[COORDINATE_SYSTEM["CARTESIAN"] = 0] = "CARTESIAN"; })(COORDINATE_SYSTEM || (COORDINATE_SYSTEM = {})); /** * A Potree data source * @version 1.0 - @see https://github.com/potree/potree/blob/1.0RC/docs/file_format.md * @version 1.7 - @see 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 = ''; /** 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() { if (this.initPromise) { await this.initPromise; return; } this.metadata = await load(`${this.baseUrl}/cloud.js`, PotreeLoader); this.projection = createProjection(this.metadata?.projection); this.parseBoundingVolume(); 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 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) { await this.initPromise; if (!this.isSupported()) { return null; } const isAvailable = await this.isNodeAvailable(nodeName); if (isAvailable) { const result = (await load(`${this.baseUrl}/${this.metadata?.octreeDir}/r/r${nodeName}.${this.getContentExtension()}`, LASLoader)); if (result) { result.cartographicOrigin = getCartographicOriginFromBoundingBox(this.projection, result.header?.boundingBox); const position = result.attributes.POSITION.value; for (let i = 0; i < (result.header?.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) { if (this.metadata?.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() { 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); } } parseBoundingVolume() { if (this.metadata?.projection && this.metadata.tightBoundingBox) { const projection = new 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 = this.metadata?.tightBoundingBox; } } } //# sourceMappingURL=potree-node-source.js.map