QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 );
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  // FIXME? the above disconnect is done potentially on a nullptr
186  // cppcheck-suppress nullPointerRedundantCheck
187  disconnect( window, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged );
188  if ( window )
189  {
190  connect( window, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged );
191  onScreenChanged( window->screen() );
192  }
193 }
194 
195 void QgsQuickMapCanvasMap::onScreenChanged( QScreen *screen )
196 {
197  if ( screen )
198  mMapSettings->setOutputDpi( screen->physicalDotsPerInch() );
199 }
200 
201 void QgsQuickMapCanvasMap::onExtentChanged()
202 {
203  updateTransform();
204 
205  // And trigger a new rendering job
206  refresh();
207 }
208 
209 void QgsQuickMapCanvasMap::updateTransform()
210 {
211  QgsMapSettings currentMapSettings = mMapSettings->mapSettings();
212  QgsMapToPixel mtp = currentMapSettings.mapToPixel();
213 
214  QgsRectangle imageExtent = mImageMapSettings.visibleExtent();
215  QgsRectangle newExtent = currentMapSettings.visibleExtent();
216  QgsPointXY pixelPt = mtp.transform( imageExtent.xMinimum(), imageExtent.yMaximum() );
217  setScale( imageExtent.width() / newExtent.width() );
218 
219  setX( pixelPt.x() );
220  setY( pixelPt.y() );
221 }
222 
224 {
225  return mMapUpdateTimer.interval();
226 }
227 
228 void QgsQuickMapCanvasMap::setMapUpdateInterval( int mapUpdateInterval )
229 {
230  if ( mMapUpdateTimer.interval() == mapUpdateInterval )
231  return;
232 
233  mMapUpdateTimer.setInterval( mapUpdateInterval );
234 
236 }
237 
239 {
240  return mIncrementalRendering;
241 }
242 
243 void QgsQuickMapCanvasMap::setIncrementalRendering( bool incrementalRendering )
244 {
245  if ( incrementalRendering == mIncrementalRendering )
246  return;
247 
248  mIncrementalRendering = incrementalRendering;
250 }
251 
253 {
254  return mFreeze;
255 }
256 
258 {
259  if ( freeze == mFreeze )
260  return;
261 
262  mFreeze = freeze;
263 
264  if ( !mFreeze && mNeedsRefresh )
265  {
266  refresh();
267  }
268 
269  // we are freezing or unfreezing - either way we can reset "needs refresh"
270  mNeedsRefresh = false;
271 
272  emit freezeChanged();
273 }
274 
276 {
277  return mJob;
278 }
279 
280 QSGNode *QgsQuickMapCanvasMap::updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * )
281 {
282  if ( mDirty )
283  {
284  delete oldNode;
285  oldNode = nullptr;
286  mDirty = false;
287  }
288 
289  QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>( oldNode );
290  if ( !node )
291  {
292  node = new QSGSimpleTextureNode();
293  QSGTexture *texture = window()->createTextureFromImage( mImage );
294  node->setTexture( texture );
295  node->setOwnsTexture( true );
296  }
297 
298  QRectF rect( boundingRect() );
299 
300  // Check for resizes that change the w/h ratio
301  if ( !rect.isEmpty() &&
302  !mImage.size().isEmpty() &&
303  !qgsDoubleNear( rect.width() / rect.height(), mImage.width() / mImage.height() ) )
304  {
305  if ( qgsDoubleNear( rect.height(), mImage.height() ) )
306  {
307  rect.setHeight( rect.width() / mImage.width() * mImage.height() );
308  }
309  else
310  {
311  rect.setWidth( rect.height() / mImage.height() * mImage.width() );
312  }
313  }
314 
315  node->setRect( rect );
316 
317  return node;
318 }
319 
320 void QgsQuickMapCanvasMap::geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry )
321 {
322  Q_UNUSED( oldGeometry )
323  // The Qt documentation advices to call the base method here.
324  // However, this introduces instabilities and heavy performance impacts on Android.
325  // It seems on desktop disabling it prevents us from downsizing the window...
326  // Be careful when re-enabling it.
327  // QQuickItem::geometryChanged( newGeometry, oldGeometry );
328 
329  mMapSettings->setOutputSize( newGeometry.size().toSize() );
330  refresh();
331 }
332 
333 void QgsQuickMapCanvasMap::onLayersChanged()
334 {
335  if ( mMapSettings->extent().isEmpty() )
336  zoomToFullExtent();
337 
338  for ( const QMetaObject::Connection &conn : qgis::as_const( mLayerConnections ) )
339  {
340  disconnect( conn );
341  }
342  mLayerConnections.clear();
343 
344  const QList<QgsMapLayer *> layers = mMapSettings->layers();
345  for ( QgsMapLayer *layer : layers )
346  {
347  mLayerConnections << connect( layer, &QgsMapLayer::repaintRequested, this, &QgsQuickMapCanvasMap::refresh );
348  }
349 
350  refresh();
351 }
352 
353 void QgsQuickMapCanvasMap::destroyJob( QgsMapRendererJob *job )
354 {
355  job->cancel();
356  job->deleteLater();
357 }
358 
360 {
361  if ( mJob )
362  {
363  disconnect( mJob, &QgsMapRendererJob::renderingLayersFinished, this, &QgsQuickMapCanvasMap::renderJobUpdated );
364  disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsQuickMapCanvasMap::renderJobFinished );
365 
366  mJob->cancelWithoutBlocking();
367  mJob = nullptr;
368  }
369 }
370 
371 void QgsQuickMapCanvasMap::zoomToFullExtent()
372 {
373  QgsRectangle extent;
374  const QList<QgsMapLayer *> layers = mMapSettings->layers();
375  for ( QgsMapLayer *layer : layers )
376  {
377  if ( mMapSettings->destinationCrs() != layer->crs() )
378  {
379  QgsCoordinateTransform transform( layer->crs(), mMapSettings->destinationCrs(), mMapSettings->transformContext() );
380  extent.combineExtentWith( transform.transformBoundingBox( layer->extent() ) );
381  }
382  else
383  {
384  extent.combineExtentWith( layer->extent() );
385  }
386  }
387  mMapSettings->setExtent( extent );
388 
389  refresh();
390 }
391 
393 {
394  if ( mMapSettings->outputSize().isNull() )
395  return; // the map image size has not been set yet
396 
397  if ( !mFreeze )
398  mRefreshTimer.start( 1 );
399 }
Class for doing transforms between two map coordinate systems.
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:85
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
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).
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 start() override
Start the rendering job and immediately return.
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.
const QgsMapToPixel & mapToPixel() const
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes takes output image size into accou...
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
QgsPointXY transform(const QgsPointXY &p) const
Transform the point p from map (world) coordinates to device coordinates.
Definition: qgsmaptopixel.h:82
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).
A class to represent a 2D point.
Definition: qgspointxy.h:44
double y
Definition: qgspointxy.h:48
Q_GADGET double x
Definition: qgspointxy.h:47
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:38
Q_GADGET double x
Definition: qgspoint.h:41
double y
Definition: qgspoint.h:42
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()
Emitted when the 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()
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:235
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:140
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:135
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:130
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:145
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:359
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:230
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316