• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / loaders.gl / 20352515932

18 Dec 2025 09:56PM UTC coverage: 35.115% (-28.4%) from 63.485%
20352515932

push

github

web-flow
feat(loader-utils): Export is-type helpers (#3258)

1188 of 1998 branches covered (59.46%)

Branch coverage included in aggregate %.

147 of 211 new or added lines in 13 files covered. (69.67%)

30011 existing lines in 424 files now uncovered.

37457 of 108056 relevant lines covered (34.66%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

41.0
/modules/potree/src/lib/potree-node-source.ts
1
// loaders.gl
1✔
2
// SPDX-License-Identifier: MIT
1✔
3
// Copyright (c) vis.gl contributors
1✔
4

1✔
5
import type {PotreeSourceOptions} from '../potree-source';
1✔
6
import {load} from '@loaders.gl/core';
1✔
7
import {Mesh} from '@loaders.gl/schema';
1✔
8
import {DataSource, resolvePath} from '@loaders.gl/loader-utils';
1✔
9
import {LASLoader} from '@loaders.gl/las';
1✔
10
import {PotreeBoundingBox, PotreeMetadata} from '../types/potree-metadata';
1✔
11
import {POTreeNode} from '../parsers/parse-potree-hierarchy-chunk';
1✔
12
import {PotreeHierarchyChunkLoader} from '../potree-hierarchy-chunk-loader';
1✔
13
import {PotreeLoader} from '../potree-loader';
1✔
14
import {parseVersion} from '../utils/parse-version';
1✔
15
import {Proj4Projection} from '@math.gl/proj4';
1✔
16
import {LASMesh} from '@loaders.gl/las/src/lib/las-types';
1✔
17
import {createProjection} from '../utils/projection-utils';
1✔
18
import {getCartographicOriginFromBoundingBox} from '../utils/bounding-box-utils';
1✔
19

1✔
20
// https://github.com/visgl/deck.gl/blob/9548f43cba2234a1f4877b6b17f6c88eb35b2e08/modules/core/src/lib/constants.js#L27
1✔
21
// Describes the format of positions
1✔
22
export enum COORDINATE_SYSTEM {
1!
23
  /**
1✔
24
   * `LNGLAT` if rendering into a geospatial viewport, `CARTESIAN` otherwise
1✔
25
   */
1✔
26
  DEFAULT = -1,
1✔
27
  /**
1✔
28
   * Positions are interpreted as [lng, lat, elevation]
1✔
29
   * lng lat are degrees, elevation is meters. distances as meters.
1✔
30
   */
1✔
31
  LNGLAT = 1,
1✔
32
  /**
1✔
33
   * Positions are interpreted as meter offsets, distances as meters
1✔
34
   */
1✔
35
  METER_OFFSETS = 2,
1✔
36
  /**
1✔
37
   * Positions are interpreted as lng lat offsets: [deltaLng, deltaLat, elevation]
1✔
38
   * deltaLng, deltaLat are delta degrees, elevation is meters.
1✔
39
   * distances as meters.
1✔
40
   */
1✔
41
  LNGLAT_OFFSETS = 3,
1✔
42
  /**
1✔
43
   * Non-geospatial
1✔
44
   */
1✔
45
  CARTESIAN = 0
1✔
46
}
1✔
47

1✔
48
export interface PotreeNodeMesh extends LASMesh {
1✔
49
  cartographicOrigin: number[];
1✔
50
  coordinateSystem: number;
1✔
51
}
1✔
52

1✔
53
/**
1✔
54
 * A Potree data source
1✔
55
 * @version 1.0 - @see https://github.com/potree/potree/blob/1.0RC/docs/file_format.md
1✔
56
 * @version 1.7 - @see https://github.com/potree/potree/blob/1.7/docs/potree-file-format.md
1✔
57
 * @note Point cloud nodes tile source
1✔
58
 */
1✔
59
export class PotreeNodesSource extends DataSource<string, PotreeSourceOptions> {
1✔
60
  /** Dataset base URL */
1✔
61
  baseUrl: string = '';
1!
UNCOV
62
  /** Meta information from `cloud.js` */
×
UNCOV
63
  metadata: PotreeMetadata | null = null;
×
UNCOV
64
  /** Root node */
×
UNCOV
65
  root: POTreeNode | null = null;
×
UNCOV
66
  /** Is data source ready to use after initial loading */
×
UNCOV
67
  isReady = false;
×
UNCOV
68
  /** local CRS to WGS84 projection */
×
UNCOV
69
  projection: Proj4Projection | null = null;
×
UNCOV
70
  /** The data set minimum bounding box */
×
UNCOV
71
  boundingBox?: PotreeBoundingBox;
×
UNCOV
72

×
UNCOV
73
  private initPromise: Promise<void> | null = null;
×
74

1✔
75
  /**
1✔
76
   * @constructor
1✔
77
   * @param data  - if string - data set path url or path to `cloud.js` metadata file
1✔
78
   *              - if Blob - single file data
1✔
79
   * @param options - data source properties
1✔
80
   */
1✔
81
  constructor(data: string, options: PotreeSourceOptions) {
1✔
UNCOV
82
    super(data, options);
×
UNCOV
83
    this.makeBaseUrl(this.data);
×
UNCOV
84

×
UNCOV
85
    this.initPromise = this.init();
×
UNCOV
86
  }
×
87

1✔
88
  /** Initial data source loading */
1✔
89
  async init() {
1✔
UNCOV
90
    if (this.initPromise) {
×
UNCOV
91
      await this.initPromise;
×
UNCOV
92
      return;
×
UNCOV
93
    }
×
UNCOV
94
    this.metadata = await load(`${this.baseUrl}/cloud.js`, PotreeLoader);
×
UNCOV
95
    this.projection = createProjection(this.metadata?.projection);
×
UNCOV
96
    this.parseBoundingVolume();
×
UNCOV
97

×
UNCOV
98
    await this.loadHierarchy();
×
UNCOV
99
    this.isReady = true;
×
UNCOV
100
  }
×
101

1✔
102
  /** Is data set supported */
1✔
103
  isSupported(): boolean {
1✔
UNCOV
104
    const {minor, major} = parseVersion(this.metadata?.version ?? '');
×
UNCOV
105
    return (
×
UNCOV
106
      this.isReady &&
×
UNCOV
107
      major === 1 &&
×
UNCOV
108
      minor <= 8 &&
×
UNCOV
109
      typeof this.metadata?.pointAttributes === 'string' &&
×
110
      ['LAS', 'LAZ'].includes(this.metadata?.pointAttributes)
×
UNCOV
111
    );
×
UNCOV
112
  }
×
113

1✔
114
  /** Get content files extension */
1✔
115
  getContentExtension(): string | null {
1✔
116
    if (!this.isReady) {
×
117
      return null;
×
118
    }
×
119
    switch (this.metadata?.pointAttributes) {
×
120
      case 'LAS':
×
121
        return 'las';
×
122
      case 'LAZ':
×
123
        return 'laz';
×
124
      default:
×
125
        return 'bin';
×
126
    }
×
127
  }
×
128

1✔
129
  /**
1✔
130
   * Load octree node content
1✔
131
   * @param nodeName name of a node, string of numbers in range 0..7
1✔
132
   * @return node content geometry or null if the node doesn't exist
1✔
133
   */
1✔
134
  async loadNodeContent(nodeName: string): Promise<Mesh | null> {
1✔
135
    await this.initPromise;
×
136

×
137
    if (!this.isSupported()) {
×
138
      return null;
×
139
    }
×
140

×
141
    const isAvailable = await this.isNodeAvailable(nodeName);
×
142
    if (isAvailable) {
×
143
      const result: PotreeNodeMesh = (await load(
×
144
        `${this.baseUrl}/${this.metadata?.octreeDir}/r/r${nodeName}.${this.getContentExtension()}`,
×
145
        LASLoader
×
146
      )) as PotreeNodeMesh;
×
147

×
148
      if (result) {
×
149
        result.cartographicOrigin = getCartographicOriginFromBoundingBox(
×
150
          this.projection,
×
151
          result.header?.boundingBox
×
152
        );
×
153
        const position = result.attributes.POSITION.value as Float32Array;
×
154
        for (let i = 0; i < (result.header?.vertexCount ?? 0); i++) {
×
155
          let vertex: Float32Array | number[] = position.slice(i * 3, i * 3 + 2);
×
156
          if (this.projection) {
×
157
            vertex = this.projection.project(Array.from(vertex));
×
158
          }
×
159

×
160
          const offsets = [
×
161
            vertex[0] - result.cartographicOrigin[0],
×
162
            vertex[1] - result.cartographicOrigin[1],
×
163
            position[i * 3 + 2] - result.cartographicOrigin[2]
×
164
          ];
×
165
          position.set(offsets, i * 3);
×
166
        }
×
167
        result.attributes.positions = result.attributes.POSITION;
×
168
        result.attributes.colors = result.attributes.COLOR_0;
×
169
        result.attributes.normals = result.attributes.NORMAL;
×
170

×
171
        result.coordinateSystem = COORDINATE_SYSTEM.LNGLAT_OFFSETS;
×
172
        return result;
×
173
      }
×
174
    }
×
175
    return null;
×
176
  }
×
177

1✔
178
  /**
1✔
179
   * Check if a node exists in the octree
1✔
180
   * @param nodeName name of a node, string of numbers in range 0..7
1✔
181
   * @returns true - the node does exist, false - the nodes doesn't exist
1✔
182
   */
1✔
183
  async isNodeAvailable(nodeName: string): Promise<boolean> {
1✔
184
    if (this.metadata?.hierarchy) {
×
185
      return this.metadata.hierarchy.findIndex((item) => item[0] === `r${nodeName}`) !== -1;
×
186
    }
×
187

×
188
    if (!this.root) {
×
189
      return false;
×
190
    }
×
191
    let currentParent = this.root;
×
192
    let name = '';
×
193
    let result = true;
×
194
    for (const char of nodeName) {
×
195
      const newName = `${name}${char}`;
×
196
      const node = currentParent.children.find((child) => child.name === newName);
×
197
      if (node) {
×
198
        currentParent = node;
×
199
        name = newName;
×
200
      } else {
×
201
        result = false;
×
202
        break;
×
203
      }
×
204
    }
×
205
    return result;
×
206
  }
×
207

1✔
208
  /**
1✔
209
   * Load data source hierarchy into tree of available nodes
1✔
210
   */
1✔
211
  private async loadHierarchy(): Promise<void> {
1✔
UNCOV
212
    this.root = await load(
×
UNCOV
213
      `${this.baseUrl}/${this.metadata?.octreeDir}/r/r.hrc`,
×
UNCOV
214
      PotreeHierarchyChunkLoader
×
UNCOV
215
    );
×
UNCOV
216
  }
×
217

1✔
218
  /**
1✔
219
   * Deduce base url from the input url sring
1✔
220
   * @param data - data source input data
1✔
221
   */
1✔
222
  private makeBaseUrl(data: string | Blob): void {
1✔
UNCOV
223
    this.baseUrl = typeof data === 'string' ? resolvePath(data) : '';
×
UNCOV
224
    if (this.baseUrl.endsWith('cloud.js')) {
×
225
      this.baseUrl = this.baseUrl.substring(0, -8);
×
226
    }
×
UNCOV
227
    if (this.baseUrl.endsWith('/')) {
×
228
      this.baseUrl = this.baseUrl.substring(0, -1);
×
229
    }
×
UNCOV
230
  }
×
231

1✔
232
  private parseBoundingVolume(): void {
1✔
UNCOV
233
    if (this.metadata?.projection && this.metadata.tightBoundingBox) {
×
234
      const projection = new Proj4Projection({
×
235
        from: this.metadata.projection,
×
236
        to: 'WGS84'
×
237
      });
×
238

×
239
      const {lx, ly, ux, uy} = this.metadata.tightBoundingBox;
×
240
      const lCoord = [lx, ly];
×
241
      const wgs84LCood = projection.project(lCoord);
×
242

×
243
      const uCoord = [ux, uy];
×
244
      const wgs84UCood = projection.project(uCoord);
×
245

×
246
      this.boundingBox = {
×
247
        ...this.metadata.tightBoundingBox,
×
248
        lx: wgs84LCood[0],
×
249
        ly: wgs84LCood[1],
×
250
        ux: wgs84UCood[0],
×
251
        uy: wgs84UCood[1]
×
252
      };
×
UNCOV
253
    } else {
×
UNCOV
254
      this.boundingBox = this.metadata?.tightBoundingBox;
×
UNCOV
255
    }
×
UNCOV
256
  }
×
257
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc