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