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