QGIS API Documentation 3.37.0-Master (614ebf5f6b2)
Loading...
Searching...
No Matches
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
29
31{
32 QMutexLocker lock( &mMutex );
33 clearInternal();
34}
35
36void QgsMapRendererCache::clearInternal()
37{
38 mExtent.setNull();
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 };
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;
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
@ Unknown
Unknown distance unit.
Base class for all map layer types.
Definition qgsmaplayer.h:75
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.
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.
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:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
A rectangle specified with double values.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double xMaximum() const
Returns the x maximum value (right side of rectangle).
bool isNull() const
Test if the rectangle is null (holding no spatial information).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
void setNull()
Mark a rectangle as being null (holding no spatial information).
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5445
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.