QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 && qgsDoubleNear( scale, mScale ) )
95 return true;
96
97 clearInternal();
98
99 // set new params
100 mExtent = extent;
101 mScale = scale;
103
104 return false;
105}
106
108{
109 QMutexLocker lock( &mMutex );
110
111 // check whether the params are the same
112 if ( extent == mExtent && 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
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 && 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 && mtp.transform() == mMtp.transform() )
231 {
232 return params.cachedImage;
233 }
234 else
235 {
236 // no not use cache when the canvas rotation just changed
237 // https://github.com/qgis/QGIS/issues/41360
238 if ( !qgsDoubleNear( mtp.mapRotation(), params.cachedMtp.mapRotation() ) )
239 return QImage();
240
241 QgsRectangle intersection = mExtent.intersect( params.cachedExtent );
242 if ( intersection.isNull() )
243 return QImage();
244
245 // Calculate target rect
246 const QPointF ulT = _transform( mtp, QgsPointXY( intersection.xMinimum(), intersection.yMaximum() ), 1.0 );
247 const QPointF lrT = _transform( mtp, QgsPointXY( intersection.xMaximum(), intersection.yMinimum() ), 1.0 );
248 const QRectF targetRect( ulT.x(), ulT.y(), lrT.x() - ulT.x(), lrT.y() - ulT.y() );
249
250 // Calculate source rect
251 const QPointF ulS = _transform( params.cachedMtp, QgsPointXY( intersection.xMinimum(), intersection.yMaximum() ), params.cachedImage.devicePixelRatio() );
252 const QPointF lrS = _transform( params.cachedMtp, QgsPointXY( intersection.xMaximum(), intersection.yMinimum() ), params.cachedImage.devicePixelRatio() );
253 const QRectF sourceRect( ulS.x(), ulS.y(), lrS.x() - ulS.x(), lrS.y() - ulS.y() );
254
255 // Draw image
256 QImage ret( params.cachedImage.size(), params.cachedImage.format() );
257 ret.setDevicePixelRatio( params.cachedImage.devicePixelRatio() );
258 ret.setDotsPerMeterX( params.cachedImage.dotsPerMeterX() );
259 ret.setDotsPerMeterY( params.cachedImage.dotsPerMeterY() );
260 ret.fill( Qt::transparent );
261 QPainter painter;
262 painter.begin( &ret );
263 painter.drawImage( targetRect, params.cachedImage, sourceRect );
264 painter.end();
265 return ret;
266 }
267}
268
269QList< QgsMapLayer * > QgsMapRendererCache::dependentLayers( const QString &cacheKey ) const
270{
271 auto it = mCachedImages.constFind( cacheKey );
272 if ( it != mCachedImages.constEnd() )
273 {
274 return _qgis_listQPointerToRaw( ( *it ).dependentLayers );
275 }
276 return QList< QgsMapLayer * >();
277}
278
279
280void QgsMapRendererCache::layerRequestedRepaint()
281{
282 QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
284}
285
287{
288 if ( !layer )
289 return;
290
291 QMutexLocker lock( &mMutex );
292
293 // check through all cached images to clear any which depend on this layer
294 QMap<QString, CacheParameters>::iterator it = mCachedImages.begin();
295 for ( ; it != mCachedImages.end(); )
296 {
297 if ( !it.value().dependentLayers.contains( layer ) )
298 {
299 ++it;
300 continue;
301 }
302
303 it = mCachedImages.erase( it );
304 }
305 dropUnusedConnections();
306}
307
309{
310 QMutexLocker lock( &mMutex );
311
312 mCachedImages.remove( cacheKey );
313 dropUnusedConnections();
314}
@ Unknown
Unknown distance unit.
Definition qgis.h:5220
Base class for all map layer types.
Definition qgsmaplayer.h:83
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:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
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:6975
const QString cacheKey(const QString &pathIn)
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.