QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsmaphittest.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaphittest.cpp
3 ---------------------
4 begin : September 2014
5 copyright : (C) 2014 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsmaphittest.h"
17
18#include "qgsfeatureiterator.h"
19#include "qgsrendercontext.h"
20#include "qgsrenderer.h"
21#include "qgsvectorlayer.h"
22#include "qgssymbollayerutils.h"
23#include "qgsgeometry.h"
24#include "qgsgeometryengine.h"
26#include "qgsmaplayerstyle.h"
28
29QgsMapHitTest::QgsMapHitTest( const QgsMapSettings &settings, const QgsGeometry &polygon, const LayerFilterExpression &layerFilterExpression )
30 : mSettings( QgsLayerTreeFilterSettings( settings ) )
31{
32 mSettings.setLayerFilterExpressions( layerFilterExpression );
33 mSettings.setFilterPolygon( polygon );
34}
35
36QgsMapHitTest::QgsMapHitTest( const QgsMapSettings &settings, const LayerFilterExpression &layerFilterExpression )
37 : mSettings( QgsLayerTreeFilterSettings( settings ) )
38{
39 mSettings.setLayerFilterExpressions( layerFilterExpression );
41}
42
44 : mSettings( settings )
45{
46
47}
48
50{
51 const QgsMapSettings &mapSettings = mSettings.mapSettings();
52
53 // TODO: do we need this temp image?
54 QImage tmpImage( mapSettings.outputSize(), mapSettings.outputImageFormat() );
55 tmpImage.setDotsPerMeterX( static_cast< int >( std::round( mapSettings.outputDpi() * 25.4 ) ) );
56 tmpImage.setDotsPerMeterY( static_cast< int >( std::round( mapSettings.outputDpi() * 25.4 ) ) );
57 QPainter painter( &tmpImage );
58
60 context.setPainter( &painter ); // we are not going to draw anything, but we still need a working painter
61
62 const QList< QgsMapLayer * > layers = mSettings.layers();
63 for ( QgsMapLayer *layer : layers )
64 {
65 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
66 if ( !vl || !vl->renderer() )
67 continue;
68
69 QgsGeometry extent;
71 {
72 if ( !vl->isInScaleRange( mapSettings.scale() ) )
73 {
74 continue;
75 }
76
77 extent = mSettings.combinedVisibleExtentForLayer( vl );
78
79 context.setCoordinateTransform( mapSettings.layerTransform( vl ) );
80 context.setExtent( extent.boundingBox() );
81 }
82
84 SymbolSet &usedSymbols = mHitTest[vl->id()];
85 SymbolSet &usedSymbolsRuleKey = mHitTestRuleKey[vl->id()];
86
87 QgsMapLayerStyleOverride styleOverride( vl );
88 if ( mapSettings.layerStyleOverrides().contains( vl->id() ) )
89 styleOverride.setOverrideStyle( mapSettings.layerStyleOverrides().value( vl->id() ) );
90
91 std::unique_ptr< QgsVectorLayerFeatureSource > source = std::make_unique< QgsVectorLayerFeatureSource >( vl );
92 runHitTestFeatureSource( source.get(),
93 vl->id(), vl->fields(), vl->renderer(),
94 usedSymbols, usedSymbolsRuleKey, context,
95 nullptr, extent );
96 }
97
98 painter.end();
99}
100
101QMap<QString, QSet<QString> > QgsMapHitTest::results() const
102{
103 return mHitTestRuleKey;
104}
105
107QMap<QString, QList<QString> > QgsMapHitTest::resultsPy() const
108{
109 QMap<QString, QList<QString> > res;
110 for ( auto it = mHitTestRuleKey.begin(); it != mHitTestRuleKey.end(); ++it )
111 {
112 res.insert( it.key(), qgis::setToList( it.value() ) );
113 }
114 return res;
115}
117
119{
120 if ( !symbol || !layer )
121 return false;
122
123 auto it = mHitTest.constFind( layer->id() );
124 if ( it == mHitTest.constEnd() )
125 return false;
126
127 return it->contains( QgsSymbolLayerUtils::symbolProperties( symbol ) );
128}
129
130bool QgsMapHitTest::legendKeyVisible( const QString &ruleKey, QgsVectorLayer *layer ) const
131{
132 if ( !layer )
133 return false;
134
135 auto it = mHitTestRuleKey.constFind( layer->id() );
136 if ( it == mHitTestRuleKey.constEnd() )
137 return false;
138
139 return it->contains( ruleKey );
140}
141
142void QgsMapHitTest::runHitTestFeatureSource( QgsAbstractFeatureSource *source,
143 const QString &layerId,
144 const QgsFields &fields,
145 const QgsFeatureRenderer *renderer,
146 SymbolSet &usedSymbols,
147 SymbolSet &usedSymbolsRuleKey,
148 QgsRenderContext &context,
149 QgsFeedback *feedback,
150 const QgsGeometry &visibleExtent )
151{
152 std::unique_ptr< QgsFeatureRenderer > r( renderer->clone() );
153 const bool moreSymbolsPerFeature = r->capabilities() & QgsFeatureRenderer::MoreSymbolsPerFeature;
154 r->startRender( context, fields );
155
156 // shortcut early if we know that there's nothing visible
157 if ( r->canSkipRender() )
158 {
159 r->stopRender( context );
160 return;
161 }
162
163 // if there's no legend items for this layer, shortcut out early
164 QSet< QString > remainingKeysToFind = r->legendKeys();
165 if ( remainingKeysToFind.empty() )
166 {
167 r->stopRender( context );
168 return;
169 }
170
171 QgsFeatureRequest request;
172 if ( feedback )
173 request.setFeedback( feedback );
174
175 const QString rendererFilterExpression = r->filter( fields );
176 if ( !rendererFilterExpression.isEmpty() )
177 {
178 request.setFilterExpression( rendererFilterExpression );
179 }
180
181 request.setExpressionContext( context.expressionContext() );
182
183 QSet<QString> requiredAttributes = r->usedAttributes( context );
184
185 QgsGeometry transformedPolygon = visibleExtent;
186 if ( transformedPolygon.type() != Qgis::GeometryType::Polygon )
187 {
188 transformedPolygon = QgsGeometry();
189 }
190
191 if ( feedback && feedback->isCanceled() )
192 {
193 r->stopRender( context );
194 return;
195 }
196
197 const QMap<QString, QString> layerFilterExpressions = mSettings.layerFilterExpressions();
198 if ( auto it = layerFilterExpressions.constFind( layerId ); it != layerFilterExpressions.constEnd() )
199 {
200 const QString expression = *it;
201 QgsExpression expr( expression );
202 expr.prepare( &context.expressionContext() );
203
204 requiredAttributes.unite( expr.referencedColumns() );
205 request.combineFilterExpression( expression );
206 }
207
208 request.setSubsetOfAttributes( requiredAttributes, fields );
209
210 std::unique_ptr< QgsGeometryEngine > polygonEngine;
212 {
213 if ( transformedPolygon.isNull() )
214 {
215 request.setFilterRect( context.extent() );
217 }
218 else
219 {
220 request.setFilterRect( transformedPolygon.boundingBox() );
221 polygonEngine.reset( QgsGeometry::createGeometryEngine( transformedPolygon.constGet() ) );
222 polygonEngine->prepareGeometry();
223 }
224 }
225
226 if ( feedback && feedback->isCanceled() )
227 {
228 r->stopRender( context );
229 return;
230 }
231
232 QgsFeatureIterator fi = source->getFeatures( request );
233
234 usedSymbols.clear();
235 usedSymbolsRuleKey.clear();
236
237 QgsFeature f;
238 while ( fi.nextFeature( f ) )
239 {
240 if ( feedback && feedback->isCanceled() )
241 break;
242
243 // filter out elements outside of the polygon
244 if ( f.hasGeometry() && polygonEngine )
245 {
246 if ( !polygonEngine->intersects( f.geometry().constGet() ) )
247 {
248 continue;
249 }
250 }
251
252 context.expressionContext().setFeature( f );
253
254 //make sure we store string representation of symbol, not pointer
255 //otherwise layer style override changes will delete original symbols and leave hanging pointers
256 const QSet< QString > legendKeysForFeature = r->legendKeysForFeature( f, context );
257 for ( const QString &legendKey : legendKeysForFeature )
258 {
259 usedSymbolsRuleKey.insert( legendKey );
260 remainingKeysToFind.remove( legendKey );
261 }
262
263 if ( moreSymbolsPerFeature )
264 {
265 const QgsSymbolList originalSymbolsForFeature = r->originalSymbolsForFeature( f, context );
266 for ( QgsSymbol *s : originalSymbolsForFeature )
267 {
268 if ( s )
269 usedSymbols.insert( QgsSymbolLayerUtils::symbolProperties( s ) );
270 }
271 }
272 else
273 {
274 QgsSymbol *s = r->originalSymbolForFeature( f, context );
275 if ( s )
276 usedSymbols.insert( QgsSymbolLayerUtils::symbolProperties( s ) );
277 }
278
279 if ( remainingKeysToFind.empty() )
280 {
281 // already found features for all legend items, no need to keep searching
282 break;
283 }
284 }
285 r->stopRender( context );
286}
287
288
289//
290// QgsMapHitTestTask
291//
292
294 : QgsTask( tr( "Updating Legend" ), QgsTask::Flag::CanCancel | QgsTask::Flag::CancelWithoutPrompt | QgsTask::Flag::Silent )
295 , mSettings( settings )
296{
297 prepare();
298}
299
300QMap<QString, QSet<QString> > QgsMapHitTestTask::results() const
301{
302 return mResults;
303}
304
306QMap<QString, QList<QString> > QgsMapHitTestTask::resultsPy() const
307{
308 QMap<QString, QList<QString> > res;
309 for ( auto it = mResults.begin(); it != mResults.end(); ++it )
310 {
311 res.insert( it.key(), qgis::setToList( it.value() ) );
312 }
313 return res;
314}
316
317void QgsMapHitTestTask::prepare()
318{
319 const QgsMapSettings &mapSettings = mSettings.mapSettings();
320
321 const QList< QgsMapLayer * > layers = mSettings.layers();
322 for ( QgsMapLayer *layer : layers )
323 {
324 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
325 if ( !vl || !vl->renderer() )
326 continue;
327
328 QgsMapLayerStyleOverride styleOverride( vl );
329 if ( mapSettings.layerStyleOverrides().contains( vl->id() ) )
330 styleOverride.setOverrideStyle( mapSettings.layerStyleOverrides().value( vl->id() ) );
331
332 QgsGeometry extent;
334 {
335 if ( !vl->isInScaleRange( mapSettings.scale() ) )
336 {
337 continue;
338 }
339
340 extent = mSettings.combinedVisibleExtentForLayer( vl );
341 }
342
343 PreparedLayerData layerData;
344 layerData.source = std::make_unique< QgsVectorLayerFeatureSource >( vl );
345 layerData.layerId = vl->id();
346 layerData.fields = vl->fields();
347 layerData.renderer.reset( vl->renderer()->clone() );
348 layerData.transform = mapSettings.layerTransform( vl );
349 layerData.extent = extent;
350 layerData.layerScope.reset( QgsExpressionContextUtils::layerScope( vl ) );
351
352 mPreparedData.emplace_back( std::move( layerData ) );
353 }
354}
355
357{
358 if ( mFeedback )
359 mFeedback->cancel();
360
362}
363
365{
366 mFeedback = std::make_unique< QgsFeedback >();
367 connect( mFeedback.get(), &QgsFeedback::progressChanged, this, &QgsTask::progressChanged );
368
369 std::unique_ptr< QgsMapHitTest > hitTest = std::make_unique< QgsMapHitTest >( mSettings );
370
371 // TODO: do we need this temp image?
372 const QgsMapSettings &mapSettings = mSettings.mapSettings();
373
374 QImage tmpImage( mapSettings.outputSize(), mapSettings.outputImageFormat() );
375 tmpImage.setDotsPerMeterX( static_cast< int >( std::round( mapSettings.outputDpi() * 25.4 ) ) );
376 tmpImage.setDotsPerMeterY( static_cast< int >( std::round( mapSettings.outputDpi() * 25.4 ) ) );
377 QPainter painter( &tmpImage );
378
380
381 context.setPainter( &painter ); // we are not going to draw anything, but we still need a working painter
382
383 std::size_t layerIdx = 0;
384 const std::size_t totalCount = mPreparedData.size();
385 for ( auto &layerData : mPreparedData )
386 {
387 mFeedback->setProgress( static_cast< double >( layerIdx ) / static_cast< double >( totalCount ) * 100.0 );
388 if ( mFeedback->isCanceled() )
389 break;
390
391 QgsMapHitTest::SymbolSet &usedSymbols = hitTest->mHitTest[layerData.layerId];
392 QgsMapHitTest::SymbolSet &usedSymbolsRuleKey = hitTest->mHitTestRuleKey[layerData.layerId];
393
394 context.setCoordinateTransform( layerData.transform );
395 context.setExtent( layerData.extent.boundingBox() );
396
397 QgsExpressionContextScope *layerScope = layerData.layerScope.release();
398 QgsExpressionContextScopePopper scopePopper( context.expressionContext(), layerScope );
399
400 hitTest->runHitTestFeatureSource( layerData.source.get(),
401 layerData.layerId,
402 layerData.fields,
403 layerData.renderer.get(),
404 usedSymbols,
405 usedSymbolsRuleKey,
406 context,
407 mFeedback.get(),
408 layerData.extent );
409 layerIdx++;
410 }
411
412 mResults = hitTest->mHitTestRuleKey;
413
414 mFeedback.reset();
415
416 return true;
417}
418
@ SkipVisibilityCheck
If set, the standard visibility check should be skipped.
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
@ Polygon
Polygons.
Base class that can be used for any class that is capable of returning features.
virtual QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())=0
Gets an iterator for features matching the specified request.
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
@ MoreSymbolsPerFeature
May use more than one symbol to render a feature: symbolsForFeature() will return them.
Definition: qgsrenderer.h:271
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & combineFilterExpression(const QString &expression)
Modifies the existing filter expression to add an additional expression filter.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
void setFeedback(QgsFeedback *feedback)
Attach a feedback object that can be queried regularly by the iterator to check if it should be cance...
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:230
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:53
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
Container of fields for a vector layer.
Definition: qgsfields.h:45
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
Q_GADGET bool isNull
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Qgis::GeometryType type
Definition: qgsgeometry.h:165
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0)
Creates and returns a new geometry engine representing the specified geometry using precision on a gr...
Contains settings relating to filtering the contents of QgsLayerTreeModel and views.
QgsMapSettings & mapSettings()
Returns the map settings used to filter the legend content.
QList< QgsMapLayer * > layers() const
Returns the layers which should be shown in the legend.
Qgis::LayerTreeFilterFlags flags() const
Returns the filter flags.
QMap< QString, QString > layerFilterExpressions() const
Returns the map of layer IDs to legend filter expression.
void setFilterPolygon(const QgsGeometry &polygon)
Sets the optional filter polygon, used when testing for symbols to show in the legend.
void setFlags(Qgis::LayerTreeFilterFlags flags)
Sets the filter flags.
void setLayerFilterExpressions(const QMap< QString, QString > &expressions)
Sets the map of layer IDs to legend filter expression.
QgsGeometry combinedVisibleExtentForLayer(const QgsMapLayer *layer)
Returns the combined visible extent for a layer.
QMap< QString, QSet< QString > > results() const
Returns the hit test results, which are a map of layer ID to visible symbol legend keys.
QgsMapHitTestTask(const QgsLayerTreeFilterSettings &settings)
Constructor for QgsMapHitTestTask, using the specified filter settings.
bool run() override
Performs the task's operation.
PRIVATE void cancel() override
Notifies the task that it should terminate.
void run()
Runs the map hit test.
PRIVATE bool symbolVisible(QgsSymbol *symbol, QgsVectorLayer *layer) const
Tests whether a symbol is visible for a specified layer.
bool legendKeyVisible(const QString &ruleKey, QgsVectorLayer *layer) const
Tests whether a given legend key is visible for a specified layer.
QgsMapHitTest(const QgsMapSettings &settings, const QgsGeometry &polygon=QgsGeometry(), const QgsMapHitTest::LayerFilterExpression &layerFilterExpression=QgsMapHitTest::LayerFilterExpression())
QMap< QString, QSet< QString > > results() const
Returns the hit test results, which are a map of layer ID to visible symbol legend keys.
QMap< QString, QString > LayerFilterExpression
Maps an expression string to a layer id.
Definition: qgsmaphittest.h:46
Restore overridden layer style on destruction.
void setOverrideStyle(const QString &style)
Temporarily apply a different style to the layer.
Base class for all map layer types.
Definition: qgsmaplayer.h:75
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
The QgsMapSettings class contains configuration for rendering of the map.
double scale() const
Returns the calculated map scale.
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Returns the coordinate transform from layer's CRS to destination CRS.
QSize outputSize() const
Returns the size of the resulting map image, in pixels.
QImage::Format outputImageFormat() const
format of internal QImage, default QImage::Format_ARGB32_Premultiplied
double outputDpi() const
Returns the DPI (dots per inch) used for conversion between real world units (e.g.
QMap< QString, QString > layerStyleOverrides() const
Returns the map of map layer style overrides (key: layer ID, value: style name) where a different sty...
Contains information about the context of a rendering operation.
void setCoordinateTransform(const QgsCoordinateTransform &t)
Sets the current coordinate transform for the context.
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
void setExtent(const QgsRectangle &extent)
When rendering a map layer, calling this method sets the "clipping" extent for the layer (in the laye...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
static QString symbolProperties(QgsSymbol *symbol)
Returns a string representing the symbol.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:94
void stopRender(QgsRenderContext &context)
Ends the rendering process.
Definition: qgssymbol.cpp:877
Abstract base class for long running background tasks.
void progressChanged(double progress)
Will be emitted by task when its progress changes.
virtual void cancel()
Notifies the task that it should terminate.
Flag
Task flags.
Represents a vector layer which manages a vector based data sets.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
QList< QgsSymbol * > QgsSymbolList
Definition: qgsrenderer.h:44