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