QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 std::unique_ptr<QgsPointCloudBlockRequest> blockRequest( asyncNodeData( n, request ) );
172 if ( !blockRequest )
173 return nullptr;
174
175 QEventLoop loop;
176 QObject::connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
177 loop.exec();
178
179 block = blockRequest->takeBlock();
180
181 if ( !block )
182 QgsDebugError( u"Error downloading node %1 data, error : %2 "_s.arg( n.toString(), blockRequest->errorStr() ) );
183 }
184
185 storeNodeDataToCache( block.get(), n, request );
186 return block;
187}
188
189QgsPointCloudBlockRequest *QgsCopcPointCloudIndex::asyncNodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request )
190{
191 if ( mAccessType == Qgis::PointCloudAccessType::Local )
192 return nullptr; // TODO
193 if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
194 {
195 return new QgsCachedPointCloudBlockRequest( cached, n, mUri, attributes(), request.attributes(), scale(), offset(), mFilterExpression, request.filterRect() );
196 }
197
198 if ( !fetchNodeHierarchy( n ) )
199 return nullptr;
200 QMutexLocker locker( &mHierarchyMutex );
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 auto [blockOffset, blockSize] = mHierarchyNodePos.value( n );
209 int pointCount = mHierarchy.value( n );
210
211 return new QgsCopcPointCloudBlockRequest( n, mUri, attributes(), requestAttributes, scale(), offset(), filterExpression, request.filterRect(), blockOffset, blockSize, pointCount, *mLazInfo.get(), mAuthCfg );
212}
213
214
215const QByteArray QgsCopcPointCloudIndex::rawNodeData( QgsPointCloudNodeId n ) const
216{
217 const bool found = fetchNodeHierarchy( n );
218 if ( !found )
219 return {};
220 mHierarchyMutex.lock();
221 auto [blockOffset, blockSize] = mHierarchyNodePos.value( n );
222 mHierarchyMutex.unlock();
223
224 if ( mAccessType == Qgis::PointCloudAccessType::Local )
225 {
226 // Open a new file descriptor so we can read multiple blocks concurrently
227 QByteArray rawBlockData( blockSize, Qt::Initialization::Uninitialized );
228 std::ifstream file( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
229 file.seekg( blockOffset );
230 file.read( rawBlockData.data(), blockSize );
231 if ( !file )
232 {
233 QgsDebugError( u"Could not read file %1"_s.arg( mUri ) );
234 return {};
235 }
236 return rawBlockData;
237 }
238 else
239 return readRange( blockOffset, blockSize );
240}
241
242QgsCoordinateReferenceSystem QgsCopcPointCloudIndex::crs() const
243{
244 return mLazInfo->crs();
245}
246
247qint64 QgsCopcPointCloudIndex::pointCount() const
248{
249 return mLazInfo->pointCount();
250}
251
252bool QgsCopcPointCloudIndex::loadHierarchy() const
253{
254 fetchHierarchyPage( mCopcInfoVlr.root_hier_offset, mCopcInfoVlr.root_hier_size );
255 return true;
256}
257
258bool QgsCopcPointCloudIndex::writeStatistics( QgsPointCloudStatistics &stats )
259{
260 if ( mAccessType == Qgis::PointCloudAccessType::Remote )
261 {
262 QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to remote file \"%1\"" ).arg( mUri ) );
263 return false;
264 }
265
266 if ( mLazInfo->version() != qMakePair<uint8_t, uint8_t>( 1, 4 ) )
267 {
268 // EVLR isn't supported in the first place
269 QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to \"%1\": laz version != 1.4" ).arg( mUri ) );
270 return false;
271 }
272
273 QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
274 if ( !statisticsEvlrData.isEmpty() )
275 {
276 QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to \"%1\": file already contains COPC statistics!" ).arg( mUri ) );
277 return false;
278 }
279
280 lazperf::evlr_header statsEvlrHeader;
281 statsEvlrHeader.user_id = "qgis";
282 statsEvlrHeader.reserved = 0;
283 statsEvlrHeader.record_id = 0;
284 statsEvlrHeader.description = "Contains calculated statistics";
285 QByteArray statsJson = stats.toStatisticsJson();
286 statsEvlrHeader.data_length = statsJson.size();
287
288 // Save the EVLRs to the end of the original file (while erasing the existing EVLRs in the file)
289 QMutexLocker locker( &mFileMutex );
290 mCopcFile.close();
291 std::fstream copcFile;
292 copcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios_base::binary | std::iostream::in | std::iostream::out );
293 if ( copcFile.is_open() && copcFile.good() )
294 {
295 // Write the new number of EVLRs
296 lazperf::header14 header = mLazInfo->header();
297 header.evlr_count = header.evlr_count + 1;
298 copcFile.seekp( 0 );
299 header.write( copcFile );
300
301 // Append EVLR data to the end
302 copcFile.seekg( 0, std::ios::end );
303
304 statsEvlrHeader.write( copcFile );
305 copcFile.write( statsJson.data(), statsEvlrHeader.data_length );
306 }
307 else
308 {
309 QgsMessageLog::logMessage( QObject::tr( "Couldn't open COPC file \"%1\" to write statistics" ).arg( mUri ) );
310 return false;
311 }
312 copcFile.close();
313 mCopcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
314 return true;
315}
316
317QgsPointCloudStatistics QgsCopcPointCloudIndex::metadataStatistics() const
318{
319 if ( !mStatistics )
320 {
321 const QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
322 if ( statisticsEvlrData.isEmpty() )
324 else
325 mStatistics = QgsPointCloudStatistics::fromStatisticsJson( statisticsEvlrData );
326 }
327
328 return *mStatistics;
329}
330
331bool QgsCopcPointCloudIndex::isValid() const
332{
333 return mIsValid;
334}
335
336bool QgsCopcPointCloudIndex::fetchNodeHierarchy( const QgsPointCloudNodeId &n ) const
337{
338 QMutexLocker locker( &mHierarchyMutex );
339
340 QVector<QgsPointCloudNodeId> ancestors;
341 QgsPointCloudNodeId foundRoot = n;
342 while ( !mHierarchy.contains( foundRoot ) )
343 {
344 ancestors.push_front( foundRoot );
345 foundRoot = foundRoot.parentNode();
346 }
347 ancestors.push_front( foundRoot );
348 for ( QgsPointCloudNodeId n : ancestors )
349 {
350 auto hierarchyIt = mHierarchy.constFind( n );
351 if ( hierarchyIt == mHierarchy.constEnd() )
352 return false;
353 int nodesCount = *hierarchyIt;
354 if ( nodesCount < 0 )
355 {
356 auto hierarchyNodePos = mHierarchyNodePos.constFind( n );
357 mHierarchyMutex.unlock();
358 fetchHierarchyPage( hierarchyNodePos->first, hierarchyNodePos->second );
359 mHierarchyMutex.lock();
360 }
361 }
362 return mHierarchy.contains( n );
363}
364
365void QgsCopcPointCloudIndex::fetchHierarchyPage( uint64_t offset, uint64_t byteSize ) const
366{
367 Q_ASSERT( byteSize > 0 );
368
369 QByteArray data = readRange( offset, byteSize );
370 if ( data.isEmpty() )
371 return;
372
373 populateHierarchy( data.constData(), byteSize );
374}
375
376void QgsCopcPointCloudIndex::populateHierarchy( const char *hierarchyPageData, uint64_t byteSize ) const
377{
378 struct CopcVoxelKey
379 {
380 int32_t level;
381 int32_t x;
382 int32_t y;
383 int32_t z;
384 };
385
386 struct CopcEntry
387 {
388 CopcVoxelKey key;
389 uint64_t offset;
390 int32_t byteSize;
391 int32_t pointCount;
392 };
393
394 QMutexLocker locker( &mHierarchyMutex );
395
396 for ( uint64_t i = 0; i < byteSize; i += sizeof( CopcEntry ) )
397 {
398 const CopcEntry *entry = reinterpret_cast<const CopcEntry *>( hierarchyPageData + i );
399 const QgsPointCloudNodeId nodeId( entry->key.level, entry->key.x, entry->key.y, entry->key.z );
400 mHierarchy[nodeId] = entry->pointCount;
401 mHierarchyNodePos.insert( nodeId, QPair<uint64_t, int32_t>( entry->offset, entry->byteSize ) );
402 }
403}
404
405bool QgsCopcPointCloudIndex::hasNode( const QgsPointCloudNodeId &n ) const
406{
407 return fetchNodeHierarchy( n );
408}
409
410QgsPointCloudNode QgsCopcPointCloudIndex::getNode( const QgsPointCloudNodeId &id ) const
411{
412 bool nodeFound = fetchNodeHierarchy( id );
413 Q_ASSERT( nodeFound );
414
415 qint64 pointCount;
416 {
417 QMutexLocker locker( &mHierarchyMutex );
418 pointCount = mHierarchy.value( id, -1 );
419 }
420
421 QList<QgsPointCloudNodeId> children;
422 children.reserve( 8 );
423 const int d = id.d() + 1;
424 const int x = id.x() * 2;
425 const int y = id.y() * 2;
426 const int z = id.z() * 2;
427
428 for ( int i = 0; i < 8; ++i )
429 {
430 int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
431 const QgsPointCloudNodeId n2( d, x + dx, y + dy, z + dz );
432 bool found = fetchNodeHierarchy( n2 );
433 {
434 QMutexLocker locker( &mHierarchyMutex );
435 if ( found && mHierarchy[id] >= 0 )
436 children.append( n2 );
437 }
438 }
439
440 QgsBox3D bounds = QgsPointCloudNode::bounds( mRootBounds, id );
441 return QgsPointCloudNode( id, pointCount, children, bounds.width() / mSpan, bounds );
442}
443
444QByteArray QgsCopcPointCloudIndex::readRange( uint64_t offset, uint64_t length ) const
445{
446 if ( mAccessType == Qgis::PointCloudAccessType::Local )
447 {
448 QMutexLocker locker( &mFileMutex );
449
450 QByteArray buffer( length, Qt::Initialization::Uninitialized );
451 mCopcFile.seekg( offset );
452 mCopcFile.read( buffer.data(), length );
453 if ( mCopcFile.eof() )
454 QgsDebugError( u"Read past end of file (path %1 offset %2 length %3)"_s.arg( mUri ).arg( offset ).arg( length ) );
455 if ( !mCopcFile )
456 QgsDebugError( u"Error reading %1"_s.arg( mUri ) );
457 return buffer;
458 }
459 else
460 {
461 QNetworkRequest nr = QNetworkRequest( QUrl( mUri ) );
462 QgsSetRequestInitiatorClass( nr, u"QgsCopcPointCloudIndex"_s );
463 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
464 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
465 QByteArray queryRange = u"bytes=%1-%2"_s.arg( offset ).arg( offset + length - 1 ).toLocal8Bit();
466 nr.setRawHeader( "Range", queryRange );
467
468 if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( nr, mAuthCfg ) )
469 {
470 QgsDebugError( u"Network request update failed for authcfg: %1"_s.arg( mAuthCfg ) );
471 return {};
472 }
473
474 std::unique_ptr<QgsTileDownloadManagerReply> reply( QgsApplication::tileDownloadManager()->get( nr ) );
475
476 QEventLoop loop;
477 QObject::connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit );
478 loop.exec();
479
480 if ( reply->error() != QNetworkReply::NoError )
481 {
482 QgsDebugError( u"Request failed: %1 (offset %1 length %2)"_s.arg( mUri ).arg( offset ).arg( length ) );
483 return {};
484 }
485
486 return reply->data();
487 }
488}
489
490QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData() const
491{
492 uint64_t offset = mLazInfo->firstEvlrOffset();
493 uint32_t evlrCount = mLazInfo->evlrCount();
494
495 QByteArray statisticsEvlrData;
496
497 for ( uint32_t i = 0; i < evlrCount; ++i )
498 {
499 lazperf::evlr_header header;
500
501 QByteArray buffer = readRange( offset, 60 );
502 header.fill( buffer.data(), buffer.size() );
503
504 if ( header.user_id == "qgis" && header.record_id == 0 )
505 {
506 statisticsEvlrData = readRange( offset + 60, header.data_length );
507 break;
508 }
509
510 offset += 60 + header.data_length;
511 }
512
513 return statisticsEvlrData;
514}
515
516void QgsCopcPointCloudIndex::reset()
517{
518 // QgsAbstractPointCloudIndex
519 mExtent = QgsRectangle();
520 mZMin = 0;
521 mZMax = 0;
522 mHierarchy.clear();
523 mScale = QgsVector3D();
524 mOffset = QgsVector3D();
525 mRootBounds = QgsBox3D();
526 mAttributes = QgsPointCloudAttributeCollection();
527 mSpan = 0;
528 mError.clear();
529
530 // QgsCopcPointCloudIndex
531 mIsValid = false;
533 mCopcFile.close();
534 mOriginalMetadata.clear();
535 mStatistics.reset();
536 mLazInfo.reset();
537 mHierarchyNodePos.clear();
538}
539
540QVariantMap QgsCopcPointCloudIndex::extraMetadata() const
541{
542 return {
543 { u"CopcGpsTimeFlag"_s, mLazInfo.get()->header().global_encoding & 1 },
544 };
545}
546
@ Local
Local means the source is a local file on the machine.
Definition qgis.h:6433
@ Remote
Remote means it's loaded through a protocol like HTTP.
Definition qgis.h:6434
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.
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
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
#define QgsSetRequestInitiatorClass(request, _class)