From dcc243d87c3b94a49a15e684ce438bd315d70e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Wed, 30 Oct 2024 22:45:59 +0100 Subject: [PATCH] Merge remote and local QgsEptPointCloudIndex classes --- src/core/CMakeLists.txt | 2 - src/core/pointcloud/qgseptpointcloudindex.cpp | 255 +++++++++++----- src/core/pointcloud/qgseptpointcloudindex.h | 15 +- src/core/pointcloud/qgspointcloudindex.h | 2 +- .../qgsremoteeptpointcloudindex.cpp | 275 ------------------ .../pointcloud/qgsremoteeptpointcloudindex.h | 86 ------ src/core/providers/ept/qgseptprovider.cpp | 6 +- src/core/providers/ept/qgseptprovider.h | 1 - .../vpc/qgsvirtualpointcloudprovider.cpp | 3 +- 9 files changed, 199 insertions(+), 446 deletions(-) delete mode 100644 src/core/pointcloud/qgsremoteeptpointcloudindex.cpp delete mode 100644 src/core/pointcloud/qgsremoteeptpointcloudindex.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 808540650d29..0c9e8a20c40a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2234,13 +2234,11 @@ if (WITH_EPT) set(QGIS_CORE_SRCS ${QGIS_CORE_SRCS} providers/ept/qgseptprovider.cpp pointcloud/qgseptpointcloudindex.cpp - pointcloud/qgsremoteeptpointcloudindex.cpp pointcloud/qgseptpointcloudblockrequest.cpp ) set(QGIS_CORE_HDRS ${QGIS_CORE_HDRS} providers/ept/qgseptprovider.h pointcloud/qgseptpointcloudindex.h - pointcloud/qgsremoteeptpointcloudindex.h pointcloud/qgseptpointcloudblockrequest.h ) diff --git a/src/core/pointcloud/qgseptpointcloudindex.cpp b/src/core/pointcloud/qgseptpointcloudindex.cpp index 0a89995aacef..7c4366fff1ca 100644 --- a/src/core/pointcloud/qgseptpointcloudindex.cpp +++ b/src/core/pointcloud/qgseptpointcloudindex.cpp @@ -26,21 +26,31 @@ #include #include #include +#include +#include "qgsapplication.h" +#include "qgsblockingnetworkrequest.h" +#include "qgscachedpointcloudblockrequest.h" #include "qgseptdecoder.h" +#include "qgseptpointcloudblockrequest.h" #include "qgslazdecoder.h" #include "qgscoordinatereferencesystem.h" +#include "qgspointcloudblockrequest.h" #include "qgspointcloudrequest.h" #include "qgspointcloudattribute.h" #include "qgslogger.h" #include "qgspointcloudexpression.h" +#include "qgssetrequestinitiator_p.h" ///@cond PRIVATE #define PROVIDER_KEY QStringLiteral( "ept" ) #define PROVIDER_DESCRIPTION QStringLiteral( "EPT point cloud provider" ) -QgsEptPointCloudIndex::QgsEptPointCloudIndex() = default; +QgsEptPointCloudIndex::QgsEptPointCloudIndex() +{ + mHierarchyNodes.insert( IndexedPointCloudNode( 0, 0, 0, 0 ) ); +} QgsEptPointCloudIndex::~QgsEptPointCloudIndex() = default; @@ -52,39 +62,70 @@ std::unique_ptr QgsEptPointCloudIndex::clone() const return std::unique_ptr( clone ); } -void QgsEptPointCloudIndex::load( const QString &fileName ) +QList QgsEptPointCloudIndex::nodeChildren( const IndexedPointCloudNode &n ) const +{ + QList lst; + if ( !loadNodeHierarchy( n ) ) + return lst; + + const int d = n.d() + 1; + const int x = n.x() * 2; + const int y = n.y() * 2; + const int z = n.z() * 2; + + lst.reserve( 8 ); + for ( int i = 0; i < 8; ++i ) + { + int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 ); + const IndexedPointCloudNode n2( d, x + dx, y + dy, z + dz ); + if ( loadNodeHierarchy( n2 ) ) + lst.append( n2 ); + } + return lst; +} + +void QgsEptPointCloudIndex::load( const QString &urlString ) { - mUri = fileName; - QFile f( fileName ); - if ( !f.open( QIODevice::ReadOnly ) ) + QUrl url = urlString; + // Treat non-URLs as local files + if ( !url.isValid() || url.scheme().isEmpty() ) + url = QUrl::fromLocalFile( urlString ); + mUri = url.toString(); + + QStringList splitUrl = mUri.split( '/' ); + + mUrlFileNamePart = splitUrl.back(); + splitUrl.pop_back(); + mUrlDirectoryPart = splitUrl.join( '/' ); + + QNetworkRequest nr = QNetworkRequest( QUrl( mUri ) ); + + QgsBlockingNetworkRequest req; + if ( req.get( nr ) != QgsBlockingNetworkRequest::NoError ) { - mError = tr( "Unable to open %1 for reading" ).arg( fileName ); + QgsDebugError( QStringLiteral( "Request failed: " ) + mUri ); mIsValid = false; + mError = req.errorMessage(); return; } - const QDir directory = QFileInfo( fileName ).absoluteDir(); - mDirectory = directory.absolutePath(); - - const QByteArray dataJson = f.readAll(); - bool success = loadSchema( dataJson ); + const QgsNetworkReplyContent reply = req.reply(); + bool success = loadSchema( reply.content() ); if ( success ) { // try to import the metadata too! - QFile manifestFile( mDirectory + QStringLiteral( "/ept-sources/manifest.json" ) ); - if ( manifestFile.open( QIODevice::ReadOnly ) ) + QUrl manifestUrl( mUrlDirectoryPart + QStringLiteral( "/ept-sources/manifest.json" ) ); + + QNetworkRequest nr = QNetworkRequest( QUrl( manifestUrl ) ); + QgsBlockingNetworkRequest req; + if ( req.get( nr ) == QgsBlockingNetworkRequest::NoError ) { - const QByteArray manifestJson = manifestFile.readAll(); + const QByteArray manifestJson = req.reply().content(); loadManifest( manifestJson ); } } - if ( success ) - { - success = loadHierarchy(); - } - mIsValid = success; } @@ -101,10 +142,12 @@ void QgsEptPointCloudIndex::loadManifest( const QByteArray &manifestJson ) { const QJsonObject sourceObject = manifestArray.at( 0 ).toObject(); const QString metadataPath = sourceObject.value( QStringLiteral( "metadataPath" ) ).toString(); - QFile metadataFile( mDirectory + QStringLiteral( "/ept-sources/" ) + metadataPath ); - if ( metadataFile.open( QIODevice::ReadOnly ) ) + QUrl metadataUrl( mUrlDirectoryPart + QStringLiteral( "/ept-sources/" ) + metadataPath ); + QNetworkRequest nr = QNetworkRequest( QUrl( metadataUrl ) ); + QgsBlockingNetworkRequest req; + if ( req.get( nr ) == QgsBlockingNetworkRequest::NoError ) { - const QByteArray metadataJson = metadataFile.readAll(); + const QByteArray metadataJson = req.reply().content(); const QJsonDocument metadataDoc = QJsonDocument::fromJson( metadataJson, &err ); if ( err.error == QJsonParseError::NoError ) { @@ -315,47 +358,65 @@ std::unique_ptr QgsEptPointCloudIndex::nodeData( const Index return std::unique_ptr( cached ); } - mHierarchyMutex.lock(); - const bool found = mHierarchy.contains( n ); - mHierarchyMutex.unlock(); - if ( !found ) + std::unique_ptr blockRequest( asyncNodeData( n, request ) ); + if ( !blockRequest ) return nullptr; - // we need to create a copy of the expression to pass to the decoder - // as the same QgsPointCloudExpression object mighgt be concurrently - // used on another thread, for example in a 3d view - QgsPointCloudExpression filterExpression = mFilterExpression; - QgsPointCloudAttributeCollection requestAttributes = request.attributes(); - requestAttributes.extend( attributes(), filterExpression.referencedAttributes() ); - QgsRectangle filterRect = request.filterRect(); + QEventLoop loop; + connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit ); + loop.exec(); + + std::unique_ptr block = blockRequest->takeBlock(); + if ( !block ) + { + QgsDebugError( QStringLiteral( "Error downloading node %1 data, error : %2 " ).arg( n.toString(), blockRequest->errorStr() ) ); + } + + storeNodeDataToCache( block.get(), n, request ); + return block; +} + +QgsPointCloudBlockRequest *QgsEptPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) +{ + if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) ) + { + return new QgsCachedPointCloudBlockRequest( cached, n, mUri, attributes(), request.attributes(), + scale(), offset(), mFilterExpression, request.filterRect() ); + } + + if ( !loadNodeHierarchy( n ) ) + return nullptr; - std::unique_ptr decoded; + QString fileUrl; if ( mDataType == QLatin1String( "binary" ) ) { - const QString filename = QStringLiteral( "%1/ept-data/%2.bin" ).arg( mDirectory, n.toString() ); - decoded = QgsEptDecoder::decompressBinary( filename, attributes(), requestAttributes, scale(), offset(), filterExpression, filterRect ); + fileUrl = QStringLiteral( "%1/ept-data/%2.bin" ).arg( mUrlDirectoryPart, n.toString() ); } else if ( mDataType == QLatin1String( "zstandard" ) ) { - const QString filename = QStringLiteral( "%1/ept-data/%2.zst" ).arg( mDirectory, n.toString() ); - decoded = QgsEptDecoder::decompressZStandard( filename, attributes(), request.attributes(), scale(), offset(), filterExpression, filterRect ); + fileUrl = QStringLiteral( "%1/ept-data/%2.zst" ).arg( mUrlDirectoryPart, n.toString() ); } else if ( mDataType == QLatin1String( "laszip" ) ) { - const QString filename = QStringLiteral( "%1/ept-data/%2.laz" ).arg( mDirectory, n.toString() ); - decoded = QgsLazDecoder::decompressLaz( filename, requestAttributes, filterExpression, filterRect ); + fileUrl = QStringLiteral( "%1/ept-data/%2.laz" ).arg( mUrlDirectoryPart, n.toString() ); + } + else + { + return nullptr; } - storeNodeDataToCache( decoded.get(), n, request ); - return decoded; + // we need to create a copy of the expression to pass to the decoder + // as the same QgsPointCloudExpression object might be concurrently + // used on another thread, for example in a 3d view + QgsPointCloudExpression filterExpression = mFilterExpression; + QgsPointCloudAttributeCollection requestAttributes = request.attributes(); + requestAttributes.extend( attributes(), filterExpression.referencedAttributes() ); + return new QgsEptPointCloudBlockRequest( n, fileUrl, mDataType, attributes(), requestAttributes, scale(), offset(), filterExpression, request.filterRect() ); } -QgsPointCloudBlockRequest *QgsEptPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) +bool QgsEptPointCloudIndex::hasNode( const IndexedPointCloudNode &n ) const { - Q_UNUSED( n ); - Q_UNUSED( request ); - Q_ASSERT( false ); - return nullptr; // unsupported + return loadNodeHierarchy( n ); } QgsCoordinateReferenceSystem QgsEptPointCloudIndex::crs() const @@ -368,6 +429,14 @@ qint64 QgsEptPointCloudIndex::pointCount() const return mPointCount; } +int QgsEptPointCloudIndex::nodePointCount( const IndexedPointCloudNode &n ) +{ + // First load the node, so the superclass method can read it + if ( !loadNodeHierarchy( n ) ) + return -1; + return QgsPointCloudIndex::nodePointCount( n ); +} + bool QgsEptPointCloudIndex::hasStatisticsMetadata() const { return !mMetadataStats.isEmpty(); @@ -439,28 +508,63 @@ QVariant QgsEptPointCloudIndex::metadataClassStatistic( const QString &attribute return values.value( value.toInt() ); } -bool QgsEptPointCloudIndex::loadHierarchy() +bool QgsEptPointCloudIndex::loadNodeHierarchy( const IndexedPointCloudNode &nodeId ) const { - QQueue queue; - queue.enqueue( QStringLiteral( "0-0-0-0" ) ); - while ( !queue.isEmpty() ) + mHierarchyMutex.lock(); + bool found = mHierarchy.contains( nodeId ); + mHierarchyMutex.unlock(); + if ( found ) + return true; + + QVector nodePathToRoot; { - const QString filename = QStringLiteral( "%1/ept-hierarchy/%2.json" ).arg( mDirectory, queue.dequeue() ); - QFile fH( filename ); - if ( !fH.open( QIODevice::ReadOnly ) ) + IndexedPointCloudNode currentNode = nodeId; + do { - QgsDebugMsgLevel( QStringLiteral( "unable to read hierarchy from file %1" ).arg( filename ), 2 ); - mError = QStringLiteral( "unable to read hierarchy from file %1" ).arg( filename ); + nodePathToRoot.push_back( currentNode ); + currentNode = currentNode.parentNode(); + } + while ( currentNode.d() >= 0 ); + } + + for ( int i = nodePathToRoot.size() - 1; i >= 0 && !mHierarchy.contains( nodeId ); --i ) + { + const IndexedPointCloudNode node = nodePathToRoot[i]; + //! The hierarchy of the node is found => No need to load its file + mHierarchyMutex.lock(); + const bool foundInHierarchy = mHierarchy.contains( node ); + const bool foundInHierarchyNodes = mHierarchyNodes.contains( node ); + mHierarchyMutex.unlock(); + if ( foundInHierarchy ) + continue; + + if ( !foundInHierarchyNodes ) + continue; + + const QString fileUrl = QStringLiteral( "%1/ept-hierarchy/%2.json" ).arg( mUrlDirectoryPart, node.toString() ); + QNetworkRequest nr( fileUrl ); + QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsEptPointCloudIndex" ) ); + nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache ); + nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true ); + + std::unique_ptr reply( QgsApplication::tileDownloadManager()->get( nr ) ); + + QEventLoop loop; + connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit ); + loop.exec(); + + if ( reply->error() != QNetworkReply::NoError ) + { + QgsDebugError( QStringLiteral( "Request failed: " ) + mUri ); return false; } - const QByteArray dataJsonH = fH.readAll(); + const QByteArray dataJsonH = reply->data(); QJsonParseError errH; const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH ); if ( errH.error != QJsonParseError::NoError ) { - QgsDebugMsgLevel( QStringLiteral( "QJsonParseError when reading hierarchy from file %1" ).arg( filename ), 2 ); - mError = QStringLiteral( "QJsonParseError when reading hierarchy from file %1" ).arg( filename ); + QgsDebugMsgLevel( QStringLiteral( "QJsonParseError when reading hierarchy from file %1" ).arg( fileUrl ), 2 ); return false; } @@ -469,27 +573,34 @@ bool QgsEptPointCloudIndex::loadHierarchy() { const QString nodeIdStr = it.key(); const int nodePointCount = it.value().toInt(); - if ( nodePointCount < 0 ) - { - queue.enqueue( nodeIdStr ); - } - else - { - const IndexedPointCloudNode nodeId = IndexedPointCloudNode::fromString( nodeIdStr ); - mHierarchyMutex.lock(); + const IndexedPointCloudNode nodeId = IndexedPointCloudNode::fromString( nodeIdStr ); + mHierarchyMutex.lock(); + if ( nodePointCount >= 0 ) mHierarchy[nodeId] = nodePointCount; - mHierarchyMutex.unlock(); - } + else if ( nodePointCount == -1 ) + mHierarchyNodes.insert( nodeId ); + mHierarchyMutex.unlock(); } } - return true; + + mHierarchyMutex.lock(); + found = mHierarchy.contains( nodeId ); + mHierarchyMutex.unlock(); + + return found; } + bool QgsEptPointCloudIndex::isValid() const { return mIsValid; } +QgsPointCloudIndex::AccessType QgsEptPointCloudIndex::accessType() const +{ + return QUrl( mUri ).scheme() == "file" ? AccessType::Local : AccessType::Remote; +} + void QgsEptPointCloudIndex::copyCommonProperties( QgsEptPointCloudIndex *destination ) const { QgsPointCloudIndex::copyCommonProperties( destination ); @@ -497,8 +608,10 @@ void QgsEptPointCloudIndex::copyCommonProperties( QgsEptPointCloudIndex *destina // QgsEptPointCloudIndex specific fields destination->mIsValid = mIsValid; destination->mDataType = mDataType; - destination->mDirectory = mDirectory; + destination->mUrlDirectoryPart = mUrlDirectoryPart; + destination->mUrlFileNamePart = mUrlFileNamePart; destination->mWkt = mWkt; + destination->mHierarchyNodes = mHierarchyNodes; destination->mPointCount = mPointCount; destination->mMetadataStats = mMetadataStats; destination->mAttributeClasses = mAttributeClasses; diff --git a/src/core/pointcloud/qgseptpointcloudindex.h b/src/core/pointcloud/qgseptpointcloudindex.h index 8d99dd4048d4..f9ba9c0b76f4 100644 --- a/src/core/pointcloud/qgseptpointcloudindex.h +++ b/src/core/pointcloud/qgseptpointcloudindex.h @@ -44,13 +44,17 @@ class CORE_EXPORT QgsEptPointCloudIndex: public QgsPointCloudIndex std::unique_ptr clone() const override; + QList nodeChildren( const IndexedPointCloudNode &n ) const override; + void load( const QString &fileName ) override; std::unique_ptr nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override; QgsPointCloudBlockRequest *asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override; + bool hasNode( const IndexedPointCloudNode &n ) const override; QgsCoordinateReferenceSystem crs() const override; qint64 pointCount() const override; + virtual int nodePointCount( const IndexedPointCloudNode &n ) override; bool hasStatisticsMetadata() const override; QVariant metadataStatistic( const QString &attribute, Qgis::Statistic statistic ) const override; QVariantList metadataClasses( const QString &attribute ) const override; @@ -58,7 +62,7 @@ class CORE_EXPORT QgsEptPointCloudIndex: public QgsPointCloudIndex QVariantMap originalMetadata() const override { return mOriginalMetadata; } bool isValid() const override; - QgsPointCloudIndex::AccessType accessType() const override { return QgsPointCloudIndex::Local; }; + QgsPointCloudIndex::AccessType accessType() const override; /** * Copies common properties to the \a destination index @@ -70,13 +74,18 @@ class CORE_EXPORT QgsEptPointCloudIndex: public QgsPointCloudIndex bool loadSchema( const QByteArray &dataJson ); void loadManifest( const QByteArray &manifestJson ); bool loadSchema( QFile &f ); - bool loadHierarchy(); + bool loadNodeHierarchy( const IndexedPointCloudNode &nodeId ) const; bool mIsValid = false; QString mDataType; - QString mDirectory; QString mWkt; + QString mUrlDirectoryPart; + QString mUrlFileNamePart; + + //! Contains the nodes that will have */ept-hierarchy/d-x-y-z.json file + mutable QSet mHierarchyNodes; + qint64 mPointCount = 0; struct AttributeStatistics diff --git a/src/core/pointcloud/qgspointcloudindex.h b/src/core/pointcloud/qgspointcloudindex.h index e476c54d4ab6..ae82eaac4c97 100644 --- a/src/core/pointcloud/qgspointcloudindex.h +++ b/src/core/pointcloud/qgspointcloudindex.h @@ -346,7 +346,7 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject /** * Returns the number of points of indexed point cloud node \a n */ - int nodePointCount( const IndexedPointCloudNode &n ); + virtual int nodePointCount( const IndexedPointCloudNode &n ); /** * Sets the string used to define a subset of the point cloud. diff --git a/src/core/pointcloud/qgsremoteeptpointcloudindex.cpp b/src/core/pointcloud/qgsremoteeptpointcloudindex.cpp deleted file mode 100644 index 0ac0630b2387..000000000000 --- a/src/core/pointcloud/qgsremoteeptpointcloudindex.cpp +++ /dev/null @@ -1,275 +0,0 @@ -/*************************************************************************** - qgsremoteeptpointcloudindex.cpp - -------------------- - begin : March 2021 - copyright : (C) 2021 by Belgacem Nedjima - email : belgacem dot nedjima at gmail dot com - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include "qgsremoteeptpointcloudindex.h" -#include "moc_qgsremoteeptpointcloudindex.cpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "qgsapplication.h" -#include "qgspointcloudrequest.h" -#include "qgspointcloudattribute.h" -#include "qgslogger.h" -#include "qgsfeedback.h" -#include "qgstiledownloadmanager.h" -#include "qgsblockingnetworkrequest.h" -#include "qgseptpointcloudblockrequest.h" -#include "qgscachedpointcloudblockrequest.h" -#include "qgspointcloudexpression.h" -#include "qgsnetworkaccessmanager.h" -#include "qgssetrequestinitiator_p.h" - -///@cond PRIVATE - -QgsRemoteEptPointCloudIndex::QgsRemoteEptPointCloudIndex() : QgsEptPointCloudIndex() -{ - mHierarchyNodes.insert( IndexedPointCloudNode( 0, 0, 0, 0 ) ); -} - -QgsRemoteEptPointCloudIndex::~QgsRemoteEptPointCloudIndex() = default; - -std::unique_ptr QgsRemoteEptPointCloudIndex::clone() const -{ - QgsRemoteEptPointCloudIndex *clone = new QgsRemoteEptPointCloudIndex; - QMutexLocker locker( &mHierarchyMutex ); - copyCommonProperties( clone ); - return std::unique_ptr( clone ); -} - -QList QgsRemoteEptPointCloudIndex::nodeChildren( const IndexedPointCloudNode &n ) const -{ - QList lst; - if ( !loadNodeHierarchy( n ) ) - return lst; - - const int d = n.d() + 1; - const int x = n.x() * 2; - const int y = n.y() * 2; - const int z = n.z() * 2; - - lst.reserve( 8 ); - for ( int i = 0; i < 8; ++i ) - { - int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 ); - const IndexedPointCloudNode n2( d, x + dx, y + dy, z + dz ); - if ( loadNodeHierarchy( n2 ) ) - lst.append( n2 ); - } - return lst; -} - -void QgsRemoteEptPointCloudIndex::load( const QString &uri ) -{ - mUri = uri; - - QStringList splitUrl = uri.split( '/' ); - - mUrlFileNamePart = splitUrl.back(); - splitUrl.pop_back(); - mUrlDirectoryPart = splitUrl.join( '/' ); - - QNetworkRequest nr = QNetworkRequest( QUrl( mUri ) ); - - QgsBlockingNetworkRequest req; - const QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr ); - if ( errCode != QgsBlockingNetworkRequest::NoError ) - { - QgsDebugError( QStringLiteral( "Request failed: " ) + uri ); - mIsValid = false; - mError = req.errorMessage(); - return; - } - - const QgsNetworkReplyContent reply = req.reply(); - mIsValid = loadSchema( reply.content() ); -} - -std::unique_ptr QgsRemoteEptPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) -{ - if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) ) - { - return std::unique_ptr( cached ); - } - - std::unique_ptr blockRequest( asyncNodeData( n, request ) ); - if ( !blockRequest ) - return nullptr; - - QEventLoop loop; - connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit ); - loop.exec(); - - std::unique_ptr block = blockRequest->takeBlock(); - if ( !block ) - { - QgsDebugError( QStringLiteral( "Error downloading node %1 data, error : %2 " ).arg( n.toString(), blockRequest->errorStr() ) ); - } - - storeNodeDataToCache( block.get(), n, request ); - return block; -} - -QgsPointCloudBlockRequest *QgsRemoteEptPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) -{ - if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) ) - { - return new QgsCachedPointCloudBlockRequest( cached, n, mUri, attributes(), request.attributes(), - scale(), offset(), mFilterExpression, request.filterRect() ); - } - - if ( !loadNodeHierarchy( n ) ) - return nullptr; - - QString fileUrl; - if ( mDataType == QLatin1String( "binary" ) ) - { - fileUrl = QStringLiteral( "%1/ept-data/%2.bin" ).arg( mUrlDirectoryPart, n.toString() ); - } - else if ( mDataType == QLatin1String( "zstandard" ) ) - { - fileUrl = QStringLiteral( "%1/ept-data/%2.zst" ).arg( mUrlDirectoryPart, n.toString() ); - } - else if ( mDataType == QLatin1String( "laszip" ) ) - { - fileUrl = QStringLiteral( "%1/ept-data/%2.laz" ).arg( mUrlDirectoryPart, n.toString() ); - } - else - { - return nullptr; - } - - // we need to create a copy of the expression to pass to the decoder - // as the same QgsPointCloudExpression object might be concurrently - // used on another thread, for example in a 3d view - QgsPointCloudExpression filterExpression = mFilterExpression; - QgsPointCloudAttributeCollection requestAttributes = request.attributes(); - requestAttributes.extend( attributes(), filterExpression.referencedAttributes() ); - return new QgsEptPointCloudBlockRequest( n, fileUrl, mDataType, attributes(), requestAttributes, scale(), offset(), filterExpression, request.filterRect() ); -} - -bool QgsRemoteEptPointCloudIndex::hasNode( const IndexedPointCloudNode &n ) const -{ - return loadNodeHierarchy( n ); -} - -bool QgsRemoteEptPointCloudIndex::loadNodeHierarchy( const IndexedPointCloudNode &nodeId ) const -{ - mHierarchyMutex.lock(); - bool found = mHierarchy.contains( nodeId ); - mHierarchyMutex.unlock(); - if ( found ) - return true; - - QVector nodePathToRoot; - { - IndexedPointCloudNode currentNode = nodeId; - do - { - nodePathToRoot.push_back( currentNode ); - currentNode = currentNode.parentNode(); - } - while ( currentNode.d() >= 0 ); - } - - for ( int i = nodePathToRoot.size() - 1; i >= 0 && !mHierarchy.contains( nodeId ); --i ) - { - const IndexedPointCloudNode node = nodePathToRoot[i]; - //! The hierarchy of the node is found => No need to load its file - mHierarchyMutex.lock(); - const bool foundInHierarchy = mHierarchy.contains( node ); - const bool foundInHierarchyNodes = mHierarchyNodes.contains( node ); - mHierarchyMutex.unlock(); - if ( foundInHierarchy ) - continue; - - if ( !foundInHierarchyNodes ) - continue; - - const QString fileUrl = QStringLiteral( "%1/ept-hierarchy/%2.json" ).arg( mUrlDirectoryPart, node.toString() ); - QNetworkRequest nr( fileUrl ); - QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsRemoteEptPointCloudIndex" ) ); - nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache ); - nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true ); - - std::unique_ptr reply( QgsApplication::tileDownloadManager()->get( nr ) ); - - QEventLoop loop; - connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit ); - loop.exec(); - - if ( reply->error() != QNetworkReply::NoError ) - { - QgsDebugError( QStringLiteral( "Request failed: " ) + mUri ); - return false; - } - - const QByteArray dataJsonH = reply->data(); - QJsonParseError errH; - const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH ); - if ( errH.error != QJsonParseError::NoError ) - { - QgsDebugMsgLevel( QStringLiteral( "QJsonParseError when reading hierarchy from file %1" ).arg( fileUrl ), 2 ); - return false; - } - - const QJsonObject rootHObj = docH.object(); - for ( auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it ) - { - const QString nodeIdStr = it.key(); - const int nodePointCount = it.value().toInt(); - const IndexedPointCloudNode nodeId = IndexedPointCloudNode::fromString( nodeIdStr ); - mHierarchyMutex.lock(); - if ( nodePointCount >= 0 ) - mHierarchy[nodeId] = nodePointCount; - else if ( nodePointCount == -1 ) - mHierarchyNodes.insert( nodeId ); - mHierarchyMutex.unlock(); - } - } - - mHierarchyMutex.lock(); - found = mHierarchy.contains( nodeId ); - mHierarchyMutex.unlock(); - - return found; -} - -bool QgsRemoteEptPointCloudIndex::isValid() const -{ - return mIsValid; -} - -void QgsRemoteEptPointCloudIndex::copyCommonProperties( QgsRemoteEptPointCloudIndex *destination ) const -{ - QgsEptPointCloudIndex::copyCommonProperties( destination ); - - // QgsRemoteEptPointCloudIndex specific fields - destination->mUrlDirectoryPart = mUrlDirectoryPart; - destination->mUrlFileNamePart = mUrlFileNamePart; - destination->mHierarchyNodes = mHierarchyNodes; -} - -///@endcond diff --git a/src/core/pointcloud/qgsremoteeptpointcloudindex.h b/src/core/pointcloud/qgsremoteeptpointcloudindex.h deleted file mode 100644 index 4a765ec84098..000000000000 --- a/src/core/pointcloud/qgsremoteeptpointcloudindex.h +++ /dev/null @@ -1,86 +0,0 @@ -/*************************************************************************** - qgsremoteeptpointcloudindex.h - -------------------- - begin : March 2021 - copyright : (C) 2021 by Belgacem Nedjima - email : belgacem dot nedjima at gmail dot com - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef QGSREMOTEEPTPOINTCLOUDINDEX_H -#define QGSREMOTEEPTPOINTCLOUDINDEX_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "qgspointcloudindex.h" -#include "qgspointcloudattribute.h" -#include "qgsstatisticalsummary.h" -#include "qgis_sip.h" -#include "qgseptpointcloudindex.h" - -///@cond PRIVATE -#define SIP_NO_FILE - -class QgsCoordinateReferenceSystem; -class QgsTileDownloadManager; - -class CORE_EXPORT QgsRemoteEptPointCloudIndex: public QgsEptPointCloudIndex -{ - Q_OBJECT - public: - - explicit QgsRemoteEptPointCloudIndex(); - ~QgsRemoteEptPointCloudIndex(); - - std::unique_ptr clone() const override; - - QList nodeChildren( const IndexedPointCloudNode &n ) const override; - - void load( const QString &fileName ) override; - - std::unique_ptr nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override; - QgsPointCloudBlockRequest *asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override; - - bool hasNode( const IndexedPointCloudNode &n ) const override; - - bool isValid() const override; - - QgsPointCloudIndex::AccessType accessType() const override { return QgsPointCloudIndex::Remote; } - - /** - * Copies common properties to the \a destination index - * \since QGIS 3.26 - */ - void copyCommonProperties( QgsRemoteEptPointCloudIndex *destination ) const; - - private: - bool loadNodeHierarchy( const IndexedPointCloudNode &nodeId ) const; - - QString mUrlDirectoryPart; - QString mUrlFileNamePart; - - QUrl mUrl; - - //! Contains the nodes that will have */ept-hierarchy/d-x-y-z.json file - mutable QSet mHierarchyNodes; -}; - -///@endcond - -#endif // QGSREMOTEEPTPOINTCLOUDINDEX_H diff --git a/src/core/providers/ept/qgseptprovider.cpp b/src/core/providers/ept/qgseptprovider.cpp index 3a3f4aaec9c2..37e25f885e06 100644 --- a/src/core/providers/ept/qgseptprovider.cpp +++ b/src/core/providers/ept/qgseptprovider.cpp @@ -19,7 +19,6 @@ #include "qgseptprovider.h" #include "moc_qgseptprovider.cpp" #include "qgseptpointcloudindex.h" -#include "qgsremoteeptpointcloudindex.h" #include "qgsruntimeprofiler.h" #include "qgsapplication.h" #include "qgsprovidersublayerdetails.h" @@ -40,10 +39,7 @@ QgsEptProvider::QgsEptProvider( Qgis::DataProviderReadFlags flags ) : QgsPointCloudDataProvider( uri, options, flags ) { - if ( uri.startsWith( QStringLiteral( "http" ), Qt::CaseSensitivity::CaseInsensitive ) ) - mIndex.reset( new QgsRemoteEptPointCloudIndex ); - else - mIndex.reset( new QgsEptPointCloudIndex ); + mIndex.reset( new QgsEptPointCloudIndex ); std::unique_ptr< QgsScopedRuntimeProfile > profile; if ( QgsApplication::profiler()->groupIsActive( QStringLiteral( "projectload" ) ) ) diff --git a/src/core/providers/ept/qgseptprovider.h b/src/core/providers/ept/qgseptprovider.h index bcb8bfd843ba..527666c7d914 100644 --- a/src/core/providers/ept/qgseptprovider.h +++ b/src/core/providers/ept/qgseptprovider.h @@ -27,7 +27,6 @@ #define SIP_NO_FILE class QgsEptPointCloudIndex; -class QgsRemoteEptPointCloudIndex; class QgsEptProvider: public QgsPointCloudDataProvider { diff --git a/src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp b/src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp index 2bd066696c17..a253897a9463 100644 --- a/src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp +++ b/src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp @@ -23,7 +23,6 @@ #include "qgscopcpointcloudindex.h" #include "qgseptpointcloudindex.h" #include "qgsremotecopcpointcloudindex.h" -#include "qgsremoteeptpointcloudindex.h" #include "qgspointcloudsubindex.h" #include "qgspointcloudclassifiedrenderer.h" #include "qgspointcloudextentrenderer.h" @@ -397,7 +396,7 @@ void QgsVirtualPointCloudProvider::loadSubIndex( int i ) if ( sl.uri().endsWith( QStringLiteral( "copc.laz" ), Qt::CaseSensitivity::CaseInsensitive ) ) sl.setIndex( new QgsRemoteCopcPointCloudIndex() ); else if ( sl.uri().endsWith( QStringLiteral( "ept.json" ), Qt::CaseSensitivity::CaseInsensitive ) ) - sl.setIndex( new QgsRemoteEptPointCloudIndex() ); + sl.setIndex( new QgsEptPointCloudIndex() ); } else {