QGIS API Documentation  3.27.0-Master (0e23467727)
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 "qgsannotationlayer.h"
28 #include "qgsvectorlayer.h"
29 #include "qgslabelingresults.h"
30 
31 #include "qgsquickmapcanvasmap.h"
32 #include "qgsquickmapsettings.h"
33 
34 
36  : QQuickItem( parent )
37  , mMapSettings( std::make_unique<QgsQuickMapSettings>() )
38  , mCache( std::make_unique<QgsMapRendererCache>() )
39 {
40  connect( this, &QQuickItem::windowChanged, this, &QgsQuickMapCanvasMap::onWindowChanged );
41  connect( &mRefreshTimer, &QTimer::timeout, this, [ = ] { refreshMap(); } );
42  connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsQuickMapCanvasMap::renderJobUpdated );
43 
44  connect( mMapSettings.get(), &QgsQuickMapSettings::extentChanged, this, &QgsQuickMapCanvasMap::onExtentChanged );
45  connect( mMapSettings.get(), &QgsQuickMapSettings::layersChanged, this, &QgsQuickMapCanvasMap::onLayersChanged );
46 
49 
50  mMapUpdateTimer.setSingleShot( false );
51  mMapUpdateTimer.setInterval( 250 );
52  mRefreshTimer.setSingleShot( true );
53  setTransformOrigin( QQuickItem::TopLeft );
54  setFlags( QQuickItem::ItemHasContents );
55 }
56 
58 
60 {
61  return mMapSettings.get();
62 }
63 
64 void QgsQuickMapCanvasMap::zoom( QPointF center, qreal scale )
65 {
66  QgsRectangle extent = mMapSettings->extent();
67  QgsPoint oldCenter( extent.center() );
68  QgsPoint mousePos( mMapSettings->screenToCoordinate( center ) );
69 
70  QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * scale ),
71  mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * scale ) );
72 
73  // same as zoomWithCenter (no coordinate transformations are needed)
74  extent.scale( scale, &newCenter );
75  mMapSettings->setExtent( extent );
76 }
77 
78 void QgsQuickMapCanvasMap::pan( QPointF oldPos, QPointF newPos )
79 {
80  QgsPoint start = mMapSettings->screenToCoordinate( oldPos.toPoint() );
81  QgsPoint end = mMapSettings->screenToCoordinate( newPos.toPoint() );
82 
83  double dx = end.x() - start.x();
84  double dy = end.y() - start.y();
85 
86  // modify the extent
87  QgsRectangle extent = mMapSettings->extent();
88 
89  extent.setXMinimum( extent.xMinimum() + dx );
90  extent.setXMaximum( extent.xMaximum() + dx );
91  extent.setYMaximum( extent.yMaximum() + dy );
92  extent.setYMinimum( extent.yMinimum() + dy );
93 
94  mMapSettings->setExtent( extent );
95 }
96 
97 void QgsQuickMapCanvasMap::refreshMap()
98 {
99  stopRendering(); // if any...
100 
101  QgsMapSettings mapSettings = mMapSettings->mapSettings();
102  if ( !mapSettings.hasValidSettings() )
103  return;
104 
105  //build the expression context
106  QgsExpressionContext expressionContext;
107  expressionContext << QgsExpressionContextUtils::globalScope()
109 
110  QgsProject *project = mMapSettings->project();
111  if ( project )
112  {
113  expressionContext << QgsExpressionContextUtils::projectScope( project );
114 
115  mapSettings.setLabelingEngineSettings( project->labelingEngineSettings() );
116 
117  // render main annotation layer above all other layers
118  QList<QgsMapLayer *> allLayers = mapSettings.layers();
119  allLayers.insert( 0, project->mainAnnotationLayer() );
120  mapSettings.setLayers( allLayers );
121  }
122 
123  mapSettings.setExpressionContext( expressionContext );
124 
125  // enables on-the-fly simplification of geometries to spend less time rendering
127  // with incremental rendering - enables updates of partially rendered layers (good for WMTS, XYZ layers)
128  mapSettings.setFlag( Qgis::MapSettingsFlag::RenderPartialOutput, mIncrementalRendering );
129 
130  // create the renderer job
131  Q_ASSERT( !mJob );
133 
134  if ( mIncrementalRendering )
135  mMapUpdateTimer.start();
136 
137  connect( mJob, &QgsMapRendererJob::renderingLayersFinished, this, &QgsQuickMapCanvasMap::renderJobUpdated );
138  connect( mJob, &QgsMapRendererJob::finished, this, &QgsQuickMapCanvasMap::renderJobFinished );
139  mJob->setCache( mCache.get() );
140 
141  mJob->start();
142 
143  if ( !mSilentRefresh )
144  {
145  emit renderStarting();
146  }
147  else
148  {
149  mSilentRefresh = false;
150  }
151 }
152 
153 void QgsQuickMapCanvasMap::renderJobUpdated()
154 {
155  if ( !mJob )
156  return;
157 
158  mImage = mJob->renderedImage();
159  mImageMapSettings = mJob->mapSettings();
160  mDirty = true;
161  // Temporarily freeze the canvas, we only need to reset the geometry but not trigger a repaint
162  bool freeze = mFreeze;
163  mFreeze = true;
164  updateTransform();
165  mFreeze = freeze;
166 
167  update();
168  emit mapCanvasRefreshed();
169 }
170 
171 void QgsQuickMapCanvasMap::renderJobFinished()
172 {
173  if ( !mJob )
174  return;
175 
176  const QgsMapRendererJob::Errors errors = mJob->errors();
177  for ( const QgsMapRendererJob::Error &error : errors )
178  {
179  QgsMessageLog::logMessage( QStringLiteral( "%1 :: %2" ).arg( error.layerID, error.message ), tr( "Rendering" ) );
180  }
181 
182  // take labeling results before emitting renderComplete, so labeling map tools
183  // connected to signal work with correct results
184  delete mLabelingResults;
185  mLabelingResults = mJob->takeLabelingResults();
186 
187  mImage = mJob->renderedImage();
188  mImageMapSettings = mJob->mapSettings();
189 
190  // now we are in a slot called from mJob - do not delete it immediately
191  // so the class is still valid when the execution returns to the class
192  mJob->deleteLater();
193  mJob = nullptr;
194  mDirty = true;
195  mMapUpdateTimer.stop();
196 
197  // Temporarily freeze the canvas, we only need to reset the geometry but not trigger a repaint
198  bool freeze = mFreeze;
199  mFreeze = true;
200  updateTransform();
201  mFreeze = freeze;
202 
203  update();
204  emit mapCanvasRefreshed();
205 
206  if ( mDeferredRefreshPending )
207  {
208  mDeferredRefreshPending = false;
209  mSilentRefresh = true;
210  refresh();
211  }
212 }
213 
214 void QgsQuickMapCanvasMap::layerRepaintRequested( bool deferred )
215 {
216  if ( mMapSettings->outputSize().isNull() )
217  return; // the map image size has not been set yet
218 
219  if ( !mFreeze )
220  {
221  if ( deferred )
222  {
223  if ( !mJob )
224  {
225  mSilentRefresh = true;
226  refresh();
227  }
228  else
229  {
230  mDeferredRefreshPending = true;
231  }
232  }
233  else
234  {
235  refresh();
236  }
237  }
238 }
239 
240 void QgsQuickMapCanvasMap::onWindowChanged( QQuickWindow *window )
241 {
242  if ( mWindow == window )
243  return;
244 
245  if ( mWindow )
246  disconnect( mWindow, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged );
247 
248  if ( window )
249  {
250  connect( window, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged );
251  onScreenChanged( window->screen() );
252  }
253 
254  mWindow = window;
255 }
256 
257 void QgsQuickMapCanvasMap::onScreenChanged( QScreen *screen )
258 {
259  if ( screen )
260  {
261  if ( screen->devicePixelRatio() > 0 )
262  {
263  mMapSettings->setDevicePixelRatio( screen->devicePixelRatio() );
264  }
265  mMapSettings->setOutputDpi( screen->physicalDotsPerInch() );
266  }
267 }
268 
269 void QgsQuickMapCanvasMap::onExtentChanged()
270 {
271  updateTransform();
272 
273  // And trigger a new rendering job
274  refresh();
275 }
276 
277 void QgsQuickMapCanvasMap::updateTransform()
278 {
279  QgsRectangle imageExtent = mImageMapSettings.visibleExtent();
280  QgsRectangle newExtent = mMapSettings->mapSettings().visibleExtent();
281  setScale( imageExtent.width() / newExtent.width() );
282 
283  QgsPointXY pixelPt = mMapSettings->coordinateToScreen( QgsPoint( imageExtent.xMinimum(), imageExtent.yMaximum() ) );
284  setX( pixelPt.x() );
285  setY( pixelPt.y() );
286 }
287 
289 {
290  return mMapUpdateTimer.interval();
291 }
292 
293 void QgsQuickMapCanvasMap::setMapUpdateInterval( int mapUpdateInterval )
294 {
295  if ( mMapUpdateTimer.interval() == mapUpdateInterval )
296  return;
297 
298  mMapUpdateTimer.setInterval( mapUpdateInterval );
299 
301 }
302 
304 {
305  return mIncrementalRendering;
306 }
307 
308 void QgsQuickMapCanvasMap::setIncrementalRendering( bool incrementalRendering )
309 {
310  if ( incrementalRendering == mIncrementalRendering )
311  return;
312 
313  mIncrementalRendering = incrementalRendering;
315 }
316 
318 {
319  return mFreeze;
320 }
321 
323 {
324  if ( freeze == mFreeze )
325  return;
326 
327  mFreeze = freeze;
328 
329  if ( mFreeze )
330  stopRendering();
331  else
332  refresh();
333 
334  emit freezeChanged();
335 }
336 
338 {
339  return mJob;
340 }
341 
342 QSGNode *QgsQuickMapCanvasMap::updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * )
343 {
344  if ( mDirty )
345  {
346  delete oldNode;
347  oldNode = nullptr;
348  mDirty = false;
349  }
350 
351  QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>( oldNode );
352  if ( !node )
353  {
354  node = new QSGSimpleTextureNode();
355  QSGTexture *texture = window()->createTextureFromImage( mImage );
356  node->setTexture( texture );
357  node->setOwnsTexture( true );
358  }
359 
360  QRectF rect( boundingRect() );
361  QSizeF size = mImage.size();
362  if ( !size.isEmpty() )
363  size /= mMapSettings->devicePixelRatio();
364 
365  // Check for resizes that change the w/h ratio
366  if ( !rect.isEmpty() && !size.isEmpty() && !qgsDoubleNear( rect.width() / rect.height(), ( size.width() ) / static_cast<double>( size.height() ), 3 ) )
367  {
368  if ( qgsDoubleNear( rect.height(), mImage.height() ) )
369  {
370  rect.setHeight( rect.width() / size.width() * size.height() );
371  }
372  else
373  {
374  rect.setWidth( rect.height() / size.height() * size.width() );
375  }
376  }
377 
378  node->setRect( rect );
379 
380  return node;
381 }
382 
383 void QgsQuickMapCanvasMap::geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry )
384 {
385  QQuickItem::geometryChanged( newGeometry, oldGeometry );
386  if ( newGeometry.size() != oldGeometry.size() )
387  {
388  mMapSettings->setOutputSize( newGeometry.size().toSize() );
389  refresh();
390  }
391 }
392 
393 void QgsQuickMapCanvasMap::onLayersChanged()
394 {
395  if ( mMapSettings->extent().isEmpty() )
396  zoomToFullExtent();
397 
398  for ( const QMetaObject::Connection &conn : std::as_const( mLayerConnections ) )
399  {
400  disconnect( conn );
401  }
402  mLayerConnections.clear();
403 
404  const QList<QgsMapLayer *> layers = mMapSettings->layers();
405  for ( QgsMapLayer *layer : layers )
406  {
407  mLayerConnections << connect( layer, &QgsMapLayer::repaintRequested, this, &QgsQuickMapCanvasMap::layerRepaintRequested );
408  }
409 
410  refresh();
411 }
412 
413 void QgsQuickMapCanvasMap::destroyJob( QgsMapRendererJob *job )
414 {
415  job->cancel();
416  job->deleteLater();
417 }
418 
420 {
421  if ( mJob )
422  {
423  disconnect( mJob, &QgsMapRendererJob::renderingLayersFinished, this, &QgsQuickMapCanvasMap::renderJobUpdated );
424  disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsQuickMapCanvasMap::renderJobFinished );
425 
426  mJob->cancelWithoutBlocking();
427  mJob = nullptr;
428  }
429 }
430 
431 void QgsQuickMapCanvasMap::zoomToFullExtent()
432 {
433  QgsRectangle extent;
434  const QList<QgsMapLayer *> layers = mMapSettings->layers();
435  for ( QgsMapLayer *layer : layers )
436  {
437  if ( mMapSettings->destinationCrs() != layer->crs() )
438  {
439  QgsCoordinateTransform transform( layer->crs(), mMapSettings->destinationCrs(), mMapSettings->transformContext() );
440  try
441  {
442  extent.combineExtentWith( transform.transformBoundingBox( layer->extent() ) );
443  }
444  catch ( const QgsCsException &exp )
445  {
446  // Ignore extent if it can't be transformed
447  }
448  }
449  else
450  {
451  extent.combineExtentWith( layer->extent() );
452  }
453  }
454  mMapSettings->setExtent( extent );
455 
456  refresh();
457 }
458 
460 {
461  if ( mMapSettings->outputSize().isNull() )
462  return; // the map image size has not been set yet
463 
464  if ( !mFreeze )
465  mRefreshTimer.start( 1 );
466 }
467 
469 {
470  if ( mCache )
471  mCache->clear();
472 }
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
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:73
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.
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
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:104
QgsAnnotationLayer * mainAnnotationLayer()
Returns the main annotation layer associated with the project.
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 clearCache()
Clears rendering cache.
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.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
QList< QgsMapLayer * > layers
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:2260