QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgseptdecoder.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspointcloudrenderer.cpp
3  --------------------
4  begin : October 2020
5  copyright : (C) 2020 by Peter Petrik
6  email : zilolv 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 "qgseptdecoder.h"
19 #include "qgseptpointcloudindex.h"
20 #include "qgspointcloudattribute.h"
21 #include "qgsvector3d.h"
22 #include "qgsconfig.h"
23 #include "qgslogger.h"
24 
25 #include <QFile>
26 #include <iostream>
27 #include <memory>
28 #include <cstring>
29 
30 #include <zstd.h>
31 
32 #include "laz-perf/io.hpp"
33 #include "laz-perf/common/common.hpp"
34 
36 
37 template <typename T>
38 bool _storeToStream( char *s, size_t position, QgsPointCloudAttribute::DataType type, T value )
39 {
40  switch ( type )
41  {
43  {
44  char val = char( value );
45  s[position] = val;
46  break;
47  }
49  {
50  short val = short( value );
51  memcpy( s + position, reinterpret_cast<char * >( &val ), sizeof( short ) );
52  break;
53  }
54 
56  {
57  unsigned short val = static_cast< unsigned short>( value );
58  memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( unsigned short ) );
59  break;
60  }
61 
63  {
64  float val = float( value );
65  memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( float ) );
66  break;
67  }
69  {
70  qint32 val = qint32( value );
71  memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( qint32 ) );
72  break;
73  }
75  {
76  double val = double( value );
77  memcpy( s + position, reinterpret_cast< char * >( &val ), sizeof( double ) );
78  break;
79  }
80  }
81 
82  return true;
83 }
84 
85 bool _serialize( char *data, size_t outputPosition, QgsPointCloudAttribute::DataType outputType,
86  const char *input, QgsPointCloudAttribute::DataType inputType, int inputSize, size_t inputPosition )
87 {
88  if ( outputType == inputType )
89  {
90  memcpy( data + outputPosition, input + inputPosition, inputSize );
91  return true;
92  }
93 
94  switch ( inputType )
95  {
97  {
98  char val = *( input + inputPosition );
99  return _storeToStream<char>( data, outputPosition, outputType, val );
100  }
102  {
103  const short val = *reinterpret_cast< const short * >( input + inputPosition );
104  return _storeToStream<short>( data, outputPosition, outputType, val );
105  }
107  {
108  const unsigned short val = *reinterpret_cast< const unsigned short * >( input + inputPosition );
109  return _storeToStream<unsigned short>( data, outputPosition, outputType, val );
110  }
112  {
113  const float val = *reinterpret_cast< const float * >( input + inputPosition );
114  return _storeToStream<float>( data, outputPosition, outputType, val );
115  }
117  {
118  const qint32 val = *reinterpret_cast<const qint32 * >( input + inputPosition );
119  return _storeToStream<qint32>( data, outputPosition, outputType, val );
120  }
122  {
123  const double val = *reinterpret_cast< const double * >( input + inputPosition );
124  return _storeToStream<double>( data, outputPosition, outputType, val );
125  }
126  }
127  return true;
128 }
129 
130 // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
131 
132 QgsPointCloudBlock *_decompressBinary( const QByteArray &dataUncompressed, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes )
133 {
134  const std::size_t pointRecordSize = attributes.pointRecordSize( );
135  const std::size_t requestedPointRecordSize = requestedAttributes.pointRecordSize();
136  const int count = dataUncompressed.size() / pointRecordSize;
137  QByteArray data;
138  data.resize( requestedPointRecordSize * count );
139  char *destinationBuffer = data.data();
140  const char *s = dataUncompressed.data();
141 
142  const QVector<QgsPointCloudAttribute> requestedAttributesVector = requestedAttributes.attributes();
143 
144  // calculate input attributes and offsets once in advance
145 
146  struct AttributeData
147  {
148  AttributeData( int inputOffset, int inputSize, QgsPointCloudAttribute::DataType inputType, int requestedSize, QgsPointCloudAttribute::DataType requestedType )
149  : inputOffset( inputOffset )
150  , inputSize( inputSize )
151  , inputType( inputType )
152  , requestedSize( requestedSize )
153  , requestedType( requestedType )
154  {}
155 
156  int inputOffset;
157  int inputSize;
159  int requestedSize;
160  QgsPointCloudAttribute::DataType requestedType;
161  };
162 
163  std::vector< AttributeData > attributeData;
164  attributeData.reserve( requestedAttributesVector.size() );
165  for ( const QgsPointCloudAttribute &requestedAttribute : requestedAttributesVector )
166  {
167  int inputAttributeOffset;
168  const QgsPointCloudAttribute *inputAttribute = attributes.find( requestedAttribute.name(), inputAttributeOffset );
169  if ( !inputAttribute )
170  {
171  return nullptr;
172  }
173  attributeData.emplace_back( AttributeData( inputAttributeOffset, inputAttribute->size(), inputAttribute->type(),
174  requestedAttribute.size(), requestedAttribute.type() ) );
175  }
176 
177  // now loop through points
178  size_t outputOffset = 0;
179  for ( int i = 0; i < count; ++i )
180  {
181  for ( const AttributeData &attribute : attributeData )
182  {
183  _serialize( destinationBuffer, outputOffset,
184  attribute.requestedType, s,
185  attribute.inputType, attribute.inputSize, i * pointRecordSize + attribute.inputOffset );
186 
187  outputOffset += attribute.requestedSize;
188  }
189  }
190  return new QgsPointCloudBlock(
191  count,
192  requestedAttributes,
193  data
194  );
195 }
196 
197 
198 QgsPointCloudBlock *QgsEptDecoder::decompressBinary( const QString &filename, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes )
199 {
200  if ( ! QFile::exists( filename ) )
201  return nullptr;
202 
203  QFile f( filename );
204  bool r = f.open( QIODevice::ReadOnly );
205  if ( !r )
206  return nullptr;
207 
208  QByteArray dataUncompressed = f.read( f.size() );
209  return _decompressBinary( dataUncompressed, attributes, requestedAttributes );
210 }
211 
212 /* *************************************************************************************** */
213 
214 QByteArray decompressZtdStream( const QByteArray &dataCompressed )
215 {
216  // NOTE: this is very primitive implementation because we expect the uncompressed
217  // data will be always less than 10 MB
218 
219  const int MAXSIZE = 10000000;
220  QByteArray dataUncompressed;
221  dataUncompressed.resize( MAXSIZE );
222 
223  ZSTD_DStream *strm = ZSTD_createDStream();
224  ZSTD_initDStream( strm );
225 
226  ZSTD_inBuffer m_inBuf;
227  m_inBuf.src = reinterpret_cast<const void *>( dataCompressed.constData() );
228  m_inBuf.size = dataCompressed.size();
229  m_inBuf.pos = 0;
230 
231  ZSTD_outBuffer outBuf { reinterpret_cast<void *>( dataUncompressed.data() ), MAXSIZE, 0 };
232  size_t ret = ZSTD_decompressStream( strm, &outBuf, &m_inBuf );
233  Q_ASSERT( !ZSTD_isError( ret ) );
234  Q_ASSERT( outBuf.pos );
235  Q_ASSERT( outBuf.pos < outBuf.size );
236 
237  ZSTD_freeDStream( strm );
238  dataUncompressed.resize( outBuf.pos );
239  return dataUncompressed;
240 }
241 
242 QgsPointCloudBlock *QgsEptDecoder::decompressZStandard( const QString &filename, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes )
243 {
244  if ( ! QFile::exists( filename ) )
245  return nullptr;
246 
247  QFile f( filename );
248  bool r = f.open( QIODevice::ReadOnly );
249  if ( !r )
250  return nullptr;
251 
252  QByteArray dataCompressed = f.readAll();
253  QByteArray dataUncompressed = decompressZtdStream( dataCompressed );
254  return _decompressBinary( dataUncompressed, attributes, requestedAttributes );
255 }
256 
257 /* *************************************************************************************** */
258 
259 QgsPointCloudBlock *QgsEptDecoder::decompressLaz( const QString &filename,
260  const QgsPointCloudAttributeCollection &attributes,
261  const QgsPointCloudAttributeCollection &requestedAttributes )
262 {
263  Q_UNUSED( attributes )
264 
265  const QByteArray arr = filename.toUtf8();
266  std::ifstream file( arr.constData(), std::ios::binary );
267  if ( ! file.good() )
268  return nullptr;
269 
270 #ifdef QGISDEBUG
271  auto start = common::tick();
272 #endif
273 
274  laszip::io::reader::file f( file );
275 
276  const size_t count = f.get_header().point_count;
277  char buf[sizeof( laszip::formats::las::point10 ) + sizeof( laszip::formats::las::gpstime ) + sizeof( laszip::formats::las::rgb ) ]; // a buffer large enough to hold our point
278 
279  const size_t requestedPointRecordSize = requestedAttributes.pointRecordSize();
280  QByteArray data;
281  data.resize( requestedPointRecordSize * count );
282  char *dataBuffer = data.data();
283 
284  const QVector<QgsPointCloudAttribute> requestedAttributesVector = requestedAttributes.attributes();
285 
286  std::size_t outputOffset = 0;
287 
288  enum class LazAttribute
289  {
290  X,
291  Y,
292  Z,
293  Classification,
294  Intensity,
295  ReturnNumber,
296  NumberOfReturns,
297  ScanDirectionFlag,
298  EdgeOfFlightLine,
299  ScanAngleRank,
300  UserData,
301  PointSourceId,
302  Red,
303  Green,
304  Blue,
305  MissingOrUnknown
306  };
307 
308  struct RequestedAttributeDetails
309  {
310  RequestedAttributeDetails( LazAttribute attribute, QgsPointCloudAttribute::DataType type, int size )
311  : attribute( attribute )
312  , type( type )
313  , size( size )
314  {}
315 
316  LazAttribute attribute;
318  int size;
319  };
320 
321  std::vector< RequestedAttributeDetails > requestedAttributeDetails;
322  requestedAttributeDetails.reserve( requestedAttributesVector.size() );
323  for ( const QgsPointCloudAttribute &requestedAttribute : requestedAttributesVector )
324  {
325  if ( requestedAttribute.name().compare( QLatin1String( "X" ), Qt::CaseInsensitive ) == 0 )
326  {
327  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::X, requestedAttribute.type(), requestedAttribute.size() ) );
328  }
329  else if ( requestedAttribute.name().compare( QLatin1String( "Y" ), Qt::CaseInsensitive ) == 0 )
330  {
331  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Y, requestedAttribute.type(), requestedAttribute.size() ) );
332  }
333  else if ( requestedAttribute.name().compare( QLatin1String( "Z" ), Qt::CaseInsensitive ) == 0 )
334  {
335  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Z, requestedAttribute.type(), requestedAttribute.size() ) );
336  }
337  else if ( requestedAttribute.name().compare( QLatin1String( "Classification" ), Qt::CaseInsensitive ) == 0 )
338  {
339  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Classification, requestedAttribute.type(), requestedAttribute.size() ) );
340  }
341  else if ( requestedAttribute.name().compare( QLatin1String( "Intensity" ), Qt::CaseInsensitive ) == 0 )
342  {
343  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Intensity, requestedAttribute.type(), requestedAttribute.size() ) );
344  }
345  else if ( requestedAttribute.name().compare( QLatin1String( "ReturnNumber" ), Qt::CaseInsensitive ) == 0 )
346  {
347  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ReturnNumber, requestedAttribute.type(), requestedAttribute.size() ) );
348  }
349  else if ( requestedAttribute.name().compare( QLatin1String( "NumberOfReturns" ), Qt::CaseInsensitive ) == 0 )
350  {
351  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::NumberOfReturns, requestedAttribute.type(), requestedAttribute.size() ) );
352  }
353  else if ( requestedAttribute.name().compare( QLatin1String( "ScanDirectionFlag" ), Qt::CaseInsensitive ) == 0 )
354  {
355  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ScanDirectionFlag, requestedAttribute.type(), requestedAttribute.size() ) );
356  }
357  else if ( requestedAttribute.name().compare( QLatin1String( "EdgeOfFlightLine" ), Qt::CaseInsensitive ) == 0 )
358  {
359  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::EdgeOfFlightLine, requestedAttribute.type(), requestedAttribute.size() ) );
360  }
361  else if ( requestedAttribute.name().compare( QLatin1String( "ScanAngleRank" ), Qt::CaseInsensitive ) == 0 )
362  {
363  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ScanAngleRank, requestedAttribute.type(), requestedAttribute.size() ) );
364  }
365  else if ( requestedAttribute.name().compare( QLatin1String( "UserData" ), Qt::CaseInsensitive ) == 0 )
366  {
367  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::UserData, requestedAttribute.type(), requestedAttribute.size() ) );
368  }
369  else if ( requestedAttribute.name().compare( QLatin1String( "PointSourceId" ), Qt::CaseInsensitive ) == 0 )
370  {
371  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::PointSourceId, requestedAttribute.type(), requestedAttribute.size() ) );
372  }
373  else if ( requestedAttribute.name().compare( QLatin1String( "Red" ), Qt::CaseInsensitive ) == 0 )
374  {
375  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Red, requestedAttribute.type(), requestedAttribute.size() ) );
376  }
377  else if ( requestedAttribute.name().compare( QLatin1String( "Green" ), Qt::CaseInsensitive ) == 0 )
378  {
379  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Green, requestedAttribute.type(), requestedAttribute.size() ) );
380  }
381  else if ( requestedAttribute.name().compare( QLatin1String( "Blue" ), Qt::CaseInsensitive ) == 0 )
382  {
383  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Blue, requestedAttribute.type(), requestedAttribute.size() ) );
384  }
385  else
386  {
387  // 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
388  requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::MissingOrUnknown, requestedAttribute.type(), requestedAttribute.size() ) );
389  }
390  }
391 
392  for ( size_t i = 0 ; i < count ; i ++ )
393  {
394  f.readPoint( buf ); // read the point out
395  laszip::formats::las::point10 p = laszip::formats::packers<laszip::formats::las::point10>::unpack( buf );
396  laszip::formats::las::rgb rgb = laszip::formats::packers<laszip::formats::las::rgb>::unpack( buf + sizeof( laszip::formats::las::point10 ) + sizeof( laszip::formats::las::gpstime ) );
397 
398  for ( const RequestedAttributeDetails &requestedAttribute : requestedAttributeDetails )
399  {
400  switch ( requestedAttribute.attribute )
401  {
402  case LazAttribute::X:
403  _storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, p.x );
404  break;
405  case LazAttribute::Y:
406  _storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, p.y );
407  break;
408  case LazAttribute::Z:
409  _storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, p.z );
410  break;
411  case LazAttribute::Classification:
412  _storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.classification );
413  break;
414  case LazAttribute::Intensity:
415  _storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, p.intensity );
416  break;
417  case LazAttribute::ReturnNumber:
418  _storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.return_number );
419  break;
420  case LazAttribute::NumberOfReturns:
421  _storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.number_of_returns_of_given_pulse );
422  break;
423  case LazAttribute::ScanDirectionFlag:
424  _storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.scan_direction_flag );
425  break;
426  case LazAttribute::EdgeOfFlightLine:
427  _storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.edge_of_flight_line );
428  break;
429  case LazAttribute::ScanAngleRank:
430  _storeToStream<char>( dataBuffer, outputOffset, requestedAttribute.type, p.scan_angle_rank );
431  break;
432  case LazAttribute::UserData:
433  _storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.user_data );
434  break;
435  case LazAttribute::PointSourceId:
436  _storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, p.point_source_ID );
437  break;
438  case LazAttribute::Red:
439  _storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.r );
440  break;
441  case LazAttribute::Green:
442  _storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.g );
443  break;
444  case LazAttribute::Blue:
445  _storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.b );
446  break;
447  case LazAttribute::MissingOrUnknown:
448  // just store 0 for unknown/missing attributes
449  _storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, 0 );
450  break;
451  }
452 
453  outputOffset += requestedAttribute.size;
454  }
455  }
456 
457 #ifdef QGISDEBUG
458  float t = common::since( start );
459  QgsDebugMsgLevel( QStringLiteral( "LAZ-PERF Read through the points in %1 seconds." ).arg( t ), 2 );
460 #endif
461 
462  return new QgsPointCloudBlock(
463  count,
464  requestedAttributes,
465  data
466  );
467 }
468 
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.
int size() const
Returns size of the attribute in bytes.
DataType type() const
Returns the data type.
Base class for storing raw data from point cloud nodes.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39