QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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"
19 #include "qgseptpointcloudindex.h"
20 #include "qgspointcloudattribute.h"
21 #include "qgsvector3d.h"
22 #include "qgsconfig.h"
23 #include "qgslogger.h"
24 #include "qgslazinfo.h"
25 
26 #include <QFile>
27 #include <QDir>
28 #include <iostream>
29 #include <memory>
30 #include <cstring>
31 #include <QElapsedTimer>
32 #include <QTemporaryFile>
33 #include <string>
34 
35 #include <zstd.h>
36 
37 #include "lazperf/las.hpp"
38 #include <lazperf/lazperf.hpp>
39 
40 #if defined(_MSC_VER)
41 #define UNICODE
42 #include <locale>
43 #include <codecvt>
44 #endif
45 
47 
48 template <typename T>
49 bool _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 
122 bool _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 
189 std::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  for ( const QgsPointCloudAttribute &requestedAttribute : requestedAttributesVector )
196  {
197  if ( requestedAttribute.name().compare( QLatin1String( "X" ), Qt::CaseInsensitive ) == 0 )
198  {
199  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::X, requestedAttribute.type(), requestedAttribute.size() ) );
200  }
201  else if ( requestedAttribute.name().compare( QLatin1String( "Y" ), Qt::CaseInsensitive ) == 0 )
202  {
203  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Y, requestedAttribute.type(), requestedAttribute.size() ) );
204  }
205  else if ( requestedAttribute.name().compare( QLatin1String( "Z" ), Qt::CaseInsensitive ) == 0 )
206  {
207  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Z, requestedAttribute.type(), requestedAttribute.size() ) );
208  }
209  else if ( requestedAttribute.name().compare( QLatin1String( "Classification" ), Qt::CaseInsensitive ) == 0 )
210  {
211  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Classification, requestedAttribute.type(), requestedAttribute.size() ) );
212  }
213  else if ( requestedAttribute.name().compare( QLatin1String( "Intensity" ), Qt::CaseInsensitive ) == 0 )
214  {
215  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Intensity, requestedAttribute.type(), requestedAttribute.size() ) );
216  }
217  else if ( requestedAttribute.name().compare( QLatin1String( "ReturnNumber" ), Qt::CaseInsensitive ) == 0 )
218  {
219  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ReturnNumber, requestedAttribute.type(), requestedAttribute.size() ) );
220  }
221  else if ( requestedAttribute.name().compare( QLatin1String( "NumberOfReturns" ), Qt::CaseInsensitive ) == 0 )
222  {
223  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::NumberOfReturns, requestedAttribute.type(), requestedAttribute.size() ) );
224  }
225  else if ( requestedAttribute.name().compare( QLatin1String( "ScanDirectionFlag" ), Qt::CaseInsensitive ) == 0 )
226  {
227  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ScanDirectionFlag, requestedAttribute.type(), requestedAttribute.size() ) );
228  }
229  else if ( requestedAttribute.name().compare( QLatin1String( "EdgeOfFlightLine" ), Qt::CaseInsensitive ) == 0 )
230  {
231  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::EdgeOfFlightLine, requestedAttribute.type(), requestedAttribute.size() ) );
232  }
233  else if ( requestedAttribute.name().compare( QLatin1String( "ScanAngleRank" ), Qt::CaseInsensitive ) == 0 )
234  {
235  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ScanAngleRank, requestedAttribute.type(), requestedAttribute.size() ) );
236  }
237  else if ( requestedAttribute.name().compare( QLatin1String( "UserData" ), Qt::CaseInsensitive ) == 0 )
238  {
239  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::UserData, requestedAttribute.type(), requestedAttribute.size() ) );
240  }
241  else if ( requestedAttribute.name().compare( QLatin1String( "PointSourceId" ), Qt::CaseInsensitive ) == 0 )
242  {
243  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::PointSourceId, requestedAttribute.type(), requestedAttribute.size() ) );
244  }
245  else if ( requestedAttribute.name().compare( QLatin1String( "GpsTime" ), Qt::CaseInsensitive ) == 0 )
246  {
247  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::GpsTime, requestedAttribute.type(), requestedAttribute.size() ) );
248  }
249  else if ( requestedAttribute.name().compare( QLatin1String( "Red" ), Qt::CaseInsensitive ) == 0 )
250  {
251  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Red, requestedAttribute.type(), requestedAttribute.size() ) );
252  }
253  else if ( requestedAttribute.name().compare( QLatin1String( "Green" ), Qt::CaseInsensitive ) == 0 )
254  {
255  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Green, requestedAttribute.type(), requestedAttribute.size() ) );
256  }
257  else if ( requestedAttribute.name().compare( QLatin1String( "Blue" ), Qt::CaseInsensitive ) == 0 )
258  {
259  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Blue, requestedAttribute.type(), requestedAttribute.size() ) );
260  }
261  else if ( requestedAttribute.name().compare( QLatin1String( "ScannerChannel" ), Qt::CaseInsensitive ) == 0 )
262  {
263  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ScannerChannel, requestedAttribute.type(), requestedAttribute.size() ) );
264  }
265  else if ( requestedAttribute.name().compare( QLatin1String( "ClassificationFlags" ), Qt::CaseInsensitive ) == 0 )
266  {
267  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ClassificationFlags, requestedAttribute.type(), requestedAttribute.size() ) );
268  }
269  else if ( requestedAttribute.name().compare( QLatin1String( "Infrared" ), Qt::CaseInsensitive ) == 0 )
270  {
271  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::NIR, requestedAttribute.type(), requestedAttribute.size() ) );
272  }
273  else
274  {
275  bool foundAttr = false;
276  for ( QgsLazInfo::ExtraBytesAttributeDetails &eba : extrabytesAttr )
277  {
278  if ( requestedAttribute.name().compare( eba.attribute.trimmed() ) == 0 )
279  {
280  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ExtraBytes, eba.type, eba.size, eba.offset ) );
281  foundAttr = true;
282  break;
283  }
284  }
285  if ( !foundAttr )
286  {
287  // 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
288  requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::MissingOrUnknown, requestedAttribute.type(), requestedAttribute.size() ) );
289  }
290  }
291  }
292  return requestedAttributeDetails;
293 }
294 
295 void decodePoint( char *buf, int lasPointFormat, char *dataBuffer, std::size_t &outputOffset, std::vector< QgsLazDecoder::RequestedAttributeDetails > &requestedAttributeDetails )
296 {
297  lazperf::las::point10 p10;
298  lazperf::las::gpstime gps;
299  lazperf::las::rgb rgb;
300  lazperf::las::nir14 nir;
301  lazperf::las::point14 p14;
302 
303  bool isLas14 = ( lasPointFormat == 6 || lasPointFormat == 7 || lasPointFormat == 8 );
304 
305  switch ( lasPointFormat )
306  {
307  // LAS 1.2 file support
308  case 0: // base
309  p10.unpack( buf );
310  break;
311  case 1: // base + gps time
312  p10.unpack( buf );
313  gps.unpack( buf + sizeof( lazperf::las::point10 ) );
314  break;
315  case 2: // base + rgb
316  p10.unpack( buf );
317  rgb.unpack( buf + sizeof( lazperf::las::point10 ) );
318  break;
319  case 3: // base + gps time + rgb
320  p10.unpack( buf );
321  gps.unpack( buf + sizeof( lazperf::las::point10 ) );
322  rgb.unpack( buf + sizeof( lazperf::las::point10 ) + sizeof( lazperf::las::gpstime ) );
323  break;
324 
325  // LAS 1.4 file support
326  case 6: // base (includes gps time)
327  p14.unpack( buf );
328  break;
329  case 7: // base + rgb
330  p14.unpack( buf );
331  rgb.unpack( buf + sizeof( lazperf::las::point14 ) );
332  break;
333  case 8: // base + rgb + nir
334  p14.unpack( buf );
335  rgb.unpack( buf + sizeof( lazperf::las::point14 ) );
336  nir.unpack( buf + sizeof( lazperf::las::point14 ) + sizeof( lazperf::las::rgb ) );
337  break;
338 
339  default:
340  Q_ASSERT( false ); // must not happen - we checked earlier that the format is supported
341  }
342 
343  for ( const QgsLazDecoder::RequestedAttributeDetails &requestedAttribute : requestedAttributeDetails )
344  {
345  switch ( requestedAttribute.attribute )
346  {
347  case QgsLazDecoder::LazAttribute::X:
348  _lazStoreToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.x() : p10.x );
349  break;
350  case QgsLazDecoder::LazAttribute::Y:
351  _lazStoreToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.y() : p10.y );
352  break;
353  case QgsLazDecoder::LazAttribute::Z:
354  _lazStoreToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.z() : p10.z );
355  break;
356  case QgsLazDecoder::LazAttribute::Classification:
357  _lazStoreToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.classification() : p10.classification );
358  break;
359  case QgsLazDecoder::LazAttribute::Intensity:
360  _lazStoreToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.intensity() : p10.intensity );
361  break;
362  case QgsLazDecoder::LazAttribute::ReturnNumber:
363  _lazStoreToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.returnNum() : p10.return_number );
364  break;
365  case QgsLazDecoder::LazAttribute::NumberOfReturns:
366  _lazStoreToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.numReturns() : p10.number_of_returns_of_given_pulse );
367  break;
368  case QgsLazDecoder::LazAttribute::ScanDirectionFlag:
369  _lazStoreToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.scanDirFlag() : p10.scan_direction_flag );
370  break;
371  case QgsLazDecoder::LazAttribute::EdgeOfFlightLine:
372  _lazStoreToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.eofFlag() : p10.edge_of_flight_line );
373  break;
374  case QgsLazDecoder::LazAttribute::ScanAngleRank:
375  _lazStoreToStream<char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.scanAngle() : p10.scan_angle_rank );
376  break;
377  case QgsLazDecoder::LazAttribute::UserData:
378  _lazStoreToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.userData() : p10.user_data );
379  break;
380  case QgsLazDecoder::LazAttribute::PointSourceId:
381  _lazStoreToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.pointSourceID() : p10.point_source_ID );
382  break;
383  case QgsLazDecoder::LazAttribute::GpsTime:
384  // lazperf internally stores gps value as int64 field, but in fact it is a double value
385  _lazStoreToStream<double>( dataBuffer, outputOffset, requestedAttribute.type,
386  isLas14 ? p14.gpsTime() : *reinterpret_cast<const double *>( reinterpret_cast<const void *>( &gps.value ) ) );
387  break;
388  case QgsLazDecoder::LazAttribute::Red:
389  _lazStoreToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.r );
390  break;
391  case QgsLazDecoder::LazAttribute::Green:
392  _lazStoreToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.g );
393  break;
394  case QgsLazDecoder::LazAttribute::Blue:
395  _lazStoreToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.b );
396  break;
397  case QgsLazDecoder::LazAttribute::ScannerChannel:
398  _lazStoreToStream<char>( dataBuffer, outputOffset, requestedAttribute.type, p14.scannerChannel() );
399  break;
400  case QgsLazDecoder::LazAttribute::ClassificationFlags:
401  _lazStoreToStream<char>( dataBuffer, outputOffset, requestedAttribute.type, p14.classFlags() );
402  break;
403  case QgsLazDecoder::LazAttribute::NIR:
404  {
405  if ( lasPointFormat == 8 || lasPointFormat == 10 )
406  {
407  _lazStoreToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, nir.val );
408  }
409  else
410  {
411  // just store 0 for missing attributes
412  _lazStoreToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, 0 );
413  }
414  break;
415 
416  }
417  case QgsLazDecoder::LazAttribute::ExtraBytes:
418  {
419  switch ( requestedAttribute.type )
420  {
422  _lazStoreToStream<char>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<char * >( &buf[requestedAttribute.offset] ) );
423  break;
425  _lazStoreToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<unsigned char * >( &buf[requestedAttribute.offset] ) );
426  break;
428  _lazStoreToStream<qint16>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint16 * >( &buf[requestedAttribute.offset] ) );
429  break;
431  _lazStoreToStream<quint16>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint16 * >( &buf[requestedAttribute.offset] ) );
432  break;
434  _lazStoreToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint32 * >( &buf[requestedAttribute.offset] ) );
435  break;
437  _lazStoreToStream<quint32>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint32 * >( &buf[requestedAttribute.offset] ) );
438  break;
440  _lazStoreToStream<qint64>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint64 * >( &buf[requestedAttribute.offset] ) );
441  break;
443  _lazStoreToStream<quint64>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint64 * >( &buf[requestedAttribute.offset] ) );
444  break;
446  _lazStoreToStream<float>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<float * >( &buf[requestedAttribute.offset] ) );
447  break;
449  _lazStoreToStream<double>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<double * >( &buf[requestedAttribute.offset] ) );
450  break;
451  }
452  }
453  break;
454  case QgsLazDecoder::LazAttribute::MissingOrUnknown:
455  // just store 0 for unknown/missing attributes
456  _lazStoreToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, 0 );
457  break;
458  }
459 
460  outputOffset += requestedAttribute.size;
461  }
462 }
463 
464 template<typename FileType>
465 QgsPointCloudBlock *__decompressLaz( FileType &file, const QgsPointCloudAttributeCollection &requestedAttributes, QgsPointCloudExpression &filterExpression )
466 {
467  if ( ! file.good() )
468  return nullptr;
469 
470 #ifdef QGISDEBUG
471  QElapsedTimer t;
472  t.start();
473 #endif
474 
475  // lazperf may throw exceptions
476  try
477  {
478  lazperf::reader::generic_file f( file );
479 
480 
481  // output file formats from entwine/untwine:
482  // - older versions write LAZ 1.2 files with point formats 0, 1, 2 or 3
483  // - newer versions write LAZ 1.4 files with point formats 6, 7 or 8
484 
485  int lasPointFormat = f.header().pointFormat();
486  if ( lasPointFormat != 0 && lasPointFormat != 1 && lasPointFormat != 2 && lasPointFormat != 3 &&
487  lasPointFormat != 6 && lasPointFormat != 7 && lasPointFormat != 8 )
488  {
489  QgsDebugMsg( QStringLiteral( "Unexpected point format record (%1) - only 0, 1, 2, 3, 6, 7, 8 are supported" ).arg( lasPointFormat ) );
490  return nullptr;
491  }
492 
493  const size_t count = f.header().point_count;
494  const QgsVector3D scale( f.header().scale.x, f.header().scale.y, f.header().scale.z );
495  const QgsVector3D offset( f.header().offset.x, f.header().offset.y, f.header().offset.z );
496 
497  QByteArray bufArray( f.header().point_record_length, 0 );
498  char *buf = bufArray.data();
499 
500  const size_t requestedPointRecordSize = requestedAttributes.pointRecordSize();
501  QByteArray data;
502  data.resize( requestedPointRecordSize * count );
503  char *dataBuffer = data.data();
504 
505  std::size_t outputOffset = 0;
506 
507  std::unique_ptr< QgsPointCloudBlock > block = std::make_unique< QgsPointCloudBlock >(
508  count,
509  requestedAttributes,
510  data, scale, offset
511  );
512 
513  int skippedPoints = 0;
514  const bool filterIsValid = filterExpression.isValid();
515  if ( !filterExpression.prepare( block.get() ) && filterIsValid )
516  {
517  // skip processing if the expression cannot be prepared
518  block->setPointCount( 0 );
519  return block.release();
520  }
521 
522  std::vector<char> rawExtrabytes = f.vlrData( "LASF_Spec", 4 );
523  QVector<QgsLazInfo::ExtraBytesAttributeDetails> extrabyteAttributesDetails = QgsLazInfo::parseExtrabytes( rawExtrabytes.data(), rawExtrabytes.size(), f.header().point_record_length );
524  std::vector< QgsLazDecoder::RequestedAttributeDetails > requestedAttributeDetails = __prepareRequestedAttributeDetails( requestedAttributes, extrabyteAttributesDetails );
525 
526  for ( size_t i = 0 ; i < count ; i ++ )
527  {
528  f.readPoint( buf ); // read the point out
529 
530  decodePoint( buf, lasPointFormat, dataBuffer, outputOffset, requestedAttributeDetails );
531 
532  // check if point needs to be filtered out
533  if ( filterIsValid )
534  {
535  // we're always evaluating the last written point in the buffer
536  double eval = filterExpression.evaluate( i - skippedPoints );
537  if ( !eval || std::isnan( eval ) )
538  {
539  // if the point is filtered out, rewind the offset so the next point is written over it
540  outputOffset -= requestedPointRecordSize;
541  ++skippedPoints;
542  }
543  }
544  }
545 
546 #ifdef QGISDEBUG
547  QgsDebugMsgLevel( QStringLiteral( "LAZ-PERF Read through the points in %1 seconds." ).arg( t.elapsed() / 1000. ), 2 );
548 #endif
549  block->setPointCount( count - skippedPoints );
550  return block.release();
551  }
552  catch ( std::exception &e )
553  {
554  QgsDebugMsg( "Error decompressing laz file: " + QString::fromLatin1( e.what() ) );
555  return nullptr;
556  }
557 }
558 
559 QgsPointCloudBlock *QgsLazDecoder::decompressLaz( const QString &filename,
560  const QgsPointCloudAttributeCollection &requestedAttributes,
561  QgsPointCloudExpression &filterExpression )
562 {
563  std::ifstream file( toNativePath( filename ), std::ios::binary );
564 
565  return __decompressLaz<std::ifstream>( file, requestedAttributes, filterExpression );
566 }
567 
568 QgsPointCloudBlock *QgsLazDecoder::decompressLaz( const QByteArray &byteArrayData,
569  const QgsPointCloudAttributeCollection &requestedAttributes,
570  QgsPointCloudExpression &filterExpression )
571 {
572  std::istringstream file( byteArrayData.toStdString() );
573  return __decompressLaz<std::istringstream>( file, requestedAttributes, filterExpression );
574 }
575 
576 QgsPointCloudBlock *QgsLazDecoder::decompressCopc( const QByteArray &data, QgsLazInfo &lazInfo, int32_t pointCount, const QgsPointCloudAttributeCollection &requestedAttributes, QgsPointCloudExpression &filterExpression )
577 {
578  // COPC only supports point formats 6, 7 and 8
579  int lasPointFormat = lazInfo.pointFormat();
580  if ( lasPointFormat != 6 && lasPointFormat != 7 && lasPointFormat != 8 )
581  {
582  QgsDebugMsg( QStringLiteral( "Unexpected point format record (%1) - only 6, 7, 8 are supported for COPC format" ).arg( lasPointFormat ) );
583  return nullptr;
584  }
585 
586  std::unique_ptr<char []> decodedData( new char[ lazInfo.pointRecordLength() ] );
587 
588  lazperf::reader::chunk_decompressor decompressor( lasPointFormat, lazInfo.extrabytesCount(), data.data() );
589 
590  const size_t requestedPointRecordSize = requestedAttributes.pointRecordSize();
591  QByteArray blockData;
592  blockData.resize( requestedPointRecordSize * pointCount );
593  char *dataBuffer = blockData.data();
594 
595  std::size_t outputOffset = 0;
596 
597  QVector<QgsLazInfo::ExtraBytesAttributeDetails> extrabyteAttributesDetails = lazInfo.extrabytes();
598  std::vector< RequestedAttributeDetails > requestedAttributeDetails = __prepareRequestedAttributeDetails( requestedAttributes, extrabyteAttributesDetails );
599  std::unique_ptr< QgsPointCloudBlock > block = std::make_unique< QgsPointCloudBlock >(
600  pointCount, requestedAttributes,
601  blockData, lazInfo.scale(), lazInfo.offset()
602  );
603 
604  int skippedPoints = 0;
605  const bool filterIsValid = filterExpression.isValid();
606  if ( !filterExpression.prepare( block.get() ) && filterIsValid )
607  {
608  // skip processing if the expression cannot be prepared
609  block->setPointCount( 0 );
610  return block.release();
611  }
612 
613  for ( int i = 0 ; i < pointCount; ++i )
614  {
615  decompressor.decompress( decodedData.get() );
616  char *buf = decodedData.get();
617 
618  decodePoint( buf, lasPointFormat, dataBuffer, outputOffset, requestedAttributeDetails );
619 
620  // check if point needs to be filtered out
621  if ( filterIsValid )
622  {
623  // we're always evaluating the last written point in the buffer
624  double eval = filterExpression.evaluate( i - skippedPoints );
625  if ( !eval || std::isnan( eval ) )
626  {
627  // if the point is filtered out, rewind the offset so the next point is written over it
628  outputOffset -= requestedPointRecordSize;
629  ++skippedPoints;
630  }
631  }
632  }
633 
634  block->setPointCount( pointCount - skippedPoints );
635  return block.release();
636 }
637 
638 #if defined(_MSC_VER)
639 std::wstring QgsLazDecoder::toNativePath( const QString &filename )
640 {
641  std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > converter;
642  return converter.from_bytes( filename.toStdString() );
643 }
644 #else
645 std::string QgsLazDecoder::toNativePath( const QString &filename )
646 {
647  return filename.toStdString();
648 }
649 #endif
650 
qgseptpointcloudindex.h
QgsPointCloudAttribute::DataType
DataType
Systems of unit measurement.
Definition: qgspointcloudattribute.h:44
qgslazinfo.h
QgsLazInfo::pointFormat
int pointFormat() const
Returns the point format of the point records contained in the LAZ file.
Definition: qgslazinfo.h:85
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsVector3D
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition: qgsvector3d.h:31
qgspointcloudattribute.h
QgsPointCloudAttribute::UInt32
@ UInt32
Unsigned int32 4 bytes.
Definition: qgspointcloudattribute.h:51
QgsPointCloudBlock
Base class for storing raw data from point cloud nodes.
Definition: qgspointcloudblock.h:38
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsLazInfo::extrabytesCount
int extrabytesCount() const
Returns the number of extrabytes contained in the LAZ dataset.
Definition: qgslazinfo.h:103
QgsPointCloudAttribute::Double
@ Double
Double 8 bytes.
Definition: qgspointcloudattribute.h:55
QgsPointCloudAttribute::Short
@ Short
Short int 2 bytes.
Definition: qgspointcloudattribute.h:48
QgsLazInfo::ExtraBytesAttributeDetails
Definition: qgslazinfo.h:48
QgsPointCloudAttribute::UInt64
@ UInt64
Unsigned int64 8 bytes.
Definition: qgspointcloudattribute.h:53
QgsPointCloudAttribute::Int32
@ Int32
Int32 4 bytes.
Definition: qgspointcloudattribute.h:50
QgsPointCloudAttributeCollection::attributes
QVector< QgsPointCloudAttribute > attributes() const
Returns all attributes.
Definition: qgspointcloudattribute.cpp:163
QgsPointCloudAttributeCollection::pointRecordSize
int pointRecordSize() const
Returns total size of record.
Definition: qgspointcloudattribute.h:187
QgsPointCloudAttribute::UShort
@ UShort
Unsigned short int 2 bytes.
Definition: qgspointcloudattribute.h:49
QgsPointCloudAttributeCollection
Collection of point cloud attributes.
Definition: qgspointcloudattribute.h:141
QgsPointCloudAttribute::Char
@ Char
Char 1 byte.
Definition: qgspointcloudattribute.h:46
QgsPointCloudAttribute
Attribute for point cloud data pair of name and size in bytes.
Definition: qgspointcloudattribute.h:40
QgsPointCloudAttribute::Int64
@ Int64
Int64 8 bytes.
Definition: qgspointcloudattribute.h:52
QgsLazInfo
Class for extracting information contained in LAZ file such as the public header block and variable l...
Definition: qgslazinfo.h:38
qgslazdecoder.h
QgsLazInfo::offset
QgsVector3D offset() const
Returns the offset of the points coordinates.
Definition: qgslazinfo.h:79
qgsvector3d.h
QgsLazInfo::extrabytes
QVector< ExtraBytesAttributeDetails > extrabytes() const
Returns the list of extrabytes contained in the LAZ file.
Definition: qgslazinfo.h:123
QgsLazInfo::pointRecordLength
int pointRecordLength() const
Returns the length of each point record in bytes.
Definition: qgslazinfo.h:101
QgsLazInfo::scale
QgsVector3D scale() const
Returns the scale of the points coordinates.
Definition: qgslazinfo.h:77
QgsLazInfo::parseExtrabytes
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:206
qgslogger.h
QgsPointCloudAttribute::UChar
@ UChar
Unsigned char 1 byte.
Definition: qgspointcloudattribute.h:47
QgsPointCloudAttribute::Float
@ Float
Float 4 bytes.
Definition: qgspointcloudattribute.h:54