QGIS API Documentation 3.41.0-Master (88383c3d16f)
Loading...
Searching...
No Matches
qgseptpointcloudindex.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointcloudindex.cpp
3 --------------------
4 begin : October 2020
5 copyright : (C) 2020 by Peter Petrik
6 email : zilolv 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#include <QFile>
20#include <QFileInfo>
21#include <QDir>
22#include <QJsonArray>
23#include <QJsonDocument>
24#include <QJsonObject>
25#include <QTime>
26#include <QtDebug>
27#include <QQueue>
28#include <QNetworkRequest>
29
30#include "qgsapplication.h"
33#include "qgseptdecoder.h"
35#include "qgslazdecoder.h"
38#include "qgspointcloudindex.h"
41#include "qgslogger.h"
42#include "qgspointcloudexpression.h"
45
47
48#define PROVIDER_KEY QStringLiteral( "ept" )
49#define PROVIDER_DESCRIPTION QStringLiteral( "EPT point cloud provider" )
50
51QgsEptPointCloudIndex::QgsEptPointCloudIndex()
52{
53 mHierarchyNodes.insert( QgsPointCloudNodeId( 0, 0, 0, 0 ) );
54}
55
56QgsEptPointCloudIndex::~QgsEptPointCloudIndex() = default;
57
58void QgsEptPointCloudIndex::load( const QString &urlString )
59{
60 QUrl url = urlString;
61 // Treat non-URLs as local files
62 if ( url.isValid() && ( url.scheme() == "http" || url.scheme() == "https" ) )
64 else
66 mUri = urlString;
67
68 QStringList splitUrl = mUri.split( '/' );
69 splitUrl.pop_back();
70 mUrlDirectoryPart = splitUrl.join( '/' );
71
72 QByteArray content;
73 if ( mAccessType == Qgis::PointCloudAccessType::Remote )
74 {
75 QNetworkRequest nr = QNetworkRequest( QUrl( mUri ) );
76 QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsEptPointCloudIndex" ) );
77
79 if ( req.get( nr ) != QgsBlockingNetworkRequest::NoError )
80 {
81 QgsDebugError( QStringLiteral( "Request failed: " ) + mUri );
82 mIsValid = false;
83 mError = req.errorMessage();
84 return;
85 }
86 content = req.reply().content();
87 }
88 else
89 {
90 QFile f( mUri );
91 if ( !f.open( QIODevice::ReadOnly ) )
92 {
93 mError = QObject::tr( "Unable to open %1 for reading" ).arg( mUri );
94 mIsValid = false;
95 return;
96 }
97 content = f.readAll();
98 }
99
100 bool success = loadSchema( content );
101 if ( success )
102 {
103 // try to import the metadata too!
104 const QString manifestPath = mUrlDirectoryPart + QStringLiteral( "/ept-sources/manifest.json" );
105 QByteArray manifestJson;
106 if ( mAccessType == Qgis::PointCloudAccessType::Remote )
107 {
108 QUrl manifestUrl( manifestPath );
109
110 QNetworkRequest nr = QNetworkRequest( QUrl( manifestUrl ) );
111 QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsEptPointCloudIndex" ) );
113 if ( req.get( nr ) == QgsBlockingNetworkRequest::NoError )
114 manifestJson = req.reply().content();
115 }
116 else
117 {
118 QFile manifestFile( manifestPath );
119 if ( manifestFile.open( QIODevice::ReadOnly ) )
120 manifestJson = manifestFile.readAll();
121 }
122
123 if ( !manifestJson.isEmpty() )
124 loadManifest( manifestJson );
125 }
126
127 if ( !loadNodeHierarchy( QgsPointCloudNodeId( 0, 0, 0, 0 ) ) )
128 {
129 QgsDebugError( QStringLiteral( "Failed to load root EPT node" ) );
130 success = false;
131 }
132
133 mIsValid = success;
134}
135
136void QgsEptPointCloudIndex::loadManifest( const QByteArray &manifestJson )
137{
138 QJsonParseError err;
139 // try to import the metadata too!
140 const QJsonDocument manifestDoc = QJsonDocument::fromJson( manifestJson, &err );
141 if ( err.error != QJsonParseError::NoError )
142 return;
143
144 const QJsonArray manifestArray = manifestDoc.array();
145 if ( manifestArray.empty() )
146 return;
147
148 // TODO how to handle multiple?
149 const QJsonObject sourceObject = manifestArray.at( 0 ).toObject();
150 const QString metadataPath = sourceObject.value( QStringLiteral( "metadataPath" ) ).toString();
151 const QString fullMetadataPath = mUrlDirectoryPart + QStringLiteral( "/ept-sources/" ) + metadataPath;
152
153 QByteArray metadataJson;
154 if ( mAccessType == Qgis::PointCloudAccessType::Remote )
155 {
156 QUrl metadataUrl( fullMetadataPath );
157 QNetworkRequest nr = QNetworkRequest( QUrl( metadataUrl ) );
158 QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsEptPointCloudIndex" ) );
160 if ( req.get( nr ) != QgsBlockingNetworkRequest::NoError )
161 return;
162 metadataJson = req.reply().content();
163 }
164 else
165 {
166 QFile metadataFile( fullMetadataPath );
167 if ( ! metadataFile.open( QIODevice::ReadOnly ) )
168 return;
169 metadataJson = metadataFile.readAll();
170 }
171
172 const QJsonDocument metadataDoc = QJsonDocument::fromJson( metadataJson, &err );
173 if ( err.error != QJsonParseError::NoError )
174 return;
175
176 const QJsonObject metadataObject = metadataDoc.object().value( QStringLiteral( "metadata" ) ).toObject();
177 if ( metadataObject.empty() )
178 return;
179
180 const QJsonObject sourceMetadata = metadataObject.constBegin().value().toObject();
181 mOriginalMetadata = sourceMetadata.toVariantMap();
182}
183
184bool QgsEptPointCloudIndex::loadSchema( const QByteArray &dataJson )
185{
186 QJsonParseError err;
187 const QJsonDocument doc = QJsonDocument::fromJson( dataJson, &err );
188 if ( err.error != QJsonParseError::NoError )
189 return false;
190 const QJsonObject result = doc.object();
191 mDataType = result.value( QLatin1String( "dataType" ) ).toString(); // "binary" or "laszip"
192 if ( mDataType != QLatin1String( "laszip" ) && mDataType != QLatin1String( "binary" ) && mDataType != QLatin1String( "zstandard" ) )
193 return false;
194
195 const QString hierarchyType = result.value( QLatin1String( "hierarchyType" ) ).toString(); // "json" or "gzip"
196 if ( hierarchyType != QLatin1String( "json" ) )
197 return false;
198
199 mSpan = result.value( QLatin1String( "span" ) ).toInt();
200 mPointCount = result.value( QLatin1String( "points" ) ).toDouble();
201
202 // WKT
203 const QJsonObject srs = result.value( QLatin1String( "srs" ) ).toObject();
204 mWkt = srs.value( QLatin1String( "wkt" ) ).toString();
205
206 // rectangular
207 const QJsonArray bounds = result.value( QLatin1String( "bounds" ) ).toArray();
208 if ( bounds.size() != 6 )
209 return false;
210
211 const QJsonArray boundsConforming = result.value( QLatin1String( "boundsConforming" ) ).toArray();
212 if ( boundsConforming.size() != 6 )
213 return false;
214 mExtent.set( boundsConforming[0].toDouble(), boundsConforming[1].toDouble(),
215 boundsConforming[3].toDouble(), boundsConforming[4].toDouble() );
216 mZMin = boundsConforming[2].toDouble();
217 mZMax = boundsConforming[5].toDouble();
218
219 const QJsonArray schemaArray = result.value( QLatin1String( "schema" ) ).toArray();
221
222 for ( const QJsonValue &schemaItem : schemaArray )
223 {
224 const QJsonObject schemaObj = schemaItem.toObject();
225 const QString name = schemaObj.value( QLatin1String( "name" ) ).toString();
226 const QString type = schemaObj.value( QLatin1String( "type" ) ).toString();
227
228 const int size = schemaObj.value( QLatin1String( "size" ) ).toInt();
229
230 if ( name == QLatin1String( "ClassFlags" ) && size == 1 )
231 {
232 attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Synthetic" ), QgsPointCloudAttribute::UChar ) );
233 attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "KeyPoint" ), QgsPointCloudAttribute::UChar ) );
234 attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Withheld" ), QgsPointCloudAttribute::UChar ) );
235 attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Overlap" ), QgsPointCloudAttribute::UChar ) );
236 }
237 else if ( type == QLatin1String( "float" ) && ( size == 4 ) )
238 {
240 }
241 else if ( type == QLatin1String( "float" ) && ( size == 8 ) )
242 {
244 }
245 else if ( size == 1 )
246 {
248 }
249 else if ( type == QLatin1String( "unsigned" ) && size == 2 )
250 {
252 }
253 else if ( size == 2 )
254 {
256 }
257 else if ( size == 4 )
258 {
260 }
261 else
262 {
263 // unknown attribute type
264 return false;
265 }
266
267 double scale = 1.f;
268 if ( schemaObj.contains( QLatin1String( "scale" ) ) )
269 scale = schemaObj.value( QLatin1String( "scale" ) ).toDouble();
270
271 double offset = 0.f;
272 if ( schemaObj.contains( QLatin1String( "offset" ) ) )
273 offset = schemaObj.value( QLatin1String( "offset" ) ).toDouble();
274
275 if ( name == QLatin1String( "X" ) )
276 {
277 mOffset.set( offset, mOffset.y(), mOffset.z() );
278 mScale.set( scale, mScale.y(), mScale.z() );
279 }
280 else if ( name == QLatin1String( "Y" ) )
281 {
282 mOffset.set( mOffset.x(), offset, mOffset.z() );
283 mScale.set( mScale.x(), scale, mScale.z() );
284 }
285 else if ( name == QLatin1String( "Z" ) )
286 {
287 mOffset.set( mOffset.x(), mOffset.y(), offset );
288 mScale.set( mScale.x(), mScale.y(), scale );
289 }
290
291 // store any metadata stats which are present for the attribute
292 AttributeStatistics stats;
293 bool foundStats = false;
294 if ( schemaObj.contains( QLatin1String( "count" ) ) )
295 {
296 stats.count = schemaObj.value( QLatin1String( "count" ) ).toInt();
297 foundStats = true;
298 }
299 if ( schemaObj.contains( QLatin1String( "minimum" ) ) )
300 {
301 stats.minimum = schemaObj.value( QLatin1String( "minimum" ) ).toDouble();
302 foundStats = true;
303 }
304 if ( schemaObj.contains( QLatin1String( "maximum" ) ) )
305 {
306 stats.maximum = schemaObj.value( QLatin1String( "maximum" ) ).toDouble();
307 foundStats = true;
308 }
309 if ( schemaObj.contains( QLatin1String( "count" ) ) )
310 {
311 stats.mean = schemaObj.value( QLatin1String( "mean" ) ).toDouble();
312 foundStats = true;
313 }
314 if ( schemaObj.contains( QLatin1String( "stddev" ) ) )
315 {
316 stats.stDev = schemaObj.value( QLatin1String( "stddev" ) ).toDouble();
317 foundStats = true;
318 }
319 if ( schemaObj.contains( QLatin1String( "variance" ) ) )
320 {
321 stats.variance = schemaObj.value( QLatin1String( "variance" ) ).toDouble();
322 foundStats = true;
323 }
324 if ( foundStats )
325 mMetadataStats.insert( name, stats );
326
327 if ( schemaObj.contains( QLatin1String( "counts" ) ) )
328 {
329 QMap< int, int > classCounts;
330 const QJsonArray counts = schemaObj.value( QLatin1String( "counts" ) ).toArray();
331 for ( const QJsonValue &count : counts )
332 {
333 const QJsonObject countObj = count.toObject();
334 classCounts.insert( countObj.value( QLatin1String( "value" ) ).toInt(), countObj.value( QLatin1String( "count" ) ).toInt() );
335 }
336 mAttributeClasses.insert( name, classCounts );
337 }
338 }
339 setAttributes( attributes );
340
341 // save mRootBounds
342
343 // bounds (cube - octree volume)
344 const double xmin = bounds[0].toDouble();
345 const double ymin = bounds[1].toDouble();
346 const double zmin = bounds[2].toDouble();
347 const double xmax = bounds[3].toDouble();
348 const double ymax = bounds[4].toDouble();
349 const double zmax = bounds[5].toDouble();
350
351 mRootBounds = QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax );
352
353#ifdef QGISDEBUG
354 double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
355 QgsDebugMsgLevel( QStringLiteral( "lvl0 node size in CRS units: %1 %2 %3" ).arg( dx ).arg( dy ).arg( dz ), 2 ); // all dims should be the same
356 QgsDebugMsgLevel( QStringLiteral( "res at lvl0 %1" ).arg( dx / mSpan ), 2 );
357 QgsDebugMsgLevel( QStringLiteral( "res at lvl1 %1" ).arg( dx / mSpan / 2 ), 2 );
358 QgsDebugMsgLevel( QStringLiteral( "res at lvl2 %1 with node size %2" ).arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
359#endif
360
361 return true;
362}
363
364std::unique_ptr<QgsPointCloudBlock> QgsEptPointCloudIndex::nodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request )
365{
366 if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
367 {
368 return std::unique_ptr<QgsPointCloudBlock>( cached );
369 }
370
371 std::unique_ptr<QgsPointCloudBlock> block;
372 if ( mAccessType == Qgis::PointCloudAccessType::Remote )
373 {
374 std::unique_ptr<QgsPointCloudBlockRequest> blockRequest( asyncNodeData( n, request ) );
375 if ( !blockRequest )
376 return nullptr;
377
378 QEventLoop loop;
379 QObject::connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
380 loop.exec();
381
382 block = blockRequest->takeBlock();
383 if ( !block )
384 {
385 QgsDebugError( QStringLiteral( "Error downloading node %1 data, error : %2 " ).arg( n.toString(), blockRequest->errorStr() ) );
386 }
387 }
388 else
389 {
390 // we need to create a copy of the expression to pass to the decoder
391 // as the same QgsPointCloudExpression object mighgt be concurrently
392 // used on another thread, for example in a 3d view
393 QgsPointCloudExpression filterExpression = request.ignoreIndexFilterEnabled() ? QgsPointCloudExpression() : mFilterExpression;
394 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
395 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
396 QgsRectangle filterRect = request.filterRect();
397
398 if ( mDataType == QLatin1String( "binary" ) )
399 {
400 const QString filename = QStringLiteral( "%1/ept-data/%2.bin" ).arg( mUrlDirectoryPart, n.toString() );
401 block = QgsEptDecoder::decompressBinary( filename, attributes(), requestAttributes, scale(), offset(), filterExpression, filterRect );
402 }
403 else if ( mDataType == QLatin1String( "zstandard" ) )
404 {
405 const QString filename = QStringLiteral( "%1/ept-data/%2.zst" ).arg( mUrlDirectoryPart, n.toString() );
406 block = QgsEptDecoder::decompressZStandard( filename, attributes(), request.attributes(), scale(), offset(), filterExpression, filterRect );
407 }
408 else if ( mDataType == QLatin1String( "laszip" ) )
409 {
410 const QString filename = QStringLiteral( "%1/ept-data/%2.laz" ).arg( mUrlDirectoryPart, n.toString() );
411 block = QgsLazDecoder::decompressLaz( filename, requestAttributes, filterExpression, filterRect );
412 }
413 }
414
415 storeNodeDataToCache( block.get(), n, request );
416 return block;
417}
418
419QgsPointCloudBlockRequest *QgsEptPointCloudIndex::asyncNodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request )
420{
421 if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
422 {
423 return new QgsCachedPointCloudBlockRequest( cached, n, mUri, attributes(), request.attributes(),
424 scale(), offset(), mFilterExpression, request.filterRect() );
425 }
426
427 if ( mAccessType != Qgis::PointCloudAccessType::Remote )
428 return nullptr;
429
430 if ( !loadNodeHierarchy( n ) )
431 return nullptr;
432
433 QString fileUrl;
434 if ( mDataType == QLatin1String( "binary" ) )
435 {
436 fileUrl = QStringLiteral( "%1/ept-data/%2.bin" ).arg( mUrlDirectoryPart, n.toString() );
437 }
438 else if ( mDataType == QLatin1String( "zstandard" ) )
439 {
440 fileUrl = QStringLiteral( "%1/ept-data/%2.zst" ).arg( mUrlDirectoryPart, n.toString() );
441 }
442 else if ( mDataType == QLatin1String( "laszip" ) )
443 {
444 fileUrl = QStringLiteral( "%1/ept-data/%2.laz" ).arg( mUrlDirectoryPart, n.toString() );
445 }
446 else
447 {
448 return nullptr;
449 }
450
451 // we need to create a copy of the expression to pass to the decoder
452 // as the same QgsPointCloudExpression object might be concurrently
453 // used on another thread, for example in a 3d view
454 QgsPointCloudExpression filterExpression = request.ignoreIndexFilterEnabled() ? QgsPointCloudExpression() : mFilterExpression;
455 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
456 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
457 return new QgsEptPointCloudBlockRequest( n, fileUrl, mDataType, attributes(), requestAttributes, scale(), offset(), filterExpression, request.filterRect() );
458}
459
460bool QgsEptPointCloudIndex::hasNode( const QgsPointCloudNodeId &n ) const
461{
462 return loadNodeHierarchy( n );
463}
464
465QgsCoordinateReferenceSystem QgsEptPointCloudIndex::crs() const
466{
468}
469
470qint64 QgsEptPointCloudIndex::pointCount() const
471{
472 return mPointCount;
473}
474
475QgsPointCloudNode QgsEptPointCloudIndex::getNode( const QgsPointCloudNodeId &id ) const
476{
478
479 // First try cached value
480 if ( node.pointCount() != -1 )
481 return node;
482
483 // Try loading all nodes' hierarchy files on the path from root and stop when
484 // one contains the point count for nodeId
485 QVector<QgsPointCloudNodeId> pathToRoot = nodePathToRoot( id );
486 for ( int i = pathToRoot.size() - 1; i >= 0; --i )
487 {
488 loadSingleNodeHierarchy( pathToRoot[i] );
489
490 QMutexLocker locker( &mHierarchyMutex );
491 qint64 pointCount = mHierarchy.value( id, -1 );
492 if ( pointCount != -1 )
493 return QgsPointCloudNode( id, pointCount, node.children(), node.error(), node.bounds() );
494 }
495
496 // If we fail, return with pointCount = -1 anyway
497 return node;
498}
499
500QgsPointCloudStatistics QgsEptPointCloudIndex::metadataStatistics() const
501{
502 QMap<QString, QgsPointCloudAttributeStatistics> statsMap;
503 for ( QgsPointCloudAttribute attribute : attributes().attributes() )
504 {
505 QString name = attribute.name();
506 const AttributeStatistics &stats = mMetadataStats[ name ];
507 if ( !stats.minimum.isValid() )
508 continue;
510 s.minimum = stats.minimum.toDouble();
511 s.maximum = stats.maximum.toDouble();
512 s.mean = stats.mean;
513 s.stDev = stats.stDev;
514 s.count = stats.count;
515
516 s.classCount = mAttributeClasses[ name ];
517
518 statsMap[ name ] = std::move( s );
519 }
520 return QgsPointCloudStatistics( pointCount(), statsMap );
521}
522
523bool QgsEptPointCloudIndex::loadSingleNodeHierarchy( const QgsPointCloudNodeId &nodeId ) const
524{
525 mHierarchyMutex.lock();
526 const bool foundInHierarchy = mHierarchy.contains( nodeId );
527 const bool foundInHierarchyNodes = mHierarchyNodes.contains( nodeId );
528 mHierarchyMutex.unlock();
529 // The hierarchy of the node is found => No need to load its file
530 if ( foundInHierarchy )
531 return true;
532 // We don't know that this node has a hierarchy file => We have nothing to load
533 if ( !foundInHierarchyNodes )
534 return true;
535
536 const QString filePath = QStringLiteral( "%1/ept-hierarchy/%2.json" ).arg( mUrlDirectoryPart, nodeId.toString() );
537
538 QByteArray dataJsonH;
539 if ( mAccessType == Qgis::PointCloudAccessType::Remote )
540 {
541 QNetworkRequest nr( filePath );
542 QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsEptPointCloudIndex" ) );
543 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
544 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
545
546 std::unique_ptr<QgsTileDownloadManagerReply> reply( QgsApplication::tileDownloadManager()->get( nr ) );
547
548 QEventLoop loop;
549 QObject::connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit );
550 loop.exec();
551
552 if ( reply->error() != QNetworkReply::NoError )
553 {
554 QgsDebugError( QStringLiteral( "Request failed: " ) + filePath );
555 return false;
556 }
557
558 dataJsonH = reply->data();
559 }
560 else
561 {
562 QFile file( filePath );
563 if ( ! file.open( QIODevice::ReadOnly ) )
564 {
565 QgsDebugError( QStringLiteral( "Loading file failed: " ) + filePath );
566 return false;
567 }
568 dataJsonH = file.readAll();
569 }
570
571 QJsonParseError errH;
572 const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH );
573 if ( errH.error != QJsonParseError::NoError )
574 {
575 QgsDebugMsgLevel( QStringLiteral( "QJsonParseError when reading hierarchy from file %1" ).arg( filePath ), 2 );
576 return false;
577 }
578
579 QMutexLocker locker( &mHierarchyMutex );
580 const QJsonObject rootHObj = docH.object();
581 for ( auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it )
582 {
583 const QString nodeIdStr = it.key();
584 const int nodePointCount = it.value().toInt();
585 const QgsPointCloudNodeId nodeId = QgsPointCloudNodeId::fromString( nodeIdStr );
586 if ( nodePointCount >= 0 )
587 mHierarchy[nodeId] = nodePointCount;
588 else if ( nodePointCount == -1 )
589 mHierarchyNodes.insert( nodeId );
590 }
591
592 return true;
593}
594
595QVector<QgsPointCloudNodeId> QgsEptPointCloudIndex::nodePathToRoot( const QgsPointCloudNodeId &nodeId ) const
596{
597 QVector<QgsPointCloudNodeId> path;
598 QgsPointCloudNodeId currentNode = nodeId;
599 do
600 {
601 path.push_back( currentNode );
602 currentNode = currentNode.parentNode();
603 }
604 while ( currentNode.d() >= 0 );
605
606 return path;
607}
608
609bool QgsEptPointCloudIndex::loadNodeHierarchy( const QgsPointCloudNodeId &nodeId ) const
610{
611 bool found;
612 {
613 QMutexLocker lock( &mHierarchyMutex );
614 found = mHierarchy.contains( nodeId );
615 }
616 if ( found )
617 return true;
618
619 QVector<QgsPointCloudNodeId> pathToRoot = nodePathToRoot( nodeId );
620 for ( int i = pathToRoot.size() - 1; i >= 0 && !mHierarchy.contains( nodeId ); --i )
621 {
622 const QgsPointCloudNodeId node = pathToRoot[i];
623 if ( !loadSingleNodeHierarchy( node ) )
624 return false;
625 }
626
627 {
628 QMutexLocker lock( &mHierarchyMutex );
629 found = mHierarchy.contains( nodeId );
630 }
631
632 return found;
633}
634
635
636bool QgsEptPointCloudIndex::isValid() const
637{
638 return mIsValid;
639}
640
641Qgis::PointCloudAccessType QgsEptPointCloudIndex::accessType() const
642{
643 return mAccessType;
644}
645
646#undef PROVIDER_KEY
647#undef PROVIDER_DESCRIPTION
648
PointCloudAccessType
The access type of the data, local is for local files and remote for remote files (over HTTP).
Definition qgis.h:5751
@ Local
Local means the source is a local file on the machine.
@ Remote
Remote means it's loaded through a protocol like HTTP.
virtual QgsPointCloudNode getNode(const QgsPointCloudNodeId &id) const
Returns object for a given node.
static QgsTileDownloadManager * tileDownloadManager()
Returns the application's tile download manager, used for download of map tiles when rendering.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ 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...
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:43
Class for handling a QgsPointCloudBlockRequest using existing cached QgsPointCloudBlock.
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
Base class for handling loading QgsPointCloudBlock asynchronously from a remote EPT dataset.
QByteArray content() const
Returns the reply content.
Collection of point cloud attributes.
void push_back(const QgsPointCloudAttribute &attribute)
Adds extra attribute.
void extend(const QgsPointCloudAttributeCollection &otherCollection, const QSet< QString > &matchingNames)
Adds specific missing attributes from another QgsPointCloudAttributeCollection.
Attribute for point cloud data pair of name and size in bytes.
@ UShort
Unsigned short int 2 bytes.
@ UChar
Unsigned char 1 byte.
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 a indexed point cloud node's position in octree.
int d() const
Returns d.
static QgsPointCloudNodeId fromString(const QString &str)
Creates node from string.
QString toString() const
Encode node to string.
QgsPointCloudNodeId parentNode() const
Returns the parent of the node.
Keeps metadata for indexed point cloud node.
QList< QgsPointCloudNodeId > children() const
Returns IDs of child nodes.
qint64 pointCount() const
Returns number of points contained in node data.
float error() const
Returns node's error in map units (used to determine in whether the node has enough detail for the cu...
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.
Class used to store statistics of a point cloud dataset.
A rectangle specified with double values.
void finished()
Emitted when the reply has finished (either with a success or with a failure)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40
#define QgsSetRequestInitiatorClass(request, _class)
Class used to store statistics of one attribute of a point cloud dataset.