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