From 73d97193937b6ba11a71731d61b9074d384e8627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Mon, 6 Jan 2025 20:06:55 +0100 Subject: [PATCH 1/2] Load COPC QGIS statistics over HTTP --- .../pointcloud/qgscopcpointcloudindex.cpp | 92 ++++++++++--------- src/core/pointcloud/qgscopcpointcloudindex.h | 3 + 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/core/pointcloud/qgscopcpointcloudindex.cpp b/src/core/pointcloud/qgscopcpointcloudindex.cpp index 25e45cc54d5b..3cd5f3dfe8fe 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.cpp +++ b/src/core/pointcloud/qgscopcpointcloudindex.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "qgsapplication.h" #include "qgsbox3d.h" @@ -356,44 +357,11 @@ void QgsCopcPointCloudIndex::fetchHierarchyPage( uint64_t offset, uint64_t byteS { Q_ASSERT( byteSize > 0 ); - switch ( mAccessType ) - { - case Qgis::PointCloudAccessType::Local: - { - mCopcFile.seekg( offset ); - std::unique_ptr data( new char[ byteSize ] ); - mCopcFile.read( data.get(), byteSize ); - - populateHierarchy( data.get(), byteSize ); - return; - } - case Qgis::PointCloudAccessType::Remote: - { - QNetworkRequest nr = QNetworkRequest( QUrl( mUri ) ); - QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsCopcPointCloudIndex" ) ); - nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache ); - nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true ); - QByteArray queryRange = QStringLiteral( "bytes=%1-%2" ).arg( offset ).arg( offset + byteSize - 1 ).toLocal8Bit(); - nr.setRawHeader( "Range", queryRange ); - - std::unique_ptr reply( QgsApplication::tileDownloadManager()->get( nr ) ); - - QEventLoop loop; - QObject::connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit ); - loop.exec(); - - if ( reply->error() != QNetworkReply::NoError ) - { - QgsDebugError( QStringLiteral( "Request failed: " ) + mUri ); - return; - } + QByteArray data = readRange( offset, byteSize ); + if ( data.isEmpty() ) + return; - QByteArray data = reply->data(); - - populateHierarchy( data.constData(), byteSize ); - return; - } - } + populateHierarchy( data.constData(), byteSize ); } void QgsCopcPointCloudIndex::populateHierarchy( const char *hierarchyPageData, uint64_t byteSize ) const @@ -480,9 +448,46 @@ void QgsCopcPointCloudIndex::copyCommonProperties( QgsCopcPointCloudIndex *desti destination->mLazInfo.reset( new QgsLazInfo( *mLazInfo ) ); } +QByteArray QgsCopcPointCloudIndex::readRange( uint64_t offset, uint64_t length ) const +{ + if ( mAccessType == Qgis::PointCloudAccessType::Local ) + { + QByteArray buffer( length, Qt::Initialization::Uninitialized ); + mCopcFile.seekg( offset ); + mCopcFile.read( buffer.data(), length ); + if ( mCopcFile.eof() ) + QgsDebugError( QStringLiteral( "Read past end of file (path %1 offset %2 length %3)" ).arg( mUri ).arg( offset ).arg( length ) ); + if ( !mCopcFile ) + QgsDebugError( QStringLiteral( "Error reading %1" ).arg( mUri ) ); + return buffer; + } + else + { + QNetworkRequest nr = QNetworkRequest( QUrl( mUri ) ); + QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsCopcPointCloudIndex" ) ); + nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache ); + nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true ); + QByteArray queryRange = QStringLiteral( "bytes=%1-%2" ).arg( offset ).arg( offset + length - 1 ).toLocal8Bit(); + nr.setRawHeader( "Range", queryRange ); + + std::unique_ptr reply( QgsApplication::tileDownloadManager()->get( nr ) ); + + QEventLoop loop; + QObject::connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit ); + loop.exec(); + + if ( reply->error() != QNetworkReply::NoError ) + { + QgsDebugError( QStringLiteral( "Request failed: %1 (offset %1 length %2)" ).arg( mUri ).arg( offset ).arg( length ) ); + return {}; + } + + return reply->data(); + } +} + QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData() const { - Q_ASSERT( mAccessType == Qgis::PointCloudAccessType::Local ); // TODO: Remote uint64_t offset = mLazInfo->firstEvlrOffset(); uint32_t evlrCount = mLazInfo->evlrCount(); @@ -491,16 +496,13 @@ QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData() const for ( uint32_t i = 0; i < evlrCount; ++i ) { lazperf::evlr_header header; - mCopcFile.seekg( offset ); - char buffer[60]; - mCopcFile.read( buffer, 60 ); - header.fill( buffer, 60 ); - // UserID: "qgis", record id: 0 + QByteArray buffer = readRange( offset, 60 ); + header.fill( buffer.data(), buffer.size() ); + if ( header.user_id == "qgis" && header.record_id == 0 ) { - statisticsEvlrData = QByteArray( header.data_length, Qt::Initialization::Uninitialized ); - mCopcFile.read( statisticsEvlrData.data(), header.data_length ); + statisticsEvlrData = readRange( offset + 60, header.data_length ); break; } diff --git a/src/core/pointcloud/qgscopcpointcloudindex.h b/src/core/pointcloud/qgscopcpointcloudindex.h index a548d13a3286..8d02b0f1622e 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.h +++ b/src/core/pointcloud/qgscopcpointcloudindex.h @@ -104,6 +104,9 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsAbstractPointCloudIndex void populateHierarchy( const char *hierarchyPageData, uint64_t byteSize ) const; + //! Utility function for reading sub-range of mUri + QByteArray readRange( uint64_t offset, uint64_t length ) const; + QByteArray fetchCopcStatisticsEvlrData() const; bool mIsValid = false; From 4703c227947afe8f53f38743d06018a4367ad7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Mon, 6 Jan 2025 21:11:19 +0100 Subject: [PATCH 2/2] Fix loading remote EPT point clouds --- src/core/pointcloud/qgseptpointcloudindex.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/pointcloud/qgseptpointcloudindex.cpp b/src/core/pointcloud/qgseptpointcloudindex.cpp index 3fcef4e80b32..fb2c8f22fd92 100644 --- a/src/core/pointcloud/qgseptpointcloudindex.cpp +++ b/src/core/pointcloud/qgseptpointcloudindex.cpp @@ -657,6 +657,7 @@ void QgsEptPointCloudIndex::copyCommonProperties( QgsEptPointCloudIndex *destina // QgsEptPointCloudIndex specific fields destination->mIsValid = mIsValid; + destination->mAccessType = mAccessType; destination->mDataType = mDataType; destination->mUrlDirectoryPart = mUrlDirectoryPart; destination->mWkt = mWkt;