QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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#include <QString>
26
27using namespace Qt::StringLiterals;
28
29// QgsLazInfo
30
33
35{
36 if ( mVersion.first == 1 && mVersion.second == 4 )
37 return 375;
38 if ( mVersion.first == 1 && mVersion.second == 3 )
39 return 235;
40 if ( mVersion.first == 1 && mVersion.second <= 2 )
41 return 227;
42 return 0;
43}
44
45void QgsLazInfo::parseRawHeader( char *data, uint64_t length )
46{
47 mIsValid = true;
48 if ( std::string( data, 4 ) != "LASF" )
49 {
50 mError = u"Supplied header is not from a LAZ file"_s;
51 mIsValid = false;
52 return;
53 }
54 std::istringstream file( std::string( data, length ) );
55 lazperf::header14 header = lazperf::header14::create( file );
56 parseHeader( header );
57}
58
59void QgsLazInfo::parseRawVlrEntries( char *data, uint64_t length )
60{
61 if ( !mIsValid )
62 return;
63 uint64_t currentOffset = 0;
64 for ( uint64_t i = 0; i < ( uint64_t ) mVlrCount && currentOffset < length; ++i )
65 {
66 lazperf::vlr_header vlrHeader;
67 vlrHeader.fill( data + currentOffset, 54 );
68
69 LazVlr vlr;
70 vlr.userId = QString::fromStdString( vlrHeader.user_id );
71 vlr.recordId = vlrHeader.record_id;
72 vlr.data = QByteArray( data + currentOffset + 54, vlrHeader.data_length );
73 mVlrVector.push_back( vlr );
74 currentOffset += 54 + vlrHeader.data_length;
75 }
76
77 parseCrs();
78 parseExtrabyteAttributes();
79}
80
81
82void QgsLazInfo::parseHeader( lazperf::header14 &header )
83{
84 mHeader = header;
85
86 mScale = QgsVector3D( header.scale.x, header.scale.y, header.scale.z );
87 mOffset = QgsVector3D( header.offset.x, header.offset.y, header.offset.z );
88 mCreationYearDay = QPair<uint16_t, uint16_t>( header.creation.year, header.creation.day );
89 mVersion = QPair<uint8_t, uint8_t>( header.version.major, header.version.minor );
90 mPointFormat = header.pointFormat();
91
92 mProjectId = QString( QByteArray( header.guid, 16 ).toHex() );
93 mSystemId = QString::fromLocal8Bit( header.system_identifier, 32 );
94 while ( !mSystemId.isEmpty() && mSystemId.back() == '\0' )
95 {
96 mSystemId.remove( mSystemId.size() - 1, 1 );
97 }
98 mSoftwareId = QString::fromLocal8Bit( header.generating_software, 32 ).trimmed();
99 while ( !mSoftwareId.isEmpty() && mSoftwareId.back() == '\0' )
100 {
101 mSoftwareId.remove( mSoftwareId.size() - 1, 1 );
102 }
103
104 mMinCoords = QgsVector3D( header.minx, header.miny, header.minz );
105 mMaxCoords = QgsVector3D( header.maxx, header.maxy, header.maxz );
106
107 mVlrCount = header.vlr_count;
108
109 parseLazAttributes();
110}
111
112
113void QgsLazInfo::parseCrs()
114{
115 // TODO: handle other kind of CRS in the laz spec
116 for ( LazVlr &vlr : mVlrVector )
117 {
118 if ( vlr.userId.trimmed() == "LASF_Projection"_L1 && vlr.recordId == 2112 )
119 {
120 mCrs = QgsCoordinateReferenceSystem::fromWkt( QString::fromStdString( vlr.data.toStdString() ) );
121 break;
122 }
123 }
124}
125
126QVariantMap QgsLazInfo::toMetadata() const
127{
128 QVariantMap metadata;
129 metadata[u"creation_year"_s] = mHeader.creation.year;
130 metadata[u"creation_day"_s] = mHeader.creation.day;
131 metadata[u"major_version"_s] = mHeader.version.major;
132 metadata[u"minor_version"_s] = mHeader.version.minor;
133 metadata[u"dataformat_id"_s] = mHeader.pointFormat();
134 metadata[u"scale_x"_s] = mScale.x();
135 metadata[u"scale_y"_s] = mScale.y();
136 metadata[u"scale_z"_s] = mScale.z();
137 metadata[u"offset_x"_s] = mOffset.x();
138 metadata[u"offset_y"_s] = mOffset.y();
139 metadata[u"offset_z"_s] = mOffset.z();
140 metadata[u"project_id"_s] = QString( QByteArray( mHeader.guid, 16 ).toHex() );
141 metadata[u"system_id"_s] = QString::fromLocal8Bit( mHeader.system_identifier, 32 );
142 metadata[u"software_id"_s] = QString::fromLocal8Bit( mHeader.generating_software, 32 );
143 return metadata;
144}
145
146QByteArray QgsLazInfo::vlrData( QString userId, int recordId )
147{
148 for ( LazVlr vlr : mVlrVector )
149 {
150 if ( vlr.userId == userId && vlr.recordId == recordId )
151 {
152 return vlr.data;
153 }
154 }
155 return QByteArray();
156}
157
158void QgsLazInfo::parseLazAttributes()
159{
160 if ( mPointFormat < 0 || mPointFormat > 10 )
161 {
162 QgsDebugMsgLevel( u"Invalid point record format %1"_s.arg( mPointFormat ), 2 );
163 return;
164 }
165 mAttributes.push_back( QgsPointCloudAttribute( "X", QgsPointCloudAttribute::Int32 ) );
166 mAttributes.push_back( QgsPointCloudAttribute( "Y", QgsPointCloudAttribute::Int32 ) );
167 mAttributes.push_back( QgsPointCloudAttribute( "Z", QgsPointCloudAttribute::Int32 ) );
168 mAttributes.push_back( QgsPointCloudAttribute( "Intensity", QgsPointCloudAttribute::UShort ) );
169 mAttributes.push_back( QgsPointCloudAttribute( "ReturnNumber", QgsPointCloudAttribute::Char ) );
170 mAttributes.push_back( QgsPointCloudAttribute( "NumberOfReturns", QgsPointCloudAttribute::Char ) );
171 mAttributes.push_back( QgsPointCloudAttribute( "ScanDirectionFlag", QgsPointCloudAttribute::Char ) );
172 mAttributes.push_back( QgsPointCloudAttribute( "EdgeOfFlightLine", QgsPointCloudAttribute::Char ) );
173 mAttributes.push_back( QgsPointCloudAttribute( "Classification", QgsPointCloudAttribute::UChar ) );
174 mAttributes.push_back( QgsPointCloudAttribute( "ScanAngleRank", QgsPointCloudAttribute::Float ) );
175 mAttributes.push_back( QgsPointCloudAttribute( "UserData", QgsPointCloudAttribute::UChar ) );
176 mAttributes.push_back( QgsPointCloudAttribute( "PointSourceId", QgsPointCloudAttribute::UShort ) );
177 mAttributes.push_back( QgsPointCloudAttribute( "Synthetic", QgsPointCloudAttribute::UChar ) );
178 mAttributes.push_back( QgsPointCloudAttribute( "KeyPoint", QgsPointCloudAttribute::UChar ) );
179 mAttributes.push_back( QgsPointCloudAttribute( "Withheld", QgsPointCloudAttribute::UChar ) );
180 mAttributes.push_back( QgsPointCloudAttribute( "Overlap", QgsPointCloudAttribute::UChar ) );
181
182 if ( mPointFormat == 6 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 9 || mPointFormat == 10 )
183 {
184 mAttributes.push_back( QgsPointCloudAttribute( "ScannerChannel", QgsPointCloudAttribute::Char ) );
185 }
186 if ( mPointFormat != 0 && mPointFormat != 2 )
187 {
188 mAttributes.push_back( QgsPointCloudAttribute( "GpsTime", QgsPointCloudAttribute::Double ) );
189 }
190 if ( mPointFormat == 2 || mPointFormat == 3 || mPointFormat == 5 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 10 )
191 {
192 mAttributes.push_back( QgsPointCloudAttribute( u"Red"_s, QgsPointCloudAttribute::UShort ) );
193 mAttributes.push_back( QgsPointCloudAttribute( u"Green"_s, QgsPointCloudAttribute::UShort ) );
194 mAttributes.push_back( QgsPointCloudAttribute( u"Blue"_s, QgsPointCloudAttribute::UShort ) );
195 }
196 if ( mPointFormat == 8 || mPointFormat == 10 )
197 {
198 mAttributes.push_back( QgsPointCloudAttribute( u"Infrared"_s, QgsPointCloudAttribute::UShort ) );
199 }
200 // Note: wave packet attributes are not handled and are unreadable
201}
202
203void QgsLazInfo::parseExtrabyteAttributes()
204{
205 QByteArray ebVlrRaw = vlrData( "LASF_Spec", 4 );
206 mExtrabyteAttributes = QgsLazInfo::parseExtrabytes( ebVlrRaw.data(), ebVlrRaw.size(), mHeader.point_record_length );
207
208 for ( QgsLazInfo::ExtraBytesAttributeDetails attr : mExtrabyteAttributes )
209 {
210 mAttributes.push_back( QgsPointCloudAttribute( attr.attribute, attr.type ) );
211 }
212}
213
214QVector<QgsLazInfo::ExtraBytesAttributeDetails> QgsLazInfo::parseExtrabytes( char *rawData, int length, int pointRecordLength )
215{
216 QVector<QgsLazInfo::ExtraBytesAttributeDetails> extrabyteAttributes;
217 lazperf::eb_vlr ebVlr;
218 ebVlr.fill( rawData, length );
219 for ( std::vector<lazperf::eb_vlr::ebfield>::reverse_iterator it = ebVlr.items.rbegin(); it != ebVlr.items.rend(); ++it )
220 {
221 lazperf::eb_vlr::ebfield &field = *it;
223 ebAtrr.attribute = QString::fromStdString( field.name );
224 switch ( field.data_type )
225 {
226 case 0:
228 ebAtrr.size = field.options;
229 break;
230 case 1:
232 ebAtrr.size = 1;
233 break;
234 case 2:
236 ebAtrr.size = 1;
237 break;
238 case 3:
240 ebAtrr.size = 2;
241 break;
242 case 4:
244 ebAtrr.size = 2;
245 break;
246 case 5:
248 ebAtrr.size = 4;
249 break;
250 case 6:
252 ebAtrr.size = 4;
253 break;
254 case 7:
256 ebAtrr.size = 8;
257 break;
258 case 8:
260 ebAtrr.size = 8;
261 break;
262 case 9:
264 ebAtrr.size = 4;
265 break;
266 case 10:
268 ebAtrr.size = 8;
269 break;
270 default:
272 ebAtrr.size = field.options;
273 break;
274 }
275 int accOffset = ( extrabyteAttributes.empty() ? pointRecordLength : extrabyteAttributes.back().offset ) - ebAtrr.size;
276 ebAtrr.offset = accOffset;
277 extrabyteAttributes.push_back( ebAtrr );
278 }
279 return extrabyteAttributes;
280}
281
282QgsLazInfo QgsLazInfo::fromFile( std::ifstream &file )
283{
284 QgsLazInfo lazInfo;
285
286 char headerRawData[375];
287 file.seekg( 0 );
288 file.read( headerRawData, 375 );
289 lazInfo.parseRawHeader( headerRawData, 375 );
290
291 int vlrDataSize = lazInfo.firstPointRecordOffset() - lazInfo.firstVariableLengthRecord();
292 std::unique_ptr<char[]> vlrEntriesRawData( new char[vlrDataSize] );
293 file.seekg( lazInfo.firstVariableLengthRecord() );
294 file.read( vlrEntriesRawData.get(), vlrDataSize );
295 lazInfo.parseRawVlrEntries( vlrEntriesRawData.get(), vlrDataSize );
296
297 return lazInfo;
298}
299
300QgsLazInfo QgsLazInfo::fromUrl( QUrl &url, const QString &authcfg )
301{
302 QgsLazInfo lazInfo;
303
304 // Fetch header data
305 {
306 QNetworkRequest nr( url );
307 QgsSetRequestInitiatorClass( nr, u"QgsLazInfo"_s );
308 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork );
309 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, false );
310 nr.setRawHeader( "Range", "bytes=0-374" );
312 req.setAuthCfg( authcfg );
313 QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
314 if ( errCode != QgsBlockingNetworkRequest::NoError )
315 {
316 QgsDebugError( u"Request failed: "_s + url.toString() );
317
318 if ( req.reply().attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt() == 200 )
319 {
320 lazInfo.mError = req.errorMessage();
321 }
322 else
323 {
324 lazInfo.mError = u"Range query 0-374 to \"%1\" failed: \"%2\""_s.arg( url.toString() ).arg( req.errorMessage() );
325 }
326 return lazInfo;
327 }
328
329 const QgsNetworkReplyContent reply = req.reply();
330
331 QByteArray lazHeaderData = reply.content();
332
333 lazInfo.parseRawHeader( lazHeaderData.data(), lazHeaderData.size() );
334
335 // If request was redirected, let's update our url for all next calls
336 const QUrl requestedUrl = reply.request().url();
337 if ( requestedUrl != url && authcfg.isEmpty() )
338 {
339 url.setUrl( requestedUrl.toString() );
340 }
341 }
342
343 // Fetch VLR data
344 {
345 QNetworkRequest nr( url );
346 QgsSetRequestInitiatorClass( nr, u"QgsLazInfo"_s );
347 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork );
348 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, false );
349 uint32_t firstVlrOffset = lazInfo.firstVariableLengthRecord();
350 QByteArray vlrRequestRange = u"bytes=%1-%2"_s.arg( firstVlrOffset ).arg( lazInfo.firstPointRecordOffset() - 1 ).toLocal8Bit();
351 nr.setRawHeader( "Range", vlrRequestRange );
353 req.setAuthCfg( authcfg );
354 QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
355 if ( errCode != QgsBlockingNetworkRequest::NoError )
356 {
357 QgsDebugError( u"Request failed: "_s + url.toString() );
358
359 lazInfo.mError = u"Range query %1-%2 to \"%3\" failed: \"%4\""_s.arg( firstVlrOffset ).arg( lazInfo.firstPointRecordOffset() - 1 ).arg( url.toString() ).arg( req.errorMessage() );
360 return lazInfo;
361 }
362 QByteArray vlrDataRaw = req.reply().content();
363
364 lazInfo.parseRawVlrEntries( vlrDataRaw.data(), vlrDataRaw.size() );
365 }
366
367 return lazInfo;
368}
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:93
lazperf::header14 header() const
Returns the LAZPERF header object.
Definition qgslazinfo.h:123
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:97
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:33
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
#define QgsSetRequestInitiatorClass(request, _class)
QgsPointCloudAttribute::DataType type
Definition qgslazinfo.h:50
QByteArray data
Definition qgslazinfo.h:44