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