QGIS API Documentation 3.34.0-Prizren (ffbdd678812)
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"
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.
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.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:32
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
#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