Add a static QCache to QgsPointCloudIndex for storing decoded data blocks

This commit is contained in:
uclaros 2024-01-30 18:00:15 +02:00 committed by Martin Dobias
parent 2d7d2dde06
commit 1285a8dfe2
10 changed files with 176 additions and 8 deletions

View File

@ -34,6 +34,12 @@ Ctor
%End
virtual ~QgsPointCloudBlock();
QgsPointCloudBlock *clone() const;
%Docstring
Clones the QgsPointCloudBlock returning a new copy.
Caller takes ownership of the returned object.
%End
const char *data() const;
%Docstring
Returns raw pointer to data

View File

@ -34,6 +34,12 @@ Ctor
%End
virtual ~QgsPointCloudBlock();
QgsPointCloudBlock *clone() const;
%Docstring
Clones the QgsPointCloudBlock returning a new copy.
Caller takes ownership of the returned object.
%End
const char *data() const;
%Docstring
Returns raw pointer to data

View File

@ -138,6 +138,11 @@ bool QgsCopcPointCloudIndex::loadSchema( QgsLazInfo &lazInfo )
std::unique_ptr<QgsPointCloudBlock> QgsCopcPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
{
if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
{
return std::unique_ptr<QgsPointCloudBlock>( cached );
}
const bool found = fetchNodeHierarchy( n );
if ( !found )
return nullptr;
@ -164,7 +169,9 @@ std::unique_ptr<QgsPointCloudBlock> QgsCopcPointCloudIndex::nodeData( const Inde
}
QgsRectangle filterRect = request.filterRect();
return QgsLazDecoder::decompressCopc( rawBlockData, *mLazInfo.get(), pointCount, requestAttributes, filterExpression, filterRect );
std::unique_ptr<QgsPointCloudBlock> decoded = QgsLazDecoder::decompressCopc( rawBlockData, *mLazInfo.get(), pointCount, requestAttributes, filterExpression, filterRect );
storeNodeDataToCache( decoded.get(), n, request );
return decoded;
}
QgsPointCloudBlockRequest *QgsCopcPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )

View File

@ -309,6 +309,11 @@ bool QgsEptPointCloudIndex::loadSchema( const QByteArray &dataJson )
std::unique_ptr<QgsPointCloudBlock> QgsEptPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
{
if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
{
return std::unique_ptr<QgsPointCloudBlock>( cached );
}
mHierarchyMutex.lock();
const bool found = mHierarchy.contains( n );
mHierarchyMutex.unlock();
@ -323,25 +328,25 @@ std::unique_ptr<QgsPointCloudBlock> QgsEptPointCloudIndex::nodeData( const Index
requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
QgsRectangle filterRect = request.filterRect();
std::unique_ptr<QgsPointCloudBlock> decoded;
if ( mDataType == QLatin1String( "binary" ) )
{
const QString filename = QStringLiteral( "%1/ept-data/%2.bin" ).arg( mDirectory, n.toString() );
return QgsEptDecoder::decompressBinary( filename, attributes(), requestAttributes, scale(), offset(), filterExpression, filterRect );
decoded = QgsEptDecoder::decompressBinary( filename, attributes(), requestAttributes, scale(), offset(), filterExpression, filterRect );
}
else if ( mDataType == QLatin1String( "zstandard" ) )
{
const QString filename = QStringLiteral( "%1/ept-data/%2.zst" ).arg( mDirectory, n.toString() );
return QgsEptDecoder::decompressZStandard( filename, attributes(), request.attributes(), scale(), offset(), filterExpression, filterRect );
decoded = QgsEptDecoder::decompressZStandard( filename, attributes(), request.attributes(), scale(), offset(), filterExpression, filterRect );
}
else if ( mDataType == QLatin1String( "laszip" ) )
{
const QString filename = QStringLiteral( "%1/ept-data/%2.laz" ).arg( mDirectory, n.toString() );
return QgsLazDecoder::decompressLaz( filename, requestAttributes, filterExpression, filterRect );
}
else
{
return nullptr; // unsupported
decoded = QgsLazDecoder::decompressLaz( filename, requestAttributes, filterExpression, filterRect );
}
storeNodeDataToCache( decoded.get(), n, request );
return decoded;
}
QgsPointCloudBlockRequest *QgsEptPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )

View File

