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