Skip to content

Commit 11f5a69

Browse files
committedMar 8, 2023
Refactor QgsMapHitTest to permit thread-safe execution
1 parent 4647cfc commit 11f5a69

File tree

3 files changed

+101
-33
lines changed

3 files changed

+101
-33
lines changed
 

‎python/core/auto_generated/qgsmaphittest.sip.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ Constructor version used with only expressions to filter symbols (no extent or p
4040
void run();
4141
%Docstring
4242
Runs the map hit test
43+
%End
44+
45+
QMap<QString, QSet<QString>> results() const;
46+
%Docstring
47+
Returns the hit test results, which are a map of layer ID to
48+
visible symbol legend keys.
49+
50+
.. versionadded:: 3.32
4351
%End
4452

4553
bool symbolVisible( QgsSymbol *symbol, QgsVectorLayer *layer ) const;

‎src/core/qgsmaphittest.cpp

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "qgsgeometryengine.h"
2525
#include "qgsexpressioncontextutils.h"
2626
#include "qgsmaplayerstyle.h"
27+
#include "qgsvectorlayerfeatureiterator.h"
2728

2829
QgsMapHitTest::QgsMapHitTest( const QgsMapSettings &settings, const QgsGeometry &polygon, const LayerFilterExpression &layerFilterExpression )
2930
: mSettings( settings )
@@ -65,8 +66,6 @@ void QgsMapHitTest::run()
6566
{
6667
if ( !vl->isInScaleRange( mSettings.scale() ) )
6768
{
68-
mHitTest[vl] = SymbolSet(); // no symbols -> will not be shown
69-
mHitTestRuleKey[vl] = SymbolSet();
7069
continue;
7170
}
7271

@@ -75,39 +74,65 @@ void QgsMapHitTest::run()
7574
}
7675

7776
context.expressionContext() << QgsExpressionContextUtils::layerScope( vl );
78-
SymbolSet &usedSymbols = mHitTest[vl];
79-
SymbolSet &usedSymbolsRuleKey = mHitTestRuleKey[vl];
80-
runHitTestLayer( vl, usedSymbols, usedSymbolsRuleKey, context );
77+
SymbolSet &usedSymbols = mHitTest[vl->id()];
78+
SymbolSet &usedSymbolsRuleKey = mHitTestRuleKey[vl->id()];
79+
80+
QgsMapLayerStyleOverride styleOverride( vl );
81+
if ( mSettings.layerStyleOverrides().contains( vl->id() ) )
82+
styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( vl->id() ) );
83+
84+
std::unique_ptr< QgsVectorLayerFeatureSource > source = std::make_unique< QgsVectorLayerFeatureSource >( vl );
85+
runHitTestFeatureSource( source.get(),
86+
vl->id(), vl->crs(), vl->fields(), vl->renderer(),
87+
usedSymbols, usedSymbolsRuleKey, context,
88+
nullptr );
8189
}
8290

8391
painter.end();
8492
}
8593

94+
QMap<QString, QSet<QString> > QgsMapHitTest::results() const
95+
{
96+
return mHitTestRuleKey;
97+
}
98+
8699
bool QgsMapHitTest::symbolVisible( QgsSymbol *symbol, QgsVectorLayer *layer ) const
87100
{
88-
if ( !symbol || !layer || !mHitTest.contains( layer ) )
101+
if ( !symbol || !layer )
89102
return false;
90103

91-
return mHitTest.value( layer ).contains( QgsSymbolLayerUtils::symbolProperties( symbol ) );
104+
auto it = mHitTest.constFind( layer->id() );
105+
if ( it == mHitTest.constEnd() )
106+
return false;
107+
108+
return it->contains( QgsSymbolLayerUtils::symbolProperties( symbol ) );
92109
}
93110

94111
bool QgsMapHitTest::legendKeyVisible( const QString &ruleKey, QgsVectorLayer *layer ) const
95112
{
96-
if ( !layer || !mHitTestRuleKey.contains( layer ) )
113+
if ( !layer )
97114
return false;
98115

99-
return mHitTestRuleKey.value( layer ).contains( ruleKey );
116+
auto it = mHitTestRuleKey.constFind( layer->id() );
117+
if ( it == mHitTestRuleKey.constEnd() )
118+
return false;
119+
120+
return it->contains( ruleKey );
100121
}
101122

102-
void QgsMapHitTest::runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols, SymbolSet &usedSymbolsRuleKey, QgsRenderContext &context )
123+
void QgsMapHitTest::runHitTestFeatureSource( QgsAbstractFeatureSource *source,
124+
const QString &layerId,
125+
const QgsCoordinateReferenceSystem &crs,
126+
const QgsFields &fields,
127+
const QgsFeatureRenderer *renderer,
128+
SymbolSet &usedSymbols,
129+
SymbolSet &usedSymbolsRuleKey,
130+
QgsRenderContext &context,
131+
QgsFeedback *feedback )
103132
{
104-
QgsMapLayerStyleOverride styleOverride( vl );
105-
if ( mSettings.layerStyleOverrides().contains( vl->id() ) )
106-
styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( vl->id() ) );
107-
108-
std::unique_ptr< QgsFeatureRenderer > r( vl->renderer()->clone() );
133+
std::unique_ptr< QgsFeatureRenderer > r( renderer->clone() );
109134
const bool moreSymbolsPerFeature = r->capabilities() & QgsFeatureRenderer::MoreSymbolsPerFeature;
110-
r->startRender( context, vl->fields() );
135+
r->startRender( context, fields );
111136

