QGIS API Documentation 3.99.0-Master (26c88405ac0)
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 "lazperf/vlr.hpp"
22#include "qgslogger.h"
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 }
160 mAttributes.push_back( QgsPointCloudAttribute( "X", QgsPointCloudAttribute::Int32 ) );
161 mAttributes.push_back( QgsPointCloudAttribute( "Y", QgsPointCloudAttribute::Int32 ) );
162 mAttributes.push_back( QgsPointCloudAttribute( "Z", QgsPointCloudAttribute::Int32 ) );
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::UChar ) );
169 mAttributes.push_back( QgsPointCloudAttribute( "ScanAngleRank", QgsPointCloudAttribute::Float ) );
170 mAttributes.push_back( QgsPointCloudAttribute( "UserData", QgsPointCloudAttribute::UChar ) );
171 mAttributes.push_back( QgsPointCloudAttribute( "PointSourceId", QgsPointCloudAttribute::UShort ) );
172 mAttributes.push_back( QgsPointCloudAttribute( "Synthetic", QgsPointCloudAttribute::UChar ) );
173 mAttributes.push_back( QgsPointCloudAttribute( "KeyPoint", QgsPointCloudAttribute::UChar ) );
174 mAttributes.push_back( QgsPointCloudAttribute( "Withheld", QgsPointCloudAttribute::UChar ) );
175 mAttributes.push_back( QgsPointCloudAttribute( "Overlap", QgsPointCloudAttribute::UChar ) );
176
177 if ( mPointFormat == 6 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 9 || mPointFormat == 10 )
178 {
179 mAttributes.push_back( QgsPointCloudAttribute( "ScannerChannel", QgsPointCloudAttribute::Char ) );
180 }
181 if ( mPointFormat != 0 && mPointFormat != 2 )
182 {
183 mAttributes.push_back( QgsPointCloudAttribute( "GpsTime", QgsPointCloudAttribute::Double ) );
184 }
185 if ( mPointFormat == 2 || mPointFormat == 3 || mPointFormat == 5 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 10 )
186 {
187 mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Red" ), QgsPointCloudAttribute::UShort ) );
188 mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Green" ), QgsPointCloudAttribute::UShort ) );
189 mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Blue" ), QgsPointCloudAttribute::UShort ) );
190 }
191 if ( mPointFormat == 8 || mPointFormat == 10 )
192 {
193 mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Infrared" ), QgsPointCloudAttribute::UShort ) );
194 }
195 // Note: wave packet attributes are not handled and are unreadable
196}
197
198void QgsLazInfo::parseExtrabyteAttributes()
199{
200 QByteArray ebVlrRaw = vlrData( "LASF_Spec", 4 );
201 mExtrabyteAttributes = QgsLazInfo::parseExtrabytes( ebVlrRaw.data(), ebVlrRaw.size(), mHeader.point_record_length );
202
203 for ( QgsLazInfo::ExtraBytesAttributeDetails attr : mExtrabyteAttributes )
204 {
205 mAttributes.push_back( QgsPointCloudAttribute( attr.attribute, attr.type ) );
206 }
207}
208
209QVector<QgsLazInfo::ExtraBytesAttributeDetails> QgsLazInfo::parseExtrabytes( char *rawData, int length, int pointRecordLength )
210{
211 QVector<QgsLazInfo::ExtraBytesAttributeDetails> extrabyteAttributes;
212 lazperf::eb_vlr ebVlr;
213 ebVlr.fill( rawData, length );
214 for ( std::vector<lazperf::eb_vlr::ebfield>::reverse_iterator it = ebVlr.items.rbegin(); it != ebVlr.items.rend(); ++it )
215 {
216 lazperf::eb_vlr::ebfield &field = *it;
218 ebAtrr.attribute = QString::fromStdString( field.name );
219 switch ( field.data_type )
220 {
221 case 0:
223 ebAtrr.size = field.options;
224 break;
225 case 1:
227 ebAtrr.size = 1;
228 break;
229 case 2:
231 ebAtrr.size = 1;
232 break;
233 case 3:
235 ebAtrr.size = 2;
236 break;
237 case 4:
239 ebAtrr.size = 2;
240 break;
241 case 5:
243 ebAtrr.size = 4;
244 break;
245 case 6:
247 ebAtrr.size = 4;
248 break;
249 case 7:
251 ebAtrr.size = 8;
252 break;
253 case 8:
255 ebAtrr.size = 8;
256 break;
257 case 9:
259 ebAtrr.size = 4;
260 break;
261 case 10:
263 ebAtrr.size = 8;
264 break;
265 default:
267 ebAtrr.size = field.options;
268 break;
269 }
270 int accOffset = ( extrabyteAttributes.empty() ? pointRecordLength : extrabyteAttributes.back().offset ) - ebAtrr.size;
271 ebAtrr.offset = accOffset;
272 extrabyteAttributes.push_back( ebAtrr );
273 }
274 return extrabyteAttributes;
275}
276
277QgsLazInfo QgsLazInfo::fromFile( std::ifstream &file )
278{
279 QgsLazInfo lazInfo;
280
281 char headerRawData[ 375 ];
282 file.seekg( 0 );
283 file.read( headerRawData, 375 );
284 lazInfo.parseRawHeader( headerRawData, 375 );
285
286 int vlrDataSize = lazInfo.firstPointRecordOffset() - lazInfo.firstVariableLengthRecord();
287 std::unique_ptr<char[]> vlrEntriesRawData( new char[ vlrDataSize ] );
288 file.seekg( lazInfo.firstVariableLengthRecord() );
289 file.read( vlrEntriesRawData.get(), vlrDataSize );
290 lazInfo.parseRawVlrEntries( vlrEntriesRawData.get(), vlrDataSize );
291
292 return lazInfo;
293}
294
295QgsLazInfo QgsLazInfo::fromUrl( QUrl &url, const QString &authcfg )
296{
297 QgsLazInfo lazInfo;
298
299 // Fetch header data
300 {
301 QNetworkRequest nr( url );
302 QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsLazInfo" ) );
303 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork );
304 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, false );
305 nr.setRawHeader( "Range", "bytes=0-374" );
307 req.setAuthCfg( authcfg );
308 QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
309 if ( errCode != QgsBlockingNetworkRequest::NoError )
310 {
311 QgsDebugError( QStringLiteral( "Request failed: " ) + url.toString() );
312
313 if ( req.reply().attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt() == 200 )
314 {
315 lazInfo.mError = req.errorMessage();
316 }
317 else
318 {
319 lazInfo.mError = QStringLiteral( "Range query 0-374 to \"%1\" failed: \"%2\"" ).arg( url.toString() ).arg( req.errorMessage() );
320 }
321 return lazInfo;
322 }
323
324 const QgsNetworkReplyContent reply = req.reply();
325
326 QByteArray lazHeaderData = reply.content();
327
328 lazInfo.parseRawHeader( lazHeaderData.data(), lazHeaderData.size() );
329
330 // If request was redirected, let's update our url for all next calls
331 const QUrl requestedUrl = reply.request().url();
332 if ( requestedUrl != url && authcfg.isEmpty() )
333 {
334 url.setUrl( requestedUrl.toString() );
335 }
336 }
337
338 // Fetch VLR data
339 {
340 QNetworkRequest nr( url );
341 QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsLazInfo" ) );
342 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork );
343 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, false );
344 uint32_t firstVlrOffset = lazInfo.firstVariableLengthRecord();
345 QByteArray vlrRequestRange = QStringLiteral( "bytes=%1-%2" ).arg( firstVlrOffset ).arg( lazInfo.firstPointRecordOffset() - 1 ).toLocal8Bit();
346 nr.setRawHeader( "Range", vlrRequestRange );
348 req.setAuthCfg( authcfg );
349 QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
350 if ( errCode != QgsBlockingNetworkRequest::NoError )
351 {
352 QgsDebugError( QStringLiteral( "Request failed: " ) + url.toString() );
353
354 lazInfo.mError = QStringLiteral( "Range query %1-%2 to \"%3\" failed: \"%4\"" ).arg( firstVlrOffset ).arg( lazInfo.firstPointRecordOffset() - 1 )
355 .arg( url.toString() ).arg( req.errorMessage() );
356 return lazInfo;
357 }
358 QByteArray vlrDataRaw = req.reply().content();
359
360 lazInfo.parseRawVlrEntries( vlrDataRaw.data(), vlrDataRaw.size() );
361 }
362
363 return lazInfo;
364}
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the request.
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ 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.
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:96
lazperf::header14 header() const
Returns the LAZPERF header object.
Definition qgslazinfo.h:126
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.
QgsLazInfo()
Constructor for an empty laz info parser.
int pointRecordLength() const
Returns the length of each point record in bytes.
Definition qgslazinfo.h:100
static QgsLazInfo fromUrl(QUrl &url, const QString &authcfg=QString())
Static function to create a QgsLazInfo class from a file over network.
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...
QVariant attribute(QNetworkRequest::Attribute code) const
Returns the attribute associated with the code.
QByteArray content() const
Returns the reply content.
QNetworkRequest request() const
Returns the original network request.
@ UShort
Unsigned short int 2 bytes.
@ UChar
Unsigned char 1 byte.
@ UInt32
Unsigned int32 4 bytes.
@ UInt64
Unsigned int64 8 bytes.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
#define QgsDebugError(str)
Definition qgslogger.h:57
#define QgsSetRequestInitiatorClass(request, _class)
QgsPointCloudAttribute::DataType type
Definition qgslazinfo.h:50
QByteArray data
Definition qgslazinfo.h:44