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