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