@ -34,6 +34,11 @@ QgsPointCloudBlock::QgsPointCloudBlock(
{
}
QgsPointCloudBlock *QgsPointCloudBlock::clone() const
{
return new QgsPointCloudBlock( *this );
}
const char *QgsPointCloudBlock::data() const
{
return mStorage.data();

View File

@ -45,6 +45,12 @@ class CORE_EXPORT QgsPointCloudBlock
//! Dtor
virtual ~QgsPointCloudBlock() = default;
/**
* Clones the QgsPointCloudBlock returning a new copy.
* Caller takes ownership of the returned object.
*/
QgsPointCloudBlock *clone() const;
//! Returns raw pointer to data
const char *data() const;

View File

@ -27,6 +27,7 @@
#include "qgstiledownloadmanager.h"
#include "qgspointcloudstatistics.h"
#include "qgslogger.h"
IndexedPointCloudNode::IndexedPointCloudNode():
mD( -1 ),
@ -87,6 +88,31 @@ uint qHash( IndexedPointCloudNode id )
///@cond PRIVATE
//
// QgsPointCloudCacheKey
//
QgsPointCloudCacheKey::QgsPointCloudCacheKey( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request, const QgsPointCloudExpression &expression, const QString &uri )
: mNode( n )
, mUri( uri )
, mRequest( request )
, mFilterExpression( expression )
{
}
bool QgsPointCloudCacheKey::operator==( const QgsPointCloudCacheKey &other ) const
{
return mNode == other.mNode &&
mUri == other.mUri &&
mRequest == other.mRequest &&
mFilterExpression == other.mFilterExpression;
}
uint qHash( const QgsPointCloudCacheKey &key )
{
return qHash( key.node() ) ^ qHash( key.request() ) ^ qHash( key.uri() ) ^ qHash( key.filterExpression() );
}
//
// QgsPointCloudDataBounds
//
@ -153,6 +179,9 @@ QgsDoubleRange QgsPointCloudDataBounds::zRange( const QgsVector3D &offset, const
// QgsPointCloudIndex
//
QMutex QgsPointCloudIndex::sBlockCacheMutex;
QCache<QgsPointCloudCacheKey, QgsPointCloudBlock> QgsPointCloudIndex::sBlockCache( 200'000'000 ); // 200MB of cached points
QgsPointCloudIndex::QgsPointCloudIndex() = default;
QgsPointCloudIndex::~QgsPointCloudIndex() = default;
@ -364,3 +393,28 @@ void QgsPointCloudIndex::copyCommonProperties( QgsPointCloudIndex *destination )
destination->mSpan = mSpan;
destination->mFilterExpression = mFilterExpression;
}
QgsPointCloudBlock *QgsPointCloudIndex::getNodeDataFromCache( const IndexedPointCloudNode &node, const QgsPointCloudRequest &request )
{
QgsPointCloudCacheKey key( node, request, mFilterExpression, mUri );
QMutexLocker l( &sBlockCacheMutex );
QgsPointCloudBlock *cached = sBlockCache.object( key );
return cached ? cached->clone() : nullptr;
}
void QgsPointCloudIndex::storeNodeDataToCache( QgsPointCloudBlock *data, const IndexedPointCloudNode &node, const QgsPointCloudRequest &request )
{
storeNodeDataToCacheStatic( data, node, request, mFilterExpression, mUri );
}
void QgsPointCloudIndex::storeNodeDataToCacheStatic( QgsPointCloudBlock *data, const IndexedPointCloudNode &node, const QgsPointCloudRequest &request, const QgsPointCloudExpression &expression, const QString &uri )
{
QgsPointCloudCacheKey key( node, request, expression, uri );
const int cost = data->pointCount() * data->pointRecordSize();
QMutexLocker l( &sBlockCacheMutex );
QgsDebugMsgLevel( QStringLiteral( "(%1/%2): Caching node %3 of %4" ).arg( sBlockCache.totalCost() ).arg( sBlockCache.maxCost() ).arg( key.node().toString() ).arg( key.uri() ), 4 );
sBlockCache.insert( key, data->clone(), cost );
}

View File

@ -25,6 +25,7 @@
#include <QVector>
#include <QList>
#include <QMutex>
#include <QCache>
#include "qgis_core.h"
#include "qgsrectangle.h"
@ -34,6 +35,7 @@
#include "qgsrange.h"
#include "qgspointcloudattribute.h"
#include "qgspointcloudexpression.h"
#include "qgspointcloudrequest.h"
#define SIP_NO_FILE
@ -105,6 +107,46 @@ Q_DECLARE_TYPEINFO( IndexedPointCloudNode, Q_PRIMITIVE_TYPE );
//! Hash function for indexed nodes
CORE_EXPORT uint qHash( IndexedPointCloudNode id );
/**
* \ingroup core
*
* \brief Container class for QgsPointCloudBlock cache keys
*
* \note The API is considered EXPERIMENTAL and can be changed without a notice
*
* \since QGIS 3.36
*/
class CORE_EXPORT QgsPointCloudCacheKey
{
public:
//! Ctor
QgsPointCloudCacheKey( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request, const QgsPointCloudExpression &expression, const QString &uri );
//! Compares keys
bool operator==( const QgsPointCloudCacheKey &other ) const;
//! Returns the key's IndexedPointCloudNode
IndexedPointCloudNode node() const { return mNode; }
//! Returns the key's uri
QString uri() const { return mUri; }
//! Returns the key's QgsPointCloudRequest
QgsPointCloudRequest request() const { return mRequest; }
//! Returns the key's QgsPointCloudExpression
QgsPointCloudExpression filterExpression() const { return mFilterExpression; }
private:
IndexedPointCloudNode mNode;
QString mUri;
QgsPointCloudRequest mRequest;
QgsPointCloudExpression mFilterExpression;
};
//! Hash function for QgsPointCloudCacheKey
uint qHash( const QgsPointCloudCacheKey &key );
/**
* \ingroup core
*
@ -330,6 +372,24 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject
*/
void copyCommonProperties( QgsPointCloudIndex *destination ) const;
/**
* Fetches the requested node data from the cache for the specified \a node and \a request.
* If not found in the cache, nullptr is returned.
* Caller takes ownership of the returned object.
*/
QgsPointCloudBlock *getNodeDataFromCache( const IndexedPointCloudNode &node, const QgsPointCloudRequest &request );
/**
* Stores existing \a data to the cache for the specified \a node and \a request. Ownership is not transferred, block gets cloned in the cache.
*/
void storeNodeDataToCache( QgsPointCloudBlock *data, const IndexedPointCloudNode &node, const QgsPointCloudRequest &request );
/**
* Stores existing \a data to the cache for the specified \a node, \a request, \a expression and \a uri. Ownership is not transferred, block gets cloned in the cache.
*/
static void storeNodeDataToCacheStatic( QgsPointCloudBlock *data, const IndexedPointCloudNode &node, const QgsPointCloudRequest &request,
const QgsPointCloudExpression &expression, const QString &uri );
protected: //TODO private
//! Sets native attributes of the data
void setAttributes( const QgsPointCloudAttributeCollection &attributes );
@ -348,6 +408,8 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject
QString mError;
QString mUri;
static QMutex sBlockCacheMutex;
static QCache<QgsPointCloudCacheKey, QgsPointCloudBlock> sBlockCache;
};
#endif // QGSPOINTCLOUDINDEX_H

View File

@ -21,6 +21,12 @@
QgsPointCloudRequest::QgsPointCloudRequest() = default;
bool QgsPointCloudRequest::operator==( const QgsPointCloudRequest &other ) const
{
return mFilterRect == other.filterRect() &&
mAttributes.toFields() == other.attributes().toFields(); //todo: QgsPointCloudAttributeCollection::operator==
}
QgsPointCloudAttributeCollection QgsPointCloudRequest::attributes() const
{
return mAttributes;
@ -30,3 +36,8 @@ void QgsPointCloudRequest::setAttributes( const QgsPointCloudAttributeCollection
{
mAttributes = attributes;
}
uint qHash( const QgsPointCloudRequest &request )
{
return qHash( request.filterRect() ) ^ qHash( request.attributes().toFields() );
}

View File

@ -44,6 +44,9 @@ class CORE_EXPORT QgsPointCloudRequest
//! Ctor
QgsPointCloudRequest();
//! Equality operator
bool operator==( const QgsPointCloudRequest &other ) const;
//! Returns attributes
QgsPointCloudAttributeCollection attributes() const;
@ -66,4 +69,7 @@ class CORE_EXPORT QgsPointCloudRequest
QgsRectangle mFilterRect;
};
//! Hash function for QgsPointCloudRequest
uint qHash( const QgsPointCloudRequest &request );
#endif // QGSPOINTCLOUDREQUEST_H