QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgslazinfo.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslazinfo.cpp
3  --------------------
4  begin : April 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 "qgslazinfo.h"
19 
20 #include "qgslogger.h"
22 
23 #include "lazperf/readers.hpp"
24 
25 // QgsLazInfo
26 
28 
30 {
31  if ( mVersion.first == 1 && mVersion.second == 4 )
32  return 375;
33  if ( mVersion.first == 1 && mVersion.second == 3 )
34  return 235;
35  if ( mVersion.first == 1 && mVersion.second <= 2 )
36  return 227;
37  return 0;
38 }
39 
40 void QgsLazInfo::parseRawHeader( char *data, uint64_t length )
41 {
42  mIsValid = true;
43  if ( std::string( data, 4 ) != "LASF" )
44  {
45  mError = QStringLiteral( "Supplied header is not from a LAZ file" );
46  mIsValid = false;
47  return;
48  }
49  std::istringstream file( std::string( data, length ) );
50  lazperf::header14 header = lazperf::header14::create( file );
51  parseHeader( header );
52 }
53 
54 void QgsLazInfo::parseRawVlrEntries( char *data, uint64_t length )
55 {
56  if ( !mIsValid )
57  return;
58  uint64_t currentOffset = 0;
59  for ( uint64_t i = 0; i < ( uint64_t )mVlrCount && currentOffset < length; ++i )
60  {
61  lazperf::vlr_header vlrHeader;
62  vlrHeader.fill( data + currentOffset, 54 );
63 
64  LazVlr vlr;
65  vlr.userId = QString::fromStdString( vlrHeader.user_id );
66  vlr.recordId = vlrHeader.record_id;
67  vlr.data = QByteArray( data + currentOffset + 54, vlrHeader.data_length );
68  mVlrVector.push_back( vlr );
69  currentOffset += 54 + vlrHeader.data_length;
70  }
71 
72  parseCrs();
73  parseExtrabyteAttributes();
74 }
75 
76 
77 void QgsLazInfo::parseHeader( lazperf::header14 &header )
78 {
79  mHeader = header;
80 
81  mScale = QgsVector3D( header.scale.x, header.scale.y, header.scale.z );
82  mOffset = QgsVector3D( header.offset.x, header.offset.y, header.offset.z );
83  mCreationYearDay = QPair<uint16_t, uint16_t>( header.creation.year, header.creation.day );
84  mVersion = QPair<uint8_t, uint8_t>( header.version.major, header.version.minor );
85  mPointFormat = header.pointFormat();
86 
87  mProjectId = QString( QByteArray( header.guid, 16 ).toHex() );
88  mSystemId = QString::fromLocal8Bit( header.system_identifier, 32 );
89  while ( !mSystemId.isEmpty() && mSystemId.back() == '\0' )
90  {
91  mSystemId.remove( mSystemId.size() - 1, 1 );
92  }
93  mSoftwareId = QString::fromLocal8Bit( header.generating_software, 32 ).trimmed();
94  while ( !mSoftwareId.isEmpty() && mSoftwareId.back() == '\0' )
95  {
96  mSoftwareId.remove( mSoftwareId.size() - 1, 1 );
97  }
98 
99  mMinCoords = QgsVector3D( header.minx, header.miny, header.minz );
100  mMaxCoords = QgsVector3D( header.maxx, header.maxy, header.maxz );
101 
102  mVlrCount = header.vlr_count;
103 
104  parseLazAttributes();
105 }
106 
107 
108 void QgsLazInfo::parseCrs()
109 {
110  // TODO: handle other kind of CRS in the laz spec
111  for ( LazVlr &vlr : mVlrVector )
112  {
113  if ( vlr.userId.trimmed() == QLatin1String( "LASF_Projection" ) && vlr.recordId == 2112 )
114  {
115  mCrs = QgsCoordinateReferenceSystem::fromWkt( QString::fromStdString( vlr.data.toStdString() ) );
116  break;
117  }
118  }
119 }
120 
121 QVariantMap QgsLazInfo::toMetadata() const
122 {
123  QVariantMap metadata;
124  metadata[ QStringLiteral( "creation_year" ) ] = mHeader.creation.year;
125  metadata[ QStringLiteral( "creation_day" ) ] = mHeader.creation.day;
126  metadata[ QStringLiteral( "major_version" ) ] = mHeader.version.major;
127  metadata[ QStringLiteral( "minor_version" ) ] = mHeader.version.minor;
128  metadata[ QStringLiteral( "dataformat_id" ) ] = mHeader.pointFormat();
129  metadata[ QStringLiteral( "scale_x" ) ] = mScale.x();
130  metadata[ QStringLiteral( "scale_y" ) ] = mScale.y();
131  metadata[ QStringLiteral( "scale_z" ) ] = mScale.z();
132  metadata[ QStringLiteral( "offset_x" ) ] = mOffset.x();
133  metadata[ QStringLiteral( "offset_y" ) ] = mOffset.y();
134  metadata[ QStringLiteral( "offset_z" ) ] = mOffset.z();
135  metadata[ QStringLiteral( "project_id" ) ] = QString( QByteArray( mHeader.guid, 16 ).toHex() );
136  metadata[ QStringLiteral( "system_id" ) ] = QString::fromLocal8Bit( mHeader.system_identifier, 32 );
137  metadata[ QStringLiteral( "software_id" ) ] = QString::fromLocal8Bit( mHeader.generating_software, 32 );
138  return metadata;
139 }
140 
141 QByteArray QgsLazInfo::vlrData( QString userId, int recordId )
142 {
143  for ( LazVlr vlr : mVlrVector )
144  {
145  if ( vlr.userId == userId && vlr.recordId == recordId )
146  {
147  return vlr.data;
148  }
149  }
150  return QByteArray();
151 }
152 
153 void QgsLazInfo::parseLazAttributes()
154 {
155  if ( mPointFormat < 0 || mPointFormat > 10 )
156  {
157  QgsDebugMsgLevel( QStringLiteral( "Invalid point record format %1" ).arg( mPointFormat ), 2 );
158  return;
159  }
163  mAttributes.push_back( QgsPointCloudAttribute( "Intensity", QgsPointCloudAttribute::UShort ) );
164  mAttributes.push_back( QgsPointCloudAttribute( "ReturnNumber", QgsPointCloudAttribute::Char ) );
165  mAttributes.push_back( QgsPointCloudAttribute( "NumberOfReturns", QgsPointCloudAttribute::Char ) );
166  mAttributes.push_back( QgsPointCloudAttribute( "ScanDirectionFlag", QgsPointCloudAttribute::Char ) );
167  mAttributes.push_back( QgsPointCloudAttribute( "EdgeOfFlightLine", QgsPointCloudAttribute::Char ) );
168  mAttributes.push_back( QgsPointCloudAttribute( "Classification", QgsPointCloudAttribute::Char ) );
169  mAttributes.push_back( QgsPointCloudAttribute( "ScanAngleRank", QgsPointCloudAttribute::Short ) );
170  mAttributes.push_back( QgsPointCloudAttribute( "UserData", QgsPointCloudAttribute::Char ) );
171  mAttributes.push_back( QgsPointCloudAttribute( "PointSourceId", QgsPointCloudAttribute::UShort ) );
172 
173  if ( mPointFormat == 6 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 9 || mPointFormat == 10 )
174  {
175  mAttributes.push_back( QgsPointCloudAttribute( "ScannerChannel", QgsPointCloudAttribute::Char ) );
176  mAttributes.push_back( QgsPointCloudAttribute( "ClassificationFlags", QgsPointCloudAttribute::Char ) );
177  }
178  if ( mPointFormat != 0 && mPointFormat != 2 )
179  {
181  }
182  if ( mPointFormat == 2 || mPointFormat == 3 || mPointFormat == 5 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 10 )
183  {
184  mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Red" ), QgsPointCloudAttribute::UShort ) );
185  mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Green" ), QgsPointCloudAttribute::UShort ) );
186  mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Blue" ), QgsPointCloudAttribute::UShort ) );
187  }
188  if ( mPointFormat == 8 || mPointFormat == 10 )
189  {
190  mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Infrared" ), QgsPointCloudAttribute::UShort ) );
191  }
192  // Note: wave packet attributes are not handled and are unreadable
193 }
194 
195 void QgsLazInfo::parseExtrabyteAttributes()
196 {
197  QByteArray ebVlrRaw = vlrData( "LASF_Spec", 4 );
198  mExtrabyteAttributes = QgsLazInfo::parseExtrabytes( ebVlrRaw.data(), ebVlrRaw.size(), mHeader.point_record_length );
199 
200  for ( QgsLazInfo::ExtraBytesAttributeDetails attr : mExtrabyteAttributes )
201  {
202  mAttributes.push_back( QgsPointCloudAttribute( attr.attribute, attr.type ) );
203  }
204 }
205 
206 QVector<QgsLazInfo::ExtraBytesAttributeDetails> QgsLazInfo::parseExtrabytes( char *rawData, int length, int pointRecordLength )
207 {
208  QVector<QgsLazInfo::ExtraBytesAttributeDetails> extrabyteAttributes;
209  lazperf::eb_vlr ebVlr;
210  ebVlr.fill( rawData, length );
211  for ( std::vector<lazperf::eb_vlr::ebfield>::reverse_iterator it = ebVlr.items.rbegin(); it != ebVlr.items.rend(); ++it )
212  {
213  lazperf::eb_vlr::ebfield &field = *it;
215  ebAtrr.attribute = QString::fromStdString( field.name );
216  switch ( field.data_type )
217  {
218  case 0:
220  ebAtrr.size = field.options;
221  break;
222  case 1:
224  ebAtrr.size = 1;
225  break;
226  case 2:
228  ebAtrr.size = 1;
229  break;
230  case 3:
232  ebAtrr.size = 2;
233  break;
234  case 4:
236  ebAtrr.size = 2;
237  break;
238  case 5:
240  ebAtrr.size = 4;
241  break;
242  case 6:
244  ebAtrr.size = 4;
245  break;
246  case 7:
248  ebAtrr.size = 8;
249  break;
250  case 8:
252  ebAtrr.size = 8;
253  break;
254  case 9:
256  ebAtrr.size = 4;
257  break;
258  case 10:
260  ebAtrr.size = 8;
261  break;
262  default:
264  ebAtrr.size = field.options;
265  break;
266  }
267  int accOffset = ( extrabyteAttributes.empty() ? pointRecordLength : extrabyteAttributes.back().offset ) - ebAtrr.size;
268  ebAtrr.offset = accOffset;
269  extrabyteAttributes.push_back( ebAtrr );
270  }
271  return extrabyteAttributes;
272 }
273 
274 QgsLazInfo QgsLazInfo::fromFile( std::ifstream &file )
275 {
276  QgsLazInfo lazInfo;
277 
278  char headerRawData[ 375 ];
279  file.seekg( 0 );
280  file.read( headerRawData, 375 );
281  lazInfo.parseRawHeader( headerRawData, 375 );
282 
283  int vlrDataSize = lazInfo.firstPointRecordOffset() - lazInfo.firstVariableLengthRecord();
284  std::unique_ptr<char[]> vlrEntriesRawData( new char[ vlrDataSize ] );
285  file.seekg( lazInfo.firstVariableLengthRecord() );
286  file.read( vlrEntriesRawData.get(), vlrDataSize );
287  lazInfo.parseRawVlrEntries( vlrEntriesRawData.get(), vlrDataSize );
288 
289  return lazInfo;
290 }
291 
293 {
294  QgsLazInfo lazInfo;
295 
296  if ( !supportsRangeQueries( url ) )
297  {
298  lazInfo.mError = QStringLiteral( "The server of submitted URL doesn't support range queries" );
299  return lazInfo;
300  }
301 
302  // Fetch header data
303  {
304  QNetworkRequest nr( url );
305  nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork );
306  nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, false );
307  nr.setRawHeader( "Range", "bytes=0-374" );
309  QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
310  if ( errCode != QgsBlockingNetworkRequest::NoError )
311  {
312  QgsDebugMsg( QStringLiteral( "Request failed: " ) + url.toString() );
313  lazInfo.mError = QStringLiteral( "Range query 0-374 to \"%1\" failed: \"%2\"" ).arg( url.toString() ).arg( req.errorMessage() );
314  return lazInfo;
315  }
316 
317  const QgsNetworkReplyContent reply = req.reply();
318  QByteArray lazHeaderData = reply.content();
319 
320  lazInfo.parseRawHeader( lazHeaderData.data(), lazHeaderData.size() );
321  }
322 
323  // Fetch VLR data
324  {
325  QNetworkRequest nr( url );
326  nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork );
327  nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, false );
328  uint32_t firstVlrOffset = lazInfo.firstVariableLengthRecord();
329  QByteArray vlrRequestRange = QStringLiteral( "bytes=%1-%2" ).arg( firstVlrOffset ).arg( lazInfo.firstPointRecordOffset() - 1 ).toLocal8Bit();
330  nr.setRawHeader( "Range", vlrRequestRange );
332  QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
333  if ( errCode != QgsBlockingNetworkRequest::NoError )
334  {
335  QgsDebugMsg( QStringLiteral( "Request failed: " ) + url.toString() );
336 
337  lazInfo.mError = QStringLiteral( "Range query %1-%2 to \"%3\" failed: \"%4\"" ).arg( firstVlrOffset ).arg( lazInfo.firstPointRecordOffset() - 1 )
338  .arg( url.toString() ).arg( req.errorMessage() );
339  return lazInfo;
340  }
341  QByteArray vlrDataRaw = req.reply().content();
342 
343  lazInfo.parseRawVlrEntries( vlrDataRaw.data(), vlrDataRaw.size() );
344  }
345 
346  return lazInfo;
347 }
348 
350 {
351  QNetworkRequest nr( url );
352  nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork );
353  nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, false );
354  nr.setRawHeader( "Range", "bytes=0-0" );
356  QgsBlockingNetworkRequest::ErrorCode errCode = req.head( nr );
357  QgsNetworkReplyContent reply = req.reply();
358  const QList<QgsNetworkReplyContent::RawHeaderPair> pairs = reply.rawHeaderPairs();
359  bool acceptsRanges = false;
360  for ( const auto &pair : pairs )
361  {
362  if ( QString::fromLocal8Bit( pair.first ).compare( QStringLiteral( "Accept-Ranges" ), Qt::CaseInsensitive ) == 0 &&
363  QString::fromLocal8Bit( pair.second ).compare( QStringLiteral( "bytes" ), Qt::CaseInsensitive ) == 0 )
364  {
365  acceptsRanges = true;
366  break;
367  }
368  }
369 
370  return errCode == QgsBlockingNetworkRequest::NoError && acceptsRanges;
371 }
QgsBlockingNetworkRequest::head
ErrorCode head(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "head" operation on the specified request.
Definition: qgsblockingnetworkrequest.cpp:77
qgslazinfo.h
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsVector3D::y
double y() const
Returns Y coordinate.
Definition: qgsvector3d.h:64
QgsVector3D
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition: qgsvector3d.h:31
qgsblockingnetworkrequest.h
QgsBlockingNetworkRequest::reply
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Definition: qgsblockingnetworkrequest.h:193
QgsLazInfo::header
lazperf::header14 header() const
Returns the LAZPERF header object.
Definition: qgslazinfo.h:127
QgsPointCloudAttribute::UInt32
@ UInt32
Unsigned int32 4 bytes.
Definition: qgspointcloudattribute.h:51
QgsLazInfo::ExtraBytesAttributeDetails::size
int size
Definition: qgslazinfo.h:52
field
const QgsField & field
Definition: qgsfield.h:463
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsLazInfo::firstVariableLengthRecord
uint32_t firstVariableLengthRecord() const
Returns the absolute offset to the first variable length record in the LAZ file.
Definition: qgslazinfo.cpp:29
QgsField::name
QString name
Definition: qgsfield.h:60
QgsPointCloudAttribute::Double
@ Double
Double 8 bytes.
Definition: qgspointcloudattribute.h:55
QgsPointCloudAttribute::Short
@ Short
Short int 2 bytes.
Definition: qgspointcloudattribute.h:48
QgsLazInfo::QgsLazInfo
QgsLazInfo()
Constructor for an empty laz info parser.
Definition: qgslazinfo.cpp:27
QgsLazInfo::ExtraBytesAttributeDetails::attribute
QString attribute
Definition: qgslazinfo.h:50
QgsBlockingNetworkRequest::ErrorCode
ErrorCode
Error codes.
Definition: qgsblockingnetworkrequest.h:52
QgsLazInfo::ExtraBytesAttributeDetails
Definition: qgslazinfo.h:48
QgsNetworkReplyContent::rawHeaderPairs
const QList< RawHeaderPair > & rawHeaderPairs() const
Returns the list of raw header pairs in the reply.
Definition: qgsnetworkreply.h:114
QgsLazInfo::ExtraBytesAttributeDetails::offset
int offset
Definition: qgslazinfo.h:53
QgsPointCloudAttribute::UInt64
@ UInt64
Unsigned int64 8 bytes.
Definition: qgspointcloudattribute.h:53
QgsPointCloudAttribute::Int32
@ Int32
Int32 4 bytes.
Definition: qgspointcloudattribute.h:50
QgsPointCloudAttribute::UShort
@ UShort
Unsigned short int 2 bytes.
Definition: qgspointcloudattribute.h:49
QgsLazInfo::supportsRangeQueries
static bool supportsRangeQueries(QUrl &url)
Static function to check whether the server of URL url supports range queries.
Definition: qgslazinfo.cpp:349
QgsVector3D::z
double z() const
Returns Z coordinate.
Definition: qgsvector3d.h:66
QgsLazInfo::toMetadata
QVariantMap toMetadata() const
Returns a map containing various metadata extracted from the LAZ file.
Definition: qgslazinfo.cpp:121
QgsBlockingNetworkRequest::NoError
@ NoError
No error was encountered.
Definition: qgsblockingnetworkrequest.h:54
QgsPointCloudAttribute::Char
@ Char
Char 1 byte.
Definition: qgspointcloudattribute.h:46
QgsBlockingNetworkRequest::errorMessage
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
Definition: qgsblockingnetworkrequest.h:188
QgsPointCloudAttribute
Attribute for point cloud data pair of name and size in bytes.
Definition: qgspointcloudattribute.h:40
QgsLazInfo::parseRawVlrEntries
void parseRawVlrEntries(char *data, uint64_t length)
Parses the variable length records found in the array data of length length.
Definition: qgslazinfo.cpp:54
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
QgsLazInfo::firstPointRecordOffset
uint32_t firstPointRecordOffset() const
Returns the absolute offset to the first point record in the LAZ file.
Definition: qgslazinfo.h:97
QgsLazInfo::fromFile
static QgsLazInfo fromFile(std::ifstream &file)
Static function to create a QgsLazInfo class from a file.
Definition: qgslazinfo.cpp:274
QgsLazInfo::LazVlr::recordId
int recordId
Definition: qgslazinfo.h:44
QgsBlockingNetworkRequest::get
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "get" operation on the specified request.
Definition: qgsblockingnetworkrequest.cpp:58
QgsNetworkReplyContent::content
QByteArray content() const
Returns the reply content.
Definition: qgsnetworkreply.h:171
QgsNetworkReplyContent
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
Definition: qgsnetworkreply.h:28
QgsLazInfo::pointRecordLength
int pointRecordLength() const
Returns the length of each point record in bytes.
Definition: qgslazinfo.h:101
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
QgsLazInfo::ExtraBytesAttributeDetails::type
QgsPointCloudAttribute::DataType type
Definition: qgslazinfo.h:51
qgslogger.h
QgsLazInfo::LazVlr::userId
QString userId
Definition: qgslazinfo.h:43
QgsPointCloudAttributeCollection::push_back
void push_back(const QgsPointCloudAttribute &attribute)
Adds extra attribute.
Definition: qgspointcloudattribute.cpp:142
QgsPointCloudAttribute::UChar
@ UChar
Unsigned char 1 byte.
Definition: qgspointcloudattribute.h:47
QgsLazInfo::fromUrl
static QgsLazInfo fromUrl(QUrl &url)
Static function to create a QgsLazInfo class from a file over network.
Definition: qgslazinfo.cpp:292
QgsLazInfo::LazVlr
Definition: qgslazinfo.h:41
QgsLazInfo::parseRawHeader
void parseRawHeader(char *data, uint64_t length)
Parses the raw header data loaded from a LAZ file.
Definition: qgslazinfo.cpp:40
QgsLazInfo::LazVlr::data
QByteArray data
Definition: qgslazinfo.h:45
QgsVector3D::x
double x() const
Returns X coordinate.
Definition: qgsvector3d.h:62
QgsLazInfo::vlrData
QByteArray vlrData(QString userId, int recordId)
Returns the binary data of the variable length record with the user identifier userId and record iden...
Definition: qgslazinfo.cpp:141
QgsCoordinateReferenceSystem::fromWkt
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
Definition: qgscoordinatereferencesystem.cpp:228
QgsPointCloudAttribute::Float
@ Float
Float 4 bytes.
Definition: qgspointcloudattribute.h:54
QgsBlockingNetworkRequest
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
Definition: qgsblockingnetworkrequest.h:46