QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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#include "qgsapplication.h"
21
22#include <QImage>
23#include <QPainter>
24#include <algorithm>
25
27{
28 clear();
29}
30
32{
33 QMutexLocker lock( &mMutex );
34 clearInternal();
35}
36
37void QgsMapRendererCache::clearInternal()
38{
39 mExtent.setMinimal();
40 mScale = 0;
41
42 // make sure we are disconnected from all layers
43 for ( const QgsWeakMapLayerPointer &layer : std::as_const( mConnectedLayers ) )
44 {
45 if ( layer.data() )
46 {
47 disconnect( layer.data(), &QgsMapLayer::repaintRequested, this, &QgsMapRendererCache::layerRequestedRepaint );
48 disconnect( layer.data(), &QgsMapLayer::willBeDeleted, this, &QgsMapRendererCache::layerRequestedRepaint );
49 }
50 }
51 mCachedImages.clear();
52 mConnectedLayers.clear();
53}
54
55void QgsMapRendererCache::dropUnusedConnections()
56{
57 QSet< QgsWeakMapLayerPointer > stillDepends = dependentLayers();
58 const QSet< QgsWeakMapLayerPointer > disconnects = mConnectedLayers.subtract( stillDepends );
59 for ( const QgsWeakMapLayerPointer &layer : disconnects )
60 {
61 if ( layer.data() )
62 {
63 disconnect( layer.data(), &QgsMapLayer::repaintRequested, this, &QgsMapRendererCache::layerRequestedRepaint );
64 disconnect( layer.data(), &QgsMapLayer::willBeDeleted, this, &QgsMapRendererCache::layerRequestedRepaint );
65 }
66 }
67
68 mConnectedLayers = stillDepends;
69}
70
71QSet<QgsWeakMapLayerPointer > QgsMapRendererCache::dependentLayers() const
72{
73 QSet< QgsWeakMapLayerPointer > result;
74 QMap<QString, CacheParameters>::const_iterator it = mCachedImages.constBegin();
75 for ( ; it != mCachedImages.constEnd(); ++it )
76 {
77 const auto dependentLayers { it.value().dependentLayers };
78 for ( const QgsWeakMapLayerPointer &l : dependentLayers )
79 {
80 if ( l.data() )
81 result << l;
82 }
83 }
84 return result;
85}
86
87bool QgsMapRendererCache::init( const QgsRectangle &extent, double scale )
88{
89 QMutexLocker lock( &mMutex );
90
91 // check whether the params are the same
92 if ( extent == mExtent &&
93 qgsDoubleNear( scale, mScale ) )
94 return true;
95
96 clearInternal();
97
98 // set new params
99 mExtent = extent;
100 mScale = scale;
101 mMtp = QgsMapToPixel::fromScale( scale, QgsUnitTypes::DistanceUnit::DistanceUnknownUnit );
102
103 return false;
104}
105
107{
108 QMutexLocker lock( &mMutex );
109
110 // check whether the params are the same
111 if ( extent == mExtent &&
112 mtp.transform() == mMtp.transform() )
113 return true;
114
115 // set new params
116
117 mExtent = extent;
118 mScale = 1.0;
119 mMtp = mtp;
120
121 return false;
122}
123
124void QgsMapRendererCache::setCacheImage( const QString &cacheKey, const QImage &image, const QList<QgsMapLayer *> &dependentLayers )
125{
126 QMutexLocker lock( &mMutex );
127
128 QgsRectangle extent = mExtent;
129 QgsMapToPixel mapToPixel = mMtp;
130
131 lock.unlock();
132 setCacheImageWithParameters( cacheKey, image, extent, mapToPixel, dependentLayers );
133}
134
135void QgsMapRendererCache::setCacheImageWithParameters( const QString &cacheKey, const QImage &image, const QgsRectangle &extent, const QgsMapToPixel &mapToPixel, const QList<QgsMapLayer *> &dependentLayers )
136{
137 QMutexLocker lock( &mMutex );
138
139 if ( extent != mExtent || mapToPixel != mMtp )
140 {
141 auto it = mCachedImages.constFind( cacheKey );
142 if ( it != mCachedImages.constEnd() )
143 {
144 // if the specified extent or map to pixel differs from the current cache parameters, AND
145 // there's an existing cached image with parameters which DO match the current cache parameters,
146 // then we leave the existing image intact and discard the one with non-matching parameters
147 if ( it->cachedExtent == mExtent && it->cachedMtp == mMtp )
148 return;
149 }
150 }
151
152 CacheParameters params;
153 params.cachedImage = image;
154 params.cachedExtent = extent;
155 params.cachedMtp = mapToPixel;
156
157 // connect to the layer to listen to layer's repaintRequested() signals
158 for ( QgsMapLayer *layer : dependentLayers )
159 {
160 if ( layer )
161 {
162 params.dependentLayers << layer;
163 if ( !mConnectedLayers.contains( QgsWeakMapLayerPointer( layer ) ) )
164 {
165 connect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapRendererCache::layerRequestedRepaint );
166 connect( layer, &QgsMapLayer::willBeDeleted, this, &QgsMapRendererCache::layerRequestedRepaint );
167 mConnectedLayers << layer;
168 }
169 }
170 }
171
172 mCachedImages[cacheKey] = params;
173}
174
175bool QgsMapRendererCache::hasCacheImage( const QString &cacheKey ) const
176{
177 QMutexLocker lock( &mMutex );
178
179 auto it = mCachedImages.constFind( cacheKey );
180 if ( it != mCachedImages.constEnd() )
181 {
182 const CacheParameters &params = it.value();
183 return ( params.cachedExtent == mExtent &&
184 params.cachedMtp.transform() == mMtp.transform() );
185 }
186 else
187 {
188 return false;
189 }
190}
191
192bool QgsMapRendererCache::hasAnyCacheImage( const QString &cacheKey, double minimumScaleThreshold, double maximumScaleThreshold ) const
193{
194 auto it = mCachedImages.constFind( cacheKey );
195 if ( it != mCachedImages.constEnd() )
196 {
197 const CacheParameters &params = it.value();
198
199 // check if cached image is outside desired scale range
200 if ( minimumScaleThreshold != 0 && mMtp.mapUnitsPerPixel() < params.cachedMtp.mapUnitsPerPixel() * minimumScaleThreshold )
201 return false;
202 if ( maximumScaleThreshold != 0 && mMtp.mapUnitsPerPixel() > params.cachedMtp.mapUnitsPerPixel() * maximumScaleThreshold )
203 return false;
204
205 return true;
206 }
207 else
208 {
209 return false;
210 }
211}
212
213QImage QgsMapRendererCache::cacheImage( const QString &cacheKey ) const
214{
215 QMutexLocker lock( &mMutex );
216 return mCachedImages.value( cacheKey ).cachedImage;
217}
218
219static QPointF _transform( const QgsMapToPixel &mtp, const QgsPointXY &point, double scale )
220{
221 qreal x = point.x(), y = point.y();
222 mtp.transformInPlace( x, y );
223 return QPointF( x, y ) * scale;
224}
225
226QImage QgsMapRendererCache::transformedCacheImage( const QString &cacheKey, const QgsMapToPixel &mtp ) const
227{
228 QMutexLocker lock( &mMutex );
229 const CacheParameters params = mCachedImages.value( cacheKey );
230
231 if ( params.cachedExtent == mExtent &&
232 mtp.transform() == mMtp.transform() )
233 {
234 return params.cachedImage;
235 }
236 else
237 {
238 // no not use cache when the canvas rotation just changed
239 // https://github.com/qgis/QGIS/issues/41360
240 if ( !qgsDoubleNear( mtp.mapRotation(), params.cachedMtp.mapRotation() ) )
241 return QImage();
242
243 QgsRectangle intersection = mExtent.intersect( params.cachedExtent );
244 if ( intersection.isNull() )
245 return QImage();
246
247 // Calculate target rect
248 const QPointF ulT = _transform( mtp, QgsPointXY( intersection.xMinimum(), intersection.yMaximum() ), 1.0 );
249 const QPointF lrT = _transform( mtp, QgsPointXY( intersection.xMaximum(), intersection.yMinimum() ), 1.0 );
250 const QRectF targetRect( ulT.x(), ulT.y(), lrT.x() - ulT.x(), lrT.y() - ulT.y() );
251
252 // Calculate source rect
253 const QPointF ulS = _transform( params.cachedMtp, QgsPointXY( intersection.xMinimum(), intersection.yMaximum() ), params.cachedImage.devicePixelRatio() );
254 const QPointF lrS = _transform( params.cachedMtp, QgsPointXY( intersection.xMaximum(), intersection.yMinimum() ), params.cachedImage.devicePixelRatio() );
255 const QRectF sourceRect( ulS.x(), ulS.y(), lrS.x() - ulS.x(), lrS.y() - ulS.y() );
256
257 // Draw image
258 QImage ret( params.cachedImage.size(), params.cachedImage.format() );
259 ret.setDevicePixelRatio( params.cachedImage.devicePixelRatio() );
260 ret.setDotsPerMeterX( params.cachedImage.dotsPerMeterX() );
261 ret.setDotsPerMeterY( params.cachedImage.dotsPerMeterY() );
262 ret.fill( Qt::transparent );
263 QPainter painter;
264 painter.begin( &ret );
265 painter.drawImage( targetRect, params.cachedImage, sourceRect );
266 painter.end();
267 return ret;
268 }
269}
270
271QList< QgsMapLayer * > QgsMapRendererCache::dependentLayers( const QString &cacheKey ) const
272{
273 auto it = mCachedImages.constFind( cacheKey );
274 if ( it != mCachedImages.constEnd() )
275 {
276 return _qgis_listQPointerToRaw( ( *it ).dependentLayers );
277 }
278 return QList< QgsMapLayer * >();
279}
280
281
282void QgsMapRendererCache::layerRequestedRepaint()
283{
284 QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
286}
287
289{
290 if ( !layer )
291 return;
292
293 QMutexLocker lock( &mMutex );
294
295 // check through all cached images to clear any which depend on this layer
296 QMap<QString, CacheParameters>::iterator it = mCachedImages.begin();
297 for ( ; it != mCachedImages.end(); )
298 {
299 if ( !it.value().dependentLayers.contains( layer ) )
300 {
301 ++it;
302 continue;
303 }
304
305 it = mCachedImages.erase( it );
306 }
307 dropUnusedConnections();
308}
309
310void QgsMapRendererCache::clearCacheImage( const QString &cacheKey )
311{
312 QMutexLocker lock( &mMutex );
313
314 mCachedImages.remove( cacheKey );
315 dropUnusedConnections();
316}
317
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.
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).
static QgsMapToPixel fromScale(double scale, QgsUnitTypes::DistanceUnit mapUnits, double dpi=96)
Returns a new QgsMapToPixel created using a specified scale and distance unit.
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:2527
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
Definition: qgsmaplayer.h:2147