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