mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-05 00:04:40 -05:00
Create QgsMapHitTestTask, a QgsTask which executes a legend hit
test in a background thread in a thread-safe way These legend hit tests can be very expensive to calculate, so background execution is desirable...
This commit is contained in:
parent
11f5a6947a
commit
f261855490
@ -76,6 +76,53 @@ Tests whether a given legend key is visible for a specified layer.
|
||||
|
||||
};
|
||||
|
||||
|
||||
class QgsMapHitTestTask : QgsTask
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
Executes a QgsMapHitTest in a background thread.
|
||||
|
||||
.. versionadded:: 3.32
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsmaphittest.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsMapHitTestTask( const QgsMapSettings &settings, const QgsGeometry &polygon = QgsGeometry(), const QgsMapHitTest::LayerFilterExpression &layerFilterExpression = QgsMapHitTest::LayerFilterExpression() );
|
||||
%Docstring
|
||||
Constructor for QgsMapHitTestTask, filtering by a visible geometry.
|
||||
|
||||
:param settings: Map settings used to evaluate symbols
|
||||
:param polygon: Polygon geometry to refine the hit test
|
||||
:param layerFilterExpression: Expression string for each layer id to evaluate in order to refine the symbol selection
|
||||
%End
|
||||
|
||||
QgsMapHitTestTask( const QgsMapSettings &settings, const QgsMapHitTest::LayerFilterExpression &layerFilterExpression );
|
||||
%Docstring
|
||||
Constructor for QgsMapHitTestTask, filtering by expressions.
|
||||
|
||||
:param settings: Map settings used to evaluate symbols
|
||||
:param layerFilterExpression: Expression string for each layer id to evaluate in order to refine the symbol selection
|
||||
%End
|
||||
|
||||
QMap<QString, QSet<QString>> results() const;
|
||||
%Docstring
|
||||
Returns the hit test results, which are a map of layer ID to
|
||||
visible symbol legend keys.
|
||||
%End
|
||||
|
||||
virtual void cancel();
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
virtual bool run();
|
||||
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
|
||||
@ -267,3 +267,131 @@ void QgsMapHitTest::runHitTestFeatureSource( QgsAbstractFeatureSource *source,
|
||||
r->stopRender( context );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// QgsMapHitTestTask
|
||||
//
|
||||
|
||||
QgsMapHitTestTask::QgsMapHitTestTask( const QgsMapSettings &settings, const QgsGeometry &polygon, const QgsMapHitTest::LayerFilterExpression &layerFilterExpression )
|
||||
: QgsTask( tr( "Updating Legend" ), QgsTask::Flag::CanCancel | QgsTask::Flag::CancelWithoutPrompt | QgsTask::Flag::Silent )
|
||||
, mSettings( settings )
|
||||
, mLayerFilterExpression( layerFilterExpression )
|
||||
, mPolygon( polygon )
|
||||
, mOnlyExpressions( false )
|
||||
{
|
||||
prepare();
|
||||
}
|
||||
|
||||
QgsMapHitTestTask::QgsMapHitTestTask( const QgsMapSettings &settings, const QgsMapHitTest::LayerFilterExpression &layerFilterExpression )
|
||||
: QgsTask( tr( "Updating Legend" ), QgsTask::Flag::CanCancel | QgsTask::Flag::CancelWithoutPrompt | QgsTask::Flag::Silent )
|
||||
, mSettings( settings )
|
||||
, mLayerFilterExpression( layerFilterExpression )
|
||||
, mOnlyExpressions( true )
|
||||
{
|
||||
prepare();
|
||||
}
|
||||
|
||||
QMap<QString, QSet<QString> > QgsMapHitTestTask::results() const
|
||||
{
|
||||
return mResults;
|
||||
}
|
||||
|
||||
void QgsMapHitTestTask::prepare()
|
||||
{
|
||||
const QList< QgsMapLayer * > layers = mSettings.layers( true );
|
||||
for ( QgsMapLayer *layer : layers )
|
||||
{
|
||||
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
|
||||
if ( !vl || !vl->renderer() )
|
||||
continue;
|
||||
|
||||
QgsMapLayerStyleOverride styleOverride( vl );
|
||||
if ( mSettings.layerStyleOverrides().contains( vl->id() ) )
|
||||
styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( vl->id() ) );
|
||||
|
||||
if ( !mOnlyExpressions )
|
||||
{
|
||||
if ( !vl->isInScaleRange( mSettings.scale() ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
PreparedLayerData layerData;
|
||||
layerData.source = std::make_unique< QgsVectorLayerFeatureSource >( vl );
|
||||
layerData.layerId = vl->id();
|
||||
layerData.crs = vl->crs();
|
||||
layerData.fields = vl->fields();
|
||||
layerData.renderer.reset( vl->renderer()->clone() );
|
||||
layerData.transform = mSettings.layerTransform( vl );
|
||||
layerData.extent = mSettings.outputExtentToLayerExtent( vl, mSettings.visibleExtent() );
|
||||
layerData.layerScope.reset( QgsExpressionContextUtils::layerScope( vl ) );
|
||||
|
||||
mPreparedData.emplace_back( std::move( layerData ) );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsMapHitTestTask::cancel()
|
||||
{
|
||||
if ( mFeedback )
|
||||
mFeedback->cancel();
|
||||
|
||||
QgsTask::cancel();
|
||||
}
|
||||
|
||||
bool QgsMapHitTestTask::run()
|
||||
{
|
||||
mFeedback = std::make_unique< QgsFeedback >();
|
||||
connect( mFeedback.get(), &QgsFeedback::progressChanged, this, &QgsTask::progressChanged );
|
||||
|
||||
std::unique_ptr< QgsMapHitTest > hitTest;
|
||||
if ( !mOnlyExpressions )
|
||||
hitTest = std::make_unique< QgsMapHitTest >( mSettings, mPolygon, mLayerFilterExpression );
|
||||
else
|
||||
hitTest = std::make_unique< QgsMapHitTest >( mSettings, mLayerFilterExpression );
|
||||
|
||||
// TODO: do we need this temp image?
|
||||
QImage tmpImage( mSettings.outputSize(), mSettings.outputImageFormat() );
|
||||
tmpImage.setDotsPerMeterX( mSettings.outputDpi() * 25.4 );
|
||||
tmpImage.setDotsPerMeterY( mSettings.outputDpi() * 25.4 );
|
||||
QPainter painter( &tmpImage );
|
||||
|
||||
QgsRenderContext context = QgsRenderContext::fromMapSettings( mSettings );
|
||||
context.setPainter( &painter ); // we are not going to draw anything, but we still need a working painter
|
||||
|
||||
int layerIdx = 0;
|
||||
const int totalCount = mPreparedData.size();
|
||||
for ( auto &layerData : mPreparedData )
|
||||
{
|
||||
mFeedback->setProgress( static_cast< double >( layerIdx ) / totalCount * 100.0 );
|
||||
if ( mFeedback->isCanceled() )
|
||||
break;
|
||||
|
||||
QgsMapHitTest::SymbolSet &usedSymbols = hitTest->mHitTest[layerData.layerId];
|
||||
QgsMapHitTest::SymbolSet &usedSymbolsRuleKey = hitTest->mHitTestRuleKey[layerData.layerId];
|
||||
|
||||
context.setCoordinateTransform( layerData.transform );
|
||||
context.setExtent( layerData.extent );
|
||||
|
||||
QgsExpressionContextScope *layerScope = layerData.layerScope.release();
|
||||
QgsExpressionContextScopePopper scopePopper( context.expressionContext(), layerScope );
|
||||
|
||||
hitTest->runHitTestFeatureSource( layerData.source.get(),
|
||||
layerData.layerId,
|
||||
layerData.crs,
|
||||
layerData.fields,
|
||||
layerData.renderer.get(),
|
||||
usedSymbols,
|
||||
usedSymbolsRuleKey,
|
||||
context,
|
||||
mFeedback.get() );
|
||||
layerIdx++;
|
||||
}
|
||||
|
||||
mResults = hitTest->mHitTestRuleKey;
|
||||
|
||||
mFeedback.reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
#include "qgis_sip.h"
|
||||
#include "qgsmapsettings.h"
|
||||
#include "qgsgeometry.h"
|
||||
#include "qgstaskmanager.h"
|
||||
#include "qgscoordinatetransform.h"
|
||||
|
||||
#include <QSet>
|
||||
|
||||
@ -125,6 +127,77 @@ class CORE_EXPORT QgsMapHitTest
|
||||
|
||||
//! Whether to use only expressions during the filtering
|
||||
bool mOnlyExpressions;
|
||||
|
||||
friend class QgsMapHitTestTask;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \brief Executes a QgsMapHitTest in a background thread.
|
||||
*
|
||||
* \since QGIS 3.32
|
||||
*/
|
||||
class CORE_EXPORT QgsMapHitTestTask : public QgsTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsMapHitTestTask, filtering by a visible geometry.
|
||||
*
|
||||
* \param settings Map settings used to evaluate symbols
|
||||
* \param polygon Polygon geometry to refine the hit test
|
||||
* \param layerFilterExpression Expression string for each layer id to evaluate in order to refine the symbol selection
|
||||
*/
|
||||
QgsMapHitTestTask( const QgsMapSettings &settings, const QgsGeometry &polygon = QgsGeometry(), const QgsMapHitTest::LayerFilterExpression &layerFilterExpression = QgsMapHitTest::LayerFilterExpression() );
|
||||
|
||||
/**
|
||||
* Constructor for QgsMapHitTestTask, filtering by expressions.
|
||||
*
|
||||
* \param settings Map settings used to evaluate symbols
|
||||
* \param layerFilterExpression Expression string for each layer id to evaluate in order to refine the symbol selection
|
||||
*/
|
||||
QgsMapHitTestTask( const QgsMapSettings &settings, const QgsMapHitTest::LayerFilterExpression &layerFilterExpression );
|
||||
|
||||
/**
|
||||
* Returns the hit test results, which are a map of layer ID to
|
||||
* visible symbol legend keys.
|
||||
*/
|
||||
QMap<QString, QSet<QString>> results() const;
|
||||
|
||||
void cancel() override;
|
||||
|
||||
protected:
|
||||
|
||||
bool run() override;
|
||||
|
||||
private:
|
||||
|
||||
void prepare();
|
||||
|
||||
struct PreparedLayerData
|
||||
{
|
||||
std::unique_ptr< QgsAbstractFeatureSource > source;
|
||||
QString layerId;
|
||||
QgsCoordinateReferenceSystem crs;
|
||||
QgsFields fields;
|
||||
std::unique_ptr< QgsFeatureRenderer > renderer;
|
||||
QgsRectangle extent;
|
||||
QgsCoordinateTransform transform;
|
||||
std::unique_ptr< QgsExpressionContextScope > layerScope;
|
||||
};
|
||||
|
||||
std::vector< PreparedLayerData > mPreparedData;
|
||||
|
||||
QgsMapSettings mSettings;
|
||||
QgsMapHitTest::LayerFilterExpression mLayerFilterExpression;
|
||||
QgsGeometry mPolygon;
|
||||
bool mOnlyExpressions = false;
|
||||
QMap<QString, QSet<QString>> mResults;
|
||||
|
||||
std::unique_ptr< QgsFeedback > mFeedback;
|
||||
};
|
||||
|
||||
#endif // QGSMAPHITTEST_H
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user