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

iTowns / itowns / 20437873702

22 Dec 2025 04:30PM UTC coverage: 88.107% (+0.2%) from 87.934%
20437873702

push

github

ftoromanoff
refactor(PointCloud): factorize load method

2771 of 3575 branches covered (77.51%)

Branch coverage included in aggregate %.

56 of 56 new or added lines in 10 files covered. (100.0%)

1 existing line in 1 file now uncovered.

28262 of 31647 relevant lines covered (89.3%)

1242.19 hits per line

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

95.96
/packages/Main/src/Parser/LASParser.js
1
import * as THREE from 'three';
1✔
2
import { spawn, Thread, Transfer } from 'threads';
1✔
3
import proj4 from 'proj4';
1✔
4

1✔
5
let _lazPerf;
1✔
6
let _thread;
1✔
7

1✔
8
function workerInstance() {
4✔
9
    return new Worker(
4✔
10
        /* webpackChunkName: "itowns_lasworker" */
4✔
11
        new URL('../Worker/LASLoaderWorker.js', import.meta.url),
4✔
12
        { type: 'module' },
4✔
13
    );
4✔
14
}
4✔
15

1✔
16
async function loader() {
4✔
17
    if (_thread) { return _thread; }
4!
18
    _thread = await spawn(workerInstance());
4✔
19
    if (_lazPerf) { _thread.lazPerf(_lazPerf); }
4✔
20
    return _thread;
4✔
21
}
4✔
22

1✔
23
function buildBufferGeometry(attributes) {
3✔
24
    const geometry = new THREE.BufferGeometry();
3✔
25

3✔
26
    const positionBuffer = new THREE.BufferAttribute(attributes.position, 3);
3✔
27
    geometry.setAttribute('position', positionBuffer);
3✔
28

3✔
29
    const intensityBuffer = new THREE.BufferAttribute(attributes.intensity, 1);
3✔
30
    geometry.setAttribute('intensity', intensityBuffer);
3✔
31

3✔
32
    const returnNumber = new THREE.BufferAttribute(attributes.returnNumber, 1);
3✔
33
    geometry.setAttribute('returnNumber', returnNumber);
3✔
34

3✔
35
    const numberOfReturns = new THREE.BufferAttribute(attributes.numberOfReturns, 1);
3✔
36
    geometry.setAttribute('numberOfReturns', numberOfReturns);
3✔
37

3✔
38
    const classBuffer = new THREE.BufferAttribute(attributes.classification, 1);
3✔
39
    geometry.setAttribute('classification', classBuffer);
3✔
40

3✔
41
    const pointSourceID = new THREE.BufferAttribute(attributes.pointSourceID, 1);
3✔
42
    geometry.setAttribute('pointSourceID', pointSourceID);
3✔
43

3✔
44
    if (attributes.color) {
3✔
45
        const colorBuffer = new THREE.BufferAttribute(attributes.color, 4, true);
2✔
46
        geometry.setAttribute('color', colorBuffer);
2✔
47
    }
2✔
48
    const scanAngle = new THREE.BufferAttribute(attributes.scanAngle, 1);
3✔
49
    geometry.setAttribute('scanAngle', scanAngle);
3✔
50

3✔
51
    return geometry;
3✔
52
}
3✔
53

1✔
54
/** The LASParser module provides a [parse]{@link
1✔
55
 * module:LASParser.parse} method that takes a LAS or LAZ (LASZip) file in, and
1✔
56
 * gives a `THREE.BufferGeometry` containing all the necessary attributes to be
1✔
57
 * displayed in iTowns. It uses the
1✔
58
 * [copc.js](https://github.com/connormanning/copc.js/) library.
1✔
59
 *
1✔
60
 * @module LASParser
1✔
61
 */
