QGIS API Documentation 3.99.0-Master (357b655ed83)
Loading...
Searching...
No Matches
qgslazdecoder.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslazdecoder.cpp
3 --------------------
4 begin : March 2022
5 copyright : (C) 2022 by Belgacem Nedjima
6 email : belgacem dot nedjima at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsconfig.h"
19#include "qgslazdecoder.h"
20
21#include <cstring>
22#include <iostream>
23#include <memory>
24#include <string>
25#include <zstd.h>
26
27#include "lazperf/las.hpp"
28#include "lazperf/readers.hpp"
29#include "qgslazinfo.h"
30#include "qgslogger.h"
32#include "qgspointcloudexpression.h"
33#include "qgsvector3d.h"
34
35#include <QDir>
36#include <QElapsedTimer>
37#include <QFile>
38#include <QString>
39#include <QTemporaryFile>
40
41using namespace Qt::StringLiterals;
42
43#if defined(_MSC_VER)
44#ifndef UNICODE
45#define UNICODE
46#endif
47#include <locale>
48#include <codecvt>
49#endif
50
52
53template <typename T>
54bool lazStoreToStream_( char *s, size_t position, QgsPointCloudAttribute::DataType type, T value )
55{
56 switch ( type )
57 {
59 {
60 const char val = char( value );
61 s[position] = val;
62 break;
63 }
65 {
66 const unsigned char val = ( unsigned char )( value );
67 s[position] = val;
68 break;
69 }
70
72 {
73 short val = short( value );
74 memcpy( s + position, reinterpret_cast<char * >( &val ), sizeof( short ) );
75 break;
76 }
78 {
79 unsigned short val = static_cast< unsigned short>( value );
80 memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( unsigned short ) );
81 break;
82 }
83
85 {
86 qint32 val = qint32( value );
87 memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( qint32 ) );
88 break;
89 }
91 {
92 quint32 val = quint32( value );
93 memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( quint32 ) );
94 break;
95 }
96
98 {
99 qint64 val = qint64( value );
100 memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( qint64 ) );
101 break;
102 }
104 {
105 quint64 val = quint64( value );
106 memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( quint64 ) );
107 break;
108 }
109
111 {
112 float val = float( value );
113 memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( float ) );
114 break;
115 }
117 {
118 double val = double( value );
119 memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( double ) );
120 break;
121 }
122 }
123
124 return true;
125}
126
127bool lazSerialize_( char *data, size_t outputPosition, QgsPointCloudAttribute::DataType outputType,
128 const char *input, QgsPointCloudAttribute::DataType inputType, int inputSize, size_t inputPosition )
129{
130 if ( outputType == inputType )
131 {
132 memcpy( data + outputPosition, input + inputPosition, inputSize );
133 return true;
134 }
135
136 switch ( inputType )
137 {
139 {
140 const char val = *( input + inputPosition );
141 return lazStoreToStream_<char>( data, outputPosition, outputType, val );
142 }
144 {
145 const unsigned char val = *( input + inputPosition );
146 return lazStoreToStream_<unsigned char>( data, outputPosition, outputType, val );
147 }
149 {
150 const short val = *reinterpret_cast< const short * >( input + inputPosition );
151 return lazStoreToStream_<short>( data, outputPosition, outputType, val );
152 }
154 {
155 const unsigned short val = *reinterpret_cast< const unsigned short * >( input + inputPosition );
156 return lazStoreToStream_<unsigned short>( data, outputPosition, outputType, val );
157 }
159 {
160 const qint32 val = *reinterpret_cast<const qint32 * >( input + inputPosition );
161 return lazStoreToStream_<qint32>( data, outputPosition, outputType, val );
162 }
164 {
165 const quint32 val = *reinterpret_cast<const quint32 * >( input + inputPosition );
166 return lazStoreToStream_<quint32>( data, outputPosition, outputType, val );
167 }
169 {
170 const qint64 val = *reinterpret_cast<const qint64 * >( input + inputPosition );
171 return lazStoreToStream_<qint64>( data, outputPosition, outputType, val );
172 }
174 {
175 const quint64 val = *reinterpret_cast<const quint64 * >( input + inputPosition );
176 return lazStoreToStream_<quint64>( data, outputPosition, outputType, val );
177 }
179 {
180 const float val = *reinterpret_cast< const float * >( input + inputPosition );
181 return lazStoreToStream_<float>( data, outputPosition, outputType, val );
182 }
184 {
185 const double val = *reinterpret_cast< const double * >( input + inputPosition );
186 return lazStoreToStream_<double>( data, outputPosition, outputType, val );
187 }
188 }
189 return true;
190}
191
192// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
193
194std::vector< QgsLazDecoder::RequestedAttributeDetails > prepareRequestedAttributeDetails_( const QgsPointCloudAttributeCollection &requestedAttributes, QVector<QgsLazInfo::ExtraBytesAttributeDetails> &extrabytesAttr )
195{
196 const QVector<QgsPointCloudAttribute> requestedAttributesVector = requestedAttributes.attributes();
197
198 std::vector< QgsLazDecoder::RequestedAttributeDetails > requestedAttributeDetails;
199 requestedAttributeDetails.reserve( requestedAttributesVector.size() );
200
201 for ( const QgsPointCloudAttribute &requestedAttribute : requestedAttributesVector )
202 {
203 if ( requestedAttribute.name().compare( 'X'_L1, Qt::CaseInsensitive ) == 0 )
204 {
205 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::X, requestedAttribute.type(), requestedAttribute.size() ) );
206 }
207 else if ( requestedAttribute.name().compare( 'Y'_L1, Qt::CaseInsensitive ) == 0 )
208 {
209 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Y, requestedAttribute.type(), requestedAttribute.size() ) );
210 }
211 else if ( requestedAttribute.name().compare( 'Z'_L1, Qt::CaseInsensitive ) == 0 )
212 {
213 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Z, requestedAttribute.type(), requestedAttribute.size() ) );
214 }
215 else if ( requestedAttribute.name().compare( "Classification"_L1, Qt::CaseInsensitive ) == 0 )
216 {
217 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Classification, requestedAttribute.type(), requestedAttribute.size() ) );
218 }
219 else if ( requestedAttribute.name().compare( "Intensity"_L1, Qt::CaseInsensitive ) == 0 )
220 {
221 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Intensity, requestedAttribute.type(), requestedAttribute.size() ) );
222 }
223 else if ( requestedAttribute.name().compare( "ReturnNumber"_L1, Qt::CaseInsensitive ) == 0 )
224 {
225 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ReturnNumber, requestedAttribute.type(), requestedAttribute.size() ) );
226 }
227 else if ( requestedAttribute.name().compare( "NumberOfReturns"_L1, Qt::CaseInsensitive ) == 0 )
228 {
229 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::NumberOfReturns, requestedAttribute.type(), requestedAttribute.size() ) );
230 }
231 else if ( requestedAttribute.name().compare( "ScanDirectionFlag"_L1, Qt::CaseInsensitive ) == 0 )
232 {
233 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ScanDirectionFlag, requestedAttribute.type(), requestedAttribute.size() ) );
234 }
235 else if ( requestedAttribute.name().compare( "EdgeOfFlightLine"_L1, Qt::CaseInsensitive ) == 0 )
236 {
237 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::EdgeOfFlightLine, requestedAttribute.type(), requestedAttribute.size() ) );
238 }
239 else if ( requestedAttribute.name().compare( "ScanAngleRank"_L1, Qt::CaseInsensitive ) == 0 )
240 {
241 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ScanAngleRank, requestedAttribute.type(), requestedAttribute.size() ) );
242 }
243 else if ( requestedAttribute.name().compare( "UserData"_L1, Qt::CaseInsensitive ) == 0 )
244 {
245 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::UserData, requestedAttribute.type(), requestedAttribute.size() ) );
246 }
247 else if ( requestedAttribute.name().compare( "PointSourceId"_L1, Qt::CaseInsensitive ) == 0 )
248 {
249 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::PointSourceId, requestedAttribute.type(), requestedAttribute.size() ) );
250 }
251 else if ( requestedAttribute.name().compare( "GpsTime"_L1, Qt::CaseInsensitive ) == 0 )
252 {
253 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::GpsTime, requestedAttribute.type(), requestedAttribute.size() ) );
254 }
255 else if ( requestedAttribute.name().compare( "Red"_L1, Qt::CaseInsensitive ) == 0 )
256 {
257 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Red, requestedAttribute.type(), requestedAttribute.size() ) );
258 }
259 else if ( requestedAttribute.name().compare( "Green"_L1, Qt::CaseInsensitive ) == 0 )
260 {
261 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Green, requestedAttribute.type(), requestedAttribute.size() ) );
262 }
263 else if ( requestedAttribute.name().compare( "Blue"_L1, Qt::CaseInsensitive ) == 0 )
264 {
265 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Blue, requestedAttribute.type(), requestedAttribute.size() ) );
266 }
267 else if ( requestedAttribute.name().compare( "ScannerChannel"_L1, Qt::CaseInsensitive ) == 0 )
268 {
269 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ScannerChannel, requestedAttribute.type(), requestedAttribute.size() ) );
270 }
271 else if ( requestedAttribute.name().compare( "Synthetic"_L1, Qt::CaseInsensitive ) == 0 )
272 {
273 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Synthetic, requestedAttribute.type(), requestedAttribute.size() ) );
274 }
275 else if ( requestedAttribute.name().compare( "KeyPoint"_L1, Qt::CaseInsensitive ) == 0 )
276 {
277 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::KeyPoint, requestedAttribute.type(), requestedAttribute.size() ) );
278 }
279 else if ( requestedAttribute.name().compare( "Withheld"_L1, Qt::CaseInsensitive ) == 0 )
280 {
281 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Withheld, requestedAttribute.type(), requestedAttribute.size() ) );
282 }
283 else if ( requestedAttribute.name().compare( "Overlap"_L1, Qt::CaseInsensitive ) == 0 )
284 {
285 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Overlap, requestedAttribute.type(), requestedAttribute.size() ) );
286 }
287 else if ( requestedAttribute.name().compare( "Infrared"_L1, Qt::CaseInsensitive ) == 0 )
288 {
289 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::NIR, requestedAttribute.type(), requestedAttribute.size() ) );
290 }
291 else
292 {
293 bool foundAttr = false;
294 for ( QgsLazInfo::ExtraBytesAttributeDetails &eba : extrabytesAttr )
295 {
296 if ( requestedAttribute.name().compare( eba.attribute.trimmed() ) == 0 )
297 {
298 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ExtraBytes, eba.type, eba.size, eba.offset ) );
299 foundAttr = true;
300 break;
301 }
302 }
303 if ( !foundAttr )
304 {
305 // this can possibly happen -- e.g. if a style built using a different point cloud format references an attribute which isn't available from the laz file
306 requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::MissingOrUnknown, requestedAttribute.type(), requestedAttribute.size() ) );
307 }
308 }
309 }
310 return requestedAttributeDetails;
311}
312
313bool decodePoint( char *buf, int lasPointFormat, char *dataBuffer, std::size_t &outputOffset, std::vector< QgsLazDecoder::RequestedAttributeDetails > &requestedAttributeDetails )
314{
315 lazperf::las::point10 p10;
316 lazperf::las::gpstime gps;
317 lazperf::las::rgb rgb;
318 lazperf::las::nir14 nir;
319 lazperf::las::point14 p14;
320
321 // Does the point record start with the common fields for formats introduced
322 // in the LAS 1.4 spec?
323 const bool isLas14 = ( lasPointFormat == 6 || lasPointFormat == 7 || lasPointFormat == 8 || lasPointFormat == 9 || lasPointFormat == 10 );
324
325 switch ( lasPointFormat )
326 {
327 // LAS 1.2 file support
328 case 0: // base
329 p10.unpack( buf );
330 break;
331 case 1: // base + gps time
332 p10.unpack( buf );
333 gps.unpack( buf + sizeof( lazperf::las::point10 ) );
334 break;
335 case 2: // base + rgb
336 p10.unpack( buf );
337 rgb.unpack( buf + sizeof( lazperf::las::point10 ) );
338 break;
339 case 3: // base + gps time + rgb
340 p10.unpack( buf );
341 gps.unpack( buf + sizeof( lazperf::las::point10 ) );
342 rgb.unpack( buf + sizeof( lazperf::las::point10 ) + sizeof( lazperf::las::gpstime ) );
343 break;
344
345 // LAS 1.4 file support
346 case 6: // base (includes gps time)
347 p14.unpack( buf );
348 break;
349 case 7: // base + rgb
350 p14.unpack( buf );
351 rgb.unpack( buf + sizeof( lazperf::las::point14 ) );
352 break;
353 case 8: // base + rgb + nir
354 p14.unpack( buf );
355 rgb.unpack( buf + sizeof( lazperf::las::point14 ) );
356 nir.unpack( buf + sizeof( lazperf::las::point14 ) + sizeof( lazperf::las::rgb ) );
357 break;
358
359 default:
360 Q_ASSERT( false ); // must not happen - we checked earlier that the format is supported
361 return false;
362 }
363
364 for ( const QgsLazDecoder::RequestedAttributeDetails &requestedAttribute : requestedAttributeDetails )
365 {
366 switch ( requestedAttribute.attribute )
367 {
368 case QgsLazDecoder::LazAttribute::X:
369 lazStoreToStream_<qint32>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.x() : p10.x );
370 break;
371 case QgsLazDecoder::LazAttribute::Y:
372 lazStoreToStream_<qint32>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.y() : p10.y );
373 break;
374 case QgsLazDecoder::LazAttribute::Z:
375 lazStoreToStream_<qint32>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.z() : p10.z );
376 break;
377 case QgsLazDecoder::LazAttribute::Classification:
378 {
379 if ( isLas14 )
380 {
381 lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p14.classification() );
382 }
383 else
384 {
385 // p10 format encoded "Overlap" as Classification=12, so in that case we set Classification=0 (Never classified) and will set Overlap=1 a few lines below
386 lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, ( p10.classification & 0x1F ) == 12 ? 0 : p10.classification & 0x1F );
387 }
388 break;
389 }
390 case QgsLazDecoder::LazAttribute::Intensity:
391 lazStoreToStream_<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.intensity() : p10.intensity );
392 break;
393 case QgsLazDecoder::LazAttribute::ReturnNumber:
394 lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.returnNum() : p10.return_number );
395 break;
396 case QgsLazDecoder::LazAttribute::NumberOfReturns:
397 lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.numReturns() : p10.number_of_returns_of_given_pulse );
398 break;
399 case QgsLazDecoder::LazAttribute::ScanDirectionFlag:
400 lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.scanDirFlag() : p10.scan_direction_flag );
401 break;
402 case QgsLazDecoder::LazAttribute::EdgeOfFlightLine:
403 lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.eofFlag() : p10.edge_of_flight_line );
404 break;
405 case QgsLazDecoder::LazAttribute::ScanAngleRank:
406 lazStoreToStream_<float>( dataBuffer, outputOffset, requestedAttribute.type,
407 isLas14
408 // Formats from LAS 1.4 spec store the angle in 0.006 degree increments
409 ? p14.scanAngle() * 0.006f
410 // Older formats store integer values
411 : p10.scan_angle_rank );
412 break;
413 case QgsLazDecoder::LazAttribute::UserData:
414 lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.userData() : p10.user_data );
415 break;
416 case QgsLazDecoder::LazAttribute::PointSourceId:
417 lazStoreToStream_<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.pointSourceID() : p10.point_source_ID );
418 break;
419 case QgsLazDecoder::LazAttribute::GpsTime:
420 // lazperf internally stores gps value as int64 field, but in fact it is a double value
421 lazStoreToStream_<double>( dataBuffer, outputOffset, requestedAttribute.type,
422 isLas14 ? p14.gpsTime() : *reinterpret_cast<const double *>( reinterpret_cast<const void *>( &gps.value ) ) );
423 break;
424 case QgsLazDecoder::LazAttribute::Red:
425 lazStoreToStream_<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.r );
426 break;
427 case QgsLazDecoder::LazAttribute::Green:
428 lazStoreToStream_<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.g );
429 break;
430 case QgsLazDecoder::LazAttribute::Blue:
431 lazStoreToStream_<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.b );
432 break;
433 case QgsLazDecoder::LazAttribute::ScannerChannel:
434 lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? char( p14.scannerChannel() ) : 0 );
435 break;
436 case QgsLazDecoder::LazAttribute::Synthetic:
437 lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? char( ( p14.classFlags() >> 0 ) & 0x01 ) : char( ( p10.classification >> 5 ) & 0x01 ) );
438 break;
439 case QgsLazDecoder::LazAttribute::KeyPoint:
440 lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? char( ( p14.classFlags() >> 1 ) & 0x01 ) : char( ( p10.classification >> 6 ) & 0x01 ) );
441 break;
442 case QgsLazDecoder::LazAttribute::Withheld:
443 lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? char( ( p14.classFlags() >> 2 ) & 0x01 ) : char( ( p10.classification >> 7 ) & 0x01 ) );
444 break;
445 case QgsLazDecoder::LazAttribute::Overlap:
446 {
447 if ( isLas14 )
448 {
449 lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, char( ( p14.classFlags() >> 3 ) & 0x01 ) );
450 }
451 else
452 {
453 // p10 format encoded "Overlap" as Classification=12, so in that case we set Overlap=1 (we have already set Classification=0)
454 lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, ( p10.classification & 0x1F ) == 12 ? 1 : 0 );
455 }
456 break;
457 }
458 case QgsLazDecoder::LazAttribute::NIR:
459 {
460 if ( lasPointFormat == 8 || lasPointFormat == 10 )
461 {
462 lazStoreToStream_<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, nir.val );
463 }
464 else
465 {
466 // just store 0 for missing attributes
467 lazStoreToStream_<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, 0 );
468 }
469 break;
470
471 }
472 case QgsLazDecoder::LazAttribute::ExtraBytes:
473 {
474 switch ( requestedAttribute.type )
475 {
477 lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<char * >( &buf[requestedAttribute.offset] ) );
478 break;
480 lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<unsigned char * >( &buf[requestedAttribute.offset] ) );
481 break;
483 lazStoreToStream_<qint16>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint16 * >( &buf[requestedAttribute.offset] ) );
484 break;
486 lazStoreToStream_<quint16>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint16 * >( &buf[requestedAttribute.offset] ) );
487 break;
489 lazStoreToStream_<qint32>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint32 * >( &buf[requestedAttribute.offset] ) );
490 break;
492 lazStoreToStream_<quint32>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint32 * >( &buf[requestedAttribute.offset] ) );
493 break;
495 lazStoreToStream_<qint64>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint64 * >( &buf[requestedAttribute.offset] ) );
496 break;
498 lazStoreToStream_<quint64>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint64 * >( &buf[requestedAttribute.offset] ) );
499 break;
501 lazStoreToStream_<float>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<float * >( &buf[requestedAttribute.offset] ) );
502 break;
504 lazStoreToStream_<double>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<double * >( &buf[requestedAttribute.offset] ) );
505 break;
506 }
507 }
508 break;
509 case QgsLazDecoder::LazAttribute::MissingOrUnknown:
510 // just store 0 for unknown/missing attributes
511 lazStoreToStream_<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, 0 );
512 break;
513 }
514
515 outputOffset += requestedAttribute.size;
516 }
517 return true;
518}
519
520template<typename FileType>
521std::unique_ptr<QgsPointCloudBlock> decompressLaz_( FileType &file, const QgsPointCloudAttributeCollection &requestedAttributes, QgsPointCloudExpression &filterExpression, QgsRectangle &filterRect )
522{
523 if ( ! file.good() )
524 return nullptr;
525
526#ifdef QGISDEBUG
527 QElapsedTimer t;
528 t.start();
529#endif
530
531 // lazperf may throw exceptions
532 try
533 {
534 lazperf::reader::generic_file f( file );
535
536
537 // output file formats from entwine/untwine:
538 // - older versions write LAZ 1.2 files with point formats 0, 1, 2 or 3
539 // - newer versions write LAZ 1.4 files with point formats 6, 7 or 8
540
541 int lasPointFormat = f.header().pointFormat();
542 if ( lasPointFormat != 0 && lasPointFormat != 1 && lasPointFormat != 2 && lasPointFormat != 3 &&
543 lasPointFormat != 6 && lasPointFormat != 7 && lasPointFormat != 8 )
544 {
545 QgsDebugError( u"Unexpected point format record (%1) - only 0, 1, 2, 3, 6, 7, 8 are supported"_s.arg( lasPointFormat ) );
546 return nullptr;
547 }
548
549 const size_t count = f.header().point_count;
550 const QgsVector3D scale( f.header().scale.x, f.header().scale.y, f.header().scale.z );
551 const QgsVector3D offset( f.header().offset.x, f.header().offset.y, f.header().offset.z );
552
553 QByteArray bufArray( f.header().point_record_length, 0 );
554 char *buf = bufArray.data();
555
556 const size_t requestedPointRecordSize = requestedAttributes.pointRecordSize();
557 QByteArray data;
558 data.resize( requestedPointRecordSize * count );
559 char *dataBuffer = data.data();
560
561 std::size_t outputOffset = 0;
562
563 auto block = std::make_unique< QgsPointCloudBlock >(
564 count,
565 requestedAttributes,
566 data, scale, offset
567 );
568
569 int skippedPoints = 0;
570 const bool filterIsValid = filterExpression.isValid();
571 if ( !filterExpression.prepare( block.get() ) && filterIsValid )
572 {
573 // skip processing if the expression cannot be prepared
574 block->setPointCount( 0 );
575 return block;
576 }
577
578 int xAttributeOffset, yAttributeOffset;
579 const QgsPointCloudAttribute *attributeX = nullptr;
580 const QgsPointCloudAttribute *attributeY = nullptr;
581 const bool hasFilterRect = !filterRect.isEmpty();
582 if ( hasFilterRect )
583 {
584 attributeX = requestedAttributes.find( "X"_L1, xAttributeOffset );
585 attributeY = requestedAttributes.find( "Y"_L1, yAttributeOffset );
586 filterRect.setXMinimum( ( filterRect.xMinimum() - offset.x() ) / scale.x() );
587 filterRect.setXMaximum( ( filterRect.xMaximum() - offset.x() ) / scale.x() );
588 filterRect.setYMinimum( ( filterRect.yMinimum() - offset.y() ) / scale.y() );
589 filterRect.setYMaximum( ( filterRect.yMaximum() - offset.y() ) / scale.y() );
590 }
591
592 std::vector<char> rawExtrabytes = f.vlrData( "LASF_Spec", 4 );
593 QVector<QgsLazInfo::ExtraBytesAttributeDetails> extrabyteAttributesDetails = QgsLazInfo::parseExtrabytes( rawExtrabytes.data(), rawExtrabytes.size(), f.header().point_record_length );
594 std::vector< QgsLazDecoder::RequestedAttributeDetails > requestedAttributeDetails = prepareRequestedAttributeDetails_( requestedAttributes, extrabyteAttributesDetails );
595
596 for ( size_t i = 0 ; i < count ; i ++ )
597 {
598 f.readPoint( buf ); // read the point out
599
600 bool skipThisPoint = !decodePoint( buf, lasPointFormat, dataBuffer, outputOffset, requestedAttributeDetails );
601
602 // check if point needs to be filtered out
603 if ( !skipThisPoint && hasFilterRect && attributeX && attributeY )
604 {
605 const double x = attributeX->convertValueToDouble( dataBuffer + outputOffset - requestedPointRecordSize + xAttributeOffset );
606 const double y = attributeY->convertValueToDouble( dataBuffer + outputOffset - requestedPointRecordSize + yAttributeOffset );
607 if ( !filterRect.contains( x, y ) )
608 skipThisPoint = true;
609 }
610 if ( !skipThisPoint && filterIsValid )
611 {
612 // we're always evaluating the last written point in the buffer
613 double eval = filterExpression.evaluate( i - skippedPoints );
614 if ( !eval || std::isnan( eval ) )
615 skipThisPoint = true;
616 }
617 if ( skipThisPoint )
618 {
619 // if the point is filtered out, rewind the offset so the next point is written over it
620 outputOffset -= requestedPointRecordSize;
621 ++skippedPoints;
622 }
623 }
624
625#ifdef QGISDEBUG
626 QgsDebugMsgLevel( u"LAZ-PERF Read through the points in %1 seconds."_s.arg( t.elapsed() / 1000. ), 2 );
627#endif
628 block->setPointCount( count - skippedPoints );
629 return block;
630 }
631 catch ( std::exception &e )
632 {
633 QgsDebugError( "Error decompressing laz file: " + QString::fromLatin1( e.what() ) );
634 return nullptr;
635 }
636}
637
638std::unique_ptr<QgsPointCloudBlock> QgsLazDecoder::decompressLaz( const QString &filename,
639 const QgsPointCloudAttributeCollection &requestedAttributes,
640 QgsPointCloudExpression &filterExpression, QgsRectangle &filterRect )
641{
642 std::ifstream file( toNativePath( filename ), std::ios::binary );
643
644 return decompressLaz_<std::ifstream>( file, requestedAttributes, filterExpression, filterRect );
645}
646
647std::unique_ptr<QgsPointCloudBlock> QgsLazDecoder::decompressLaz( const QByteArray &byteArrayData,
648 const QgsPointCloudAttributeCollection &requestedAttributes,
649 QgsPointCloudExpression &filterExpression, QgsRectangle &filterRect )
650{
651 std::istringstream file( byteArrayData.toStdString() );
652 return decompressLaz_<std::istringstream>( file, requestedAttributes, filterExpression, filterRect );
653}
654
655std::unique_ptr<QgsPointCloudBlock> QgsLazDecoder::decompressCopc( const QByteArray &data, QgsLazInfo &lazInfo, int32_t pointCount, const QgsPointCloudAttributeCollection &requestedAttributes, QgsPointCloudExpression &filterExpression, QgsRectangle &filterRect )
656{
657 // COPC only supports point formats 6, 7 and 8
658 int lasPointFormat = lazInfo.pointFormat();
659 if ( lasPointFormat != 6 && lasPointFormat != 7 && lasPointFormat != 8 )
660 {
661 QgsDebugError( u"Unexpected point format record (%1) - only 6, 7, 8 are supported for COPC format"_s.arg( lasPointFormat ) );
662 return nullptr;
663 }
664
665 std::unique_ptr<char []> decodedData( new char[ lazInfo.pointRecordLength() ] );
666
667 lazperf::reader::chunk_decompressor decompressor( lasPointFormat, lazInfo.extrabytesCount(), data.data() );
668
669 const size_t requestedPointRecordSize = requestedAttributes.pointRecordSize();
670 QByteArray blockData;
671 blockData.resize( requestedPointRecordSize * pointCount );
672 char *dataBuffer = blockData.data();
673
674 std::size_t outputOffset = 0;
675
676 QVector<QgsLazInfo::ExtraBytesAttributeDetails> extrabyteAttributesDetails = lazInfo.extrabytes();
677 std::vector< RequestedAttributeDetails > requestedAttributeDetails = prepareRequestedAttributeDetails_( requestedAttributes, extrabyteAttributesDetails );
678 auto block = std::make_unique< QgsPointCloudBlock >(
679 pointCount, requestedAttributes,
680 blockData, lazInfo.scale(), lazInfo.offset()
681 );
682
683 int skippedPoints = 0;
684 const bool filterIsValid = filterExpression.isValid();
685 if ( !filterExpression.prepare( block.get() ) && filterIsValid )
686 {
687 // skip processing if the expression cannot be prepared
688 block->setPointCount( 0 );
689 return block;
690 }
691
692 int xAttributeOffset, yAttributeOffset;
693 const QgsPointCloudAttribute *attributeX = nullptr;
694 const QgsPointCloudAttribute *attributeY = nullptr;
695 const bool hasFilterRect = !filterRect.isEmpty();
696 if ( hasFilterRect )
697 {
698 attributeX = requestedAttributes.find( "X"_L1, xAttributeOffset );
699 attributeY = requestedAttributes.find( "Y"_L1, yAttributeOffset );
700 filterRect.setXMinimum( ( filterRect.xMinimum() - lazInfo.offset().x() ) / lazInfo.scale().x() );
701 filterRect.setXMaximum( ( filterRect.xMaximum() - lazInfo.offset().x() ) / lazInfo.scale().x() );
702 filterRect.setYMinimum( ( filterRect.yMinimum() - lazInfo.offset().y() ) / lazInfo.scale().y() );
703 filterRect.setYMaximum( ( filterRect.yMaximum() - lazInfo.offset().y() ) / lazInfo.scale().y() );
704 }
705 for ( int i = 0 ; i < pointCount; ++i )
706 {
707 decompressor.decompress( decodedData.get() );
708 char *buf = decodedData.get();
709
710 bool skipThisPoint = !decodePoint( buf, lasPointFormat, dataBuffer, outputOffset, requestedAttributeDetails );
711
712 // check if point needs to be filtered out
713 if ( !skipThisPoint && hasFilterRect && attributeX && attributeY )
714 {
715 const double x = attributeX->convertValueToDouble( dataBuffer + outputOffset - requestedPointRecordSize + xAttributeOffset );
716 const double y = attributeY->convertValueToDouble( dataBuffer + outputOffset - requestedPointRecordSize + yAttributeOffset );
717 if ( !filterRect.contains( x, y ) )
718 skipThisPoint = true;
719 }
720 if ( !skipThisPoint && filterIsValid )
721 {
722 // we're always evaluating the last written point in the buffer
723 double eval = filterExpression.evaluate( i - skippedPoints );
724 if ( !eval || std::isnan( eval ) )
725 skipThisPoint = true;
726 }
727 if ( skipThisPoint )
728 {
729 // if the point is filtered out, rewind the offset so the next point is written over it
730 outputOffset -= requestedPointRecordSize;
731 ++skippedPoints;
732 }
733 }
734
735 block->setPointCount( pointCount - skippedPoints );
736 return block;
737}
738
739#if defined(_MSC_VER)
740std::wstring QgsLazDecoder::toNativePath( const QString &filename )
741{
742 std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > converter;
743 return converter.from_bytes( filename.toStdString() );
744}
745#else
746std::string QgsLazDecoder::toNativePath( const QString &filename )
747{
748 return filename.toStdString();
749}
750#endif
751
Extracts information contained in a LAZ file, such as the public header block and variable length rec...
Definition qgslazinfo.h:38
int extrabytesCount() const
Returns the number of extrabytes contained in the LAZ dataset.
Definition qgslazinfo.h:102
QgsVector3D scale() const
Returns the scale of the points coordinates.
Definition qgslazinfo.h:76
int pointFormat() const
Returns the point format of the point records contained in the LAZ file.
Definition qgslazinfo.h:84
QVector< ExtraBytesAttributeDetails > extrabytes() const
Returns the list of extrabytes contained in the LAZ file.
Definition qgslazinfo.h:122
QgsVector3D offset() const
Returns the offset of the points coordinates.
Definition qgslazinfo.h:78
int pointRecordLength() const
Returns the length of each point record in bytes.
Definition qgslazinfo.h:100
static QVector< ExtraBytesAttributeDetails > parseExtrabytes(char *rawData, int length, int pointRecordLength)
Static function to parse the raw extrabytes VLR into a list of recognizable extrabyte attributes.
A collection of point cloud attributes.
int pointRecordSize() const
Returns total size of record.
const QgsPointCloudAttribute * find(const QString &attributeName, int &offset) const
Finds the attribute with the name.
QVector< QgsPointCloudAttribute > attributes() const
Returns all attributes.
Attribute for point cloud data pair of name and size in bytes.
DataType
Systems of unit measurement.
@ UShort
Unsigned short int 2 bytes.
@ UChar
Unsigned char 1 byte.
@ UInt32
Unsigned int32 4 bytes.
@ UInt64
Unsigned int64 8 bytes.
double convertValueToDouble(const char *ptr) const
Returns the attribute's value as a double for data pointed to by ptr.
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
double xMinimum
double yMinimum
double xMaximum
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
double yMaximum
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:52
double x() const
Returns X coordinate.
Definition qgsvector3d.h:50
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59