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