QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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(), boundsConforming[3].toDouble(), boundsConforming[4].toDouble() );
224 mZMin = boundsConforming[2].toDouble();
225 mZMax = boundsConforming[5].toDouble();
226
227 const QJsonArray schemaArray = result.value( "schema"_L1 ).toArray();
229
230 for ( const QJsonValue &schemaItem : schemaArray )
231 {
232 const QJsonObject schemaObj = schemaItem.toObject();
233 const QString name = schemaObj.value( "name"_L1 ).toString();
234 const QString type = schemaObj.value( "type"_L1 ).toString();
235
236 const int size = schemaObj.value( "size"_L1 ).toInt();
237
238 if ( name == "ClassFlags"_L1 && size == 1 )
239 {
240 attributes.push_back( QgsPointCloudAttribute( u"Synthetic"_s, QgsPointCloudAttribute::UChar ) );
241 attributes.push_back( QgsPointCloudAttribute( u"KeyPoint"_s, QgsPointCloudAttribute::UChar ) );
242 attributes.push_back( QgsPointCloudAttribute( u"Withheld"_s, QgsPointCloudAttribute::UChar ) );
244 }
245 else if ( type == "float"_L1 && ( size == 4 ) )
246 {
248 }
249 else if ( type == "float"_L1 && ( size == 8 ) )
250 {
252 }
253 else if ( size == 1 )
254 {
256 }
257 else if ( type == "unsigned"_L1 && 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( "scale"_L1 ) )
277 scale = schemaObj.value( "scale"_L1 ).toDouble();
278
279 double offset = 0.f;
280 if ( schemaObj.contains( "offset"_L1 ) )
281 offset = schemaObj.value( "offset"_L1 ).toDouble();
282
283 if ( name == "X"_L1 )
284 {
285 mOffset.set( offset, mOffset.y(), mOffset.z() );
286 mScale.set( scale, mScale.y(), mScale.z() );
287 }
288 else if ( name == "Y"_L1 )
289 {
290 mOffset.set( mOffset.x(), offset, mOffset.z() );
291 mScale.set( mScale.x(), scale, mScale.z() );
292 }
293 else if ( name == "Z"_L1 )
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( "count"_L1 ) )
303 {
304 stats.count = schemaObj.value( "count"_L1 ).toInt();
305 foundStats = true;
306 }
307 if ( schemaObj.contains( "minimum"_L1 ) )
308 {
309 stats.minimum = schemaObj.value( "minimum"_L1 ).toDouble();
310 foundStats = true;
311 }
312 if ( schemaObj.contains( "maximum"_L1 ) )
313 {
314 stats.maximum = schemaObj.value( "maximum"_L1 ).toDouble();
315 foundStats = true;
316 }
317 if ( schemaObj.contains( "count"_L1 ) )
318 {
319 stats.mean = schemaObj.value( "mean"_L1 ).toDouble();
320 foundStats = true;
321 }
322 if ( schemaObj.contains( "stddev"_L1 ) )
323 {
324 stats.stDev = schemaObj.value( "stddev"_L1 ).toDouble();
325 foundStats = true;
326 }
327 if ( schemaObj.contains( "variance"_L1 ) )
328 {
329 stats.variance = schemaObj.value( "variance"_L1 ).toDouble();
330 foundStats = true;
331 }
332 if ( foundStats )
333 mMetadataStats.insert( name, stats );
334
335 if ( schemaObj.contains( "counts"_L1 ) )
336 {
337 QMap< int, int > classCounts;
338 const QJsonArray counts = schemaObj.value( "counts"_L1 ).toArray();
339 for ( const QJsonValue &count : counts )
340 {
341 const QJsonObject countObj = count.toObject();
342 classCounts.insert( countObj.value( "value"_L1 ).toInt(), countObj.value( "count"_L1 ).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 QGISDEBUG
362 double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
363 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
364 QgsDebugMsgLevel( u"res at lvl0 %1"_s.arg( dx / mSpan ), 2 );
365 QgsDebugMsgLevel( u"res at lvl1 %1"_s.arg( dx / mSpan / 2 ), 2 );
366 QgsDebugMsgLevel( u"res at lvl2 %1 with node size %2"_s.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 == Qgis::PointCloudAccessType::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( u"Error downloading node %1 data, error : %2 "_s.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 = request.ignoreIndexFilterEnabled() ? QgsPointCloudExpression() : mFilterExpression;
402 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
403 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
404 QgsRectangle filterRect = request.filterRect();
405
406 if ( mDataType == "binary"_L1 )
407 {
408 const QString filename = u"%1/ept-data/%2.bin"_s.arg( mUrlDirectoryPart, n.toString() );
409 block = QgsEptDecoder::decompressBinary( filename, attributes(), requestAttributes, scale(), offset(), filterExpression, filterRect );
410 }
411 else if ( mDataType == "zstandard"_L1 )
412 {
413 const QString filename = u"%1/ept-data/%2.zst"_s.arg( mUrlDirectoryPart, n.toString() );
414 block = QgsEptDecoder::decompressZStandard( filename, attributes(), request.attributes(), scale(), offset(), filterExpression, filterRect );
415 }
416 else if ( mDataType == "laszip"_L1 )
417 {
418 const QString filename = u"%1/ept-data/%2.laz"_s.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(), scale(), offset(), mFilterExpression, request.filterRect() );
432 }
433
434 if ( mAccessType != Qgis::PointCloudAccessType::Remote )
435 return nullptr;
436
437 if ( !loadNodeHierarchy( n ) )
438 return nullptr;
439
440 QString fileUrl;
441 if ( mDataType == "binary"_L1 )
442 {
443 fileUrl = u"%1/ept-data/%2.bin"_s.arg( mUrlDirectoryPart, n.toString() );
444 }
445 else if ( mDataType == "zstandard"_L1 )
446 {
447 fileUrl = u"%1/ept-data/%2.zst"_s.arg( mUrlDirectoryPart, n.toString() );
448 }
449 else if ( mDataType == "laszip"_L1 )
450 {
451 fileUrl = u"%1/ept-data/%2.laz"_s.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 = request.ignoreIndexFilterEnabled() ? QgsPointCloudExpression() : 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(), mAuthCfg );
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] = std::move( 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 = u"%1/ept-hierarchy/%2.json"_s.arg( mUrlDirectoryPart, nodeId.toString() );
544
545 QByteArray dataJsonH;
546 if ( mAccessType == Qgis::PointCloudAccessType::Remote )
547 {
548 QNetworkRequest nr( filePath );
549 QgsSetRequestInitiatorClass( nr, u"QgsEptPointCloudIndex"_s );
550 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
551 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
552
553 if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( nr, mAuthCfg ) )
554 {
555 QgsDebugError( u"Network request update failed for authcfg: %1"_s.arg( mAuthCfg ) );
556 return false;
557 }
558
559 std::unique_ptr<QgsTileDownloadManagerReply> reply( QgsApplication::tileDownloadManager()->get( nr ) );
560
561 QEventLoop loop;
562 QObject::connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit );
563 loop.exec();
564
565 if ( reply->error() != QNetworkReply::NoError )
566 {
567 QgsDebugError( u"Request failed: "_s + filePath );
568 return false;
569 }
570
571 dataJsonH = reply->data();
572 }
573 else
574 {
575 QFile file( filePath );
576 if ( !file.open( QIODevice::ReadOnly ) )
577 {
578 QgsDebugError( u"Loading file failed: "_s + filePath );
579 return false;
580 }
581 dataJsonH = file.readAll();
582 }
583
584 QJsonParseError errH;
585 const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH );
586 if ( errH.error != QJsonParseError::NoError )
587 {
588 QgsDebugMsgLevel( u"QJsonParseError when reading hierarchy from file %1"_s.arg( filePath ), 2 );
589 return false;
590 }
591
592 QMutexLocker locker( &mHierarchyMutex );
593 const QJsonObject rootHObj = docH.object();
594 for ( auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it )
595 {
596 const QString nodeIdStr = it.key();
597 const int nodePointCount = it.value().toInt();
598 const QgsPointCloudNodeId nodeId = QgsPointCloudNodeId::fromString( nodeIdStr );
599 if ( nodePointCount >= 0 )
600 mHierarchy[nodeId] = nodePointCount;
601 else if ( nodePointCount == -1 )
602 mHierarchyNodes.insert( nodeId );
603 }
604
605 return true;
606}
607
608QVector<QgsPointCloudNodeId> QgsEptPointCloudIndex::nodePathToRoot( const QgsPointCloudNodeId &nodeId ) const
609{
610 QVector<QgsPointCloudNodeId> path;
611 QgsPointCloudNodeId currentNode = nodeId;
612 do
613 {
614 path.push_back( currentNode );
615 currentNode = currentNode.parentNode();
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:6432
@ Local
Local means the source is a local file on the machine.
Definition qgis.h:6433
@ Remote
Remote means it's loaded through a protocol like HTTP.
Definition qgis.h:6434
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.