QGIS API Documentation 3.41.0-Master (3440c17df1d)
Loading...
Searching...
No Matches
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#include "moc_qgsmaphittest.cpp"
18
19#include "qgsfeatureiterator.h"
20#include "qgsrendercontext.h"
21#include "qgsrenderer.h"
22#include "qgsvectorlayer.h"
23#include "qgssymbollayerutils.h"
24#include "qgsgeometry.h"
25#include "qgsgeometryengine.h"
27#include "qgsmaplayerstyle.h"
29
30QgsMapHitTest::QgsMapHitTest( const QgsMapSettings &settings, const QgsGeometry &polygon, const LayerFilterExpression &layerFilterExpression )
31 : mSettings( QgsLayerTreeFilterSettings( settings ) )
32{
33 mSettings.setLayerFilterExpressions( layerFilterExpression );
34 mSettings.setFilterPolygon( polygon );
35}
36
37QgsMapHitTest::QgsMapHitTest( const QgsMapSettings &settings, const LayerFilterExpression &layerFilterExpression )
38 : mSettings( QgsLayerTreeFilterSettings( settings ) )
39{
40 mSettings.setLayerFilterExpressions( layerFilterExpression );
42}
43
45 : mSettings( settings )
46{
47
48}
49
51{
52 const QgsMapSettings &mapSettings = mSettings.mapSettings();
53
54 // TODO: do we need this temp image?
55 QImage tmpImage( mapSettings.outputSize(), mapSettings.outputImageFormat() );
56 tmpImage.setDotsPerMeterX( static_cast< int >( std::round( mapSettings.outputDpi() * 25.4 ) ) );
57 tmpImage.setDotsPerMeterY( static_cast< int >( std::round( mapSettings.outputDpi() * 25.4 ) ) );
58 QPainter painter( &tmpImage );
59
61 context.setPainter( &painter ); // we are not going to draw anything, but we still need a working painter
62
63 const QList< QgsMapLayer * > layers = mSettings.layers();
64 for ( QgsMapLayer *layer : layers )
65 {
66 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
67 if ( !vl || !vl->renderer() )
68 continue;
69
70 QgsGeometry extent;
72 {
73 if ( !vl->isInScaleRange( mapSettings.scale() ) )
74 {
75 continue;
76 }
77
78 extent = mSettings.combinedVisibleExtentForLayer( vl );
79
80 context.setCoordinateTransform( mapSettings.layerTransform( vl ) );
81 context.setExtent( extent.boundingBox() );
82 }
83
85 SymbolSet &usedSymbols = mHitTest[vl->id()];
86 SymbolSet &usedSymbolsRuleKey = mHitTestRuleKey[vl->id()];
87
88 QgsMapLayerStyleOverride styleOverride( vl );
89 if ( mapSettings.layerStyleOverrides().contains( vl->id() ) )
90 styleOverride.setOverrideStyle( mapSettings.layerStyleOverrides().value( vl->id() ) );
91
92 std::unique_ptr< QgsVectorLayerFeatureSource > source = std::make_unique< QgsVectorLayerFeatureSource >( vl );
93 runHitTestFeatureSource( source.get(),
94 vl->id(), vl->fields(), vl->renderer(),
95 usedSymbols, usedSymbolsRuleKey, context,
96 nullptr, extent );
97 }
98
99 painter.end();
100}
101
102QMap<QString, QSet<QString> > QgsMapHitTest::results() const
103{
104 return mHitTestRuleKey;
105}
106
108QMap<QString, QList<QString> > QgsMapHitTest::resultsPy() const
109{
110 QMap<QString, QList<QString> > res;
111 for ( auto it = mHitTestRuleKey.begin(); it != mHitTestRuleKey.end(); ++it )
112 {
113 res.insert( it.key(), qgis::setToList( it.value() ) );
114 }
115 return res;
116}
118
120{
121 if ( !symbol || !layer )
122 return false;
123
124 auto it = mHitTest.constFind( layer->id() );
125 if ( it == mHitTest.constEnd() )
126 return false;
127
128 return it->contains( QgsSymbolLayerUtils::symbolProperties( symbol ) );
129}
130
131bool QgsMapHitTest::legendKeyVisible( const QString &ruleKey, QgsVectorLayer *layer ) const
132{
133 if ( !layer )
134 return false;
135
136 auto it = mHitTestRuleKey.constFind( layer->id() );
137 if ( it == mHitTestRuleKey.constEnd() )
138 return false;
139
140 return it->contains( ruleKey );
141}
142
143void QgsMapHitTest::runHitTestFeatureSource( QgsAbstractFeatureSource *source,
144 const QString &layerId,
145 const QgsFields &fields,
146 const QgsFeatureRenderer *renderer,
147 SymbolSet &usedSymbols,
148 SymbolSet &usedSymbolsRuleKey,
149 QgsRenderContext &context,
150 QgsFeedback *feedback,
151 const QgsGeometry &visibleExtent )
152{
153 std::unique_ptr< QgsFeatureRenderer > r( renderer->clone() );
154 const bool moreSymbolsPerFeature = r->capabilities() & QgsFeatureRenderer::MoreSymbolsPerFeature;
155 r->startRender( context, fields );
156
157 // shortcut early if we know that there's nothing visible
158 if ( r->canSkipRender() )
159 {
160 r->stopRender( context );
161 return;
162 }
163
164 // if there's no legend items for this layer, shortcut out early
165 QSet< QString > remainingKeysToFind = r->legendKeys();
166 if ( remainingKeysToFind.empty() )
167 {
168 r->stopRender( context );
169 return;
170 }
171
172 QgsFeatureRequest request;
173 if ( feedback )
174 request.setFeedback( feedback );
175
176 const QString rendererFilterExpression = r->filter( fields );
177 if ( !rendererFilterExpression.isEmpty() )
178 {
179 request.setFilterExpression( rendererFilterExpression );
180 }
181
182 request.setExpressionContext( context.expressionContext() );
183
184 QSet<QString> requiredAttributes = r->usedAttributes( context );
185
186 QgsGeometry transformedPolygon = visibleExtent;
187 if ( transformedPolygon.type() != Qgis::GeometryType::Polygon )
188 {
189 transformedPolygon = QgsGeometry();
190 }
191
192 if ( feedback && feedback->isCanceled() )
193 {
194 r->stopRender( context );
195 return;
196 }
197
198 const QMap<QString, QString> layerFilterExpressions = mSettings.layerFilterExpressions();
199 if ( auto it = layerFilterExpressions.constFind( layerId ); it != layerFilterExpressions.constEnd() )
200 {
201 const QString expression = *it;
202 QgsExpression expr( expression );
203 expr.prepare( &context.expressionContext() );
204
205 requiredAttributes.unite( expr.referencedColumns() );
206 request.combineFilterExpression( expression );
207 }
208
209 request.setSubsetOfAttributes( requiredAttributes, fields );
210
211 std::unique_ptr< QgsGeometryEngine > polygonEngine;
213 {
214 if ( transformedPolygon.isNull() )
215 {
216 request.setFilterRect( context.extent() );
218 }
219 else
220 {
221 request.setFilterRect( transformedPolygon.boundingBox() );
222 polygonEngine.reset( QgsGeometry::createGeometryEngine( transformedPolygon.constGet() ) );
223 polygonEngine->prepareGeometry();
224 }
225 }
226
227 if ( feedback && feedback->isCanceled() )
228 {
229 r->stopRender( context );
230 return;
231 }
232
233 QgsFeatureIterator fi = source->getFeatures( request );
234
235 usedSymbols.clear();
236 usedSymbolsRuleKey.clear();
237
238 QgsFeature f;
239 while ( fi.nextFeature( f ) )
240 {
241 if ( feedback && feedback->isCanceled() )
242 break;
243
244 // filter out elements outside of the polygon
245 if ( f.hasGeometry() && polygonEngine )
246 {
247 if ( !polygonEngine->intersects( f.geometry().constGet() ) )
248 {
249 continue;
250 }
251 }
252
253 context.expressionContext().setFeature( f );
254
255 //make sure we store string representation of symbol, not pointer
256 //otherwise layer style override changes will delete original symbols and leave hanging pointers
257 const QSet< QString > legendKeysForFeature = r->legendKeysForFeature( f, context );
258 for ( const QString &legendKey : legendKeysForFeature )
259 {
260 usedSymbolsRuleKey.insert( legendKey );
261 remainingKeysToFind.remove( legendKey );
262 }
263
264 if ( moreSymbolsPerFeature )
265 {
266 const QgsSymbolList originalSymbolsForFeature = r->originalSymbolsForFeature( f, context );
267 for ( QgsSymbol *s : originalSymbolsForFeature )
268 {
269 if ( s )
270 usedSymbols.insert( QgsSymbolLayerUtils::symbolProperties( s ) );
271 }
272 }
273 else
274 {
275 QgsSymbol *s = r->originalSymbolForFeature( f, context );
276 if ( s )
277 usedSymbols.insert( QgsSymbolLayerUtils::symbolProperties( s ) );
278 }
279
280 if ( remainingKeysToFind.empty() )
281 {
282 // already found features for all legend items, no need to keep searching
283 break;
284 }
285 }
286 r->stopRender( context );
287}
288
289
290//
291// QgsMapHitTestTask
292//
293
295 : QgsTask( tr( "Updating Legend" ), QgsTask::Flag::CanCancel | QgsTask::Flag::CancelWithoutPrompt | QgsTask::Flag::Silent )
296 , mSettings( settings )
297{
298 prepare();
299}
300
301QMap<QString, QSet<QString> > QgsMapHitTestTask::results() const
302{
303 return mResults;
304}
305
307QMap<QString, QList<QString> > QgsMapHitTestTask::resultsPy() const
308{
309 QMap<QString, QList<QString> > res;
310 for ( auto it = mResults.begin(); it != mResults.end(); ++it )
311 {
312 res.insert( it.key(), qgis::setToList( it.value() ) );
313 }
314 return res;
315}
317
318void QgsMapHitTestTask::prepare()
319{
320 const QgsMapSettings &mapSettings = mSettings.mapSettings();
321
322 const QList< QgsMapLayer * > layers = mSettings.layers();
323 for ( QgsMapLayer *layer : layers )
324 {
325 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
326 if ( !vl || !vl->renderer() )
327 continue;
328
329 QgsMapLayerStyleOverride styleOverride( vl );
330 if ( mapSettings.layerStyleOverrides().contains( vl->id() ) )
331 styleOverride.setOverrideStyle( mapSettings.layerStyleOverrides().value( vl->id() ) );
332
333 QgsGeometry extent;
335 {
336 if ( !vl->isInScaleRange( mapSettings.scale() ) )
337 {
338 continue;
339 }
340
341 extent = mSettings.combinedVisibleExtentForLayer( vl );
342 }
343
344 PreparedLayerData layerData;
345 layerData.source = std::make_unique< QgsVectorLayerFeatureSource >( vl );
346 layerData.layerId = vl->id();
347 layerData.fields = vl->fields();
348 layerData.renderer.reset( vl->renderer()->clone() );
349 layerData.transform = mapSettings.layerTransform( vl );
350 layerData.extent = extent;
351 layerData.layerScope.reset( QgsExpressionContextUtils::layerScope( vl ) );
352
353 mPreparedData.emplace_back( std::move( layerData ) );
354 }
355}
356
358{
359 if ( mFeedback )
360 mFeedback->cancel();
361
363}
364
366{
367 mFeedback = std::make_unique< QgsFeedback >();
368 connect( mFeedback.get(), &QgsFeedback::progressChanged, this, &QgsTask::progressChanged );
369
370 std::unique_ptr< QgsMapHitTest > hitTest = std::make_unique< QgsMapHitTest >( mSettings );
371
372 // TODO: do we need this temp image?
373 const QgsMapSettings &mapSettings = mSettings.mapSettings();
374
375 QImage tmpImage( mapSettings.outputSize(), mapSettings.outputImageFormat() );
376 tmpImage.setDotsPerMeterX( static_cast< int >( std::round( mapSettings.outputDpi() * 25.4 ) ) );
377 tmpImage.setDotsPerMeterY( static_cast< int >( std::round( mapSettings.outputDpi() * 25.4 ) ) );
378 QPainter painter( &tmpImage );
379
381
382 context.setPainter( &painter ); // we are not going to draw anything, but we still need a working painter
383
384 std::size_t layerIdx = 0;
385 const std::size_t totalCount = mPreparedData.size();
386 for ( auto &layerData : mPreparedData )
387 {
388 mFeedback->setProgress( static_cast< double >( layerIdx ) / static_cast< double >( totalCount ) * 100.0 );
389 if ( mFeedback->isCanceled() )
390 break;
391
392 QgsMapHitTest::SymbolSet &usedSymbols = hitTest->mHitTest[layerData.layerId];
393 QgsMapHitTest::SymbolSet &usedSymbolsRuleKey = hitTest->mHitTestRuleKey[layerData.layerId];
394
395 context.setCoordinateTransform( layerData.transform );
396 context.setExtent( layerData.extent.boundingBox() );
397
398 QgsExpressionContextScope *layerScope = layerData.layerScope.release();
399 QgsExpressionContextScopePopper scopePopper( context.expressionContext(), layerScope );
400
401 hitTest->runHitTestFeatureSource( layerData.source.get(),
402 layerData.layerId,
403 layerData.fields,
404 layerData.renderer.get(),
405 usedSymbols,
406 usedSymbolsRuleKey,
407 context,
408 mFeedback.get(),
409 layerData.extent );
410 layerIdx++;
411 }
412
413 mResults = hitTest->mHitTestRuleKey;
414
415 mFeedback.reset();
416
417 return true;
418}
419
@ 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.
Abstract base class for all 2D vector feature renderers.
@ MoreSymbolsPerFeature
May use more than one symbol to render a feature: symbolsForFeature() will return them.
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:58
QgsGeometry geometry
Definition qgsfeature.h:69
bool hasGeometry() const
Returns true if the feature has an associated geometry.
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:46
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Qgis::GeometryType type
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
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.
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:76
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
QString id
Definition qgsmaplayer.h:79
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:231
void stopRender(QgsRenderContext &context)
Ends the rendering process.
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.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
QList< QgsSymbol * > QgsSymbolList
Definition qgsrenderer.h:47