QGIS API Documentation 3.41.0-Master (45a0abf3bec)
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#include "moc_qgsmaprenderercache.cpp"
18
19#include "qgsmaplayer.h"
21
22#include <QImage>
23#include <QPainter>
24#include <algorithm>
25
30
32{
33 QMutexLocker lock( &mMutex );
34 clearInternal();
35}
36
37void QgsMapRendererCache::clearInternal()
38{
39 mExtent.setNull();
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 };
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;
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
@ Unknown
Unknown distance unit.
Base class for all map layer types.
Definition qgsmaplayer.h:76
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 map coordinates to device 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:5958
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.