QGIS API Documentation  3.2.0-Bonn (bc43194)
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"
30 
31 
33  : QQuickItem( parent )
34  , mMapSettings( new QgsQuickMapSettings() )
35 {
36  connect( this, &QQuickItem::windowChanged, this, &QgsQuickMapCanvasMap::onWindowChanged );
37  connect( &mRefreshTimer, &QTimer::timeout, this, &QgsQuickMapCanvasMap::refreshMap );
38  connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsQuickMapCanvasMap::renderJobUpdated );
39 
40  connect( mMapSettings.get(), &QgsQuickMapSettings::extentChanged, this, &QgsQuickMapCanvasMap::onExtentChanged );
41  connect( mMapSettings.get(), &QgsQuickMapSettings::layersChanged, this, &QgsQuickMapCanvasMap::onLayersChanged );
42 
45 
46  mMapUpdateTimer.setSingleShot( false );
47  mMapUpdateTimer.setInterval( 250 );
48  mRefreshTimer.setSingleShot( true );
49  setTransformOrigin( QQuickItem::TopLeft );
50  setFlags( QQuickItem::ItemHasContents );
51 }
52 
54 {
55  return mMapSettings.get();
56 }
57 
58 void QgsQuickMapCanvasMap::zoom( QPointF center, qreal scale )
59 {
60  QgsRectangle extent = mMapSettings->extent();
61  QgsPoint oldCenter( extent.center() );
62  QgsPoint mousePos( mMapSettings->screenToCoordinate( center ) );
63  QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * scale ),
64  mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * scale ) );
65 
66  // same as zoomWithCenter (no coordinate transformations are needed)
67  extent.scale( scale, &newCenter );
68  mMapSettings->setExtent( extent );
69 }
70 
71 void QgsQuickMapCanvasMap::pan( QPointF oldPos, QPointF newPos )
72 {
73  QgsPoint start = mMapSettings->screenToCoordinate( oldPos.toPoint() );
74  QgsPoint end = mMapSettings->screenToCoordinate( newPos.toPoint() );
75 
76  double dx = end.x() - start.x();
77  double dy = end.y() - start.y();
78 
79  // modify the extent
80  QgsRectangle extent = mMapSettings->extent();
81 
82  extent.setXMinimum( extent.xMinimum() + dx );
83  extent.setXMaximum( extent.xMaximum() + dx );
84  extent.setYMaximum( extent.yMaximum() + dy );
85  extent.setYMinimum( extent.yMinimum() + dy );
86 
87  mMapSettings->setExtent( extent );
88 }
89 
90 void QgsQuickMapCanvasMap::refreshMap()
91 {
92  stopRendering(); // if any...
93 
94  QgsMapSettings mapSettings = mMapSettings->mapSettings();
95 
96  //build the expression context
97  QgsExpressionContext expressionContext;
98  expressionContext << QgsExpressionContextUtils::globalScope()
100 
101  QgsProject *project = mMapSettings->project();
102  if ( project )
103  {
104  expressionContext << QgsExpressionContextUtils::projectScope( project );
105  }
106 
107  mapSettings.setExpressionContext( expressionContext );
108 
109  // create the renderer job
110  Q_ASSERT( !mJob );
111  mJob = new QgsMapRendererParallelJob( mapSettings );
112 
113  if ( mIncrementalRendering )
114  mMapUpdateTimer.start();
115 
116  connect( mJob, &QgsMapRendererJob::renderingLayersFinished, this, &QgsQuickMapCanvasMap::renderJobUpdated );
117  connect( mJob, &QgsMapRendererJob::finished, this, &QgsQuickMapCanvasMap::renderJobFinished );
118  mJob->setCache( mCache );
119 
120  mJob->start();
121 
122  emit renderStarting();
123 }
124 
125 void QgsQuickMapCanvasMap::renderJobUpdated()
126 {
127  mImage = mJob->renderedImage();
128  mImageMapSettings = mJob->mapSettings();
129  mDirty = true;
130  // Temporarily freeze the canvas, we only need to reset the geometry but not trigger a repaint
131  bool freeze = mFreeze;
132  mFreeze = true;
133  updateTransform();
134  mFreeze = freeze;
135 
136  update();
137  emit mapCanvasRefreshed();
138 }
139 
140 void QgsQuickMapCanvasMap::renderJobFinished()
141 {
142  const QgsMapRendererJob::Errors errors = mJob->errors();
143  for ( const QgsMapRendererJob::Error &error : errors )
144  {
145  QgsMessageLog::logMessage( QStringLiteral( "%1 :: %2" ).arg( error.layerID, error.message ), tr( "Rendering" ) );
146  }
147 
148  // take labeling results before emitting renderComplete, so labeling map tools
149  // connected to signal work with correct results
150  delete mLabelingResults;
151  mLabelingResults = mJob->takeLabelingResults();
152 
153  mImage = mJob->renderedImage();
154  mImageMapSettings = mJob->mapSettings();
155 
156  // now we are in a slot called from mJob - do not delete it immediately
157  // so the class is still valid when the execution returns to the class
158  mJob->deleteLater();
159  mJob = nullptr;
160  mDirty = true;
161  mMapUpdateTimer.stop();
162 
163  // Temporarily freeze the canvas, we only need to reset the geometry but not trigger a repaint
164  bool freeze = mFreeze;
165  mFreeze = true;
166  updateTransform();
167  mFreeze = freeze;
168 
169  update();
170  emit mapCanvasRefreshed();
171 }
172 
173 void QgsQuickMapCanvasMap::onWindowChanged( QQuickWindow *window )
174 {
175  disconnect( window, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged );
176  if ( window )
177  {
178  connect( window, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged );
179  onScreenChanged( window->screen() );
180  }
181 }
182 
183 void QgsQuickMapCanvasMap::onScreenChanged( QScreen *screen )
184 {
185  if ( screen )
186  mMapSettings->setOutputDpi( screen->physicalDotsPerInch() );
187 }
188 
189 void QgsQuickMapCanvasMap::onExtentChanged()
190 {
191  updateTransform();
192 
193  // And trigger a new rendering job
194  refresh();
195 }
196 
197 void QgsQuickMapCanvasMap::updateTransform()
198 {
199  QgsMapSettings currentMapSettings = mMapSettings->mapSettings();
200  QgsMapToPixel mtp = currentMapSettings.mapToPixel();
201 
202  QgsRectangle imageExtent = mImageMapSettings.visibleExtent();
203  QgsRectangle newExtent = currentMapSettings.visibleExtent();
204  QgsPointXY pixelPt = mtp.transform( imageExtent.xMinimum(), imageExtent.yMaximum() );
205  setScale( imageExtent.width() / newExtent.width() );
206 
207  setX( pixelPt.x() );
208  setY( pixelPt.y() );
209 }
210 
212 {
213  return mMapUpdateTimer.interval();
214 }
215 
217 {
218  if ( mMapUpdateTimer.interval() == mapUpdateInterval )
219  return;
220 
221  mMapUpdateTimer.setInterval( mapUpdateInterval );
222 
224 }
225 
227 {
228  return mIncrementalRendering;
229 }
230 
232 {
233  if ( incrementalRendering == mIncrementalRendering )
234  return;
235 
236  mIncrementalRendering = incrementalRendering;
238 }
239 
240 bool QgsQuickMapCanvasMap::freeze() const
241 {
242  return mFreeze;
243 }
244 
246 {
247  if ( freeze == mFreeze )
248  return;
249 
250  mFreeze = freeze;
251 
252  if ( !mFreeze )
253  refresh();
254 
255  emit freezeChanged();
256 }
257 
259 {
260  return mJob;
261 }
262 
263 QSGNode *QgsQuickMapCanvasMap::updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * )
264 {
265  if ( mDirty )
266  {
267  delete oldNode;
268  oldNode = nullptr;
269  mDirty = false;
270  }
271 
272  QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>( oldNode );
273  if ( !node )
274  {
275  node = new QSGSimpleTextureNode();
276  QSGTexture *texture = window()->createTextureFromImage( mImage );
277  node->setTexture( texture );
278  node->setOwnsTexture( true );
279  }
280 
281  QRectF rect( boundingRect() );
282 
283  // Check for resizes that change the w/h ratio
284  if ( !rect.isEmpty() &&
285  !mImage.size().isEmpty() &&
286  !qgsDoubleNear( rect.width() / rect.height(), mImage.width() / mImage.height() ) )
287  {
288  if ( qgsDoubleNear( rect.height(), mImage.height() ) )
289  {
290  rect.setHeight( rect.width() / mImage.width() * mImage.height() );
291  }
292  else
293  {
294  rect.setWidth( rect.height() / mImage.height() * mImage.width() );
295  }
296  }
297 
298  node->setRect( rect );
299 
300  return node;
301 }
302 
303 void QgsQuickMapCanvasMap::geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry )
304 {
305  Q_UNUSED( oldGeometry )
306  // The Qt documentation advices to call the base method here.
307  // However, this introduces instabilities and heavy performance impacts on Android.
308  // It seems on desktop disabling it prevents us from downsizing the window...
309  // Be careful when re-enabling it.
310  // QQuickItem::geometryChanged( newGeometry, oldGeometry );
311 
312  mMapSettings->setOutputSize( newGeometry.size().toSize() );
313  refresh();
314 }
315 
316 void QgsQuickMapCanvasMap::onLayersChanged()
317 {
318  if ( mMapSettings->extent().isEmpty() )
319  zoomToFullExtent();
320 
321  for ( const QMetaObject::Connection &conn : qgis::as_const( mLayerConnections ) )
322  {
323  disconnect( conn );
324  }
325  mLayerConnections.clear();
326 
327  const QList<QgsMapLayer *> layers = mMapSettings->layers();
328  for ( QgsMapLayer *layer : layers )
329  {
330  mLayerConnections << connect( layer, &QgsMapLayer::repaintRequested, this, &QgsQuickMapCanvasMap::refresh );
331  }
332 
333  refresh();
334 }
335 
336 void QgsQuickMapCanvasMap::destroyJob( QgsMapRendererJob *job )
337 {
338  job->cancel();
339  job->deleteLater();
340 }
341 
343 {
344  if ( mJob )
345  {
346  disconnect( mJob, &QgsMapRendererJob::renderingLayersFinished, this, &QgsQuickMapCanvasMap::renderJobUpdated );
347  disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsQuickMapCanvasMap::renderJobFinished );
348 
349  mJob->cancelWithoutBlocking();
350  mJob = nullptr;
351  }
352 }
353 
354 void QgsQuickMapCanvasMap::zoomToFullExtent()
355 {
356  QgsRectangle extent;
357  const QList<QgsMapLayer *> layers = mMapSettings->layers();
358  for ( QgsMapLayer *layer : layers )
359  {
360  if ( mMapSettings->destinationCrs() != layer->crs() )
361  {
362  QgsCoordinateTransform transform( layer->crs(), mMapSettings->destinationCrs(), mMapSettings->transformContext() );
363  extent.combineExtentWith( transform.transformBoundingBox( layer->extent() ) );
364  }
365  else
366  {
367  extent.combineExtentWith( layer->extent() );
368  }
369  }
370  mMapSettings->setExtent( extent );
371 
372  refresh();
373 }
374 
376 {
377  if ( mMapSettings->outputSize().isNull() )
378  return; // the map image size has not been set yet
379 
380  if ( !mFreeze )
381  mRefreshTimer.start( 1 );
382 }
void finished()
emitted when asynchronous rendering is finished (or canceled).
A rectangle specified with double values.
Definition: qgsrectangle.h:40
Base class for all map layer types.
Definition: qgsmaplayer.h:61
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:134
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:234
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:251
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.
The QgsMapSettings class contains configuration for rendering of the map.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:36
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:201
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:139
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:85
void renderStarting()
Signal is 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()
Signal is emitted when a 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:176
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:161
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:352
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.
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:144
void cancelWithoutBlocking() override
Triggers cancelation 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:166
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:171
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:229
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:129
void layersChanged()
Set list of layers for map rendering.
QImage renderedImage() override
Gets a preview/resulting image.
double x
Definition: qgspoint.h:41
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results). ...