QGIS API Documentation  3.37.0-Master (a5b4d9743e8)
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"
19 #include "qgsmaplayerlistutils_p.h"
20 
21 #include <QImage>
22 #include <QPainter>
23 #include <algorithm>
24 
26 {
27  clear();
28 }
29 
31 {
32  QMutexLocker lock( &mMutex );
33  clearInternal();
34 }
35 
36 void 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 
54 void 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 
70 QSet<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 };
77  for ( const QgsWeakMapLayerPointer &l : dependentLayers )
78  {
79  if ( l.data() )
80  result << l;
81  }
82  }
83  return result;
84 }
85 
86 bool 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 
123 void 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 
134 void 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 
174 bool 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 
191 bool 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 
212 QImage QgsMapRendererCache::cacheImage( const QString &cacheKey ) const
213 {
214  QMutexLocker lock( &mMutex );
215  return mCachedImages.value( cacheKey ).cachedImage;
216 }
217 
218 static 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 
225 QImage 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 
270 QList< 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 
281 void QgsMapRendererCache::layerRequestedRepaint()
282 {
283  QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
284  invalidateCacheForLayer( layer );
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 
309 void 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.
Definition: qgsmaptopixel.h:39
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.
Definition: qgsmaptopixel.h:88
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
Q_GADGET double x
Definition: qgspointxy.h:63
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
bool isNull() const
Test if the rectangle is null (holding no spatial information).
Definition: qgsrectangle.h:505
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:206
void setNull()
Mark a rectangle as being null (holding no spatial information).
Definition: qgsrectangle.h:176
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:355
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5172
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
Definition: qgsmaplayer.h:2349