1✔
62
export default {
1✔
63
    /*
1✔
64
     * Set the laz-perf decoder path.
1✔
65
     * @param {string} path - path to `laz-perf.wasm` folder.
1✔
66
     */
1✔
67
    enableLazPerf(path) {
1✔
68
        if (!path) {
2!
69
            throw new Error('Path to laz-perf is mandatory');
×
70
        }
×
71
        _lazPerf = path;
2✔
72
    },
1✔
73

1✔
74
    /**
1✔
75
     * Terminate all worker instances.
1✔
76
     * @returns {Promise<void>}
1✔
77
     */
1✔
78
    terminate() {
1✔
79
        const currentThread = _thread;
4✔
80
        _thread = undefined;
4✔
81
        return Thread.terminate(currentThread);
4✔
82
    },
1✔
83

1✔
84
    /**
1✔
85
     * Parses a chunk of a LAS or LAZ (LASZip) and returns the corresponding
1✔
86
     * `THREE.BufferGeometry`.
1✔
87
     *
1✔
88
     * @param {ArrayBuffer} data - The file content to parse.
1✔
89
     * @param {Object} options
1✔
90
     * @param {Object} options.in - Options to give to the parser.
1✔
91
     * @param {number} options.in.numPoints - Number of points encoded in this
1✔
92
     * data chunk.
1✔
93
     * @param {Object} options.in.source - Source information.
1✔
94
     * @param {Object} options.in.source.header - Partial LAS file header.
1✔
95
     * @param {number} options.in.source.header.pointDataRecordFormat - Type of
1✔
96
     * Point Data Record contained in the LAS file.
1✔
97
     * @param {number} options.in.source.header.pointDataRecordLength - Size
1✔
98
     * (in bytes) of the Point Data Record.
1✔
99
     * @param {Object} [options.in.source.eb] - Extra bytes LAS VLRs headers.
1✔
100
     * @param { 8 | 16 } [options.in.colorDepth] - Color depth (in bits).
1✔
101
     * Defaults to 8 bits for LAS 1.2 and 16 bits for later versions
1✔
102
     * (as mandatory by the specification)
1✔
103
     *
1✔
104
     * @return {Promise<THREE.BufferGeometry>} A promise resolving with a
1✔
105
     * `THREE.BufferGeometry`.
1✔
106
     */
1✔
107
    async parseChunk(data, options = {}) {
1✔
108
        const lasLoader = await loader();
1✔
109
        const source = options.in.source;
1✔
110
        const origin = options.in.origin;
1✔
111
        const quaternion = options.in.rotation;
1✔
112
        const parsedData = await lasLoader.parseChunk(Transfer(data), {
1✔
113
            pointCount: options.in.numPoints,
1✔
114
            header: source.header,
1✔
115
            eb: source.eb,
1✔
116
            colorDepth: source.colorDepth,
1✔
117
            in: {
1✔
118
                crs: source.crs,
1✔
119
                projDefs: proj4.defs(source.crs),
1✔
120
            },
1✔
121
            out: {
1✔
122
                crs: options.in.crs,
1✔
123
                projDefs: proj4.defs(options.in.crs),
1✔
124
                origin: origin.toArray(),
1✔
125
                rotation: quaternion.toArray(),
1✔
126
            },
1✔
127
        });
1✔
128

1✔
129
        const geometry = buildBufferGeometry(parsedData.attributes);
1✔
130
        geometry.boundingBox = new THREE.Box3().setFromArray(parsedData.attributes.bbox);
1✔
131
        geometry.userData.origin = origin;
1✔
132
        geometry.userData.rotation = quaternion;
1✔
133
        return geometry;
1✔
134
    },
1✔
135

1✔
136
    /**
1✔
137
     * Parses a LAS file or a LAZ (LASZip) file and return the corresponding
1✔
138
     * `THREE.BufferGeometry`.
1✔
139
     *
1✔
140
     * @param {ArrayBuffer} data - The file content to parse.
1✔
141
     * @param {Object} [options]
1✔
142
     * @param {Object} [options.in] - Options to give to the parser.
1✔
143
     * @param {String} options.in.crs - Crs of the source.
1✔
144
     * @param { 8 | 16 } [options.in.colorDepth] - Color depth (in bits).
1✔
145
     * Defaults to 8 bits for LAS 1.2 and 16 bits for later versions
1✔
146
     * (as mandatory by the specification)
1✔
147
     * @param {String} options.out.crs - Crs of the view.
1✔
148
     * @param {String} options.out.origin - The coordinate of the local origin
1✔
149
     * in the world referentiel.
1✔
150
     * @param {String} options.out.rotation - Rotation to go from the local referetiel
1✔
151
     * to a geocentrique one (in appliable).
1✔
152
     *
1✔
153
     * @return {Promise} A promise resolving with a `THREE.BufferGeometry`. The
1✔
154
     * header of the file is contained in `userData`.
1✔
155
     */
1✔
156
    async parse(data, options = {}) {
1✔
157
        if (options.out?.skip) {
3!
158
            console.warn("Warning: options 'skip' not supported anymore");
×
UNCOV
159
        }
×
160

3✔
161
        const lasLoader = await loader();
3✔
162
        const source = options.in.source;
3✔
163
        const origin = options.in.origin;
3✔
164
        const quaternion = options.in.rotation;
3✔
165
        const parsedData = await lasLoader.parseFile(Transfer(data), {
3✔
166
            colorDepth: source.colorDepth,
3✔
167
            in: {
3✔
168
                crs: source.crs,
3✔
169
                projDefs: proj4.defs(source.crs),
3✔
170
            },
3✔
171
            out: {
3✔
172
                crs: options.in.crs,
3✔
173
                projDefs: proj4.defs(options.in.crs),
3✔
174
                origin: origin.toArray(),
3✔
175
                rotation: quaternion.toArray(),
3✔
176
            },
3✔
177
        });
3✔
178

2✔
179
        const geometry = buildBufferGeometry(parsedData.attributes);
2✔
180
        geometry.boundingBox = new THREE.Box3().setFromArray(parsedData.attributes.bbox);
2✔
181
        geometry.userData.header = parsedData.header;
2✔
182

2✔
183
        return geometry;
2✔
184
    },
2✔
185
};
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