QGIS API Documentation  3.23.0-Master (dd0cd13a00)
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, &QgsQuickMapCanvasMap::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  emit renderStarting();
144 }
145 
146 void QgsQuickMapCanvasMap::renderJobUpdated()
147 {
148  if ( !mJob )
149  return;
150 
151  mImage = mJob->renderedImage();
152  mImageMapSettings = mJob->mapSettings();
153  mDirty = true;
154  // Temporarily freeze the canvas, we only need to reset the geometry but not trigger a repaint
155  bool freeze = mFreeze;
156  mFreeze = true;
157  updateTransform();
158  mFreeze = freeze;
159 
160  update();
161  emit mapCanvasRefreshed();
162 }
163 
164 void QgsQuickMapCanvasMap::renderJobFinished()
165 {
166  if ( !mJob )
167  return;
168 
169  const QgsMapRendererJob::Errors errors = mJob->errors();
170  for ( const QgsMapRendererJob::Error &error : errors )
171  {
172  QgsMessageLog::logMessage( QStringLiteral( "%1 :: %2" ).arg( error.layerID, error.message ), tr( "Rendering" ) );
173  }
174 
175  // take labeling results before emitting renderComplete, so labeling map tools
176  // connected to signal work with correct results
177  delete mLabelingResults;
178  mLabelingResults = mJob->takeLabelingResults();
179 
180  mImage = mJob->renderedImage();
181  mImageMapSettings = mJob->mapSettings();
182 
183  // now we are in a slot called from mJob - do not delete it immediately
184  // so the class is still valid when the execution returns to the class
185  mJob->deleteLater();
186  mJob = nullptr;
187  mDirty = true;
188  mMapUpdateTimer.stop();
189 
190  // Temporarily freeze the canvas, we only need to reset the geometry but not trigger a repaint
191  bool freeze = mFreeze;
192  mFreeze = true;
193  updateTransform();
194  mFreeze = freeze;
195 
196  update();
197  emit mapCanvasRefreshed();
198 }
199 
200 void QgsQuickMapCanvasMap::onWindowChanged( QQuickWindow *window )
201 {
202  if ( mWindow == window )
203  return;
204 
205  if ( mWindow )
206  disconnect( mWindow, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged );
207 
208  if ( window )
209  {
210  connect( window, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged );
211  onScreenChanged( window->screen() );
212  }
213 
214  mWindow = window;
215 }
216 
217 void QgsQuickMapCanvasMap::onScreenChanged( QScreen *screen )
218 {
219  if ( screen )
220  {
221  if ( screen->devicePixelRatio() > 0 )
222  {
223  mMapSettings->setDevicePixelRatio( screen->devicePixelRatio() );
224  }
225  mMapSettings->setOutputDpi( screen->physicalDotsPerInch() );
226  }
227 }
228 
229 void QgsQuickMapCanvasMap::onExtentChanged()
230 {
231  updateTransform();
232 
233  // And trigger a new rendering job
234  refresh();
235 }
236 
237 void QgsQuickMapCanvasMap::updateTransform()
238 {
239  QgsRectangle imageExtent = mImageMapSettings.visibleExtent();
240  QgsRectangle newExtent = mMapSettings->mapSettings().visibleExtent();
241  setScale( imageExtent.width() / newExtent.width() );
242 
243  QgsPointXY pixelPt = mMapSettings->coordinateToScreen( QgsPoint( imageExtent.xMinimum(), imageExtent.yMaximum() ) );
244  setX( pixelPt.x() );
245  setY( pixelPt.y() );
246 }
247 
249 {
250  return mMapUpdateTimer.interval();
251 }
252 
253 void QgsQuickMapCanvasMap::setMapUpdateInterval( int mapUpdateInterval )
254 {
255  if ( mMapUpdateTimer.interval() == mapUpdateInterval )
256  return;
257 
258  mMapUpdateTimer.setInterval( mapUpdateInterval );
259 
261 }
262 
264 {
265  return mIncrementalRendering;
266 }
267 
268 void QgsQuickMapCanvasMap::setIncrementalRendering( bool incrementalRendering )
269 {
270  if ( incrementalRendering == mIncrementalRendering )
271  return;
272 
273  mIncrementalRendering = incrementalRendering;
275 }
276 
278 {
279  return mFreeze;
280 }
281 
283 {
284  if ( freeze == mFreeze )
285  return;
286 
287  mFreeze = freeze;
288 
289  if ( mFreeze )
290  stopRendering();
291  else
292  refresh();
293 
294  emit freezeChanged();
295 }
296 
298 {
299  return mJob;
300 }
301 
302 QSGNode *QgsQuickMapCanvasMap::updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * )
303 {
304  if ( mDirty )
305  {
306  delete oldNode;
307  oldNode = nullptr;
308  mDirty = false;
309  }
310 
311  QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>( oldNode );
312  if ( !node )
313  {
314  node = new QSGSimpleTextureNode();
315  QSGTexture *texture = window()->createTextureFromImage( mImage );
316  node->setTexture( texture );
317  node->setOwnsTexture( true );
318  }
319 
320  QRectF rect( boundingRect() );
321  QSizeF size = mImage.size();
322  if ( !size.isEmpty() )
323  size /= mMapSettings->devicePixelRatio();
324 
325  // Check for resizes that change the w/h ratio
326  if ( !rect.isEmpty() && !size.isEmpty() && !qgsDoubleNear( rect.width() / rect.height(), ( size.width() ) / static_cast<double>( size.height() ), 3 ) )
327  {
328  if ( qgsDoubleNear( rect.height(), mImage.height() ) )
329  {
330  rect.setHeight( rect.width() / size.width() * size.height() );
331  }
332  else
333  {
334  rect.setWidth( rect.height() / size.height() * size.width() );
335  }
336  }
337 
338  node->setRect( rect );
339 
340  return node;
341 }
342 
343 void QgsQuickMapCanvasMap::geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry )
344 {
345  QQuickItem::geometryChanged( newGeometry, oldGeometry );
346  if ( newGeometry.size() != oldGeometry.size() )
347  {
348  mMapSettings->setOutputSize( newGeometry.size().toSize() );
349  refresh();
350  }
351 }
352 
353 void QgsQuickMapCanvasMap::onLayersChanged()
354 {
355  if ( mMapSettings->extent().isEmpty() )
356  zoomToFullExtent();
357 
358  for ( const QMetaObject::Connection &conn : std::as_const( mLayerConnections ) )
359  {
360  disconnect( conn );
361  }
362  mLayerConnections.clear();
363 
364  const QList<QgsMapLayer *> layers = mMapSettings->layers();
365  for ( QgsMapLayer *layer : layers )
366  {
367  mLayerConnections << connect( layer, &QgsMapLayer::repaintRequested, this, &QgsQuickMapCanvasMap::refresh );
368  }
369 
370  refresh();
371 }
372 
373 void QgsQuickMapCanvasMap::destroyJob( QgsMapRendererJob *job )
374 {
375  job->cancel();
376  job->deleteLater();
377 }
378 
380 {
381  if ( mJob )
382  {
383  disconnect( mJob, &QgsMapRendererJob::renderingLayersFinished, this, &QgsQuickMapCanvasMap::renderJobUpdated );
384  disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsQuickMapCanvasMap::renderJobFinished );
385 
386  mJob->cancelWithoutBlocking();
387  mJob = nullptr;
388  }
389 }
390 
391 void QgsQuickMapCanvasMap::zoomToFullExtent()
392 {
393  QgsRectangle extent;
394  const QList<QgsMapLayer *> layers = mMapSettings->layers();
395  for ( QgsMapLayer *layer : layers )
396  {
397  if ( mMapSettings->destinationCrs() != layer->crs() )
398  {
399  QgsCoordinateTransform transform( layer->crs(), mMapSettings->destinationCrs(), mMapSettings->transformContext() );
400  try
401  {
402  extent.combineExtentWith( transform.transformBoundingBox( layer->extent() ) );
403  }
404  catch ( const QgsCsException &exp )
405  {
406  // Ignore extent if it can't be transformed
407  }
408  }
409  else
410  {
411  extent.combineExtentWith( layer->extent() );
412  }
413  }
414  mMapSettings->setExtent( extent );
415 
416  refresh();
417 }
418 
420 {
421  if ( mMapSettings->outputSize().isNull() )
422  return; // the map image size has not been set yet
423 
424  if ( !mFreeze )
425  mRefreshTimer.start( 1 );
426 }
@ 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:101
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 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:1504