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