QGIS API Documentation 4.1.0-Master (376402f9aeb)
Loading...
Searching...
No Matches
qgscopcpointcloudindex.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscopcpointcloudindex.cpp
3 --------------------
4 begin : March 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
19
20#include <algorithm>
21#include <fstream>
22#include <memory>
23
24#include "lazperf/vlr.hpp"
25#include "qgsapplication.h"
26#include "qgsauthmanager.h"
27#include "qgsbox3d.h"
31#include "qgseptdecoder.h"
32#include "qgslazdecoder.h"
33#include "qgslogger.h"
34#include "qgsmessagelog.h"
37#include "qgspointcloudexpression.h"
38#include "qgspointcloudindex.h"
41
42#include <QFile>
43#include <QJsonDocument>
44#include <QJsonObject>
45#include <QMutexLocker>
46#include <QQueue>
47#include <QString>
48#include <QtDebug>
49#include <qnamespace.h>
50
51using namespace Qt::StringLiterals;
52
54
55#define PROVIDER_KEY u"copc"_s
56#define PROVIDER_DESCRIPTION u"COPC point cloud provider"_s
57
58QgsCopcPointCloudIndex::QgsCopcPointCloudIndex() = default;
59
60QgsCopcPointCloudIndex::~QgsCopcPointCloudIndex() = default;
61
62void QgsCopcPointCloudIndex::load( const QString &urlString, const QString &authcfg )
63{
64 QUrl url = urlString;
65 // Treat non-URLs as local files
66 if ( url.isValid() && ( url.scheme() == "http" || url.scheme() == "https" ) )
67 {
68 mAuthCfg = authcfg;
70 mLazInfo = std::make_unique<QgsLazInfo>( QgsLazInfo::fromUrl( url, authcfg ) );
71 // now store the uri as it might have been updated due to redirects
72 mUri = url.toString();
73 }
74 else
75 {
77 mUri = urlString;
78 mCopcFile.open( QgsLazDecoder::toNativePath( urlString ), std::ios::binary );
79 if ( mCopcFile.fail() )
80 {
81 mError = QObject::tr( "Unable to open %1 for reading" ).arg( urlString );
82 mIsValid = false;
83 return;
84 }
85 mLazInfo = std::make_unique<QgsLazInfo>( QgsLazInfo::fromFile( mCopcFile ) );
86 }
87
88 mIsValid = mLazInfo->isValid() && loadSchema( *mLazInfo.get() ) && loadHierarchy();
89 if ( !mIsValid )
90 {
91 mError = QObject::tr( "Unable to recognize %1 as a LAZ file: \"%2\"" ).arg( urlString, mLazInfo->error() );
92 }
93}
94
95bool QgsCopcPointCloudIndex::loadSchema( QgsLazInfo &lazInfo )
96{
97 QByteArray copcInfoVlrData = lazInfo.vlrData( u"copc"_s, 1 );
98 if ( copcInfoVlrData.isEmpty() )
99 {
100 mError = QObject::tr( "Invalid COPC file" );
101 return false;
102 }
103 mCopcInfoVlr.fill( copcInfoVlrData.data(), copcInfoVlrData.size() );
104
105 mScale = lazInfo.scale();
106 mOffset = lazInfo.offset();
107
108 mOriginalMetadata = lazInfo.toMetadata();
109
110 QgsVector3D minCoords = lazInfo.minCoords();
111 QgsVector3D maxCoords = lazInfo.maxCoords();
112 mExtent.set( minCoords.x(), minCoords.y(), maxCoords.x(), maxCoords.y() );
113 mZMin = minCoords.z();
114 mZMax = maxCoords.z();
115
116 setAttributes( lazInfo.attributes() );
117
118 const double xmin = mCopcInfoVlr.center_x - mCopcInfoVlr.halfsize;
119 const double ymin = mCopcInfoVlr.center_y - mCopcInfoVlr.halfsize;
120 const double zmin = mCopcInfoVlr.center_z - mCopcInfoVlr.halfsize;
121 const double xmax = mCopcInfoVlr.center_x + mCopcInfoVlr.halfsize;
122 const double ymax = mCopcInfoVlr.center_y + mCopcInfoVlr.halfsize;
123 const double zmax = mCopcInfoVlr.center_z + mCopcInfoVlr.halfsize;
124
125 mRootBounds = QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax );
126
127 // TODO: Rounding?
128 mSpan = mRootBounds.width() / mCopcInfoVlr.spacing;
129
130#ifdef QGISDEBUG
131 double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
132 QgsDebugMsgLevel( u"lvl0 node size in CRS units: %1 %2 %3"_s.arg( dx ).arg( dy ).arg( dz ), 2 ); // all dims should be the same
133 QgsDebugMsgLevel( u"res at lvl0 %1"_s.arg( dx / mSpan ), 2 );
134 QgsDebugMsgLevel( u"res at lvl1 %1"_s.arg( dx / mSpan / 2 ), 2 );
135 QgsDebugMsgLevel( u"res at lvl2 %1 with node size %2"_s.arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
136#endif
137
138 return true;
139}
140
141std::unique_ptr<QgsPointCloudBlock> QgsCopcPointCloudIndex::nodeData( QgsPointCloudNodeId n, const QgsPointCloudRequest &request )
142{
143 if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
144 {
145 return std::unique_ptr<QgsPointCloudBlock>( cached );
146 }
147
148 std::unique_ptr<QgsPointCloudBlock> block;
149 if ( mAccessType == Qgis::PointCloudAccessType::Local )
150 {
151 QByteArray rawBlockData = rawNodeData( n );
152 if ( rawBlockData.isEmpty() )
153 return nullptr; // Error fetching block
154
155 mHierarchyMutex.lock();
156 auto pointCount = mHierarchy.value( n );
157 mHierarchyMutex.unlock();
158
159 // we need to create a copy of the expression to pass to the decoder
160 // as the same QgsPointCloudExpression object mighgt be concurrently
161 // used on another thread, for example in a 3d view
162 QgsPointCloudExpression filterExpression = request.ignoreIndexFilterEnabled() ? QgsPointCloudExpression() : mFilterExpression;
163 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
164 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
165
166 QgsRectangle filterRect = request.filterRect();
167
168 block = QgsLazDecoder::decompressCopc( rawBlockData, *mLazInfo.get(), pointCount, requestAttributes, filterExpression, filterRect );
169 }
170 else
171 {
172 std::unique_ptr<QgsPointCloudBlockRequest> blockRequest( asyncNodeData( n, request ) );
173 if ( !blockRequest )
174 return nullptr;
175
176 QEventLoop loop;
177 QObject::connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
178 loop.exec();
179
180 block = blockRequest->takeBlock();
181
182 if ( !block )
183 QgsDebugError( u"Error downloading node %1 data, error : %2 "_s.arg( n.toString(), blockRequest->errorStr() ) );
184 }
185
186 storeNodeDataToCache( block.get(), n, request );
187 return block;
188}
189
190QgsPointCloudBlockRequest *QgsCopcPointCloudIndex::asyncNodeData( QgsPointCloudNodeId n, const QgsPointCloudRequest &request )
191{
192 if ( mAccessType == Qgis::PointCloudAccessType::Local )
193 return nullptr; // TODO
194 if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
195 {
196 return new QgsCachedPointCloudBlockRequest( cached, n, mUri, attributes(), request.attributes(), scale(), offset(), mFilterExpression, request.filterRect() );
197 }
198
199 if ( !fetchNodeHierarchy( n ) )
200 return nullptr;
201
202 // we need to create a copy of the expression to pass to the decoder
203 // as the same QgsPointCloudExpression object might be concurrently
204 // used on another thread, for example in a 3d view
205 QgsPointCloudExpression filterExpression = request.ignoreIndexFilterEnabled() ? QgsPointCloudExpression() : mFilterExpression;
206 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
207 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
208
209 mHierarchyMutex.lock();
210 auto [blockOffset, blockSize] = mHierarchyNodePos.value( n );
211 int pointCount = mHierarchy.value( n );
212 mHierarchyMutex.unlock();
213
214 return new QgsCopcPointCloudBlockRequest( n, mUri, attributes(), requestAttributes, scale(), offset(), filterExpression, request.filterRect(), blockOffset, blockSize, pointCount, *mLazInfo.get(), mAuthCfg );
215}
216
217
218const QByteArray QgsCopcPointCloudIndex::rawNodeData( QgsPointCloudNodeId n ) const
219{
220 const bool found = fetchNodeHierarchy( n );
221 if ( !found )
222 return {};
223 mHierarchyMutex.lock();
224 auto [blockOffset, blockSize] = mHierarchyNodePos.value( n );
225 mHierarchyMutex.unlock();
226
227 if ( mAccessType == Qgis::PointCloudAccessType::Local )
228 {
229 // Open a new file descriptor so we can read multiple blocks concurrently
230 QByteArray rawBlockData( blockSize, Qt::Initialization::Uninitialized );
231 std::ifstream file( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
232 file.seekg( blockOffset );
233 file.read( rawBlockData.data(), blockSize );
234 if ( !file )
235 {
236 QgsDebugError( u"Could not read file %1"_s.arg( mUri ) );
237 return {};
238 }
239 return rawBlockData;
240 }
241 else
242 return readRange( blockOffset, blockSize );
243}
244
245QgsCoordinateReferenceSystem QgsCopcPointCloudIndex::crs() const
246{
247 return mLazInfo->crs();
248}
249
250qint64 QgsCopcPointCloudIndex::pointCount() const
251{
252 return mLazInfo->pointCount();
253}
254
255bool QgsCopcPointCloudIndex::loadHierarchy() const
256{
257 fetchHierarchyPage( mCopcInfoVlr.root_hier_offset, mCopcInfoVlr.root_hier_size );
258 return true;
259}
260
261bool QgsCopcPointCloudIndex::writeStatistics( QgsPointCloudStatistics &stats )
262{
263 if ( mAccessType == Qgis::PointCloudAccessType::Remote )
264 {
265 QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to remote file \"%1\"" ).arg( mUri ) );
266 return false;
267 }
268
269 if ( mLazInfo->version() != qMakePair<uint8_t, uint8_t>( 1, 4 ) )
270 {
271 // EVLR isn't supported in the first place
272 QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to \"%1\": laz version != 1.4" ).arg( mUri ) );
273 return false;
274 }
275
276 QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
277 if ( !statisticsEvlrData.isEmpty() )
278 {
279 QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to \"%1\": file already contains COPC statistics!" ).arg( mUri ) );
280 return false;
281 }
282
283 lazperf::evlr_header statsEvlrHeader;
284 statsEvlrHeader.user_id = "qgis";
285 statsEvlrHeader.reserved = 0;
286 statsEvlrHeader.record_id = 0;
287 statsEvlrHeader.description = "Contains calculated statistics";
288 QByteArray statsJson = stats.toStatisticsJson();
289 statsEvlrHeader.data_length = statsJson.size();
290
291 // Save the EVLRs to the end of the original file (while erasing the existing EVLRs in the file)
292 QMutexLocker locker( &mFileMutex );
293 mCopcFile.close();
294 std::fstream copcFile;
295 copcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios_base::binary | std::iostream::in | std::iostream::out );
296 if ( copcFile.is_open() && copcFile.good() )
297 {
298 // Write the new number of EVLRs
299 lazperf::header14 header = mLazInfo->header();
300 header.evlr_count = header.evlr_count + 1;
301 copcFile.seekp( 0 );
302 header.write( copcFile );
303
304 // Append EVLR data to the end
305 copcFile.seekg( 0, std::ios::end );
306
307 statsEvlrHeader.write( copcFile );
308 copcFile.write( statsJson.data(), statsEvlrHeader.data_length );
309 }
310 else
311 {
312 QgsMessageLog::logMessage( QObject::tr( "Couldn't open COPC file \"%1\" to write statistics" ).arg( mUri ) );
313 return false;
314 }
315 copcFile.close();
316 mCopcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
317 return true;
318}
319
320QgsPointCloudStatistics QgsCopcPointCloudIndex::metadataStatistics() const
321{
322 if ( !mStatistics )
323 {
324 const QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
325 if ( statisticsEvlrData.isEmpty() )
327 else
328 mStatistics = QgsPointCloudStatistics::fromStatisticsJson( statisticsEvlrData );
329 }
330
331 return *mStatistics;
332}
333
334bool QgsCopcPointCloudIndex::isValid() const
335{
336 return mIsValid;
337}
338
339bool QgsCopcPointCloudIndex::fetchNodeHierarchy( QgsPointCloudNodeId n ) const
340{
341 QMutexLocker locker( &mHierarchyMutex );
342
343 QVector<QgsPointCloudNodeId> ancestors;
344 QgsPointCloudNodeId foundRoot = n;
345 while ( !mHierarchy.contains( foundRoot ) )
346 {
347 ancestors.push_front( foundRoot );
348 foundRoot = foundRoot.parentNode();
349 }
350 ancestors.push_front( foundRoot );
351 for ( QgsPointCloudNodeId n : ancestors )
352 {
353 auto hierarchyIt = mHierarchy.constFind( n );
354 if ( hierarchyIt == mHierarchy.constEnd() )
355 return false;
356 int nodesCount = *hierarchyIt;
357 if ( nodesCount < 0 )
358 {
359 auto hierarchyNodePos = mHierarchyNodePos.constFind( n );
360 mHierarchyMutex.unlock();
361 fetchHierarchyPage( hierarchyNodePos->first, hierarchyNodePos->second );
362 mHierarchyMutex.lock();
363 }
364 }
365 return mHierarchy.contains( n );
366}
367
368void QgsCopcPointCloudIndex::fetchHierarchyPage( uint64_t offset, uint64_t byteSize ) const
369{
370 Q_ASSERT( byteSize > 0 );
371
372 QByteArray data = readRange( offset, byteSize );
373 if ( data.isEmpty() )
374 return;
375
376 populateHierarchy( data.constData(), byteSize );
377}
378
379void QgsCopcPointCloudIndex::populateHierarchy( const char *hierarchyPageData, uint64_t byteSize ) const
380{
381 struct CopcVoxelKey
382 {
383 int32_t level;
384 int32_t x;
385 int32_t y;
386 int32_t z;
387 };
388
389 struct CopcEntry
390 {
391 CopcVoxelKey key;
392 uint64_t offset;
393 int32_t byteSize;
394 int32_t pointCount;
395 };
396
397 QMutexLocker locker( &mHierarchyMutex );
398
399 for ( uint64_t i = 0; i < byteSize; i += sizeof( CopcEntry ) )
400 {
401 const CopcEntry *entry = reinterpret_cast<const CopcEntry *>( hierarchyPageData + i );
402 const QgsPointCloudNodeId nodeId( entry->key.level, entry->key.x, entry->key.y, entry->key.z );
403 mHierarchy[nodeId] = entry->pointCount;
404 mHierarchyNodePos.insert( nodeId, QPair<uint64_t, int32_t>( entry->offset, entry->byteSize ) );
405 }
406}
407
408bool QgsCopcPointCloudIndex::hasNode( QgsPointCloudNodeId n ) const
409{
410 return fetchNodeHierarchy( n );
411}
412
413QgsPointCloudNode QgsCopcPointCloudIndex::getNode( QgsPointCloudNodeId id ) const
414{
415 bool nodeFound = fetchNodeHierarchy( id );
416 Q_ASSERT( nodeFound );
417
418 qint64 pointCount;
419 {
420 QMutexLocker locker( &mHierarchyMutex );
421 pointCount = mHierarchy.value( id, -1 );
422 }
423
424 QList<QgsPointCloudNodeId> children = id.childrenNodes();
425 children.erase(
426 std::remove_if(
427 children.begin(),
428 children.end(),
429 [this]( const QgsPointCloudNodeId &c ) {
430 const bool found = fetchNodeHierarchy( c );
431 QMutexLocker locker( &mHierarchyMutex );
432 const bool shouldRemove = !found || mHierarchy[c] < 0;
433 return shouldRemove;
434 }
435 ),
436 children.end()
437 );
438
439 QgsBox3D bounds = QgsPointCloudNode::bounds( mRootBounds, id );
440 return QgsPointCloudNode( id, pointCount, children, bounds.width() / mSpan, bounds );
441}
442
443QByteArray QgsCopcPointCloudIndex::readRange( uint64_t offset, uint64_t length ) const
444{
445 if ( mAccessType == Qgis::PointCloudAccessType::Local )
446 {
447 QMutexLocker locker( &mFileMutex );
448
449 QByteArray buffer( length, Qt::Initialization::Uninitialized );
450 mCopcFile.seekg( offset );
451 mCopcFile.read( buffer.data(), length );
452 if ( mCopcFile.eof() )
453 QgsDebugError( u"Read past end of file (path %1 offset %2 length %3)"_s.arg( mUri ).arg( offset ).arg( length ) );
454 if ( !mCopcFile )
455 QgsDebugError( u"Error reading %1"_s.arg( mUri ) );
456 return buffer;
457 }
458 else
459 {
460 QNetworkRequest nr = QNetworkRequest( QUrl( mUri ) );
461 QgsSetRequestInitiatorClass( nr, u"QgsCopcPointCloudIndex"_s );
462 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
463 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
464 QByteArray queryRange = u"bytes=%1-%2"_s.arg( offset ).arg( offset + length - 1 ).toLocal8Bit();
465 nr.setRawHeader( "Range", queryRange );
466
467 if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( nr, mAuthCfg ) )
468 {
469 QgsDebugError( u"Network request update failed for authcfg: %1"_s.arg( mAuthCfg ) );
470 return {};
471 }
472
473 std::unique_ptr<QgsTileDownloadManagerReply> reply( QgsApplication::tileDownloadManager()->get( nr ) );
474
475 QEventLoop loop;
476 QObject::connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit );
477 loop.exec();
478
479 if ( reply->error() != QNetworkReply::NoError )
480 {
481 QgsDebugError( u"Request failed: %1 (offset %1 length %2)"_s.arg( mUri ).arg( offset ).arg( length ) );
482 return {};
483 }
484
485 return reply->data();
486 }
487}
488
489QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData() const
490{
491 uint64_t offset = mLazInfo->firstEvlrOffset();
492 uint32_t evlrCount = mLazInfo->evlrCount();
493
494 QByteArray statisticsEvlrData;
495
496 for ( uint32_t i = 0; i < evlrCount; ++i )
497 {
498 lazperf::evlr_header header;
499
500 QByteArray buffer = readRange( offset, 60 );
501 header.fill( buffer.data(), buffer.size() );
502
503 if ( header.user_id == "qgis" && header.record_id == 0 )
504 {
505 statisticsEvlrData = readRange( offset + 60, header.data_length );
506 break;
507 }
508
509 offset += 60 + header.data_length;
510 }
511
512 return statisticsEvlrData;
513}
514
515void QgsCopcPointCloudIndex::reset()
516{
517 // QgsAbstractPointCloudIndex
518 mExtent = QgsRectangle();
519 mZMin = 0;
520 mZMax = 0;
521 mHierarchy.clear();
522 mScale = QgsVector3D();
523 mOffset = QgsVector3D();
524 mRootBounds = QgsBox3D();
525 mAttributes = QgsPointCloudAttributeCollection();
526 mSpan = 0;
527 mError.clear();
528
529 // QgsCopcPointCloudIndex
530 mIsValid = false;
532 mCopcFile.close();
533 mOriginalMetadata.clear();
534 mStatistics.reset();
535 mLazInfo.reset();
536 mHierarchyNodePos.clear();
537}
538
539QVariantMap QgsCopcPointCloudIndex::extraMetadata() const
540{
541 return {
542 { u"CopcGpsTimeFlag"_s, mLazInfo.get()->header().global_encoding & 1 },
543 };
544}
545
546bool QgsCopcPointCloudIndex::needsHierarchyFetching( const QgsPointCloudNodeId &n ) const
547{
548 QMutexLocker locker( &mHierarchyMutex );
549
550 auto hierarchyIt = mHierarchy.constFind( n );
551 if ( hierarchyIt == mHierarchy.constEnd() )
552 return false;
553 const int pointsCount = *hierarchyIt;
554 if ( pointsCount < 0 )
555 return true;
556
557 const QVector<QgsPointCloudNodeId> children = n.childrenNodes();
558 for ( const QgsPointCloudNodeId &ch : children )
559 {
560 auto hierarchyIt = mHierarchy.constFind( ch );
561 if ( hierarchyIt == mHierarchy.constEnd() )
562 continue;
563 const int pointsCount = *hierarchyIt;
564 if ( pointsCount < 0 )
565 return true;
566 }
567
568 for ( const QgsPointCloudNodeId &ch : children )
569 {
570 const QVector<QgsPointCloudNodeId> grandChildren = ch.childrenNodes();
571 for ( const QgsPointCloudNodeId &gch : grandChildren )
572 {
573 auto hierarchyIt = mHierarchy.constFind( gch );
574 if ( hierarchyIt == mHierarchy.constEnd() )
575 continue;
576 const int pointsCount = *hierarchyIt;
577 if ( pointsCount < 0 )
578 return true;
579 }
580 }
581
582 return false;
583}
584
@ Local
Local means the source is a local file on the machine.
Definition qgis.h:6589
@ Remote
Remote means it's loaded through a protocol like HTTP.
Definition qgis.h:6590
virtual QgsPointCloudStatistics metadataStatistics() const
Returns the object containing the statistics metadata extracted from the dataset.
static QgsTileDownloadManager * tileDownloadManager()
Returns the application's tile download manager, used for download of map tiles when rendering.
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
double width() const
Returns the width of the box.
Definition qgsbox3d.h:287
Handles a QgsPointCloudBlockRequest using existing cached QgsPointCloudBlock.
Represents a coordinate reference system (CRS).
Base class for handling loading QgsPointCloudBlock asynchronously from a remote COPC dataset.
Extracts information contained in a LAZ file, such as the public header block and variable length rec...
Definition qgslazinfo.h:38
QgsVector3D maxCoords() const
Returns the maximum coordinate across X, Y and Z axis.
Definition qgslazinfo.h:91
QgsPointCloudAttributeCollection attributes() const
Returns the list of attributes contained in the LAZ file.
Definition qgslazinfo.h:116
QByteArray vlrData(QString userId, int recordId)
Returns the binary data of the variable length record with the user identifier userId and record iden...
QVariantMap toMetadata() const
Returns a map containing various metadata extracted from the LAZ file.
QgsVector3D scale() const
Returns the scale of the points coordinates.
Definition qgslazinfo.h:73
static QgsLazInfo fromFile(std::ifstream &file)
Static function to create a QgsLazInfo class from a file.
QgsVector3D minCoords() const
Returns the minimum coordinate across X, Y and Z axis.
Definition qgslazinfo.h:89
QgsVector3D offset() const
Returns the offset of the points coordinates.
Definition qgslazinfo.h:75
static QgsLazInfo fromUrl(QUrl &url, const QString &authcfg=QString())
Static function to create a QgsLazInfo class from a file over network.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
A collection of point cloud attributes.
void extend(const QgsPointCloudAttributeCollection &otherCollection, const QSet< QString > &matchingNames)
Adds specific missing attributes from another QgsPointCloudAttributeCollection.
Base class for handling loading QgsPointCloudBlock asynchronously.
void finished()
Emitted when the request processing has finished.
Base class for storing raw data from point cloud nodes.
Represents an indexed point cloud node's position in octree.
QString toString() const
Encode node to string.
QgsPointCloudNodeId parentNode() const
Returns the parent of the node.
QVector< QgsPointCloudNodeId > childrenNodes() const
Returns the node's 8 direct child nodes.
Keeps metadata for an indexed point cloud node.
QgsBox3D bounds() const
Returns node's bounding cube in CRS coords.
Point cloud data request.
bool ignoreIndexFilterEnabled() const
Returns whether the request will ignore the point cloud index's filter expression,...
QgsPointCloudAttributeCollection attributes() const
Returns attributes.
QgsRectangle filterRect() const
Returns the rectangle from which points will be taken, in point cloud's crs.
Used to store statistics of a point cloud dataset.
static QgsPointCloudStatistics fromStatisticsJson(const QByteArray &stats)
Creates a statistics object from the JSON object stats.
QByteArray toStatisticsJson() const
Converts the current statistics object into JSON object.
A rectangle specified with double values.
void finished()
Emitted when the reply has finished (either with a success or with a failure).
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
void set(double x, double y, double z)
Sets vector coordinates.
Definition qgsvector3d.h:83
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
#define QgsSetRequestInitiatorClass(request, _class)