QGIS API Documentation  3.21.0-Master (5b68dc587e)
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.h"
20 #include "qgsapplication.h"
21 
22 #include <QImage>
23 #include <QPainter>
24 #include <algorithm>
25 
27 {
28  clear();
29 }
30 
32 {
33  QMutexLocker lock( &mMutex );
34  clearInternal();
35 }
36 
37 void QgsMapRendererCache::clearInternal()
38 {
39  mExtent.setMinimal();
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 
55 void 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 
71 QSet<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 };
78  for ( const QgsWeakMapLayerPointer &l : dependentLayers )
79  {
80  if ( l.data() )
81  result << l;
82  }
83  }
84  return result;
85 }
86 
87 bool 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;
101  mMtp = QgsMapToPixel::fromScale( scale, QgsUnitTypes::DistanceUnit::DistanceUnknownUnit );
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 
124 void 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 
135 void 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 
175 bool 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 
192 bool 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 
213 QImage QgsMapRendererCache::cacheImage( const QString &cacheKey ) const
214 {
215  QMutexLocker lock( &mMutex );
216  return mCachedImages.value( cacheKey ).cachedImage;
217 }
218 
219 static 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 
226 QImage 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 
271 QList< 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 
282 void QgsMapRendererCache::layerRequestedRepaint()
283 {
284  QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
285  invalidateCacheForLayer( layer );
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 
310 void QgsMapRendererCache::clearCacheImage( const QString &cacheKey )
311 {
312  QMutexLocker lock( &mMutex );
313 
314  mCachedImages.remove( cacheKey );
315  dropUnusedConnections();
316 }
317 
Base class for all map layer types.
Definition: qgsmaplayer.h:73
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.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
Definition: qgsmaptopixel.h:90
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
static QgsMapToPixel fromScale(double scale, QgsUnitTypes::DistanceUnit mapUnits, double dpi=96)
Returns a new QgsMapToPixel created using a specified scale and distance unit.
void transformInPlace(double &x, double &y) const
Transforms device coordinates to map coordinates.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setMinimal() SIP_HOLDGIL
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:172
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:333
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1234
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
Definition: qgsmaplayer.h:2129