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