QGIS API Documentation 3.99.0-Master (09f76ad7019)
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 <fstream>
21#include <memory>
22
23#include "lazperf/vlr.hpp"
24#include "qgsapplication.h"
25#include "qgsauthmanager.h"
26#include "qgsbox3d.h"
30#include "qgseptdecoder.h"
31#include "qgslazdecoder.h"
32#include "qgslogger.h"
33#include "qgsmessagelog.h"
36#include "qgspointcloudexpression.h"
37#include "qgspointcloudindex.h"
40
41#include <QFile>
42#include <QJsonDocument>
43#include <QJsonObject>
44#include <QMutexLocker>
45#include <QQueue>
46#include <QString>
47#include <QtDebug>
48#include <qnamespace.h>
49
50using namespace Qt::StringLiterals;
51
53
54#define PROVIDER_KEY u"copc"_s
55#define PROVIDER_DESCRIPTION u"COPC point cloud provider"_s
56
57QgsCopcPointCloudIndex::QgsCopcPointCloudIndex() = default;
58
59QgsCopcPointCloudIndex::~QgsCopcPointCloudIndex() = default;
60
61void QgsCopcPointCloudIndex::load( const QString &urlString, const QString &authcfg )
62{
63 QUrl url = urlString;
64 // Treat non-URLs as local files
65 if ( url.isValid() && ( url.scheme() == "http" || url.scheme() == "https" ) )
66 {
67 mAuthCfg = authcfg;
69 mLazInfo = std::make_unique<QgsLazInfo>( QgsLazInfo::fromUrl( url, authcfg ) );
70 // now store the uri as it might have been updated due to redirects
71 mUri = url.toString();
72 }
73 else
74 {
76 mUri = urlString;
77 mCopcFile.open( QgsLazDecoder::toNativePath( urlString ), std::ios::binary );
78 if ( mCopcFile.fail() )
79 {
80 mError = QObject::tr( "Unable to open %1 for reading" ).arg( urlString );
81 mIsValid = false;
82 return;
83 }
84 mLazInfo = std::make_unique<QgsLazInfo>( QgsLazInfo::fromFile( mCopcFile ) );
85 }
86
87 mIsValid = mLazInfo->isValid() && loadSchema( *mLazInfo.get() ) && loadHierarchy();
88 if ( !mIsValid )
89 {
90 mError = QObject::tr( "Unable to recognize %1 as a LAZ file: \"%2\"" ).arg( urlString, mLazInfo->error() );
91 }
92}
93
94bool QgsCopcPointCloudIndex::loadSchema( QgsLazInfo &lazInfo )
95{
96 QByteArray copcInfoVlrData = lazInfo.vlrData( u"copc"_s, 1 );
97 if ( copcInfoVlrData.isEmpty() )
98 {
99 mError = QObject::tr( "Invalid COPC file" );
100 return false;
101 }
102 mCopcInfoVlr.fill( copcInfoVlrData.data(), copcInfoVlrData.size() );
103
104 mScale = lazInfo.scale();
105 mOffset = lazInfo.offset();
106
107 mOriginalMetadata = lazInfo.toMetadata();
108
109 QgsVector3D minCoords = lazInfo.minCoords();
110 QgsVector3D maxCoords = lazInfo.maxCoords();
111 mExtent.set( minCoords.x(), minCoords.y(), maxCoords.x(), maxCoords.y() );
112 mZMin = minCoords.z();
113 mZMax = maxCoords.z();
114
115 setAttributes( lazInfo.attributes() );
116
117 const double xmin = mCopcInfoVlr.center_x - mCopcInfoVlr.halfsize;
118 const double ymin = mCopcInfoVlr.center_y - mCopcInfoVlr.halfsize;
119 const double zmin = mCopcInfoVlr.center_z - mCopcInfoVlr.halfsize;
120 const double xmax = mCopcInfoVlr.center_x + mCopcInfoVlr.halfsize;
121 const double ymax = mCopcInfoVlr.center_y + mCopcInfoVlr.halfsize;
122 const double zmax = mCopcInfoVlr.center_z + mCopcInfoVlr.halfsize;
123
124 mRootBounds = QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax );
125
126 // TODO: Rounding?
127 mSpan = mRootBounds.width() / mCopcInfoVlr.spacing;
128
129#ifdef QGISDEBUG
130 double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
131 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
132 QgsDebugMsgLevel( u"res at lvl0 %1"_s.arg( dx / mSpan ), 2 );
133 QgsDebugMsgLevel( u"res at lvl1 %1"_s.arg( dx / mSpan / 2 ), 2 );
134 QgsDebugMsgLevel( u"res at lvl2 %1 with node size %2"_s.arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
135#endif
136
137 return true;
138}
139
140std::unique_ptr<QgsPointCloudBlock> QgsCopcPointCloudIndex::nodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request )
141{
142 if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
143 {
144 return std::unique_ptr<QgsPointCloudBlock>( cached );
145 }
146
147 std::unique_ptr<QgsPointCloudBlock> block;
148 if ( mAccessType == Qgis::PointCloudAccessType::Local )
149 {
150 QByteArray rawBlockData = rawNodeData( n );
151 if ( rawBlockData.isEmpty() )
152 return nullptr; // Error fetching block
153
154 mHierarchyMutex.lock();
155 auto pointCount = mHierarchy.value( n );
156 mHierarchyMutex.unlock();
157
158 // we need to create a copy of the expression to pass to the decoder
159 // as the same QgsPointCloudExpression object mighgt be concurrently
160 // used on another thread, for example in a 3d view
161 QgsPointCloudExpression filterExpression = request.ignoreIndexFilterEnabled() ? QgsPointCloudExpression() : mFilterExpression;
162 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
163 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
164
165 QgsRectangle filterRect = request.filterRect();
166
167 block = QgsLazDecoder::decompressCopc( rawBlockData, *mLazInfo.get(), pointCount, requestAttributes, filterExpression, filterRect );
168 }
169 else
170 {
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( const 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(),
197 scale(), offset(), mFilterExpression, request.filterRect() );
198 }
199
200 if ( !fetchNodeHierarchy( n ) )
201 return nullptr;
202 QMutexLocker locker( &mHierarchyMutex );
203
204 // we need to create a copy of the expression to pass to the decoder
205 // as the same QgsPointCloudExpression object might be concurrently
206 // used on another thread, for example in a 3d view
207 QgsPointCloudExpression filterExpression = request.ignoreIndexFilterEnabled() ? QgsPointCloudExpression() : mFilterExpression;
208 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
209 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
210 auto [ blockOffset, blockSize ] = mHierarchyNodePos.value( n );
211 int pointCount = mHierarchy.value( n );
212
213 return new QgsCopcPointCloudBlockRequest( n, mUri, attributes(), requestAttributes,
214 scale(), offset(), filterExpression, request.filterRect(),
215 blockOffset, blockSize, pointCount, *mLazInfo.get(), mAuthCfg );
216}
217
218
219const QByteArray QgsCopcPointCloudIndex::rawNodeData( QgsPointCloudNodeId n ) const
220{
221 const bool found = fetchNodeHierarchy( n );
222 if ( !found )
223 return {};
224 mHierarchyMutex.lock();
225 auto [blockOffset, blockSize] = mHierarchyNodePos.value( n );
226 mHierarchyMutex.unlock();
227
228 if ( mAccessType == Qgis::PointCloudAccessType::Local )
229 {
230 // Open a new file descriptor so we can read multiple blocks concurrently
231 QByteArray rawBlockData( blockSize, Qt::Initialization::Uninitialized );
232 std::ifstream file( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
233 file.seekg( blockOffset );
234 file.read( rawBlockData.data(), blockSize );
235 if ( !file )
236 {
237 QgsDebugError( u"Could not read file %1"_s.arg( mUri ) );
238 return {};
239 }
240 return rawBlockData;
241 }
242 else
243 return readRange( blockOffset, blockSize );
244}
245
246QgsCoordinateReferenceSystem QgsCopcPointCloudIndex::crs() const
247{
248 return mLazInfo->crs();
249}
250
251qint64 QgsCopcPointCloudIndex::pointCount() const
252{
253 return mLazInfo->pointCount();
254}
255
256bool QgsCopcPointCloudIndex::loadHierarchy() const
257{
258 fetchHierarchyPage( mCopcInfoVlr.root_hier_offset, mCopcInfoVlr.root_hier_size );
259 return true;
260}
261
262bool QgsCopcPointCloudIndex::writeStatistics( QgsPointCloudStatistics &stats )
263{
264 if ( mAccessType == Qgis::PointCloudAccessType::Remote )
265 {
266 QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to remote file \"%1\"" ).arg( mUri ) );
267 return false;
268 }
269
270 if ( mLazInfo->version() != qMakePair<uint8_t, uint8_t>( 1, 4 ) )
271 {
272 // EVLR isn't supported in the first place
273 QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to \"%1\": laz version != 1.4" ).arg( mUri ) );
274 return false;
275 }
276
277 QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
278 if ( !statisticsEvlrData.isEmpty() )
279 {
280 QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to \"%1\": file already contains COPC statistics!" ).arg( mUri ) );
281 return false;
282 }
283
284 lazperf::evlr_header statsEvlrHeader;
285 statsEvlrHeader.user_id = "qgis";
286 statsEvlrHeader.reserved = 0;
287 statsEvlrHeader.record_id = 0;
288 statsEvlrHeader.description = "Contains calculated statistics";
289 QByteArray statsJson = stats.toStatisticsJson();
290 statsEvlrHeader.data_length = statsJson.size();
291
292 // Save the EVLRs to the end of the original file (while erasing the existing EVLRs in the file)
293 QMutexLocker locker( &mFileMutex );
294 mCopcFile.close();
295 std::fstream copcFile;
296 copcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios_base::binary | std::iostream::in | std::iostream::out );
297 if ( copcFile.is_open() && copcFile.good() )
298 {
299 // Write the new number of EVLRs
300 lazperf::header14 header = mLazInfo->header();
301 header.evlr_count = header.evlr_count + 1;
302 copcFile.seekp( 0 );
303 header.write( copcFile );
304
305 // Append EVLR data to the end
306 copcFile.seekg( 0, std::ios::end );
307
308 statsEvlrHeader.write( copcFile );
309 copcFile.write( statsJson.data(), statsEvlrHeader.data_length );
310 }
311 else
312 {
313 QgsMessageLog::logMessage( QObject::tr( "Couldn't open COPC file \"%1\" to write statistics" ).arg( mUri ) );
314 return false;
315 }
316 copcFile.close();
317 mCopcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
318 return true;
319}
320
321QgsPointCloudStatistics QgsCopcPointCloudIndex::metadataStatistics() const
322{
323 if ( ! mStatistics )
324 {
325 const QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
326 if ( statisticsEvlrData.isEmpty() )
328 else
329 mStatistics = QgsPointCloudStatistics::fromStatisticsJson( statisticsEvlrData );
330 }
331
332 return *mStatistics;
333}
334
335bool QgsCopcPointCloudIndex::isValid() const
336{
337 return mIsValid;
338}
339
340bool QgsCopcPointCloudIndex::fetchNodeHierarchy( const QgsPointCloudNodeId &n ) const
341{
342 QMutexLocker locker( &mHierarchyMutex );
343
344 QVector<QgsPointCloudNodeId> ancestors;
345 QgsPointCloudNodeId foundRoot = n;
346 while ( !mHierarchy.contains( foundRoot ) )
347 {
348 ancestors.push_front( foundRoot );
349 foundRoot = foundRoot.parentNode();
350 }
351 ancestors.push_front( foundRoot );
352 for ( QgsPointCloudNodeId n : ancestors )
353 {
354 auto hierarchyIt = mHierarchy.constFind( n );
355 if ( hierarchyIt == mHierarchy.constEnd() )
356 return false;
357 int nodesCount = *hierarchyIt;
358 if ( nodesCount < 0 )
359 {
360 auto hierarchyNodePos = mHierarchyNodePos.constFind( n );
361 mHierarchyMutex.unlock();
362 fetchHierarchyPage( hierarchyNodePos->first, hierarchyNodePos->second );
363 mHierarchyMutex.lock();
364 }
365 }
366 return mHierarchy.contains( n );
367}
368
369void QgsCopcPointCloudIndex::fetchHierarchyPage( uint64_t offset, uint64_t byteSize ) const
370{
371 Q_ASSERT( byteSize > 0 );
372
373 QByteArray data = readRange( offset, byteSize );
374 if ( data.isEmpty() )
375 return;
376
377 populateHierarchy( data.constData(), byteSize );
378}
379
380void QgsCopcPointCloudIndex::populateHierarchy( const char *hierarchyPageData, uint64_t byteSize ) const
381{
382 struct CopcVoxelKey
383 {
384 int32_t level;
385 int32_t x;
386 int32_t y;
387 int32_t z;
388 };
389
390 struct CopcEntry
391 {
392 CopcVoxelKey key;
393 uint64_t offset;
394 int32_t byteSize;
395 int32_t pointCount;
396 };
397
398 QMutexLocker locker( &mHierarchyMutex );
399
400 for ( uint64_t i = 0; i < byteSize; i += sizeof( CopcEntry ) )
401 {
402 const CopcEntry *entry = reinterpret_cast<const CopcEntry *>( hierarchyPageData + i );
403 const QgsPointCloudNodeId nodeId( entry->key.level, entry->key.x, entry->key.y, entry->key.z );
404 mHierarchy[nodeId] = entry->pointCount;
405 mHierarchyNodePos.insert( nodeId, QPair<uint64_t, int32_t>( entry->offset, entry->byteSize ) );
406 }
407}
408
409bool QgsCopcPointCloudIndex::hasNode( const QgsPointCloudNodeId &n ) const
410{
411 return fetchNodeHierarchy( n );
412}
413
414QgsPointCloudNode QgsCopcPointCloudIndex::getNode( const QgsPointCloudNodeId &id ) const
415{
416 bool nodeFound = fetchNodeHierarchy( id );
417 Q_ASSERT( nodeFound );
418
419 qint64 pointCount;
420 {
421 QMutexLocker locker( &mHierarchyMutex );
422 pointCount = mHierarchy.value( id, -1 );
423 }
424
425 QList<QgsPointCloudNodeId> children;
426 children.reserve( 8 );
427 const int d = id.d() + 1;
428 const int x = id.x() * 2;
429 const int y = id.y() * 2;
430 const int z = id.z() * 2;
431
432 for ( int i = 0; i < 8; ++i )
433 {
434 int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
435 const QgsPointCloudNodeId n2( d, x + dx, y + dy, z + dz );
436 bool found = fetchNodeHierarchy( n2 );
437 {
438 QMutexLocker locker( &mHierarchyMutex );
439 if ( found && mHierarchy[id] >= 0 )
440 children.append( n2 );
441 }
442 }
443
444 QgsBox3D bounds = QgsPointCloudNode::bounds( mRootBounds, id );
445 return QgsPointCloudNode( id, pointCount, children, bounds.width() / mSpan, bounds );
446}
447
448QByteArray QgsCopcPointCloudIndex::readRange( uint64_t offset, uint64_t length ) const
449{
450 if ( mAccessType == Qgis::PointCloudAccessType::Local )
451 {
452 QMutexLocker locker( &mFileMutex );
453
454 QByteArray buffer( length, Qt::Initialization::Uninitialized );
455 mCopcFile.seekg( offset );
456 mCopcFile.read( buffer.data(), length );
457 if ( mCopcFile.eof() )
458 QgsDebugError( u"Read past end of file (path %1 offset %2 length %3)"_s.arg( mUri ).arg( offset ).arg( length ) );
459 if ( !mCopcFile )
460 QgsDebugError( u"Error reading %1"_s.arg( mUri ) );
461 return buffer;
462 }
463 else
464 {
465 QNetworkRequest nr = QNetworkRequest( QUrl( mUri ) );
466 QgsSetRequestInitiatorClass( nr, u"QgsCopcPointCloudIndex"_s );
467 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
468 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
469 QByteArray queryRange = u"bytes=%1-%2"_s.arg( offset ).arg( offset + length - 1 ).toLocal8Bit();
470 nr.setRawHeader( "Range", queryRange );
471
472 if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( nr, mAuthCfg ) )
473 {
474 QgsDebugError( u"Network request update failed for authcfg: %1"_s.arg( mAuthCfg ) );
475 return {};
476 }
477
478 std::unique_ptr<QgsTileDownloadManagerReply> reply( QgsApplication::tileDownloadManager()->get( nr ) );
479
480 QEventLoop loop;
481 QObject::connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit );
482 loop.exec();
483
484 if ( reply->error() != QNetworkReply::NoError )
485 {
486 QgsDebugError( u"Request failed: %1 (offset %1 length %2)"_s.arg( mUri ).arg( offset ).arg( length ) );
487 return {};
488 }
489
490 return reply->data();
491 }
492}
493
494QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData() const
495{
496 uint64_t offset = mLazInfo->firstEvlrOffset();
497 uint32_t evlrCount = mLazInfo->evlrCount();
498
499 QByteArray statisticsEvlrData;
500
501 for ( uint32_t i = 0; i < evlrCount; ++i )
502 {
503 lazperf::evlr_header header;
504
505 QByteArray buffer = readRange( offset, 60 );
506 header.fill( buffer.data(), buffer.size() );
507
508 if ( header.user_id == "qgis" && header.record_id == 0 )
509 {
510 statisticsEvlrData = readRange( offset + 60, header.data_length );
511 break;
512 }
513
514 offset += 60 + header.data_length;
515 }
516
517 return statisticsEvlrData;
518}
519
520void QgsCopcPointCloudIndex::reset()
521{
522 // QgsAbstractPointCloudIndex
523 mExtent = QgsRectangle();
524 mZMin = 0;
525 mZMax = 0;
526 mHierarchy.clear();
527 mScale = QgsVector3D();
528 mOffset = QgsVector3D();
529 mRootBounds = QgsBox3D();
530 mAttributes = QgsPointCloudAttributeCollection();
531 mSpan = 0;
532 mError.clear();
533
534 // QgsCopcPointCloudIndex
535 mIsValid = false;
537 mCopcFile.close();
538 mOriginalMetadata.clear();
539 mStatistics.reset();
540 mLazInfo.reset();
541 mHierarchyNodePos.clear();
542}
543
544QVariantMap QgsCopcPointCloudIndex::extraMetadata() const
545{
546 return
547 {
548 { u"CopcGpsTimeFlag"_s, mLazInfo.get()->header().global_encoding & 1 },
549 };
550}
551
@ Local
Local means the source is a local file on the machine.
Definition qgis.h:6376
@ Remote
Remote means it's loaded through a protocol like HTTP.
Definition qgis.h:6377
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:280
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:94
QgsPointCloudAttributeCollection attributes() const
Returns the list of attributes contained in the LAZ file.
Definition qgslazinfo.h:119
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:76
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:92
QgsVector3D offset() const
Returns the offset of the points coordinates.
Definition qgslazinfo.h:78
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())
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.
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:52
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:54
double x() const
Returns X coordinate.
Definition qgsvector3d.h:50
void set(double x, double y, double z)
Sets vector coordinates.
Definition qgsvector3d.h:75
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
#define QgsSetRequestInitiatorClass(request, _class)