Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge remote and local QgsCopcPointCloudIndex classes #59418

Merged
merged 2 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2256,15 +2256,13 @@ if (WITH_COPC)
providers/copc/qgscopcprovider.cpp
providers/vpc/qgsvirtualpointcloudprovider.cpp
pointcloud/qgscopcpointcloudindex.cpp
pointcloud/qgsremotecopcpointcloudindex.cpp
pointcloud/qgscopcpointcloudblockrequest.cpp
pointcloud/qgscachedpointcloudblockrequest.cpp
)
set(QGIS_CORE_HDRS ${QGIS_CORE_HDRS}
providers/copc/qgscopcprovider.h
providers/vpc/qgsvirtualpointcloudprovider.h
pointcloud/qgscopcpointcloudindex.h
pointcloud/qgsremotecopcpointcloudindex.h
pointcloud/qgscopcpointcloudblockrequest.h
pointcloud/qgscachedpointcloudblockrequest.h
)
Expand Down
198 changes: 148 additions & 50 deletions src/core/pointcloud/qgscopcpointcloudindex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,21 @@
#include <QJsonDocument>
#include <QJsonObject>

#include "qgsapplication.h"
#include "qgscachedpointcloudblockrequest.h"
#include "qgscopcpointcloudblockrequest.h"
#include "qgseptdecoder.h"
#include "qgslazdecoder.h"
#include "qgscoordinatereferencesystem.h"
#include "qgspointcloudblockrequest.h"
#include "qgspointcloudrequest.h"
#include "qgspointcloudattribute.h"
#include "qgslogger.h"
#include "qgsfeedback.h"
#include "qgsmessagelog.h"
#include "qgspointcloudexpression.h"

#include "lazperf/lazperf.hpp"
#include "lazperf/readers.hpp"
#include "lazperf/vlr.hpp"
#include "qgssetrequestinitiator_p.h"

///@cond PRIVATE

Expand All @@ -57,31 +59,42 @@ std::unique_ptr<QgsPointCloudIndex> QgsCopcPointCloudIndex::clone() const
return std::unique_ptr<QgsPointCloudIndex>( clone );
}

void QgsCopcPointCloudIndex::load( const QString &fileName )
void QgsCopcPointCloudIndex::load( const QString &urlString )
{
mUri = fileName;
mCopcFile.open( QgsLazDecoder::toNativePath( fileName ), std::ios::binary );

if ( !mCopcFile.is_open() || !mCopcFile.good() )
QUrl url = urlString;
// Treat non-URLs as local files
if ( url.isValid() && ( url.scheme() == "http" || url.scheme() == "https" ) )
mAccessType = Remote;
else
{
mError = tr( "Unable to open %1 for reading" ).arg( fileName );
mIsValid = false;
return;
mAccessType = Local;
mCopcFile.open( QgsLazDecoder::toNativePath( urlString ), std::ios::binary );
dvdkon marked this conversation as resolved.
Show resolved Hide resolved
if ( mCopcFile.fail() )
{
mError = tr( "Unable to open %1 for reading" ).arg( urlString );
mIsValid = false;
return;
}
}
mUri = urlString;

mLazInfo.reset( new QgsLazInfo( QgsLazInfo::fromFile( mCopcFile ) ) );
if ( mAccessType == Remote )
mLazInfo.reset( new QgsLazInfo( QgsLazInfo::fromUrl( url ) ) );
else
mLazInfo.reset( new QgsLazInfo( QgsLazInfo::fromFile( mCopcFile ) ) );
mIsValid = mLazInfo->isValid();
if ( mIsValid )
{
mIsValid = loadSchema( *mLazInfo.get() );
if ( mIsValid )
{
loadHierarchy();
}
}
if ( !mIsValid )
{
mError = tr( "Unable to recognize %1 as a LAZ file: \"%2\"" ).arg( fileName, mLazInfo->error() );
return;
mError = tr( "Unable to recognize %1 as a LAZ file: \"%2\"" ).arg( urlString, mLazInfo->error() );
}

loadHierarchy();
}