112137
// shortcut early if we know that there's nothing visible
113138
if ( r->canSkipRender() )
@@ -125,8 +150,10 @@ void QgsMapHitTest::runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols,
125150
}
126151

127152
QgsFeatureRequest request;
153+
if ( feedback )
154+
request.setFeedback( feedback );
128155

129-
const QString rendererFilterExpression = r->filter( vl->fields() );
156+
const QString rendererFilterExpression = r->filter( fields );
130157
if ( !rendererFilterExpression.isEmpty() )
131158
{
132159
request.setFilterExpression( rendererFilterExpression );
@@ -137,25 +164,30 @@ void QgsMapHitTest::runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols,
137164
QgsGeometry transformedPolygon = mPolygon;
138165
if ( !mOnlyExpressions && !mPolygon.isNull() )
139166
{
140-
if ( mSettings.destinationCrs() != vl->crs() )
167+
if ( mSettings.destinationCrs() != crs )
141168
{
142-
const QgsCoordinateTransform ct( mSettings.destinationCrs(), vl->crs(), mSettings.transformContext() );
169+
const QgsCoordinateTransform ct( mSettings.destinationCrs(), crs, mSettings.transformContext() );
143170
transformedPolygon.transform( ct );
144171
}
145172
}
146173

147-
std::unique_ptr<QgsExpression> expr;
148-
if ( mLayerFilterExpression.contains( vl->id() ) )
174+
if ( feedback && feedback->isCanceled() )
149175
{
150-
const QString expression = mLayerFilterExpression[vl->id()];
151-
expr.reset( new QgsExpression( expression ) );
152-
expr->prepare( &context.expressionContext() );
176+
r->stopRender( context );
177+
return;
178+
}
153179

154-
requiredAttributes.unite( expr->referencedColumns() );
180+
if ( auto it = mLayerFilterExpression.constFind( layerId ); it != mLayerFilterExpression.constEnd() )
181+
{
182+
const QString expression = *it;
183+
QgsExpression expr( expression );
184+
expr.prepare( &context.expressionContext() );
185+
186+
requiredAttributes.unite( expr.referencedColumns() );
155187
request.combineFilterExpression( expression );
156188
}
157189

158-
request.setSubsetOfAttributes( requiredAttributes, vl->fields() );
190+
request.setSubsetOfAttributes( requiredAttributes, fields );
159191

160192
std::unique_ptr< QgsGeometryEngine > polygonEngine;
161193
if ( !mOnlyExpressions )
@@ -172,14 +204,24 @@ void QgsMapHitTest::runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols,
172204
polygonEngine->prepareGeometry();
173205
}
174206
}
175-
QgsFeatureIterator fi = vl->getFeatures( request );
207+
208+
if ( feedback && feedback->isCanceled() )
209+
{
210+
r->stopRender( context );
211+
return;
212+
}
213+
214+
QgsFeatureIterator fi = source->getFeatures( request );
176215

177216
usedSymbols.clear();
178217
usedSymbolsRuleKey.clear();
179218

180219
QgsFeature f;
181220
while ( fi.nextFeature( f ) )
182221
{
222+
if ( feedback && feedback->isCanceled() )
223+
break;
224+
183225
// filter out elements outside of the polygon
184226
if ( f.hasGeometry() && polygonEngine )
185227
{

‎src/core/qgsmaphittest.h

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class QgsRenderContext;
2626
class QgsSymbol;
2727
class QgsVectorLayer;
2828
class QgsExpression;
29+
class QgsAbstractFeatureSource;
30+
class QgsFeatureRenderer;
2931

3032
/**
3133
* \ingroup core
@@ -53,6 +55,14 @@ class CORE_EXPORT QgsMapHitTest
5355
//! Runs the map hit test
5456
void run();
5557

58+
/**
59+
* Returns the hit test results, which are a map of layer ID to
60+
* visible symbol legend keys.
61+
*
62+
* \since QGIS 3.32
63+
*/
64+
QMap<QString, QSet<QString>> results() const;
65+
5666
/**
5767
* Tests whether a symbol is visible for a specified layer.
5868
* \param symbol symbol to find
@@ -76,19 +86,27 @@ class CORE_EXPORT QgsMapHitTest
7686
//! \note not available in Python bindings
7787
typedef QSet<QString> SymbolSet;
7888

79-
//! \note not available in Python bindings
80-
typedef QMap<QgsVectorLayer *, SymbolSet> HitTest;
89+
//! Layer ID to symbol set
90+
typedef QMap<QString, SymbolSet> HitTest;
8191

8292
/**
83-
* Runs test for visible symbols within a layer
84-
* \param vl vector layer
93+
* Runs test for visible symbols from a feature \a source
94+
* \param source feature source
95+
* \param fields source fields
8596
* \param usedSymbols set for storage of visible symbols
8697
* \param usedSymbolsRuleKey set of storage of visible legend rule keys
8798
* \param context render context
8899
* \note not available in Python bindings
89-
* \since QGIS 2.12
90100
*/
91-
void runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols, SymbolSet &usedSymbolsRuleKey, QgsRenderContext &context );
101+
void runHitTestFeatureSource( QgsAbstractFeatureSource *source,
102+
const QString &layerId,
103+
const QgsCoordinateReferenceSystem &crs,
104+
const QgsFields &fields,
105+
const QgsFeatureRenderer *renderer,
106+
SymbolSet &usedSymbols,
107+
SymbolSet &usedSymbolsRuleKey,
108+
QgsRenderContext &context,
109+
QgsFeedback *feedback );
92110

93111
//! The initial map settings
94112
QgsMapSettings mSettings;

0 commit comments

Comments
 (0)
Please sign in to comment.