QGIS API Documentation 3.28.14-Firenze (exported)
Loading...
Searching...
No Matches
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
40void 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
54void 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
77void 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
108void 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
121QVariantMap 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
141QByteArray 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
153void QgsLazInfo::parseLazAttributes()
154{
155 if ( mPointFormat < 0 || mPointFormat > 10 )
156 {
157 QgsDebugMsgLevel( QStringLiteral( "Invalid point record format %1" ).arg( mPointFormat ), 2 );
158 return;
159 }
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::UChar ) );
169 mAttributes.push_back( QgsPointCloudAttribute( "ScanAngleRank", QgsPointCloudAttribute::Short ) );
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
195void 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
206QVector<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
274QgsLazInfo 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 // ignore the reply's status, we only care if accept-ranges is in the headers
357 req.head( nr );
358 QgsNetworkReplyContent reply = req.reply();
359
360 const QString acceptRangesHeader = reply.rawHeader( QStringLiteral( "Accept-Ranges" ).toLocal8Bit() );
361 return acceptRangesHeader.compare( QStringLiteral( "bytes" ), Qt::CaseSensitivity::CaseInsensitive ) == 0;
362}
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:60
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.
QByteArray vlrData(QString userId, int recordId)
Returns the binary data of the variable length record with the user identifier userId and record iden...
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.
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.
QVariantMap toMetadata() const
Returns a map containing various metadata extracted from the LAZ file.
void parseRawHeader(char *data, uint64_t length)
Parses the raw header data loaded from a LAZ file.
static QgsLazInfo fromFile(std::ifstream &file)
Static function to create a QgsLazInfo class from a file.
static bool supportsRangeQueries(QUrl &url)
Static function to check whether the server of URL url supports range queries.
QgsLazInfo()
Constructor for an empty laz info parser.
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.
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:476
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugMsg(str)
Definition qgslogger.h:38
QgsPointCloudAttribute::DataType type
Definition qgslazinfo.h:51
QByteArray data
Definition qgslazinfo.h:45