bool QgsCopcPointCloudIndex::loadSchema( QgsLazInfo &lazInfo )
Expand Down Expand Up @@ -144,43 +157,84 @@ std::unique_ptr<QgsPointCloudBlock> QgsCopcPointCloudIndex::nodeData( const Inde
return std::unique_ptr<QgsPointCloudBlock>( cached );
}

const bool found = fetchNodeHierarchy( n );
if ( !found )
return nullptr;
mHierarchyMutex.lock();
int pointCount = mHierarchy.value( n );
auto [blockOffset, blockSize] = mHierarchyNodePos.value( n );
mHierarchyMutex.unlock();

// 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() );
std::unique_ptr<QgsPointCloudBlock> block;
if ( mAccessType == Local )
{
const bool found = fetchNodeHierarchy( n );
if ( !found )
return nullptr;
mHierarchyMutex.lock();
int pointCount = mHierarchy.value( n );
auto [blockOffset, blockSize] = mHierarchyNodePos.value( n );
mHierarchyMutex.unlock();

// 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() );

QByteArray rawBlockData( blockSize, Qt::Initialization::Uninitialized );
std::ifstream file( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
file.seekg( blockOffset );
file.read( rawBlockData.data(), blockSize );
if ( !file )
{
QgsDebugError( QStringLiteral( "Could not read file %1" ).arg( mUri ) );
return nullptr;
}
QgsRectangle filterRect = request.filterRect();

QByteArray rawBlockData( blockSize, Qt::Initialization::Uninitialized );
std::ifstream file( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
file.seekg( blockOffset );
file.read( rawBlockData.data(), blockSize );
if ( !file )
block = QgsLazDecoder::decompressCopc( rawBlockData, *mLazInfo.get(), pointCount, requestAttributes, filterExpression, filterRect );
}
else
{
QgsDebugError( QStringLiteral( "Could not read file %1" ).arg( mUri ) );
return nullptr;

std::unique_ptr<QgsPointCloudBlockRequest> blockRequest( asyncNodeData( n, request ) );
if ( !blockRequest )
return nullptr;

QEventLoop loop;
connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
loop.exec();

block = blockRequest->takeBlock();

if ( !block )
QgsDebugError( QStringLiteral( "Error downloading node %1 data, error : %2 " ).arg( n.toString(), blockRequest->errorStr() ) );
}
QgsRectangle filterRect = request.filterRect();

std::unique_ptr<QgsPointCloudBlock> decoded = QgsLazDecoder::decompressCopc( rawBlockData, *mLazInfo.get(), pointCount, requestAttributes, filterExpression, filterRect );
storeNodeDataToCache( decoded.get(), n, request );
return decoded;
storeNodeDataToCache( block.get(), n, request );
return block;
}

QgsPointCloudBlockRequest *QgsCopcPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
{
Q_UNUSED( n )
Q_UNUSED( request )
Q_ASSERT( false );
return nullptr; // unsupported
if ( mAccessType == Local )
return nullptr; // TODO
if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
{
return new QgsCachedPointCloudBlockRequest( cached, n, mUri, attributes(), request.attributes(),
scale(), offset(), mFilterExpression, request.filterRect() );
}

if ( !fetchNodeHierarchy( n ) )
return nullptr;
QMutexLocker locker( &mHierarchyMutex );

// 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() );
auto [ blockOffset, blockSize ] = mHierarchyNodePos.value( n );
int pointCount = mHierarchy.value( n );

return new QgsCopcPointCloudBlockRequest( n, mUri, attributes(), requestAttributes,
scale(), offset(), filterExpression, request.filterRect(),
blockOffset, blockSize, pointCount, *mLazInfo.get() );
}

QgsCoordinateReferenceSystem QgsCopcPointCloudIndex::crs() const
Expand All @@ -201,6 +255,12 @@ bool QgsCopcPointCloudIndex::loadHierarchy()

bool QgsCopcPointCloudIndex::writeStatistics( QgsPointCloudStatistics &stats )
{
if ( mAccessType == Remote )
{
QgsMessageLog::logMessage( tr( "Can't write statistics to remote file \"%1\"" ).arg( mUri ) );
return false;
}

if ( mLazInfo->version() != qMakePair<uint8_t, uint8_t>( 1, 4 ) )
{
// EVLR isn't supported in the first place
Expand Down Expand Up @@ -298,11 +358,46 @@ bool QgsCopcPointCloudIndex::fetchNodeHierarchy( const IndexedPointCloudNode &n

void QgsCopcPointCloudIndex::fetchHierarchyPage( uint64_t offset, uint64_t byteSize ) const
{
mCopcFile.seekg( offset );
std::unique_ptr<char []> data( new char[ byteSize ] );
mCopcFile.read( data.get(), byteSize );
Q_ASSERT( byteSize > 0 );

switch ( mAccessType )
{
case Local:
{
mCopcFile.seekg( offset );
std::unique_ptr<char []> data( new char[ byteSize ] );
mCopcFile.read( data.get(), byteSize );

populateHierarchy( data.get(), byteSize );
return;
}
case 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<QgsTileDownloadManagerReply> 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;
}

populateHierarchy( data.get(), byteSize );
QByteArray data = reply->data();

populateHierarchy( data.constData(), byteSize );
return;
}
}
}

void QgsCopcPointCloudIndex::populateHierarchy( const char *hierarchyPageData, uint64_t byteSize ) const
Expand Down Expand Up @@ -371,8 +466,10 @@ void QgsCopcPointCloudIndex::copyCommonProperties( QgsCopcPointCloudIndex *desti

// QgsCopcPointCloudIndex specific fields
destination->mIsValid = mIsValid;
destination->mAccessType = mAccessType;
destination->mUri = mUri;
destination->mCopcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
if ( mAccessType == Local )
destination->mCopcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
destination->mCopcInfoVlr = mCopcInfoVlr;
destination->mHierarchyNodePos = mHierarchyNodePos;
destination->mOriginalMetadata = mOriginalMetadata;
Expand All @@ -381,6 +478,7 @@ void QgsCopcPointCloudIndex::copyCommonProperties( QgsCopcPointCloudIndex *desti

QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData()
{
Q_ASSERT( mAccessType == Local ); // TODO: Remote
uint64_t offset = mLazInfo->firstEvlrOffset();
uint32_t evlrCount = mLazInfo->evlrCount();

Expand Down
6 changes: 2 additions & 4 deletions src/core/pointcloud/qgscopcpointcloudindex.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@
#include <fstream>

#include "qgspointcloudindex.h"
#include "qgspointcloudattribute.h"
#include "qgsstatisticalsummary.h"
#include "qgis_sip.h"
#include "qgspointcloudstatistics.h"

#include "qgslazinfo.h"
Expand Down Expand Up @@ -66,7 +63,7 @@ class CORE_EXPORT QgsCopcPointCloudIndex: 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 { return mAccessType; };

/**
* Writes the statistics object \a stats into the COPC dataset as an Extended Variable Length Record (EVLR).
Expand Down Expand Up @@ -105,6 +102,7 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex
QByteArray fetchCopcStatisticsEvlrData();

bool mIsValid = false;
QgsPointCloudIndex::AccessType mAccessType = Local;
mutable std::ifstream mCopcFile;
mutable lazperf::copc_info_vlr mCopcInfoVlr;
mutable QHash<IndexedPointCloudNode, QPair<uint64_t, int32_t>> mHierarchyNodePos; //!< Additional data hierarchy for COPC
Expand Down
5 changes: 5 additions & 0 deletions src/core/pointcloud/qgspointcloudstatscalculator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ struct StatsProcessor
else
{
QgsPointCloudBlockRequest *request = mIndex->asyncNodeData( node, mRequest );
if ( request == nullptr )
{
QgsDebugError( QStringLiteral( "Unable to calculate statistics for node %1: Got nullptr async request" ).arg( node.toString() ) );
return QgsPointCloudStatistics();
}
QEventLoop loop;
QObject::connect( request, &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
QObject::connect( mFeedback, &QgsFeedback::canceled, &loop, &QEventLoop::quit );
Expand Down
Loading
Loading