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