QGIS API Documentation  3.20.0-Odense (decaadbb31)
qgsquickmapcanvasmap.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsquickmapcanvasmap.cpp
3  --------------------------------------
4  Date : 10.12.2014
5  Copyright : (C) 2014 by Matthias Kuhn
6  Email : matthias (at) opengis.ch
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 <QQuickWindow>
17 #include <QSGSimpleTextureNode>
18 #include <QScreen>
19 
20 #include "qgis.h"
22 #include "qgsmaprenderercache.h"
24 #include "qgsmessagelog.h"
25 #include "qgspallabeling.h"
26 #include "qgsproject.h"
27 #include "qgsvectorlayer.h"
28 #include "qgslabelingresults.h"
29 
30 #include "qgsquickmapcanvasmap.h"
31 #include "qgsquickmapsettings.h"
32 
33 
35  : QQuickItem( parent )
36  , mMapSettings( std::make_unique<QgsQuickMapSettings>() )
37  , mCache( std::make_unique<QgsMapRendererCache>() )
38 {
39  connect( this, &QQuickItem::windowChanged, this, &QgsQuickMapCanvasMap::onWindowChanged );
40  connect( &mRefreshTimer, &QTimer::timeout, this, &QgsQuickMapCanvasMap::refreshMap );
41  connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsQuickMapCanvasMap::renderJobUpdated );
42 
43  connect( mMapSettings.get(), &QgsQuickMapSettings::extentChanged, this, &QgsQuickMapCanvasMap::onExtentChanged );
44  connect( mMapSettings.get(), &QgsQuickMapSettings::layersChanged, this, &QgsQuickMapCanvasMap::onLayersChanged );
45 
48 
49  mMapUpdateTimer.setSingleShot( false );
50  mMapUpdateTimer.setInterval( 250 );
51  mRefreshTimer.setSingleShot( true );
52  setTransformOrigin( QQuickItem::TopLeft );
53  setFlags( QQuickItem::ItemHasContents );
54 }
55 
57 
59 {
60  return mMapSettings.get();
61 }
62 
63 void QgsQuickMapCanvasMap::zoom( QPointF center, qreal scale )
64 {
65  QgsRectangle extent = mMapSettings->extent();
66  QgsPoint oldCenter( extent.center() );
67  QgsPoint mousePos( mMapSettings->screenToCoordinate( center ) );
68 
69  QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * scale ),
70  mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * scale ) );
71 
72  // same as zoomWithCenter (no coordinate transformations are needed)
73  extent.scale( scale, &newCenter );
74  mMapSettings->setExtent( extent );
75 }
76 
77 void QgsQuickMapCanvasMap::pan( QPointF oldPos, QPointF newPos )
78 {
79  QgsPoint start = mMapSettings->screenToCoordinate( oldPos.toPoint() );
80  QgsPoint end = mMapSettings->screenToCoordinate( newPos.toPoint() );
81 
82  double dx = end.x() - start.x();
83  double dy = end.y() - start.y();
84 
85  // modify the extent
86  QgsRectangle extent = mMapSettings->extent();
87 
88  extent.setXMinimum( extent.xMinimum() + dx );
89  extent.setXMaximum( extent.xMaximum() + dx );
90  extent.setYMaximum( extent.yMaximum() + dy );
91  extent.setYMinimum( extent.yMinimum() + dy );
92 
93  mMapSettings->setExtent( extent );
94 }
95 
96 void QgsQuickMapCanvasMap::refreshMap()
97 {
98  stopRendering(); // if any...
99 
100  QgsMapSettings mapSettings = mMapSettings->mapSettings();
101 
102  //build the expression context
103  QgsExpressionContext expressionContext;
104  expressionContext << QgsExpressionContextUtils::globalScope()
106 
107  QgsProject *project = mMapSettings->project();
108  if ( project )
109  {
110  expressionContext << QgsExpressionContextUtils::projectScope( project );
111 
112  mapSettings.setLabelingEngineSettings( project->labelingEngineSettings() );
113  }
114 
115  mapSettings.setExpressionContext( expressionContext );
116 
117  // enables on-the-fly simplification of geometries to spend less time rendering
119  // with incremental rendering - enables updates of partially rendered layers (good for WMTS, XYZ layers)
120  mapSettings.setFlag( QgsMapSettings::RenderPartialOutput, mIncrementalRendering );
121 
122  // create the renderer job
123  Q_ASSERT( !mJob );
125 
126  if ( mIncrementalRendering )
127  mMapUpdateTimer.start();
128 
129  connect( mJob, &QgsMapRendererJob::renderingLayersFinished, this, &QgsQuickMapCanvasMap::renderJobUpdated );
130  connect( mJob, &QgsMapRendererJob::finished, this, &QgsQuickMapCanvasMap::renderJobFinished );
131  mJob->setCache( mCache.get() );
132 
133  mJob->start();
134 
135  emit renderStarting();
136 }
137 
138 void QgsQuickMapCanvasMap::renderJobUpdated()
139 {
140  if ( !mJob )
141  return;
142 
143  mImage = mJob->renderedImage();
144  mImageMapSettings = mJob->mapSettings();
145  mDirty = true;
146  // Temporarily freeze the canvas, we only need to reset the geometry but not trigger a repaint
147  bool freeze = mFreeze;
148  mFreeze = true;
149  updateTransform();
150  mFreeze = freeze;
151 
152  update();
153  emit mapCanvasRefreshed();
154 }
155 
156 void QgsQuickMapCanvasMap::renderJobFinished()
157 {
158  if ( !mJob )
159  return;
160 
161  const QgsMapRendererJob::Errors errors = mJob->errors();
162  for ( const QgsMapRendererJob::Error &error : errors )
163  {
164  QgsMessageLog::logMessage( QStringLiteral( "%1 :: %2" ).arg( error.layerID, error.message ), tr( "Rendering" ) );
165  }
166 
167  // take labeling results before emitting renderComplete, so labeling map tools
168  // connected to signal work with correct results
169  delete mLabelingResults;
170  mLabelingResults = mJob->takeLabelingResults();
171 
172  mImage = mJob->renderedImage();
173  mImageMapSettings = mJob->mapSettings();
174 
175  // now we are in a slot called from mJob - do not delete it immediately
176  // so the class is still valid when the execution returns to the class
177  mJob->deleteLater();
178  mJob = nullptr;
179  mDirty = true;
180  mMapUpdateTimer.stop();
181 
182  // Temporarily freeze the canvas, we only need to reset the geometry but not trigger a repaint
183  bool freeze = mFreeze;
184  mFreeze = true;
185  updateTransform();
186  mFreeze = freeze;
187 
188  update();
189  emit mapCanvasRefreshed();
190 }
191 
192 void QgsQuickMapCanvasMap::onWindowChanged( QQuickWindow *window )
193 {
194  if ( mWindow == window )
195  return;
196 
197  if ( mWindow )
198  disconnect( mWindow, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged );
199 
200  if ( window )
201  {
202  connect( window, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged );
203  onScreenChanged( window->screen() );
204  }
205 
206  mWindow = window;
207 }
208 
209 void QgsQuickMapCanvasMap::onScreenChanged( QScreen *screen )
210 {
211  if ( screen )
212  {
213  if ( screen->devicePixelRatio() > 0 )
214  {
215  mMapSettings->setDevicePixelRatio( screen->devicePixelRatio() );
216  }
217  mMapSettings->setOutputDpi( screen->physicalDotsPerInch() );
218  }
219 }
220 
221 void QgsQuickMapCanvasMap::onExtentChanged()
222 {
223  updateTransform();
224 
225  // And trigger a new rendering job
226  refresh();
227 }
228 
229 void QgsQuickMapCanvasMap::updateTransform()
230 {
231  QgsRectangle imageExtent = mImageMapSettings.visibleExtent();
232  QgsRectangle newExtent = mMapSettings->mapSettings().visibleExtent();
233  setScale( imageExtent.width() / newExtent.width() );
234 
235  QgsPointXY pixelPt = mMapSettings->coordinateToScreen( QgsPoint( imageExtent.xMinimum(), imageExtent.yMaximum() ) );
236  setX( pixelPt.x() );
237  setY( pixelPt.y() );
238 }
239 
241 {
242  return mMapUpdateTimer.interval();
243 }
244 
245 void QgsQuickMapCanvasMap::setMapUpdateInterval( int mapUpdateInterval )
246 {
247  if ( mMapUpdateTimer.interval() == mapUpdateInterval )
248  return;
249 
250  mMapUpdateTimer.setInterval( mapUpdateInterval );
251 
253 }
254 
256 {
257  return mIncrementalRendering;
258 }
259 
260 void QgsQuickMapCanvasMap::setIncrementalRendering( bool incrementalRendering )
261 {
262  if ( incrementalRendering == mIncrementalRendering )
263  return;
264 
265  mIncrementalRendering = incrementalRendering;
267 }
268 
270 {
271  return mFreeze;
272 }
273 
275 {
276  if ( freeze == mFreeze )
277  return;
278 
279  mFreeze = freeze;
280 
281  if ( mFreeze )
282  stopRendering();
283  else
284  refresh();
285 
286  emit freezeChanged();
287 }
288 
290 {
291  return mJob;
292 }
293 
294 QSGNode *QgsQuickMapCanvasMap::updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * )
295 {
296  if ( mDirty )
297  {
298  delete oldNode;
299  oldNode = nullptr;
300  mDirty = false;
301  }
302 
303  QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>( oldNode );
304  if ( !node )
305  {
306  node = new QSGSimpleTextureNode();
307  QSGTexture *texture = window()->createTextureFromImage( mImage );
308  node->setTexture( texture );
309  node->setOwnsTexture( true );
310  }
311 
312  QRectF rect( boundingRect() );
313  QSizeF size = mImage.size();
314  if ( !size.isEmpty() )
315  size /= mMapSettings->devicePixelRatio();
316 
317  // Check for resizes that change the w/h ratio
318  if ( !rect.isEmpty() && !size.isEmpty() && !qgsDoubleNear( rect.width() / rect.height(), ( size.width() ) / static_cast<double>( size.height() ), 3 ) )
319  {
320  if ( qgsDoubleNear( rect.height(), mImage.height() ) )
321  {
322  rect.setHeight( rect.width() / size.width() * size.height() );
323  }
324  else
325  {
326  rect.setWidth( rect.height() / size.height() * size.width() );
327  }
328  }
329 
330  node->setRect( rect );
331 
332  return node;
333 }
334 
335 void QgsQuickMapCanvasMap::geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry )
336 {
337  QQuickItem::geometryChanged( newGeometry, oldGeometry );
338  if ( newGeometry.size() != oldGeometry.size() )
339  {
340  mMapSettings->setOutputSize( newGeometry.size().toSize() );
341  refresh();
342  }
343 }
344 
345 void QgsQuickMapCanvasMap::onLayersChanged()
346 {
347  if ( mMapSettings->extent().isEmpty() )
348  zoomToFullExtent();
349 
350  for ( const QMetaObject::Connection &conn : std::as_const( mLayerConnections ) )
351  {
352  disconnect( conn );
353  }
354  mLayerConnections.clear();
355 
356  const QList<QgsMapLayer *> layers = mMapSettings->layers();
357  for ( QgsMapLayer *layer : layers )
358  {
359  mLayerConnections << connect( layer, &QgsMapLayer::repaintRequested, this, &QgsQuickMapCanvasMap::refresh );
360  }
361 
362  refresh();
363 }
364 
365 void QgsQuickMapCanvasMap::destroyJob( QgsMapRendererJob *job )
366 {
367  job->cancel();
368  job->deleteLater();
369 }
370 
372 {
373  if ( mJob )
374  {
375  disconnect( mJob, &QgsMapRendererJob::renderingLayersFinished, this, &QgsQuickMapCanvasMap::renderJobUpdated );
376  disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsQuickMapCanvasMap::renderJobFinished );
377 
378  mJob->cancelWithoutBlocking();
379  mJob = nullptr;
380  }
381 }
382 
383 void QgsQuickMapCanvasMap::zoomToFullExtent()
384 {
385  QgsRectangle extent;
386  const QList<QgsMapLayer *> layers = mMapSettings->layers();
387  for ( QgsMapLayer *layer : layers )
388  {
389  if ( mMapSettings->destinationCrs() != layer->crs() )
390  {
391  QgsCoordinateTransform transform( layer->crs(), mMapSettings->destinationCrs(), mMapSettings->transformContext() );
392  try
393  {
394  extent.combineExtentWith( transform.transformBoundingBox( layer->extent() ) );
395  }
396  catch ( const QgsCsException &exp )
397  {
398  // Ignore extent if it can't be transformed
399  }
400  }
401  else
402  {
403  extent.combineExtentWith( layer->extent() );
404  }
405  }
406  mMapSettings->setExtent( extent );
407 
408  refresh();
409 }
410 
412 {
413  if ( mMapSettings->outputSize().isNull() )
414  return; // the map image size has not been set yet
415 
416  if ( !mFreeze )
417  mRefreshTimer.start( 1 );
418 }
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Base class for all map layer types.
Definition: qgsmaplayer.h:70
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
This class is responsible for keeping cache of rendered images resulting from a map rendering job.
Abstract base class for map rendering implementations.
void setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
void renderingLayersFinished()
Emitted when the layers are rendered.
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
void finished()
emitted when asynchronous rendering is finished (or canceled).
void start()
Start the rendering job and immediately return.
QList< QgsMapRendererJob::Error > Errors
virtual void cancel()=0
Stop the rendering job - does not return until the job has terminated.
Job implementation that renders all layers in parallel.
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results).
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
QImage renderedImage() override
Gets a preview/resulting image.
The QgsMapSettings class contains configuration for rendering of the map.
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes takes output image size into accou...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
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
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Q_GADGET double x
Definition: qgspoint.h:52
double y
Definition: qgspoint.h:53
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:99
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns project's global labeling engine settings.
void freezeChanged()
When freeze property is set to true, the map canvas does not refresh.
bool isRendering
The isRendering property is set to true while a rendering job is pending for this map canvas map.
void mapCanvasRefreshed()
Signal is emitted when a canvas is refreshed.
void incrementalRenderingChanged()
When the incrementalRendering property is set to true, the automatic refresh of map canvas during ren...
int mapUpdateInterval
Interval in milliseconds after which the map canvas will be updated while a rendering job is ongoing.
void setMapUpdateInterval(int mapUpdateInterval)
Interval in milliseconds after which the map canvas will be updated while a rendering job is ongoing.
void pan(QPointF oldPos, QPointF newPos)
Set map setting's extent (pan the map) based on the difference of positions.
void renderStarting()
Signal is emitted when a rendering is starting.
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override
void stopRendering()
Stop map rendering.
void zoom(QPointF center, qreal scale)
Set map setting's extent (zoom the map) on the center by given scale.
void setIncrementalRendering(bool incrementalRendering)
When the incrementalRendering property is set to true, the automatic refresh of map canvas during ren...
void setFreeze(bool freeze)
When freeze property is set to true, the map canvas does not refresh.
QgsQuickMapSettings * mapSettings
The mapSettings property contains configuration for rendering of the map.
void refresh()
Refresh the map canvas.
QSGNode * updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) override
void mapUpdateIntervalChanged()
Interval in milliseconds after which the map canvas will be updated while a rendering job is ongoing.
bool incrementalRendering
When the incrementalRendering property is set to true, the automatic refresh of map canvas during ren...
bool freeze
When freeze property is set to true, the map canvas does not refresh.
QgsQuickMapCanvasMap(QQuickItem *parent=nullptr)
Create map canvas map.
void isRenderingChanged()
The isRendering property is set to true while a rendering job is pending for this map canvas map.
The QgsQuickMapSettings class encapsulates QgsMapSettings class to offer settings of configuration of...
void extentChanged()
Geographical coordinates of the rectangle that should be rendered.
void layersChanged()
Set list of layers for map rendering.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:256
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
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598