QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
qgsmaprenderercache.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaprenderercache.cpp
3 --------------------------------------
4 Date : December 2013
5 Copyright : (C) 2013 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 "qgsmaprenderercache.h"
17
18#include "qgsmaplayer.h"
20
21#include <QImage>
22#include <QPainter>
23#include <algorithm>
24
26{
27 clear();
28}
29
31{
32 QMutexLocker lock( &mMutex );
33 clearInternal();
34}
35
36void QgsMapRendererCache::clearInternal()
37{
38 mExtent.setMinimal();
39 mScale = 0;
40
41 // make sure we are disconnected from all layers
42 for ( const QgsWeakMapLayerPointer &layer : std::as_const( mConnectedLayers ) )
43 {
44 if ( layer.data() )
45 {
46 disconnect( layer.data(), &QgsMapLayer::repaintRequested, this, &QgsMapRendererCache::layerRequestedRepaint );
47 disconnect( layer.data(), &QgsMapLayer::willBeDeleted, this, &QgsMapRendererCache::layerRequestedRepaint );
48 }
49 }
50 mCachedImages.clear();
51 mConnectedLayers.clear();
52}
53
54void QgsMapRendererCache::dropUnusedConnections()
55{
56 QSet< QgsWeakMapLayerPointer > stillDepends = dependentLayers();
57 const QSet< QgsWeakMapLayerPointer > disconnects = mConnectedLayers.subtract( stillDepends );
58 for ( const QgsWeakMapLayerPointer &layer : disconnects )
59 {
60 if ( layer.data() )
61 {
62 disconnect( layer.data(), &QgsMapLayer::repaintRequested, this, &QgsMapRendererCache::layerRequestedRepaint );
63 disconnect( layer.data(), &QgsMapLayer::willBeDeleted, this, &QgsMapRendererCache::layerRequestedRepaint );
64 }
65 }
66
67 mConnectedLayers = stillDepends;
68}
69
70QSet<QgsWeakMapLayerPointer > QgsMapRendererCache::dependentLayers() const
71{
72 QSet< QgsWeakMapLayerPointer > result;
73 QMap<QString, CacheParameters>::const_iterator it = mCachedImages.constBegin();
74 for ( ; it != mCachedImages.constEnd(); ++it )
75 {
76 const auto dependentLayers { it.value().dependentLayers };
77 for ( const QgsWeakMapLayerPointer &l : dependentLayers )
78 {
79 if ( l.data() )
80 result << l;
81 }
82 }
83 return result;
84}
85
86bool QgsMapRendererCache::init( const QgsRectangle &extent, double scale )
87{
88 QMutexLocker lock( &mMutex );
89
90 // check whether the params are the same
91 if ( extent == mExtent &&
92 qgsDoubleNear( scale, mScale ) )
93 return true;
94
95 clearInternal();
96
97 // set new params
98 mExtent = extent;
99 mScale = scale;
100 mMtp = QgsMapToPixel::fromScale( scale, Qgis::DistanceUnit::Unknown );
101
102 return false;
103}
104
106{
107 QMutexLocker lock( &mMutex );
108
109 // check whether the params are the same
110 if ( extent == mExtent &&
111 mtp.transform() == mMtp.transform() )
112 return true;
113
114 // set new params
115
116 mExtent = extent;
117 mScale = 1.0;
118 mMtp = mtp;
119
120 return false;
121}
122
123void QgsMapRendererCache::setCacheImage( const QString &cacheKey, const QImage &image, const QList<QgsMapLayer *> &dependentLayers )
124{
125 QMutexLocker lock( &mMutex );
126
127 QgsRectangle extent = mExtent;
128 QgsMapToPixel mapToPixel = mMtp;
129
130 lock.unlock();
131 setCacheImageWithParameters( cacheKey, image, extent, mapToPixel, dependentLayers );
132}
133
134void QgsMapRendererCache::setCacheImageWithParameters( const QString &cacheKey, const QImage &image, const QgsRectangle &extent, const QgsMapToPixel &mapToPixel, const QList<QgsMapLayer *> &dependentLayers )
135{
136 QMutexLocker lock( &mMutex );
137
138 if ( extent != mExtent || mapToPixel != mMtp )
139 {
140 auto it = mCachedImages.constFind( cacheKey );
141 if ( it != mCachedImages.constEnd() )
142 {
143 // if the specified extent or map to pixel differs from the current cache parameters, AND
144 // there's an existing cached image with parameters which DO match the current cache parameters,
145 // then we leave the existing image intact and discard the one with non-matching parameters
146 if ( it->cachedExtent == mExtent && it->cachedMtp == mMtp )
147 return;
148 }
149 }
150
151 CacheParameters params;
152 params.cachedImage = image;
153 params.cachedExtent = extent;
154 params.cachedMtp = mapToPixel;
155
156 // connect to the layer to listen to layer's repaintRequested() signals
157 for ( QgsMapLayer *layer : dependentLayers )
158 {
159 if ( layer )
160 {
161 params.dependentLayers << layer;
162 if ( !mConnectedLayers.contains( QgsWeakMapLayerPointer( layer ) ) )
163 {
164 connect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapRendererCache::layerRequestedRepaint );
165 connect( layer, &QgsMapLayer::willBeDeleted, this, &QgsMapRendererCache::layerRequestedRepaint );
166 mConnectedLayers << layer;
167 }
168 }
169 }
170
171 mCachedImages[cacheKey] = params;
172}
173
174bool QgsMapRendererCache::hasCacheImage( const QString &cacheKey ) const
175{
176 QMutexLocker lock( &mMutex );
177
178 auto it = mCachedImages.constFind( cacheKey );
179 if ( it != mCachedImages.constEnd() )
180 {
181 const CacheParameters &params = it.value();
182 return ( params.cachedExtent == mExtent &&
183 params.cachedMtp.transform() == mMtp.transform() );
184 }
185 else
186 {
187 return false;
188 }
189}
190
191bool QgsMapRendererCache::hasAnyCacheImage( const QString &cacheKey, double minimumScaleThreshold, double maximumScaleThreshold ) const
192{
193 auto it = mCachedImages.constFind( cacheKey );
194 if ( it != mCachedImages.constEnd() )
195 {
196 const CacheParameters &params = it.value();
197
198 // check if cached image is outside desired scale range
199 if ( minimumScaleThreshold != 0 && mMtp.mapUnitsPerPixel() < params.cachedMtp.mapUnitsPerPixel() * minimumScaleThreshold )
200 return false;
201 if ( maximumScaleThreshold != 0 && mMtp.mapUnitsPerPixel() > params.cachedMtp.mapUnitsPerPixel() * maximumScaleThreshold )
202 return false;
203
204 return true;
205 }
206 else
207 {
208 return false;
209 }
210}
211
212QImage QgsMapRendererCache::cacheImage( const QString &cacheKey ) const
213{
214 QMutexLocker lock( &mMutex );
215 return mCachedImages.value( cacheKey ).cachedImage;
216}
217
218static QPointF _transform( const QgsMapToPixel &mtp, const QgsPointXY &point, double scale )
219{
220 qreal x = point.x(), y = point.y();
221 mtp.transformInPlace( x, y );
222 return QPointF( x, y ) * scale;
223}
224
225QImage QgsMapRendererCache::transformedCacheImage( const QString &cacheKey, const QgsMapToPixel &mtp ) const
226{
227 QMutexLocker lock( &mMutex );
228 const CacheParameters params = mCachedImages.value( cacheKey );
229
230 if ( params.cachedExtent == mExtent &&
231 mtp.transform() == mMtp.transform() )
232 {
233 return params.cachedImage;
234 }
235 else
236 {
237 // no not use cache when the canvas rotation just changed
238 // https://github.com/qgis/QGIS/issues/41360
239 if ( !qgsDoubleNear( mtp.mapRotation(), params.cachedMtp.mapRotation() ) )
240 return QImage();
241
242 QgsRectangle intersection = mExtent.intersect( params.cachedExtent );
243 if ( intersection.isNull() )
244 return QImage();
245
246 // Calculate target rect
247 const QPointF ulT = _transform( mtp, QgsPointXY( intersection.xMinimum(), intersection.yMaximum() ), 1.0 );
248 const QPointF lrT = _transform( mtp, QgsPointXY( intersection.xMaximum(), intersection.yMinimum() ), 1.0 );
249 const QRectF targetRect( ulT.x(), ulT.y(), lrT.x() - ulT.x(), lrT.y() - ulT.y() );
250
251 // Calculate source rect
252 const QPointF ulS = _transform( params.cachedMtp, QgsPointXY( intersection.xMinimum(), intersection.yMaximum() ), params.cachedImage.devicePixelRatio() );
253 const QPointF lrS = _transform( params.cachedMtp, QgsPointXY( intersection.xMaximum(), intersection.yMinimum() ), params.cachedImage.devicePixelRatio() );
254 const QRectF sourceRect( ulS.x(), ulS.y(), lrS.x() - ulS.x(), lrS.y() - ulS.y() );
255
256 // Draw image
257 QImage ret( params.cachedImage.size(), params.cachedImage.format() );
258 ret.setDevicePixelRatio( params.cachedImage.devicePixelRatio() );
259 ret.setDotsPerMeterX( params.cachedImage.dotsPerMeterX() );
260 ret.setDotsPerMeterY( params.cachedImage.dotsPerMeterY() );
261 ret.fill( Qt::transparent );
262 QPainter painter;
263 painter.begin( &ret );
264 painter.drawImage( targetRect, params.cachedImage, sourceRect );
265 painter.end();
266 return ret;
267 }
268}
269
270QList< QgsMapLayer * > QgsMapRendererCache::dependentLayers( const QString &cacheKey ) const
271{
272 auto it = mCachedImages.constFind( cacheKey );
273 if ( it != mCachedImages.constEnd() )
274 {
275 return _qgis_listQPointerToRaw( ( *it ).dependentLayers );
276 }
277 return QList< QgsMapLayer * >();
278}
279
280
281void QgsMapRendererCache::layerRequestedRepaint()
282{
283 QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
285}
286
288{
289 if ( !layer )
290 return;
291
292 QMutexLocker lock( &mMutex );
293
294 // check through all cached images to clear any which depend on this layer
295 QMap<QString, CacheParameters>::iterator it = mCachedImages.begin();
296 for ( ; it != mCachedImages.end(); )
297 {
298 if ( !it.value().dependentLayers.contains( layer ) )
299 {
300 ++it;
301 continue;
302 }
303
304 it = mCachedImages.erase( it );
305 }
306 dropUnusedConnections();
307}
308
309void QgsMapRendererCache::clearCacheImage( const QString &cacheKey )
310{
311 QMutexLocker lock( &mMutex );
312
313 mCachedImages.remove( cacheKey );
314 dropUnusedConnections();
315}
316
Base class for all map layer types.
Definition: qgsmaplayer.h:73
void willBeDeleted()
Emitted in the destructor when the layer is about to be deleted, but it is still in a perfectly valid...
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
bool updateParameters(const QgsRectangle &extent, const QgsMapToPixel &mtp)
Sets extent and scale parameters.
QList< QgsMapLayer * > dependentLayers(const QString &cacheKey) const
Returns a list of map layers on which an image in the cache depends.
void clear()
Invalidates the cache contents, clearing all cached images.
bool hasCacheImage(const QString &cacheKey) const
Returns true if the cache contains an image with the specified cacheKey that has the same extent and ...
QImage cacheImage(const QString &cacheKey) const
Returns the cached image for the specified cacheKey.
bool hasAnyCacheImage(const QString &cacheKey, double minimumScaleThreshold=0, double maximumScaleThreshold=0) const
Returns true if the cache contains an image with the specified cacheKey with any cache's parameters (...
void setCacheImageWithParameters(const QString &cacheKey, const QImage &image, const QgsRectangle &extent, const QgsMapToPixel &mapToPixel, const QList< QgsMapLayer * > &dependentLayers=QList< QgsMapLayer * >())
Set the cached image for a particular cacheKey, using a specific extent and mapToPixel (which may dif...
void setCacheImage(const QString &cacheKey, const QImage &image, const QList< QgsMapLayer * > &dependentLayers=QList< QgsMapLayer * >())
Set the cached image for a particular cacheKey, using the current cache parameters.
void invalidateCacheForLayer(QgsMapLayer *layer)
Invalidates cached images which relate to the specified map layer.
void clearCacheImage(const QString &cacheKey)
Removes an image from the cache with matching cacheKey.
QImage transformedCacheImage(const QString &cacheKey, const QgsMapToPixel &mtp) const
Returns the cached image for the specified cacheKey transformed to the particular extent and scale.
Q_DECL_DEPRECATED bool init(const QgsRectangle &extent, double scale)
Initialize cache: sets extent and scale parameters and clears the cache if any parameters have change...
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns the current map units per pixel.
static QgsMapToPixel fromScale(double scale, Qgis::DistanceUnit mapUnits, double dpi=96)
Returns a new QgsMapToPixel created using a specified scale and distance unit.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
Definition: qgsmaptopixel.h:90
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
void transformInPlace(double &x, double &y) const
Transforms device coordinates to map coordinates.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setMinimal() SIP_HOLDGIL
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:172
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:333
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3509
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
Definition: qgsmaplayer.h:2206