QGIS API Documentation 3.99.0-Master (21b3aa880ba)
Loading...
Searching...
No Matches
qgsmapcanvas.cpp
Go to the documentation of this file.
1/***************************************************************************
2qgsmapcanvas.cpp - description
3------------------ -
4begin : Sun Jun 30 2002
5copyright : (C) 2002 by Gary E.Sherman
6email : sherman at mrcc.com
7***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsmapcanvas.h"
19
20#include <cmath>
21#include <memory>
22
23#include "qgis.h"
24#include "qgs2dmapcontroller.h"
25#include "qgsannotationlayer.h"
26#include "qgsapplication.h"
31#include "qgsexception.h"
33#include "qgsfeatureiterator.h"
34#include "qgsgrouplayer.h"
35#include "qgsimagecache.h"
36#include "qgslabelingresults.h"
37#include "qgslogger.h"
39#include "qgsmapcanvasmap.h"
41#include "qgsmaplayer.h"
44#include "qgsmaplayerutils.h"
45#include "qgsmapmouseevent.h"
46#include "qgsmaprenderercache.h"
48#include "qgsmaprendererjob.h"
51#include "qgsmapsettingsutils.h"
53#include "qgsmaptoolpan.h"
54#include "qgsmaptopixel.h"
55#include "qgsmessagelog.h"
56#include "qgsmimedatautils.h"
58#include "qgsproject.h"
63#include "qgsrubberband.h"
64#include "qgsruntimeprofiler.h"
65#include "qgsscreenhelper.h"
66#include "qgssettings.h"
68#include "qgssvgcache.h"
69#include "qgssymbollayerutils.h"
72#include "qgsvectorlayer.h"
73#include "qgsvectortilelayer.h"
74
75#include <QApplication>
76#include <QClipboard>
77#include <QCursor>
78#include <QDir>
79#include <QFile>
80#include <QGraphicsItem>
81#include <QGraphicsScene>
82#include <QGraphicsView>
83#include <QKeyEvent>
84#include <QMenu>
85#include <QPaintEvent>
86#include <QPainter>
87#include <QPixmap>
88#include <QPropertyAnimation>
89#include <QRect>
90#include <QResizeEvent>
91#include <QScreen>
92#include <QString>
93#include <QStringList>
94#include <QTextStream>
95#include <QVariantAnimation>
96#include <QWheelEvent>
97#include <QWindow>
98#include <QtGlobal>
99
100#include "moc_qgsmapcanvas.cpp"
101
107//TODO QGIS 4.0 - remove
109{
110 public:
111
115 CanvasProperties() = default;
116
118 bool mouseButtonDown { false };
119
122
125
127 bool panSelectorDown { false };
128};
129
130
132 : QGraphicsView( parent )
134 , mExpressionContextScope( tr( "Map Canvas" ) )
135{
136 mScene = new QGraphicsScene();
137 mLayout = new QgsOverlayWidgetLayout();
138 setLayout( mLayout );
139
140 setScene( mScene );
141 setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
142 setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
143 setMouseTracking( true );
144 setFocusPolicy( Qt::StrongFocus );
145
146 mScreenHelper = new QgsScreenHelper( this );
147 connect( mScreenHelper, &QgsScreenHelper::screenDpiChanged, this, &QgsMapCanvas::updateDevicePixelFromScreen );
148
149 mResizeTimer = new QTimer( this );
150 mResizeTimer->setSingleShot( true );
151 connect( mResizeTimer, &QTimer::timeout, this, &QgsMapCanvas::refresh );
152
153 mRefreshTimer = new QTimer( this );
154 mRefreshTimer->setSingleShot( true );
155 connect( mRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::refreshMap );
156
157 // create map canvas item which will show the map
158 mMap = new QgsMapCanvasMap( this );
159
160 // project handling
163
164 connect( QgsProject::instance()->mainAnnotationLayer(), &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
165 connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsMapCanvas::mapThemeChanged );
166 connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsMapCanvas::mapThemeRenamed );
167 connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemesChanged, this, &QgsMapCanvas::projectThemesChanged );
168
169 {
170 QgsScopedRuntimeProfile profile( "Map settings initialization" );
171 mSettings.setFlag( Qgis::MapSettingsFlag::DrawEditingInfo );
174 mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
175 connect( QgsProject::instance(), &QgsProject::ellipsoidChanged, this, [this] {
176 mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
177 refresh();
178 } );
179 mSettings.setTransformContext( QgsProject::instance()->transformContext() );
181 mSettings.setTransformContext( QgsProject::instance()->transformContext() );
183 refresh();
184 } );
185
186 mSettings.setScaleMethod( QgsProject::instance()->scaleMethod() );
187 connect( QgsProject::instance(), &QgsProject::scaleMethodChanged, this, [this] {
188 mSettings.setScaleMethod( QgsProject::instance()->scaleMethod() );
189 updateScale();
190 refresh();
191 } );
192
194 QgsCoordinateReferenceSystem crs = mSettings.destinationCrs();
195 crs.updateDefinition();
196 if ( mSettings.destinationCrs() != crs )
197 {
198 // user crs has changed definition, refresh the map
199 setDestinationCrs( crs );
200 }
201 } );
202 }
203
204 // refresh canvas when a remote svg/image has finished downloading
207 // refresh canvas when project color scheme is changed -- if layers use project colors, they need to be redrawn
209
210 //segmentation parameters
211 QgsSettings settings;
212 double segmentationTolerance = settings.value( QStringLiteral( "qgis/segmentationTolerance" ), "0.01745" ).toDouble();
213 QgsAbstractGeometry::SegmentationToleranceType toleranceType = settings.enumValue( QStringLiteral( "qgis/segmentationToleranceType" ), QgsAbstractGeometry::MaximumAngle );
214 mSettings.setSegmentationTolerance( segmentationTolerance );
215 mSettings.setSegmentationToleranceType( toleranceType );
216
217 mWheelZoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
218
219 QSize s = viewport()->size();
220 mSettings.setOutputSize( s );
221
222 mSettings.setRendererUsage( Qgis::RendererUsage::View );
223
224 setSceneRect( 0, 0, s.width(), s.height() );
225 mScene->setSceneRect( QRectF( 0, 0, s.width(), s.height() ) );
226
227 moveCanvasContents( true );
228
229 connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsMapCanvas::mapUpdateTimeout );
230 mMapUpdateTimer.setInterval( 250 );
231
232#ifdef Q_OS_WIN
233 // Enable touch event on Windows.
234 // Qt on Windows needs to be told it can take touch events or else it ignores them.
235 grabGesture( Qt::PinchGesture );
236 grabGesture( Qt::TapAndHoldGesture );
237 viewport()->setAttribute( Qt::WA_AcceptTouchEvents );
238#endif
239
240 mPreviewEffect = new QgsPreviewEffect( this );
241 viewport()->setGraphicsEffect( mPreviewEffect );
242
244
245 connect( &mAutoRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::autoRefreshTriggered );
246
248
249 setInteractive( false );
250
251 // make sure we have the same default in QgsMapSettings and the scene's background brush
252 // (by default map settings has white bg color, scene background brush is black)
253 setCanvasColor( mSettings.backgroundColor() );
254
255 setTemporalRange( mSettings.temporalRange() );
256 refresh();
257}
258
259
261{
262 if ( mMapTool )
263 {
264 mMapTool->deactivate();
265 disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
266 mMapTool = nullptr;
267 }
268
269 // we also clear the canvas pointer for all child map tools. We're now in a partially destroyed state and it's
270 // no longer safe for map tools to try to cleanup things in the canvas during their destruction (such as removing
271 // associated canvas items)
272 // NOTE -- it may be better to just delete the map tool children here upfront?
273 const QList<QgsMapTool *> tools = findChildren<QgsMapTool *>();
274 for ( QgsMapTool *tool : tools )
275 {
276 tool->mCanvas = nullptr;
277 }
278
279 cancelJobs();
280
281 // delete canvas items prior to deleting the canvas
282 // because they might try to update canvas when it's
283 // already being destructed, ends with segfault
284 qDeleteAll( mScene->items() );
285
286 mScene->deleteLater(); // crashes in python tests on windows
287
288 delete mCache;
289}
290
291void QgsMapCanvas::addOverlayWidget( QWidget *widget, Qt::Edge edge )
292{
293 mLayout->addWidget( widget, edge );
294}
295
297{
298 // rendering job may still end up writing into canvas map item
299 // so kill it before deleting canvas items
300 if ( mJob )
301 {
302 whileBlocking( mJob )->cancel();
303 delete mJob;
304 mJob = nullptr;
305 }
306
307 for ( auto previewJob = mPreviewJobs.constBegin(); previewJob != mPreviewJobs.constEnd(); ++previewJob )
308 {
309 if ( *previewJob )
310 {
311 whileBlocking( *previewJob )->cancel();
312 delete *previewJob;
313 }
314 }
315 mPreviewJobs.clear();
316}
317
319{
320 // do not go higher or lower than min max magnification ratio
321 double magnifierMin = QgsGuiUtils::CANVAS_MAGNIFICATION_MIN;
322 double magnifierMax = QgsGuiUtils::CANVAS_MAGNIFICATION_MAX;
323 factor = std::clamp( factor, magnifierMin, magnifierMax );
324
325 // the magnifier widget is in integer percent
326 if ( !qgsDoubleNear( factor, mSettings.magnificationFactor(), 0.01 ) )
327 {
328 mSettings.setMagnificationFactor( factor, center );
329 refresh();
330 emit magnificationChanged( factor );
331 }
332}
333
335{
336 return mSettings.magnificationFactor();
337}
338
340{
341 mSettings.setFlag( Qgis::MapSettingsFlag::Antialiasing, flag );
342 mSettings.setFlag( Qgis::MapSettingsFlag::HighQualityImageTransforms, flag );
343}
344
346{
347 return mSettings.testFlag( Qgis::MapSettingsFlag::Antialiasing );
348}
349
351{
352 mSettings.setFlag( Qgis::MapSettingsFlag::RenderMapTile, flag );
353}
354
356{
357 QList<QgsMapLayer *> layers = mapSettings().layers();
358 if ( index >= 0 && index < layers.size() )
359 return layers[index];
360 else
361 return nullptr;
362}
363
364QgsMapLayer *QgsMapCanvas::layer( const QString &id )
365{
366 // first check for layers from canvas map settings
367 const QList<QgsMapLayer *> layers = mapSettings().layers();
368 for ( QgsMapLayer *layer : layers )
369 {
370 if ( layer && layer->id() == id )
371 return layer;
372 }
373
374 // else fallback to searching project layers
375 // TODO: allow a specific project to be associated with a canvas!
376 return QgsProject::instance()->mapLayer( id );
377}
378
380{
381 if ( mCurrentLayer == layer )
382 return;
383
384 mCurrentLayer = layer;
386}
387
389{
390 return mapSettings().scale();
391}
392
394{
395 return nullptr != mJob;
396} // isDrawing
397
398// return the current coordinate transform based on the extents and
399// device size
404
405void QgsMapCanvas::setLayers( const QList<QgsMapLayer *> &layers )
406{
407 // following a theme => request denied!
408 if ( !mTheme.isEmpty() )
409 return;
410
411 setLayersPrivate( layers );
412}
413
418
420{
421 return mFlags;
422}
423
424void QgsMapCanvas::setLayersPrivate( const QList<QgsMapLayer *> &layers )
425{
426 const QList<QgsMapLayer *> oldLayers = mSettings.layers();
427
428 // update only if needed
429 if ( layers == oldLayers )
430 return;
431
432 for ( QgsMapLayer *layer : oldLayers )
433 {
434 disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
435 disconnect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
436 switch ( layer->type() )
437 {
439 {
440 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
442 disconnect( vlayer, &QgsVectorLayer::rendererChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
443 break;
444 }
445
447 {
448 QgsVectorTileLayer *vtlayer = qobject_cast<QgsVectorTileLayer *>( layer );
450 break;
451 }
452
460 break;
461 }
462 }
463
464 mSettings.setLayers( layers );
465
466 for ( QgsMapLayer *layer : std::as_const( layers ) )
467 {
468 if ( !layer )
469 continue;
470 connect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
471 connect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
472
473 switch ( layer->type() )
474 {
476 {
477 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
479 connect( vlayer, &QgsVectorLayer::rendererChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
480 break;
481 }
482
484 {
485 QgsVectorTileLayer *vtlayer = qobject_cast<QgsVectorTileLayer *>( layer );
487 break;
488 }
489
497 break;
498 }
499 }
500
501 QgsDebugMsgLevel( QStringLiteral( "Layers have changed, refreshing" ), 2 );
502 emit layersChanged();
503
504 updateAutoRefreshTimer();
505 refresh();
506}
507
508
510{
511 return mSettings;
512}
513
515{
516 return mSettings;
517}
518
520{
521 if ( mSettings.destinationCrs() == crs )
522 return;
523
524 // try to reproject current extent to the new one
525 QgsRectangle rect;
526 if ( !mSettings.visibleExtent().isEmpty() )
527 {
529 try
530 {
531 rect = transform.transformBoundingBox( mSettings.visibleExtent() );
532 }
533 catch ( QgsCsException &e )
534 {
535 Q_UNUSED( e )
536 QgsDebugError( QStringLiteral( "Transform error caught: %1" ).arg( e.what() ) );
537 }
538 }
539
540 // defer extent and scale changed signals until we've correctly
541 // set the destination crs, otherwise slots which connect to these signals
542 // may retrieve an outdated CRS for the map canvas
543 mBlockExtentChangedSignal++;
544 mBlockScaleChangedSignal++;
545
546 if ( !rect.isEmpty() )
547 {
548 // we will be manually calling updateCanvasItemPositions() later, AFTER setting the updating the mSettings destination CRS, and we don't
549 // want to do that twice!
550 mBlockItemPositionUpdates++;
551 setExtent( rect );
552 mBlockItemPositionUpdates--;
553 }
554
555 mBlockExtentChangedSignal--;
556 mBlockScaleChangedSignal--;
557
558 mSettings.setDestinationCrs( crs );
559 updateScale();
561
563
564 QgsDebugMsgLevel( QStringLiteral( "refreshing after destination CRS changed" ), 2 );
565 refresh();
566
568}
569
571{
572 if ( mController )
574 if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast<QgsTemporalNavigationObject *>( mController ) )
575 {
576 disconnect( temporalNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsMapCanvas::temporalControllerModeChanged );
577
578 // clear any existing animation settings from map settings. We don't do this on every render, as a 3rd party plugin
579 // might be in control of these!
580 mSettings.setFrameRate( -1 );
581 mSettings.setCurrentFrame( -1 );
582 }
583
584 mController = controller;
586 if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast<QgsTemporalNavigationObject *>( mController ) )
587 connect( temporalNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsMapCanvas::temporalControllerModeChanged );
588}
589
590void QgsMapCanvas::temporalControllerModeChanged()
591{
592 if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast<QgsTemporalNavigationObject *>( mController ) )
593 {
594 switch ( temporalNavigationObject->navigationMode() )
595 {
598 mSettings.setFrameRate( temporalNavigationObject->framesPerSecond() );
599 mSettings.setCurrentFrame( temporalNavigationObject->currentFrameNumber() );
600 break;
601
604 // clear any existing animation settings from map settings. We don't do this on every render, as a 3rd party plugin
605 // might be in control of these!
606 mSettings.setFrameRate( -1 );
607 mSettings.setCurrentFrame( -1 );
608 break;
609 }
610 }
611}
612
614{
615 return mController;
616}
617
619{
620 mSettings.setFlags( flags );
621 clearCache();
622 refresh();
623}
624
625const QgsLabelingResults *QgsMapCanvas::labelingResults( bool allowOutdatedResults ) const
626{
627 if ( !allowOutdatedResults && mLabelingResultsOutdated )
628 return nullptr;
629
630 return mLabelingResults.get();
631}
632
633const QgsRenderedItemResults *QgsMapCanvas::renderedItemResults( bool allowOutdatedResults ) const
634{
635 if ( !allowOutdatedResults && mRenderedItemResultsOutdated )
636 return nullptr;
637
638 return mRenderedItemResults.get();
639}
640
642{
643 if ( enabled == isCachingEnabled() )
644 return;
645
646 if ( mJob && mJob->isActive() )
647 {
648 // wait for the current rendering to finish, before touching the cache
649 mJob->waitForFinished();
650 }
651
652 if ( enabled )
653 {
654 mCache = new QgsMapRendererCache;
655 }
656 else
657 {
658 delete mCache;
659 mCache = nullptr;
660 }
661 mPreviousRenderedItemResults.reset();
662}
663
665{
666 return nullptr != mCache;
667}
668
670{
671 if ( mCache )
672 mCache->clear();
673
674 if ( mPreviousRenderedItemResults )
675 mPreviousRenderedItemResults.reset();
676 if ( mRenderedItemResults )
677 mRenderedItemResults.reset();
678}
679
681{
682 return mCache;
683}
684
686{
687 mUseParallelRendering = enabled;
688}
689
691{
692 return mUseParallelRendering;
693}
694
695void QgsMapCanvas::setMapUpdateInterval( int timeMilliseconds )
696{
697 mMapUpdateTimer.setInterval( timeMilliseconds );
698}
699
701{
702 return mMapUpdateTimer.interval();
703}
704
705
707{
708 return mCurrentLayer;
709}
710
712{
713 QgsExpressionContextScope *s = new QgsExpressionContextScope( QObject::tr( "Map Canvas" ) );
714 s->setVariable( QStringLiteral( "canvas_cursor_point" ), QgsGeometry::fromPointXY( cursorPoint() ), true );
715 return s;
716}
717
719{
720 //build the expression context
721 QgsExpressionContext expressionContext;
722 expressionContext << QgsExpressionContextUtils::globalScope()
726 if ( QgsExpressionContextScopeGenerator *generator = dynamic_cast<QgsExpressionContextScopeGenerator *>( mController ) )
727 {
728 expressionContext << generator->createExpressionContextScope();
729 }
730 expressionContext << defaultExpressionContextScope()
731 << new QgsExpressionContextScope( mExpressionContextScope );
732 return expressionContext;
733}
734
736{
737 if ( !mSettings.hasValidSettings() )
738 {
739 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh - invalid settings -> nothing to do" ), 2 );
740 return;
741 }
742
743 if ( !mRenderFlag || mFrozen )
744 {
745 QgsDebugMsgLevel( QStringLiteral( "CANVAS render flag off" ), 2 );
746 return;
747 }
748
749 if ( mRefreshScheduled )
750 {
751 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh already scheduled" ), 2 );
752 return;
753 }
754
755 mRefreshScheduled = true;
756
757 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh scheduling" ), 2 );
758
759 // schedule a refresh
760 mRefreshTimer->start( 1 );
761
762 mLabelingResultsOutdated = true;
763 mRenderedItemResultsOutdated = true;
764}
765
766QList<QgsMapLayer *> filterLayersForRender( const QList<QgsMapLayer *> &layers )
767{
768 QList<QgsMapLayer *> filteredLayers;
769 for ( QgsMapLayer *layer : layers )
770 {
771 if ( QgsAnnotationLayer *annotationLayer = qobject_cast<QgsAnnotationLayer *>( layer ) )
772 {
773 if ( QgsMapLayer *linkedLayer = annotationLayer->linkedVisibilityLayer() )
774 {
775 if ( !layers.contains( linkedLayer ) )
776 continue;
777 }
778 }
779 filteredLayers.append( layer );
780 }
781
782 // remove any invalid layers
783 filteredLayers.erase( std::remove_if( filteredLayers.begin(), filteredLayers.end(), []( QgsMapLayer *layer ) {
784 return !layer || !layer->isValid();
785 } ),
786 filteredLayers.end() );
787
788 return filteredLayers;
789}
790
791void QgsMapCanvas::refreshMap()
792{
793 Q_ASSERT( mRefreshScheduled );
794
795 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh!" ), 3 );
796
797 stopRendering(); // if any...
798 stopPreviewJobs();
799
800 if ( mCacheInvalidations.testFlag( CacheInvalidationType::Temporal ) )
801 {
802 clearTemporalCache();
803 mCacheInvalidations &= ~( static_cast<int>( CacheInvalidationType::Temporal ) );
804 }
805 if ( mCacheInvalidations.testFlag( CacheInvalidationType::Elevation ) )
806 {
807 clearElevationCache();
808 mCacheInvalidations &= ~( static_cast<int>( CacheInvalidationType::Elevation ) );
809 }
810
811 mSettings.setExpressionContext( createExpressionContext() );
812
813 // if using the temporal controller in animation mode, get the frame settings from that
814 if ( QgsTemporalNavigationObject *temporalNavigationObject = dynamic_cast<QgsTemporalNavigationObject *>( mController ) )
815 {
816 switch ( temporalNavigationObject->navigationMode() )
817 {
820 mSettings.setFrameRate( temporalNavigationObject->framesPerSecond() );
821 mSettings.setCurrentFrame( temporalNavigationObject->currentFrameNumber() );
822 break;
823
826 break;
827 }
828 }
829
830 mSettings.setPathResolver( QgsProject::instance()->pathResolver() );
831
832 if ( !mTheme.isEmpty() )
833 {
834 // IMPORTANT: we MUST set the layer style overrides here! (At the time of writing this
835 // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
836 // current state of the style. If we had stored the style overrides earlier (such as in
837 // mapThemeChanged slot) then this xml could be out of date...
838 // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
839 // just return the style name, we can instead set the overrides in mapThemeChanged and not here
840 mSettings.setLayerStyleOverrides( QgsProject::instance()->mapThemeCollection()->mapThemeStyleOverrides( mTheme ) );
841 }
842
843 // render main annotation layer above all other layers
844 QgsMapSettings renderSettings = mSettings;
845 QList<QgsMapLayer *> allLayers = renderSettings.layers();
847 allLayers.insert( 0, QgsProject::instance()->mainAnnotationLayer() );
848
849 renderSettings.setLayers( filterLayersForRender( allLayers ) );
850
851 // create the renderer job
852
853 QgsApplication::profiler()->clear( QStringLiteral( "rendering" ) );
854
855 Q_ASSERT( !mJob );
856 mJobCanceled = false;
857 if ( mUseParallelRendering )
858 mJob = new QgsMapRendererParallelJob( renderSettings );
859 else
860 mJob = new QgsMapRendererSequentialJob( renderSettings );
861
862 connect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
863 mJob->setCache( mCache );
864 mJob->setLayerRenderingTimeHints( mLastLayerRenderTime );
865
866 mJob->start();
867
868 // from now on we can accept refresh requests again
869 // this must be reset only after the job has been started, because
870 // some providers (yes, it's you WCS and AMS!) during preparation
871 // do network requests and start an internal event loop, which may
872 // end up calling refresh() and would schedule another refresh,
873 // deleting the one we have just started.
874 mRefreshScheduled = false;
875
876 mMapUpdateTimer.start();
877
878 emit renderStarting();
879}
880
881void QgsMapCanvas::mapThemeChanged( const QString &theme )
882{
883 if ( theme == mTheme )
884 {
885 // set the canvas layers to match the new layers contained in the map theme
886 // NOTE: we do this when the theme layers change and not when we are refreshing the map
887 // as setLayers() sets up necessary connections to handle changes to the layers
888 setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
889 // IMPORTANT: we don't set the layer style overrides here! (At the time of writing this
890 // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
891 // current state of the style. If changes were made to the style then this xml
892 // snapshot goes out of sync...
893 // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
894 // just return the style name, we can instead set the overrides here and not in refreshMap()
895
896 clearCache();
897 refresh();
898 }
899}
900
901void QgsMapCanvas::mapThemeRenamed( const QString &theme, const QString &newTheme )
902{
903 if ( mTheme.isEmpty() || theme != mTheme )
904 {
905 return;
906 }
907
908 setTheme( newTheme );
909 refresh();
910}
911
912void QgsMapCanvas::rendererJobFinished()
913{
914 QgsDebugMsgLevel( QStringLiteral( "CANVAS finish! %1" ).arg( !mJobCanceled ), 2 );
915
916 mMapUpdateTimer.stop();
917
918 notifyRendererErrors( mJob->errors() );
919
920 if ( !mJobCanceled )
921 {
922 // take labeling results before emitting renderComplete, so labeling map tools
923 // connected to signal work with correct results
924 if ( !mJob->usedCachedLabels() )
925 {
926 mLabelingResults.reset( mJob->takeLabelingResults() );
927 }
928 mLabelingResultsOutdated = false;
929
930 std::unique_ptr<QgsRenderedItemResults> renderedItemResults( mJob->takeRenderedItemResults() );
931 // if a layer was redrawn from the cached version, we should copy any existing rendered item results from that layer
932 if ( mRenderedItemResults )
933 {
934 renderedItemResults->transferResults( mRenderedItemResults.get(), mJob->layersRedrawnFromCache() );
935 }
936 if ( mPreviousRenderedItemResults )
937 {
938 // also transfer any results from previous renders which happened before this
939 renderedItemResults->transferResults( mPreviousRenderedItemResults.get(), mJob->layersRedrawnFromCache() );
940 }
941
942 if ( mCache && !mPreviousRenderedItemResults )
943 mPreviousRenderedItemResults = std::make_unique<QgsRenderedItemResults>( mJob->mapSettings().extent() );
944
945 if ( mRenderedItemResults && mPreviousRenderedItemResults )
946 {
947 // for other layers which ARE present in the most recent rendered item results BUT were not part of this render, we
948 // store the results in a temporary store in case they are later switched back on and the layer's image is taken
949 // from the cache
950 mPreviousRenderedItemResults->transferResults( mRenderedItemResults.get() );
951 }
952 if ( mPreviousRenderedItemResults )
953 {
954 mPreviousRenderedItemResults->eraseResultsFromLayers( mJob->mapSettings().layerIds() );
955 }
956
957 mRenderedItemResults = std::move( renderedItemResults );
958 mRenderedItemResultsOutdated = false;
959
960 QImage img = mJob->renderedImage();
961
962 // emit renderComplete to get our decorations drawn and to handle computed statistics
963 QPainter p( &img );
964 emit renderComplete( &p );
965
967 {
968 QString logMsg = tr( "Canvas refresh: %1 ms" ).arg( mJob->renderingTime() );
969 QgsMessageLog::logMessage( logMsg, tr( "Rendering" ) );
970 }
971
972 if ( mDrawRenderingStats )
973 {
974 int w = img.width(), h = img.height();
975 QFont fnt = p.font();
976 fnt.setBold( true );
977 p.setFont( fnt );
978 int lh = p.fontMetrics().height() * 2;
979 QRect r( 0, h - lh, w, lh );
980 p.setPen( Qt::NoPen );
981 p.setBrush( QColor( 0, 0, 0, 110 ) );
982 p.drawRect( r );
983 p.setPen( Qt::white );
984 QString msg = QStringLiteral( "%1 :: %2 ms" ).arg( mUseParallelRendering ? QStringLiteral( "PARALLEL" ) : QStringLiteral( "SEQUENTIAL" ) ).arg( mJob->renderingTime() );
985 p.drawText( r, msg, QTextOption( Qt::AlignCenter ) );
986 }
987
988 p.end();
989
990 mMap->setContent( img, imageRect( img, mSettings ) );
991
992 mLastLayerRenderTime.clear();
993 const auto times = mJob->perLayerRenderingTime();
994 for ( auto it = times.constBegin(); it != times.constEnd(); ++it )
995 {
996 mLastLayerRenderTime.insert( it.key()->id(), it.value() );
997 }
998 if ( mUsePreviewJobs && !mRefreshAfterJob )
999 startPreviewJobs();
1000 }
1001 else
1002 {
1003 mRefreshAfterJob = false;
1004 }
1005
1006 // now we are in a slot called from mJob - do not delete it immediately
1007 // so the class is still valid when the execution returns to the class
1008 mJob->deleteLater();
1009 mJob = nullptr;
1010
1011 emit mapCanvasRefreshed();
1012
1013 if ( mRefreshAfterJob )
1014 {
1015 mRefreshAfterJob = false;
1016 refresh();
1017 }
1018}
1019
1020void QgsMapCanvas::previewJobFinished()
1021{
1022 QgsMapRendererQImageJob *job = qobject_cast<QgsMapRendererQImageJob *>( sender() );
1023 Q_ASSERT( job );
1024
1025 if ( mMap )
1026 {
1027 mMap->addPreviewImage( job->renderedImage(), job->mapSettings().visiblePolygon() );
1028 mPreviewJobs.removeAll( job );
1029
1030 int number = job->property( "number" ).toInt();
1031 if ( number < 8 )
1032 {
1033 startPreviewJob( number + 1 );
1034 }
1035
1036 delete job;
1037 }
1038}
1039
1040QgsRectangle QgsMapCanvas::imageRect( const QImage &img, const QgsMapSettings &mapSettings )
1041{
1042 // This is a hack to pass QgsMapCanvasItem::setRect what it
1043 // expects (encoding of position and size of the item)
1044 const QgsMapToPixel &m2p = mapSettings.mapToPixel();
1045 QgsPointXY topLeft = m2p.toMapCoordinates( 0, 0 );
1046#ifdef QGISDEBUG
1047 // do not assert this, since it might lead to crashes when changing screen while rendering
1048 if ( img.devicePixelRatio() != mapSettings.devicePixelRatio() )
1049 {
1050 QgsLogger::warning( QStringLiteral( "The renderer map has a wrong device pixel ratio" ) );
1051 }
1052#endif
1053 double res = m2p.mapUnitsPerPixel() / img.devicePixelRatioF();
1054 QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + img.width() * res, topLeft.y() - img.height() * res );
1055 return rect;
1056}
1057
1059{
1060 return mUsePreviewJobs;
1061}
1062
1064{
1065 mUsePreviewJobs = enabled;
1066}
1067
1068void QgsMapCanvas::setCustomDropHandlers( const QVector<QPointer<QgsCustomDropHandler>> &handlers )
1069{
1070 mDropHandlers = handlers;
1071}
1072
1073void QgsMapCanvas::clearTemporalCache()
1074{
1075 if ( mCache )
1076 {
1077 bool invalidateLabels = false;
1078 const QList<QgsMapLayer *> layerList = mapSettings().layers();
1079 for ( QgsMapLayer *layer : layerList )
1080 {
1081 bool alreadyInvalidatedThisLayer = false;
1082 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
1083 {
1084 if ( vl->renderer() && QgsSymbolLayerUtils::rendererFrameRate( vl->renderer() ) > -1 )
1085 {
1086 // layer has an animated symbol assigned, so we have to redraw it regardless of whether
1087 // or not it has temporal settings
1088 mCache->invalidateCacheForLayer( layer );
1089 alreadyInvalidatedThisLayer = true;
1090 // we can't shortcut and "continue" here, as we still need to check whether the layer
1091 // will cause label invalidation using the logic below
1092 }
1093 }
1094
1095 if ( layer->temporalProperties() && layer->temporalProperties()->isActive() )
1096 {
1097 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
1098 {
1099 if ( vl->labelsEnabled() || vl->diagramsEnabled() || ( vl->renderer() && vl->renderer()->flags().testFlag( Qgis::FeatureRendererFlag::AffectsLabeling ) ) )
1100 invalidateLabels = true;
1101 }
1102
1104 continue;
1105
1106 if ( !alreadyInvalidatedThisLayer )
1107 {
1108 mCache->invalidateCacheForLayer( layer );
1109 }
1110 }
1111 else if ( QgsGroupLayer *gl = qobject_cast<QgsGroupLayer *>( layer ) )
1112 {
1113 const QList<QgsMapLayer *> childLayerList = gl->childLayers();
1114 for ( QgsMapLayer *childLayer : childLayerList )
1115 {
1116 if ( childLayer->temporalProperties() && childLayer->temporalProperties()->isActive() )
1117 {
1118 if ( childLayer->temporalProperties()->flags() & QgsTemporalProperty::FlagDontInvalidateCachedRendersWhenRangeChanges )
1119 continue;
1120
1121 mCache->invalidateCacheForLayer( layer );
1122 break;
1123 }
1124 }
1125 }
1126 }
1127
1128 if ( invalidateLabels )
1129 {
1130 mCache->clearCacheImage( QStringLiteral( "_labels_" ) );
1131 mCache->clearCacheImage( QStringLiteral( "_preview_labels_" ) );
1132 }
1133 }
1134}
1135
1136void QgsMapCanvas::clearElevationCache()
1137{
1138 if ( mCache )
1139 {
1140 bool invalidateLabels = false;
1141 const QList<QgsMapLayer *> layerList = mapSettings().layers();
1142 for ( QgsMapLayer *layer : layerList )
1143 {
1144 if ( layer->elevationProperties() && layer->elevationProperties()->hasElevation() )
1145 {
1146 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
1147 {
1148 if ( vl->labelsEnabled() || vl->diagramsEnabled() || ( vl->renderer() && vl->renderer()->flags().testFlag( Qgis::FeatureRendererFlag::AffectsLabeling ) ) )
1149 invalidateLabels = true;
1150 }
1151
1153 continue;
1154
1155 mCache->invalidateCacheForLayer( layer );
1156 }
1157 else if ( QgsGroupLayer *gl = qobject_cast<QgsGroupLayer *>( layer ) )
1158 {
1159 const QList<QgsMapLayer *> childLayerList = gl->childLayers();
1160 for ( QgsMapLayer *childLayer : childLayerList )
1161 {
1162 if ( childLayer->elevationProperties() && childLayer->elevationProperties()->hasElevation() )
1163 {
1164 if ( childLayer->elevationProperties()->flags() & QgsMapLayerElevationProperties::FlagDontInvalidateCachedRendersWhenRangeChanges )
1165 continue;
1166
1167 mCache->invalidateCacheForLayer( layer );
1168 break;
1169 }
1170 }
1171 }
1172 }
1173
1174 if ( invalidateLabels )
1175 {
1176 mCache->clearCacheImage( QStringLiteral( "_labels_" ) );
1177 mCache->clearCacheImage( QStringLiteral( "_preview_labels_" ) );
1178 }
1179 }
1180}
1181
1182void QgsMapCanvas::showContextMenu( QgsMapMouseEvent *event )
1183{
1184 const QgsPointXY mapPoint = event->originalMapPoint();
1185
1186 QMenu menu;
1187
1188 QMenu *copyCoordinateMenu = new QMenu( tr( "Copy Coordinate" ), &menu );
1189 copyCoordinateMenu->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ) );
1190
1191 auto addCoordinateFormat = [&, this]( const QString identifier, const QgsCoordinateReferenceSystem &crs ) {
1192 const QgsCoordinateTransform ct( mSettings.destinationCrs(), crs, mSettings.transformContext() );
1193 try
1194 {
1195 const QgsPointXY transformedPoint = ct.transform( mapPoint );
1196
1197 // calculate precision based on visible map extent -- if user is zoomed in, we get better precision!
1198 int displayPrecision = 0;
1199 try
1200 {
1201 QgsCoordinateTransform extentTransform = ct;
1202 extentTransform.setBallparkTransformsAreAppropriate( true );
1203 QgsRectangle extentReproj = extentTransform.transformBoundingBox( extent() );
1204 const double mapUnitsPerPixel = ( extentReproj.width() / width() + extentReproj.height() / height() ) * 0.5;
1205 if ( mapUnitsPerPixel > 10 )
1206 displayPrecision = 0;
1207 else if ( mapUnitsPerPixel > 1 )
1208 displayPrecision = 1;
1209 else if ( mapUnitsPerPixel > 0.1 )
1210 displayPrecision = 2;
1211 else if ( mapUnitsPerPixel > 0.01 )
1212 displayPrecision = 3;
1213 else if ( mapUnitsPerPixel > 0.001 )
1214 displayPrecision = 4;
1215 else if ( mapUnitsPerPixel > 0.0001 )
1216 displayPrecision = 5;
1217 else if ( mapUnitsPerPixel > 0.00001 )
1218 displayPrecision = 6;
1219 else if ( mapUnitsPerPixel > 0.000001 )
1220 displayPrecision = 7;
1221 else if ( mapUnitsPerPixel > 0.0000001 )
1222 displayPrecision = 8;
1223 else
1224 displayPrecision = 9;
1225 }
1226 catch ( QgsCsException & )
1227 {
1228 displayPrecision = crs.mapUnits() == Qgis::DistanceUnit::Degrees ? 5 : 3;
1229 }
1230
1231 const QList<Qgis::CrsAxisDirection> axisList = crs.axisOrdering();
1232 QString firstSuffix;
1233 QString secondSuffix;
1234 if ( axisList.size() >= 2 )
1235 {
1238 }
1239
1240 QString firstNumber;
1241 QString secondNumber;
1243 {
1244 firstNumber = QString::number( transformedPoint.y(), 'f', displayPrecision );
1245 secondNumber = QString::number( transformedPoint.x(), 'f', displayPrecision );
1246 }
1247 else
1248 {
1249 firstNumber = QString::number( transformedPoint.x(), 'f', displayPrecision );
1250 secondNumber = QString::number( transformedPoint.y(), 'f', displayPrecision );
1251 }
1252
1253 QAction *copyCoordinateAction = new QAction( QStringLiteral( "%5 (%1%2, %3%4)" ).arg( firstNumber, firstSuffix, secondNumber, secondSuffix, identifier ), &menu );
1254
1255 connect( copyCoordinateAction, &QAction::triggered, this, [firstNumber, firstSuffix, secondNumber, secondSuffix, transformedPoint] {
1256 QClipboard *clipboard = QApplication::clipboard();
1257
1258 const QString coordinates = QStringLiteral( "%1%2, %3%4" ).arg( firstNumber, firstSuffix, secondNumber, secondSuffix );
1259
1260 //if we are on x11 system put text into selection ready for middle button pasting
1261 if ( clipboard->supportsSelection() )
1262 {
1263 clipboard->setText( coordinates, QClipboard::Selection );
1264 }
1265 clipboard->setText( coordinates, QClipboard::Clipboard );
1266 } );
1267 copyCoordinateMenu->addAction( copyCoordinateAction );
1268 }
1269 catch ( QgsCsException & )
1270 {
1271 }
1272 };
1273
1274 addCoordinateFormat( tr( "Map CRS — %1" ).arg( mSettings.destinationCrs().userFriendlyIdentifier( Qgis::CrsIdentifierType::MediumString ) ), mSettings.destinationCrs() );
1275 QgsCoordinateReferenceSystem wgs84( QStringLiteral( "EPSG:4326" ) );
1276 if ( mSettings.destinationCrs() != wgs84 )
1277 addCoordinateFormat( wgs84.userFriendlyIdentifier( Qgis::CrsIdentifierType::MediumString ), wgs84 );
1278
1279 QgsSettings settings;
1280 const QString customCrsString = settings.value( QStringLiteral( "qgis/custom_coordinate_crs" ) ).toString();
1281 if ( !customCrsString.isEmpty() )
1282 {
1283 QgsCoordinateReferenceSystem customCrs( customCrsString );
1284 if ( customCrs != mSettings.destinationCrs() && customCrs != QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) )
1285 {
1286 addCoordinateFormat( customCrs.userFriendlyIdentifier( Qgis::CrsIdentifierType::MediumString ), customCrs );
1287 }
1288 }
1289 copyCoordinateMenu->addSeparator();
1290 QAction *setCustomCrsAction = new QAction( tr( "Set Custom CRS…" ), &menu );
1291 connect( setCustomCrsAction, &QAction::triggered, this, [this, customCrsString] {
1292 QgsProjectionSelectionDialog selector( this );
1293 selector.setCrs( QgsCoordinateReferenceSystem( customCrsString ) );
1294 if ( selector.exec() )
1295 {
1296 QgsSettings().setValue( QStringLiteral( "qgis/custom_coordinate_crs" ), selector.crs().authid().isEmpty() ? selector.crs().toWkt( Qgis::CrsWktVariant::Preferred ) : selector.crs().authid() );
1297 }
1298 } );
1299 copyCoordinateMenu->addAction( setCustomCrsAction );
1300
1301 menu.addMenu( copyCoordinateMenu );
1302
1303 if ( mMapTool )
1304 if ( !mapTool()->populateContextMenuWithEvent( &menu, event ) )
1305 mMapTool->populateContextMenu( &menu );
1306
1307 emit contextMenuAboutToShow( &menu, event );
1308
1309 if ( !menu.isEmpty() ) // menu can be empty after populateContextMenu() and contextMenuAboutToShow()
1310 menu.exec( event->globalPos() );
1311}
1312
1313void QgsMapCanvas::notifyRendererErrors( const QgsMapRendererJob::Errors &errors )
1314{
1315 const QDateTime currentTime = QDateTime::currentDateTime();
1316
1317 // remove errors too old
1318 for ( const QgsMapRendererJob::Error &error : errors )
1319 {
1320 const QString errorKey = error.layerID + ':' + error.message;
1321 if ( mRendererErrors.contains( errorKey ) )
1322 {
1323 const QDateTime sameErrorTime = mRendererErrors.value( errorKey );
1324
1325 if ( sameErrorTime.secsTo( currentTime ) < 60 )
1326 continue;
1327 }
1328
1329 mRendererErrors[errorKey] = currentTime;
1330
1331 if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( error.layerID ) )
1332 emit renderErrorOccurred( error.message, layer );
1333 }
1334}
1335
1336void QgsMapCanvas::updateDevicePixelFromScreen()
1337{
1338 mSettings.setDevicePixelRatio( static_cast<float>( devicePixelRatioF() ) );
1339 // TODO: QGIS 4 -> always respect screen dpi
1341 {
1342 if ( window()->windowHandle() )
1343 {
1344 mSettings.setOutputDpi( window()->windowHandle()->screen()->physicalDotsPerInch() );
1345 mSettings.setDpiTarget( window()->windowHandle()->screen()->physicalDotsPerInch() );
1346 }
1347 }
1348 else
1349 {
1350 // Fallback: compatibility with QGIS <= 3.20; always assume low dpi screens
1351 mSettings.setOutputDpi( window()->windowHandle()->screen()->logicalDotsPerInch() );
1352 mSettings.setDpiTarget( window()->windowHandle()->screen()->logicalDotsPerInch() );
1353 }
1354 refresh();
1355}
1356
1357void QgsMapCanvas::onElevationShadingRendererChanged()
1358{
1359 if ( !mProject )
1360 return;
1361 bool wasDeactivated = !mSettings.elevationShadingRenderer().isActive();
1362 mSettings.setElevationShadingRenderer( mProject->elevationShadingRenderer() );
1363 if ( mCache && wasDeactivated )
1364 mCache->clear();
1365 refresh();
1366}
1367
1369{
1370 if ( temporalRange() == dateTimeRange )
1371 return;
1372
1373 mSettings.setTemporalRange( dateTimeRange );
1374 mSettings.setIsTemporal( dateTimeRange.begin().isValid() || dateTimeRange.end().isValid() );
1375
1376 emit temporalRangeChanged();
1377
1378 // we need to discard any previously cached images which have temporal properties enabled, so that these will be updated when
1379 // the canvas is redrawn
1380 mCacheInvalidations |= CacheInvalidationType::Temporal;
1381
1382 autoRefreshTriggered();
1383}
1384
1386{
1387 return mSettings.temporalRange();
1388}
1389
1391{
1392 mInteractionBlockers.append( blocker );
1393}
1394
1396{
1397 mInteractionBlockers.removeAll( blocker );
1398}
1399
1401{
1402 for ( const QgsMapCanvasInteractionBlocker *block : mInteractionBlockers )
1403 {
1404 if ( block->blockCanvasInteraction( interaction ) )
1405 return false;
1406 }
1407 return true;
1408}
1409
1411{
1412 if ( mMapController )
1413 {
1414 delete mMapController;
1415 mMapController = nullptr;
1416 }
1417
1418 if ( !controller )
1419 return;
1420
1421 mMapController = controller;
1422 mMapController->setParent( this );
1423
1424#if 0
1425 // connect high level signals to the canvas, e.g.
1426 connect( mMapController, &QgsAbstract2DMapController::zoomMap, this, []( double factor ) { zoomByFactor( factor ); } );
1427#endif
1428}
1429
1430void QgsMapCanvas::mapUpdateTimeout()
1431{
1432 if ( mJob )
1433 {
1434 const QImage &img = mJob->renderedImage();
1435 mMap->setContent( img, imageRect( img, mSettings ) );
1436 }
1437}
1438
1440{
1441 if ( mJob )
1442 {
1443 QgsDebugMsgLevel( QStringLiteral( "CANVAS stop rendering!" ), 2 );
1444 mJobCanceled = true;
1445 disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
1446 connect( mJob, &QgsMapRendererQImageJob::finished, mJob, &QgsMapRendererQImageJob::deleteLater );
1447 mJob->cancelWithoutBlocking();
1448 mJob = nullptr;
1449 emit mapRefreshCanceled();
1450 }
1451 stopPreviewJobs();
1452}
1453
1454//the format defaults to "PNG" if not specified
1455void QgsMapCanvas::saveAsImage( const QString &fileName, QPixmap *theQPixmap, const QString &format )
1456{
1457 QPainter painter;
1458 QImage image;
1459
1460 //
1461 //check if the optional QPaintDevice was supplied
1462 //
1463 if ( theQPixmap )
1464 {
1465 image = theQPixmap->toImage();
1466 painter.begin( &image );
1467
1468 // render
1469 QgsMapRendererCustomPainterJob job( mSettings, &painter );
1470 job.start();
1471 job.waitForFinished();
1472 emit renderComplete( &painter );
1473 }
1474 else //use the map view
1475 {
1476 image = mMap->contentImage().copy();
1477 painter.begin( &image );
1478 }
1479
1480 // draw annotations
1481 QStyleOptionGraphicsItem option;
1482 option.initFrom( this );
1483 QGraphicsItem *item = nullptr;
1484 QListIterator<QGraphicsItem *> i( items() );
1485 i.toBack();
1486 while ( i.hasPrevious() )
1487 {
1488 item = i.previous();
1489
1490 if ( !( item && dynamic_cast<QgsMapCanvasAnnotationItem *>( item ) ) )
1491 {
1492 continue;
1493 }
1494
1495 QgsScopedQPainterState painterState( &painter );
1496
1497 QPointF itemScenePos = item->scenePos();
1498 painter.translate( itemScenePos.x(), itemScenePos.y() );
1499
1500 item->paint( &painter, &option );
1501 }
1502
1503 painter.end();
1504 image.save( fileName, format.toLocal8Bit().data() );
1505
1506 QFileInfo myInfo = QFileInfo( fileName );
1507
1508 // build the world file name
1509 QString outputSuffix = myInfo.suffix();
1510 QString myWorldFileName = myInfo.absolutePath() + '/' + myInfo.completeBaseName() + '.'
1511 + outputSuffix.at( 0 ) + outputSuffix.at( myInfo.suffix().size() - 1 ) + 'w';
1512 QFile myWorldFile( myWorldFileName );
1513 if ( !myWorldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) //don't use QIODevice::Text
1514 {
1515 return;
1516 }
1517 QTextStream myStream( &myWorldFile );
1519}
1520
1522{
1523 return mapSettings().visibleExtent();
1524}
1525
1527{
1528 return QgsMapLayerUtils::combinedExtent( mSettings.layers(), mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
1529}
1530
1532{
1533 const QgsReferencedRectangle extent = mProject ? mProject->viewSettings()->fullExtent() : QgsProject::instance()->viewSettings()->fullExtent();
1534 QgsCoordinateTransform ct( extent.crs(), mapSettings().destinationCrs(), mProject ? mProject->transformContext() : QgsProject::instance()->transformContext() );
1536 QgsRectangle rect;
1537 try
1538 {
1539 rect = ct.transformBoundingBox( extent );
1540 }
1541 catch ( QgsCsException & )
1542 {
1543 rect = mapSettings().fullExtent();
1544 }
1545
1546 return rect;
1547}
1548
1549void QgsMapCanvas::setExtent( const QgsRectangle &r, bool magnified )
1550{
1551 QgsRectangle current = extent();
1552
1553 if ( ( r == current ) && magnified )
1554 return;
1555
1556 if ( r.isEmpty() )
1557 {
1558 if ( !mSettings.hasValidSettings() )
1559 {
1560 // we can't even just move the map center
1561 QgsDebugMsgLevel( QStringLiteral( "Empty extent - ignoring" ), 2 );
1562 return;
1563 }
1564
1565 // ### QGIS 3: do not allow empty extent - require users to call setCenter() explicitly
1566 QgsDebugMsgLevel( QStringLiteral( "Empty extent - keeping old scale with new center!" ), 2 );
1567
1568 setCenter( r.center() );
1569 }
1570 else
1571 {
1572 // If scale is locked we need to maintain the current scale, so we
1573 // - magnify and recenter the map
1574 // - restore locked scale
1575 if ( mScaleLocked && magnified )
1576 {
1577 ScaleRestorer restorer( this );
1578 const double ratio { mapSettings().extent().width() / mapSettings().extent().height() };
1579 const double factor { r.width() / r.height() > ratio ? mapSettings().extent().width() / r.width() : mapSettings().extent().height() / r.height() };
1580 const double scaleFactor { std::clamp( mSettings.magnificationFactor() * factor, QgsGuiUtils::CANVAS_MAGNIFICATION_MIN, QgsGuiUtils::CANVAS_MAGNIFICATION_MAX ) };
1581 const QgsPointXY newCenter { r.center() };
1582 mSettings.setMagnificationFactor( scaleFactor, &newCenter );
1583 emit magnificationChanged( scaleFactor );
1584 }
1585 else
1586 {
1587 mSettings.setExtent( r, magnified );
1588 }
1589 }
1591 updateScale();
1592
1593 //clear all extent items after current index
1594 for ( int i = mLastExtent.size() - 1; i > mLastExtentIndex; i-- )
1595 {
1596 mLastExtent.removeAt( i );
1597 }
1598
1599 if ( !mLastExtent.isEmpty() && mLastExtent.last() != mSettings.extent() )
1600 {
1601 mLastExtent.append( mSettings.extent() );
1602 }
1603
1604 // adjust history to no more than 100
1605 if ( mLastExtent.size() > 100 )
1606 {
1607 mLastExtent.removeAt( 0 );
1608 }
1609
1610 // the last item is the current extent
1611 mLastExtentIndex = mLastExtent.size() - 1;
1612
1613 // update controls' enabled state
1614 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1615 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1616}
1617
1619{
1620 QgsRectangle canvasExtent = extent;
1621 if ( extent.crs() != mapSettings().destinationCrs() )
1622 {
1623 QgsCoordinateTransform ct( extent.crs(), mapSettings().destinationCrs(), QgsProject::instance() );
1625 canvasExtent = ct.transformBoundingBox( extent );
1626
1627 if ( canvasExtent.isEmpty() )
1628 {
1629 return false;
1630 }
1631 }
1632
1633 setExtent( canvasExtent, true );
1634 return true;
1635}
1636
1638{
1639 const QgsRectangle r = mapSettings().extent();
1640 const double xMin = center.x() - r.width() / 2.0;
1641 const double yMin = center.y() - r.height() / 2.0;
1642 const QgsRectangle rect(
1643 xMin, yMin,
1644 xMin + r.width(), yMin + r.height()
1645 );
1646 if ( !rect.isEmpty() )
1647 {
1648 setExtent( rect, true );
1649 }
1650} // setCenter
1651
1653{
1655 return r.center();
1656}
1657
1658QgsPointXY QgsMapCanvas::cursorPoint() const
1659{
1660 return mCursorPoint;
1661}
1662
1664{
1665 return mapSettings().rotation();
1666}
1667
1668void QgsMapCanvas::setRotation( double degrees )
1669{
1670 double current = rotation();
1671
1672 if ( qgsDoubleNear( degrees, current ) )
1673 return;
1674
1675 mSettings.setRotation( degrees );
1676 emit rotationChanged( degrees );
1677 emitExtentsChanged(); // visible extent changes with rotation
1678}
1679
1681{
1682 if ( !mBlockScaleChangedSignal )
1683 emit scaleChanged( mapSettings().scale() );
1684}
1685
1687{
1689 // If the full extent is an empty set, don't do the zoom
1690 if ( !extent.isEmpty() )
1691 {
1692 // Add a 5% margin around the full extent
1693 extent.scale( 1.05 );
1694 setExtent( extent, true );
1695 }
1696 refresh();
1697}
1698
1700{
1702
1703 // If the full extent is an empty set, don't do the zoom
1704 if ( !extent.isEmpty() )
1705 {
1706 // Add a 5% margin around the full extent
1707 extent.scale( 1.05 );
1708 setExtent( extent, true );
1709 }
1710 refresh();
1711}
1712
1714{
1715 if ( mLastExtentIndex > 0 )
1716 {
1717 mLastExtentIndex--;
1718 mSettings.setExtent( mLastExtent[mLastExtentIndex] );
1720 updateScale();
1721 refresh();
1722 // update controls' enabled state
1723 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1724 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1725 }
1726
1727} // zoomToPreviousExtent
1728
1730{
1731 if ( mLastExtentIndex < mLastExtent.size() - 1 )
1732 {
1733 mLastExtentIndex++;
1734 mSettings.setExtent( mLastExtent[mLastExtentIndex] );
1736 updateScale();
1737 refresh();
1738 // update controls' enabled state
1739 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1740 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1741 }
1742} // zoomToNextExtent
1743
1745{
1746 mLastExtent.clear(); // clear the zoom history list
1747 mLastExtent.append( mSettings.extent() ); // set the current extent in the list
1748 mLastExtentIndex = mLastExtent.size() - 1;
1749 // update controls' enabled state
1750 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1751 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1752} // clearExtentHistory
1753
1754QgsRectangle QgsMapCanvas::optimalExtentForPointLayer( QgsVectorLayer *layer, const QgsPointXY &center, int scaleFactor )
1755{
1756 QgsRectangle rect( center, center );
1757
1758 if ( layer->geometryType() == Qgis::GeometryType::Point )
1759 {
1760 QgsPointXY centerLayerCoordinates = mSettings.mapToLayerCoordinates( layer, center );
1761 QgsRectangle extentRect = mSettings.mapToLayerCoordinates( layer, extent() ).scaled( 1.0 / scaleFactor, &centerLayerCoordinates );
1763 QgsFeatureIterator fit = layer->getFeatures( req );
1764 QgsFeature f;
1765 QgsPointXY closestPoint;
1766 double closestSquaredDistance = pow( extentRect.width(), 2.0 ) + pow( extentRect.height(), 2.0 );
1767 bool pointFound = false;
1768 while ( fit.nextFeature( f ) )
1769 {
1770 QgsPointXY point = f.geometry().asPoint();
1771 double sqrDist = point.sqrDist( centerLayerCoordinates );
1772 if ( sqrDist > closestSquaredDistance || sqrDist < 4 * std::numeric_limits<double>::epsilon() )
1773 continue;
1774 pointFound = true;
1775 closestPoint = point;
1776 closestSquaredDistance = sqrDist;
1777 }
1778 if ( pointFound )
1779 {
1780 // combine selected point with closest point and scale this rect
1781 rect.combineExtentWith( mSettings.layerToMapCoordinates( layer, closestPoint ) );
1782 rect.scale( scaleFactor, &center );
1783 }
1784 }
1785 return rect;
1786}
1787
1789{
1790 QgsTemporaryCursorOverride cursorOverride( Qt::WaitCursor );
1791
1792 if ( !layer )
1793 {
1794 // use current layer by default
1795 layer = mCurrentLayer;
1796 }
1797
1798 if ( !layer || !layer->isSpatial() )
1799 return;
1800
1801 QgsRectangle rect;
1802
1803 switch ( layer->type() )
1804 {
1806 {
1807 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
1808 if ( vlayer->selectedFeatureCount() == 0 )
1809 return;
1810
1811 rect = vlayer->boundingBoxOfSelected();
1812 if ( rect.isNull() )
1813 {
1814 cursorOverride.release();
1815 emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1816 return;
1817 }
1818
1820
1821 // zoom in if point cannot be distinguished from others
1822 // also check that rect is empty, as it might not in case of multi points
1823 if ( vlayer->geometryType() == Qgis::GeometryType::Point && rect.isEmpty() )
1824 {
1825 rect = optimalExtentForPointLayer( vlayer, rect.center() );
1826 }
1827 break;
1828 }
1829
1831 {
1832 QgsVectorTileLayer *vtLayer = qobject_cast<QgsVectorTileLayer *>( layer );
1833 if ( vtLayer->selectedFeatureCount() == 0 )
1834 return;
1835
1836 const QList<QgsFeature> selectedFeatures = vtLayer->selectedFeatures();
1837 for ( const QgsFeature &feature : selectedFeatures )
1838 {
1839 if ( !feature.hasGeometry() )
1840 continue;
1841
1842 rect.combineExtentWith( feature.geometry().boundingBox() );
1843 }
1844
1845 if ( rect.isNull() )
1846 {
1847 cursorOverride.release();
1848 emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1849 return;
1850 }
1851
1853 break;
1854 }
1855
1863 return; // not supported
1864 }
1865
1866 zoomToFeatureExtent( rect );
1867}
1868
1869void QgsMapCanvas::zoomToSelected( const QList<QgsMapLayer *> &layers )
1870{
1871 QgsRectangle rect;
1872 rect.setNull();
1873 QgsRectangle selectionExtent;
1874 selectionExtent.setNull();
1875
1876 for ( QgsMapLayer *mapLayer : layers )
1877 {
1878 if ( !mapLayer || !mapLayer->isSpatial() )
1879 continue;
1880
1881 switch ( mapLayer->type() )
1882 {
1884 {
1885 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
1886
1887 if ( layer->selectedFeatureCount() == 0 )
1888 continue;
1889
1890 rect = layer->boundingBoxOfSelected();
1891
1892 if ( rect.isNull() )
1893 continue;
1894
1896
1897 if ( layer->geometryType() == Qgis::GeometryType::Point && rect.isEmpty() )
1898 rect = optimalExtentForPointLayer( layer, rect.center() );
1899
1900 selectionExtent.combineExtentWith( rect );
1901 break;
1902 }
1903
1905 {
1906 QgsVectorTileLayer *vtLayer = qobject_cast<QgsVectorTileLayer *>( mapLayer );
1907 if ( vtLayer->selectedFeatureCount() == 0 )
1908 continue;
1909
1910 const QList<QgsFeature> selectedFeatures = vtLayer->selectedFeatures();
1911 QgsRectangle rect;
1912 for ( const QgsFeature &feature : selectedFeatures )
1913 {
1914 if ( !feature.hasGeometry() )
1915 continue;
1916
1917 rect.combineExtentWith( feature.geometry().boundingBox() );
1918 }
1919
1920 rect = mapSettings().layerExtentToOutputExtent( vtLayer, rect );
1921 selectionExtent.combineExtentWith( rect );
1922 break;
1923 }
1924
1932 break;
1933 }
1934 }
1935
1936 if ( selectionExtent.isNull() )
1937 {
1938 emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1939 return;
1940 }
1941
1942 zoomToFeatureExtent( selectionExtent );
1943}
1944
1946{
1947 return mSettings.zRange();
1948}
1949
1951{
1952 if ( zRange() == range )
1953 return;
1954
1955 mSettings.setZRange( range );
1956
1957 emit zRangeChanged();
1958
1959 // we need to discard any previously cached images which are elevation aware, so that these will be updated when
1960 // the canvas is redrawn
1961 mCacheInvalidations |= CacheInvalidationType::Elevation;
1962
1963 autoRefreshTriggered();
1964}
1965
1967{
1968 // no selected features, only one selected point feature
1969 //or two point features with the same x- or y-coordinates
1970 if ( rect.isEmpty() )
1971 {
1972 // zoom in
1973 QgsPointXY c = rect.center();
1974 rect = extent();
1975 rect.scale( 1.0, &c );
1976 }
1977 //zoom to an area
1978 else
1979 {
1980 // Expand rect to give a bit of space around the selected
1981 // objects so as to keep them clear of the map boundaries
1982 // The same 5% should apply to all margins.
1983 rect.scale( 1.05 );
1984 }
1985
1986 setExtent( rect, true );
1987 refresh();
1988}
1989
1991{
1992 if ( !layer )
1993 {
1994 return;
1995 }
1996
1997 QgsRectangle bbox;
1998 QString errorMsg;
1999 if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
2000 {
2001 if ( bbox.isEmpty() )
2002 {
2003 bbox = optimalExtentForPointLayer( layer, bbox.center() );
2004 }
2005 zoomToFeatureExtent( bbox );
2006 }
2007 else
2008 {
2009 emit messageEmitted( tr( "Zoom to feature id failed" ), errorMsg, Qgis::MessageLevel::Warning );
2010 }
2011}
2012
2013void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter )
2014{
2015 if ( !layer )
2016 {
2017 return;
2018 }
2019
2020 QgsRectangle bbox;
2021 QString errorMsg;
2022 if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
2023 {
2024 if ( alwaysRecenter || !mapSettings().extent().contains( bbox ) )
2025 setCenter( bbox.center() );
2026 refresh();
2027 }
2028 else
2029 {
2030 emit messageEmitted( tr( "Pan to feature id failed" ), errorMsg, Qgis::MessageLevel::Warning );
2031 }
2032}
2033
2034bool QgsMapCanvas::boundingBoxOfFeatureIds( const QgsFeatureIds &ids, QgsVectorLayer *layer, QgsRectangle &bbox, QString &errorMsg ) const
2035{
2036 QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
2037 bbox.setNull();
2038 QgsFeature fet;
2039 int featureCount = 0;
2040 errorMsg.clear();
2041
2042 while ( it.nextFeature( fet ) )
2043 {
2044 QgsGeometry geom = fet.geometry();
2045 if ( geom.isNull() )
2046 {
2047 errorMsg = tr( "Feature does not have a geometry" );
2048 }
2049 else if ( geom.constGet()->isEmpty() )
2050 {
2051 errorMsg = tr( "Feature geometry is empty" );
2052 }
2053 if ( !errorMsg.isEmpty() )
2054 {
2055 return false;
2056 }
2057 QgsRectangle r = mapSettings().layerExtentToOutputExtent( layer, geom.boundingBox() );
2058 bbox.combineExtentWith( r );
2059 featureCount++;
2060 }
2061
2062 if ( featureCount != ids.count() )
2063 {
2064 errorMsg = tr( "Feature not found" );
2065 return false;
2066 }
2067
2068 return true;
2069}
2070
2072{
2073 if ( !layer )
2074 {
2075 // use current layer by default
2076 layer = mCurrentLayer;
2077 }
2078 if ( !layer || !layer->isSpatial() )
2079 return;
2080
2081 QgsRectangle rect;
2082 switch ( layer->type() )
2083 {
2085 {
2086 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer );
2087 if ( vLayer->selectedFeatureCount() == 0 )
2088 return;
2089
2090 rect = vLayer->boundingBoxOfSelected();
2091 break;
2092 }
2094 {
2095 QgsVectorTileLayer *vtLayer = qobject_cast<QgsVectorTileLayer *>( layer );
2096 if ( vtLayer->selectedFeatureCount() == 0 )
2097 return;
2098
2099 const QList<QgsFeature> selectedFeatures = vtLayer->selectedFeatures();
2100 for ( const QgsFeature &feature : selectedFeatures )
2101 {
2102 if ( !feature.hasGeometry() )
2103 continue;
2104
2105 rect.combineExtentWith( feature.geometry().boundingBox() );
2106 }
2107 break;
2108 }
2109
2117 return;
2118 }
2119
2120 if ( rect.isNull() )
2121 {
2122 emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
2123 return;
2124 }
2125
2127 setCenter( rect.center() );
2128 refresh();
2129}
2130
2131void QgsMapCanvas::panToSelected( const QList<QgsMapLayer *> &layers )
2132{
2133 QgsRectangle selectionExtent;
2134 selectionExtent.setNull();
2135
2136 for ( QgsMapLayer *mapLayer : layers )
2137 {
2138 if ( !mapLayer || !mapLayer->isSpatial() )
2139 continue;
2140
2141 QgsRectangle rect;
2142 switch ( mapLayer->type() )
2143 {
2145 {
2146 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
2147 if ( layer->selectedFeatureCount() == 0 )
2148 continue;
2149
2150 rect = layer->boundingBoxOfSelected();
2151
2152 if ( rect.isNull() )
2153 continue;
2154
2156
2157 if ( layer->geometryType() == Qgis::GeometryType::Point && rect.isEmpty() )
2158 rect = optimalExtentForPointLayer( layer, rect.center() );
2159 break;
2160 }
2161
2163 {
2164 QgsVectorTileLayer *vtLayer = qobject_cast<QgsVectorTileLayer *>( mapLayer );
2165 if ( vtLayer->selectedFeatureCount() == 0 )
2166 continue;
2167
2168 const QList<QgsFeature> selectedFeatures = vtLayer->selectedFeatures();
2169 for ( const QgsFeature &feature : selectedFeatures )
2170 {
2171 if ( !feature.hasGeometry() )
2172 continue;
2173
2174 rect.combineExtentWith( feature.geometry().boundingBox() );
2175 }
2176
2177 rect = mapSettings().layerExtentToOutputExtent( vtLayer, rect );
2178 break;
2179 }
2180
2188 continue;
2189 }
2190
2191 selectionExtent.combineExtentWith( rect );
2192 }
2193
2194 if ( selectionExtent.isNull() )
2195 {
2196 emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
2197 return;
2198 }
2199
2200 setCenter( selectionExtent.center() );
2201 refresh();
2202}
2203
2204void QgsMapCanvas::flashFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, const QColor &color1, const QColor &color2, int flashes, int duration )
2205{
2206 if ( !layer )
2207 {
2208 return;
2209 }
2210
2211 QList<QgsGeometry> geoms;
2212
2213 QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
2214 QgsFeature fet;
2215 while ( it.nextFeature( fet ) )
2216 {
2217 if ( !fet.hasGeometry() )
2218 continue;
2219 geoms << fet.geometry();
2220 }
2221
2222 flashGeometries( geoms, layer->crs(), color1, color2, flashes, duration );
2223}
2224
2225void QgsMapCanvas::flashGeometries( const QList<QgsGeometry> &geometries, const QgsCoordinateReferenceSystem &crs, const QColor &color1, const QColor &color2, int flashes, int duration )
2226{
2227 if ( geometries.isEmpty() )
2228 return;
2229
2230 Qgis::GeometryType geomType = QgsWkbTypes::geometryType( geometries.at( 0 ).wkbType() );
2231 QgsRubberBand *rb = new QgsRubberBand( this, geomType );
2232 for ( const QgsGeometry &geom : geometries )
2233 rb->addGeometry( geom, crs, false );
2234 rb->updatePosition();
2235 rb->update();
2236
2237 if ( geomType == Qgis::GeometryType::Line || geomType == Qgis::GeometryType::Point )
2238 {
2239 rb->setWidth( 2 );
2240 rb->setSecondaryStrokeColor( QColor( 255, 255, 255 ) );
2241 }
2242 if ( geomType == Qgis::GeometryType::Point )
2244
2245 QColor startColor = color1;
2246 if ( !startColor.isValid() )
2247 {
2248 if ( geomType == Qgis::GeometryType::Polygon )
2249 {
2250 startColor = rb->fillColor();
2251 }
2252 else
2253 {
2254 startColor = rb->strokeColor();
2255 }
2256 startColor.setAlpha( 255 );
2257 }
2258 QColor endColor = color2;
2259 if ( !endColor.isValid() )
2260 {
2261 endColor = startColor;
2262 endColor.setAlpha( 0 );
2263 }
2264
2265
2266 QVariantAnimation *animation = new QVariantAnimation( this );
2267 connect( animation, &QVariantAnimation::finished, this, [animation, rb] {
2268 animation->deleteLater();
2269 delete rb;
2270 } );
2271 connect( animation, &QPropertyAnimation::valueChanged, this, [rb, geomType]( const QVariant &value ) {
2272 QColor c = value.value<QColor>();
2273 if ( geomType == Qgis::GeometryType::Polygon )
2274 {
2275 rb->setFillColor( c );
2276 }
2277 else
2278 {
2279 rb->setStrokeColor( c );
2280 QColor c = rb->secondaryStrokeColor();
2281 c.setAlpha( c.alpha() );
2283 }
2284 rb->update();
2285 } );
2286
2287 animation->setDuration( duration * flashes );
2288 animation->setStartValue( endColor );
2289 double midStep = 0.2 / flashes;
2290 for ( int i = 0; i < flashes; ++i )
2291 {
2292 double start = static_cast<double>( i ) / flashes;
2293 animation->setKeyValueAt( start + midStep, startColor );
2294 double end = static_cast<double>( i + 1 ) / flashes;
2295 if ( !qgsDoubleNear( end, 1.0 ) )
2296 animation->setKeyValueAt( end, endColor );
2297 }
2298 animation->setEndValue( endColor );
2299 animation->start();
2300}
2301
2303{
2304 if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )
2305 {
2306 emit keyPressed( e );
2307 return;
2308 }
2309
2310 // Don't want to interfer with mouse events
2311 if ( !mCanvasProperties->mouseButtonDown )
2312 {
2313 // this is backwards, but we can't change now without breaking api because
2314 // forever QgsMapTools have had to explicitly mark events as ignored in order to
2315 // indicate that they've consumed the event and that the default behavior should not
2316 // be applied..!
2317 e->accept();
2318 if ( mMapTool )
2319 {
2320 mMapTool->keyPressEvent( e );
2321 if ( !e->isAccepted() ) // map tool consumed event
2322 return;
2323 }
2324
2325 QgsRectangle currentExtent = mapSettings().visibleExtent();
2326 double dx = std::fabs( currentExtent.width() / 4 );
2327 double dy = std::fabs( currentExtent.height() / 4 );
2328
2329 switch ( e->key() )
2330 {
2331 case Qt::Key_Left:
2332 QgsDebugMsgLevel( QStringLiteral( "Pan left" ), 2 );
2333 setCenter( center() - QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
2334 refresh();
2335 break;
2336
2337 case Qt::Key_Right:
2338 QgsDebugMsgLevel( QStringLiteral( "Pan right" ), 2 );
2339 setCenter( center() + QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
2340 refresh();
2341 break;
2342
2343 case Qt::Key_Up:
2344 QgsDebugMsgLevel( QStringLiteral( "Pan up" ), 2 );
2345 setCenter( center() + QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
2346 refresh();
2347 break;
2348
2349 case Qt::Key_Down:
2350 QgsDebugMsgLevel( QStringLiteral( "Pan down" ), 2 );
2351 setCenter( center() - QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
2352 refresh();
2353 break;
2354
2355 case Qt::Key_Space:
2356 QgsDebugMsgLevel( QStringLiteral( "Pressing pan selector" ), 2 );
2357
2358 //mCanvasProperties->dragging = true;
2359 if ( !e->isAutoRepeat() )
2360 {
2361 mTemporaryCursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::ClosedHandCursor );
2362 mCanvasProperties->panSelectorDown = true;
2363 panActionStart( mCanvasProperties->mouseLastXY );
2364 }
2365 break;
2366
2367 case Qt::Key_PageUp:
2368 QgsDebugMsgLevel( QStringLiteral( "Zoom in" ), 2 );
2369 zoomIn();
2370 break;
2371
2372 case Qt::Key_PageDown:
2373 QgsDebugMsgLevel( QStringLiteral( "Zoom out" ), 2 );
2374 zoomOut();
2375 break;
2376
2377#if 0
2378 case Qt::Key_P:
2379 mUseParallelRendering = !mUseParallelRendering;
2380 refresh();
2381 break;
2382
2383 case Qt::Key_S:
2384 mDrawRenderingStats = !mDrawRenderingStats;
2385 refresh();
2386 break;
2387#endif
2388
2389 default:
2390 // Pass it on
2391 if ( !mMapTool )
2392 {
2393 e->ignore();
2394 QgsDebugMsgLevel( "Ignoring key: " + QString::number( e->key() ), 2 );
2395 }
2396 }
2397 }
2398
2399 emit keyPressed( e );
2400}
2401
2403{
2404 QgsDebugMsgLevel( QStringLiteral( "keyRelease event" ), 2 );
2405
2406 switch ( e->key() )
2407 {
2408 case Qt::Key_Space:
2409 if ( !e->isAutoRepeat() && mCanvasProperties->panSelectorDown )
2410 {
2411 QgsDebugMsgLevel( QStringLiteral( "Releasing pan selector" ), 2 );
2412 mTemporaryCursorOverride.reset();
2413 mCanvasProperties->panSelectorDown = false;
2414 panActionEnd( mCanvasProperties->mouseLastXY );
2415 }
2416 break;
2417
2418 default:
2419 // Pass it on
2420 if ( mMapTool )
2421 {
2422 mMapTool->keyReleaseEvent( e );
2423 }
2424 else
2425 e->ignore();
2426
2427 QgsDebugMsgLevel( "Ignoring key release: " + QString::number( e->key() ), 2 );
2428 }
2429
2430 emit keyReleased( e );
2431
2432} //keyReleaseEvent()
2433
2434
2436{
2437 // call handler of current map tool
2438 if ( mMapTool )
2439 {
2440 auto me = std::make_unique<QgsMapMouseEvent>( this, e );
2441 mMapTool->canvasDoubleClickEvent( me.get() );
2442 }
2443} // mouseDoubleClickEvent
2444
2445
2446void QgsMapCanvas::beginZoomRect( QPoint pos )
2447{
2448 mZoomRect.setRect( 0, 0, 0, 0 );
2449 mTemporaryCursorOverride = std::make_unique<QgsTemporaryCursorOverride>( mZoomCursor );
2450 mZoomDragging = true;
2451 mZoomRubberBand = std::make_unique<QgsRubberBand>( this, Qgis::GeometryType::Polygon );
2452 QColor color( Qt::blue );
2453 color.setAlpha( 63 );
2454 mZoomRubberBand->setColor( color );
2455 mZoomRect.setTopLeft( pos );
2456}
2457
2458void QgsMapCanvas::stopZoomRect()
2459{
2460 if ( mZoomDragging )
2461 {
2462 mZoomDragging = false;
2463 mZoomRubberBand.reset( nullptr );
2464 mTemporaryCursorOverride.reset();
2465 }
2466}
2467
2468void QgsMapCanvas::endZoomRect( QPoint pos )
2469{
2470 stopZoomRect();
2471
2472 // store the rectangle
2473 mZoomRect.setRight( pos.x() );
2474 mZoomRect.setBottom( pos.y() );
2475
2476 //account for bottom right -> top left dragging
2477 mZoomRect = mZoomRect.normalized();
2478
2479 if ( mZoomRect.width() < 5 && mZoomRect.height() < 5 )
2480 {
2481 //probably a mistake - would result in huge zoom!
2482 return;
2483 }
2484
2485 // set center and zoom
2486 const QSize &zoomRectSize = mZoomRect.size();
2487 const QSize &canvasSize = mSettings.outputSize();
2488 double sfx = static_cast<double>( zoomRectSize.width() ) / canvasSize.width();
2489 double sfy = static_cast<double>( zoomRectSize.height() ) / canvasSize.height();
2490 double sf = std::max( sfx, sfy );
2491
2492 QgsPointXY c = mSettings.mapToPixel().toMapCoordinates( mZoomRect.center() );
2493
2494 zoomByFactor( sf, &c );
2495 refresh();
2496}
2497
2498void QgsMapCanvas::startPan()
2499{
2500 if ( !mCanvasProperties->panSelectorDown )
2501 {
2502 mCanvasProperties->panSelectorDown = true;
2503 mTemporaryCursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::ClosedHandCursor );
2504 panActionStart( mCanvasProperties->mouseLastXY );
2505 }
2506}
2507
2508void QgsMapCanvas::stopPan()
2509{
2510 if ( mCanvasProperties->panSelectorDown )
2511 {
2512 mCanvasProperties->panSelectorDown = false;
2513 mTemporaryCursorOverride.reset();
2514 panActionEnd( mCanvasProperties->mouseLastXY );
2515 }
2516}
2517
2518void QgsMapCanvas::mousePressEvent( QMouseEvent *e )
2519{
2520 // use shift+middle mouse button for zooming, map tools won't receive any events in that case
2521 if ( e->button() == Qt::MiddleButton && e->modifiers() & Qt::ShiftModifier )
2522 {
2523 beginZoomRect( e->pos() );
2524 return;
2525 }
2526 //use middle mouse button for panning, map tools won't receive any events in that case
2527 else if ( e->button() == Qt::MiddleButton )
2528 {
2529 startPan();
2530 }
2531 else
2532 {
2533 // If doing a middle-button-click, followed by a right-button-click,
2534 // cancel the pan or zoomRect action started above.
2535 stopPan();
2536 stopZoomRect();
2537
2538 // call handler of current map tool
2539 if ( mMapTool )
2540 {
2541 if ( mMapTool->flags() & QgsMapTool::AllowZoomRect && e->button() == Qt::LeftButton
2542 && e->modifiers() & Qt::ShiftModifier )
2543 {
2544 beginZoomRect( e->pos() );
2545 return;
2546 }
2547 else if ( mMapTool->flags() & QgsMapTool::ShowContextMenu && e->button() == Qt::RightButton )
2548 {
2549 auto me = std::make_unique<QgsMapMouseEvent>( this, e );
2550 showContextMenu( me.get() );
2551 return;
2552 }
2553 else
2554 {
2555 auto me = std::make_unique<QgsMapMouseEvent>( this, e );
2556 mMapTool->canvasPressEvent( me.get() );
2557 }
2558 }
2559 }
2560
2561 if ( mCanvasProperties->panSelectorDown )
2562 {
2563 return;
2564 }
2565
2566 mCanvasProperties->mouseButtonDown = true;
2567 mCanvasProperties->rubberStartPoint = e->pos();
2568}
2569
2571{
2572 // if using shift+middle mouse button for zooming, end zooming and return
2573 if ( mZoomDragging && e->button() == Qt::MiddleButton )
2574 {
2575 endZoomRect( e->pos() );
2576 return;
2577 }
2578 //use middle mouse button for panning, map tools won't receive any events in that case
2579 else if ( e->button() == Qt::MiddleButton )
2580 {
2581 stopPan();
2582 }
2583 else if ( e->button() == Qt::BackButton )
2584 {
2586 return;
2587 }
2588 else if ( e->button() == Qt::ForwardButton )
2589 {
2591 return;
2592 }
2593 else
2594 {
2595 if ( mZoomDragging && e->button() == Qt::LeftButton )
2596 {
2597 endZoomRect( e->pos() );
2598 return;
2599 }
2600
2601 // call handler of current map tool
2602 if ( mMapTool )
2603 {
2604 auto me = std::make_unique<QgsMapMouseEvent>( this, e );
2605 mMapTool->canvasReleaseEvent( me.get() );
2606 }
2607 }
2608
2609
2610 mCanvasProperties->mouseButtonDown = false;
2611
2612 if ( mCanvasProperties->panSelectorDown )
2613 return;
2614}
2615
2616void QgsMapCanvas::resizeEvent( QResizeEvent *e )
2617{
2618 QGraphicsView::resizeEvent( e );
2619 mResizeTimer->start( 500 ); // in charge of refreshing canvas
2620
2621 double oldScale = mSettings.scale();
2622 QSize lastSize = viewport()->size();
2623 mSettings.setOutputSize( lastSize );
2624
2625 mScene->setSceneRect( QRectF( 0, 0, lastSize.width(), lastSize.height() ) );
2626
2627 moveCanvasContents( true );
2628
2629 if ( mScaleLocked )
2630 {
2631 double scaleFactor = oldScale / mSettings.scale();
2632 QgsRectangle r = mSettings.extent();
2633 QgsPointXY center = r.center();
2634 r.scale( scaleFactor, &center );
2635 mSettings.setExtent( r );
2636 }
2637 else
2638 {
2639 updateScale();
2640 }
2641
2643}
2644
2645void QgsMapCanvas::paintEvent( QPaintEvent *e )
2646{
2647 // no custom event handling anymore
2648
2649 QGraphicsView::paintEvent( e );
2650} // paintEvent
2651
2653{
2654 if ( mBlockItemPositionUpdates )
2655 return;
2656
2657 const QList<QGraphicsItem *> items = mScene->items();
2658 for ( QGraphicsItem *gi : items )
2659 {
2660 QgsMapCanvasItem *item = dynamic_cast<QgsMapCanvasItem *>( gi );
2661
2662 if ( item )
2663 {
2664 item->updatePosition();
2665 }
2666 }
2667}
2668
2669
2670void QgsMapCanvas::wheelEvent( QWheelEvent *e )
2671{
2672 // Zoom the map canvas in response to a mouse wheel event. Moving the
2673 // wheel forward (away) from the user zooms in
2674
2675 QgsDebugMsgLevel( "Wheel event delta " + QString::number( e->angleDelta().y() ), 2 );
2676
2677 if ( mMapTool )
2678 {
2679 mMapTool->wheelEvent( e );
2680 if ( e->isAccepted() )
2681 return;
2682 }
2683
2684 if ( e->angleDelta().y() == 0 )
2685 {
2686 e->accept();
2687 return;
2688 }
2689
2690 QgsSettings settings;
2691 bool reverseZoom = settings.value( QStringLiteral( "qgis/reverse_wheel_zoom" ), false ).toBool();
2692 bool zoomIn = reverseZoom ? e->angleDelta().y() < 0 : e->angleDelta().y() > 0;
2693 double zoomFactor = zoomIn ? 1. / zoomInFactor() : zoomOutFactor();
2694
2695 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
2696 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( e->angleDelta().y() );
2697
2698 if ( e->modifiers() & Qt::ControlModifier )
2699 {
2700 //holding ctrl while wheel zooming results in a finer zoom
2701 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
2702 }
2703
2704 double signedWheelFactor = zoomIn ? 1 / zoomFactor : zoomFactor;
2705
2706 // zoom map to mouse cursor by scaling
2707 QgsPointXY oldCenter = center();
2708 QgsPointXY mousePos( getCoordinateTransform()->toMapCoordinates( e->position().x(), e->position().y() ) );
2709 QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * signedWheelFactor ), mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * signedWheelFactor ) );
2710
2711 zoomByFactor( signedWheelFactor, &newCenter );
2712 e->accept();
2713}
2714
2715void QgsMapCanvas::setWheelFactor( double factor )
2716{
2717 mWheelZoomFactor = std::max( factor, 1.01 );
2718}
2719
2721{
2722 // magnification is already handled in zoomByFactor
2724}
2725
2727{
2728 // magnification is already handled in zoomByFactor
2730}
2731
2732void QgsMapCanvas::zoomScale( double newScale, bool ignoreScaleLock )
2733{
2734 zoomByFactor( newScale / scale(), nullptr, ignoreScaleLock );
2735}
2736
2737void QgsMapCanvas::zoomWithCenter( int x, int y, bool zoomIn )
2738{
2739 double scaleFactor = ( zoomIn ? zoomInFactor() : zoomOutFactor() );
2740
2741 // transform the mouse pos to map coordinates
2743
2744 if ( mScaleLocked )
2745 {
2746 ScaleRestorer restorer( this );
2748 }
2749 else
2750 {
2751 zoomByFactor( scaleFactor, &center );
2752 }
2753}
2754
2755void QgsMapCanvas::setScaleLocked( bool isLocked )
2756{
2757 if ( mScaleLocked != isLocked )
2758 {
2759 mScaleLocked = isLocked;
2760 emit scaleLockChanged( mScaleLocked );
2761 }
2762}
2763
2764void QgsMapCanvas::mouseMoveEvent( QMouseEvent *e )
2765{
2766 mCanvasProperties->mouseLastXY = e->pos();
2767
2768 if ( mCanvasProperties->panSelectorDown )
2769 {
2770 panAction( e );
2771 }
2772 else if ( mZoomDragging )
2773 {
2774 mZoomRect.setBottomRight( e->pos() );
2775 mZoomRubberBand->setToCanvasRectangle( mZoomRect );
2776 mZoomRubberBand->show();
2777 }
2778 else
2779 {
2780 // call handler of current map tool
2781 if ( mMapTool )
2782 {
2783 auto me = std::make_unique<QgsMapMouseEvent>( this, e );
2784 mMapTool->canvasMoveEvent( me.get() );
2785 }
2786 }
2787
2788 // show x y on status bar (if we are mid pan operation, then the cursor point hasn't changed!)
2789 if ( !panOperationInProgress() )
2790 {
2791 mCursorPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->mouseLastXY );
2792 emit xyCoordinates( mCursorPoint );
2793 }
2794}
2795
2796void QgsMapCanvas::setMapTool( QgsMapTool *tool, bool clean )
2797{
2798 if ( !tool )
2799 return;
2800
2801 if ( tool == mMapTool )
2802 {
2803 mMapTool->reactivate();
2804 return;
2805 }
2806
2807 if ( mMapTool )
2808 {
2809 if ( clean )
2810 mMapTool->clean();
2811
2812 disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2813 mMapTool->deactivate();
2814 }
2815
2816 QgsMapTool *oldTool = mMapTool;
2817
2818 // set new map tool and activate it
2819 mMapTool = tool;
2820 emit mapToolSet( mMapTool, oldTool );
2821 if ( mMapTool )
2822 {
2823 connect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2824 mMapTool->activate();
2825 }
2826
2827} // setMapTool
2828
2830{
2831 if ( mMapTool && mMapTool == tool )
2832 {
2833 disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2834 QgsMapTool *oldTool = mMapTool;
2835 mMapTool = nullptr;
2836 oldTool->deactivate();
2837 emit mapToolSet( nullptr, oldTool );
2838 setCursor( Qt::ArrowCursor );
2839 }
2840}
2841
2843{
2844 if ( mProject )
2845 disconnect( mProject, &QgsProject::elevationShadingRendererChanged, this, &QgsMapCanvas::onElevationShadingRendererChanged );
2846
2847 mProject = project;
2848
2849 if ( mProject )
2850 connect( mProject, &QgsProject::elevationShadingRendererChanged, this, &QgsMapCanvas::onElevationShadingRendererChanged );
2851}
2852
2853void QgsMapCanvas::setCanvasColor( const QColor &color )
2854{
2855 if ( canvasColor() == color )
2856 return;
2857
2858 // background of map's pixmap
2859 mSettings.setBackgroundColor( color );
2860
2861 // background of the QGraphicsView
2862 QBrush bgBrush( color );
2863 setBackgroundBrush( bgBrush );
2864#if 0
2865 QPalette palette;
2866 palette.setColor( backgroundRole(), color );
2867 setPalette( palette );
2868#endif
2869
2870 // background of QGraphicsScene
2871 mScene->setBackgroundBrush( bgBrush );
2872
2873 refresh();
2874
2875 emit canvasColorChanged();
2876}
2877
2879{
2880 return mScene->backgroundBrush().color();
2881}
2882
2883void QgsMapCanvas::setSelectionColor( const QColor &color )
2884{
2885 if ( mSettings.selectionColor() == color )
2886 return;
2887
2888 mSettings.setSelectionColor( color );
2889
2890 if ( mCache )
2891 {
2892 bool hasSelectedFeatures = false;
2893 const auto layers = mSettings.layers();
2894 for ( QgsMapLayer *layer : layers )
2895 {
2896 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
2897 if ( vlayer && vlayer->selectedFeatureCount() )
2898 {
2899 hasSelectedFeatures = true;
2900 break;
2901 }
2902 }
2903
2904 if ( hasSelectedFeatures )
2905 {
2906 mCache->clear();
2907 refresh();
2908 }
2909 }
2910}
2911
2913{
2914 return mSettings.selectionColor();
2915}
2916
2918{
2919 return mapSettings().layers().size();
2920}
2921
2922QList<QgsMapLayer *> QgsMapCanvas::layers( bool expandGroupLayers ) const
2923{
2924 return mapSettings().layers( expandGroupLayers );
2925}
2926
2928{
2929 // called when a layer has changed visibility setting
2930 refresh();
2931}
2932
2933void QgsMapCanvas::freeze( bool frozen )
2934{
2935 mFrozen = frozen;
2936}
2937
2939{
2940 return mFrozen;
2941}
2942
2944{
2945 return mapSettings().mapUnitsPerPixel();
2946}
2947
2952
2953QMap<QString, QString> QgsMapCanvas::layerStyleOverrides() const
2954{
2955 return mSettings.layerStyleOverrides();
2956}
2957
2958void QgsMapCanvas::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
2959{
2960 if ( overrides == mSettings.layerStyleOverrides() )
2961 return;
2962
2963 mSettings.setLayerStyleOverrides( overrides );
2964 clearCache();
2966}
2967
2968void QgsMapCanvas::setTheme( const QString &theme )
2969{
2970 if ( mTheme == theme )
2971 return;
2972
2973 clearCache();
2974 if ( theme.isEmpty() || !QgsProject::instance()->mapThemeCollection()->hasMapTheme( theme ) )
2975 {
2976 mTheme.clear();
2977 mSettings.setLayerStyleOverrides( QMap<QString, QString>() );
2978 setLayers( QgsProject::instance()->mapThemeCollection()->masterVisibleLayers() );
2979 emit themeChanged( QString() );
2980 }
2981 else
2982 {
2983 mTheme = theme;
2984 setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
2985 emit themeChanged( theme );
2986 }
2987}
2988
2990{
2991 mRenderFlag = flag;
2992
2993 if ( mRenderFlag )
2994 {
2995 refresh();
2996 }
2997 else
2998 stopRendering();
2999}
3000
3001#if 0
3002void QgsMapCanvas::connectNotify( const char *signal )
3003{
3004 Q_UNUSED( signal )
3005 QgsDebugMsgLevel( "QgsMapCanvas connected to " + QString( signal ), 2 );
3006} //connectNotify
3007#endif
3008
3009void QgsMapCanvas::layerRepaintRequested( bool deferred )
3010{
3011 if ( !deferred )
3012 refresh();
3013}
3014
3015void QgsMapCanvas::autoRefreshTriggered()
3016{
3017 if ( mJob )
3018 {
3019 // canvas is currently being redrawn, so we defer the last requested
3020 // auto refresh until current rendering job finishes
3021 mRefreshAfterJob = true;
3022 return;
3023 }
3024
3025 refresh();
3026}
3027
3028void QgsMapCanvas::updateAutoRefreshTimer()
3029{
3030 // min auto refresh interval stores the smallest interval between layer auto refreshes. We automatically
3031 // trigger a map refresh on this minimum interval
3032 int minAutoRefreshInterval = -1;
3033 const auto layers = mSettings.layers();
3034 for ( QgsMapLayer *layer : layers )
3035 {
3036 int layerRefreshInterval = 0;
3037
3038 if ( layer->hasAutoRefreshEnabled() && layer->autoRefreshInterval() > 0 )
3039 {
3040 layerRefreshInterval = layer->autoRefreshInterval();
3041 }
3042 else if ( QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer ) )
3043 {
3044 if ( const QgsFeatureRenderer *renderer = vectorLayer->renderer() )
3045 {
3046 const double rendererRefreshRate = QgsSymbolLayerUtils::rendererFrameRate( renderer );
3047 if ( rendererRefreshRate > 0 )
3048 {
3049 layerRefreshInterval = 1000 / rendererRefreshRate;
3050 }
3051 }
3052 }
3053
3054 if ( layerRefreshInterval == 0 )
3055 continue;
3056
3057 minAutoRefreshInterval = minAutoRefreshInterval > 0 ? std::min( layerRefreshInterval, minAutoRefreshInterval ) : layerRefreshInterval;
3058 }
3059
3060 if ( minAutoRefreshInterval > 0 )
3061 {
3062 mAutoRefreshTimer.setInterval( minAutoRefreshInterval );
3063 mAutoRefreshTimer.start();
3064 }
3065 else
3066 {
3067 mAutoRefreshTimer.stop();
3068 }
3069}
3070
3071void QgsMapCanvas::projectThemesChanged()
3072{
3073 if ( mTheme.isEmpty() )
3074 return;
3075
3076 if ( !QgsProject::instance()->mapThemeCollection()->hasMapTheme( mTheme ) )
3077 {
3078 // theme has been removed - stop following
3079 setTheme( QString() );
3080 }
3081}
3082
3084{
3085 return mMapTool;
3086}
3087
3089{
3090 return mProject;
3091}
3092
3093void QgsMapCanvas::panActionEnd( QPoint releasePoint )
3094{
3095 // move map image and other items to standard position
3096 moveCanvasContents( true ); // true means reset
3097
3098 // use start and end box points to calculate the extent
3100 QgsPointXY end = getCoordinateTransform()->toMapCoordinates( releasePoint );
3101
3102 // modify the center
3103 double dx = end.x() - start.x();
3104 double dy = end.y() - start.y();
3105 QgsPointXY c = center();
3106 c.set( c.x() - dx, c.y() - dy );
3107 setCenter( c );
3108
3109 refresh();
3110}
3111
3112void QgsMapCanvas::panActionStart( QPoint releasePoint )
3113{
3114 mCanvasProperties->rubberStartPoint = releasePoint;
3115
3116 mDa = QgsDistanceArea();
3117 mDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
3118 mDa.setSourceCrs( mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
3119}
3120
3121void QgsMapCanvas::panAction( QMouseEvent *e )
3122{
3123 Q_UNUSED( e )
3124
3125 QgsPointXY currentMapPoint = getCoordinateTransform()->toMapCoordinates( e->pos() );
3126 QgsPointXY startMapPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
3127 try
3128 {
3129 emit panDistanceBearingChanged( mDa.measureLine( currentMapPoint, startMapPoint ), mDa.lengthUnits(), mDa.bearing( currentMapPoint, startMapPoint ) * 180 / M_PI );
3130 }
3131 catch ( QgsCsException & )
3132 {}
3133
3134 // move all map canvas items
3136}
3137
3139{
3140 QPoint pnt( 0, 0 );
3141 if ( !reset )
3142 pnt += mCanvasProperties->mouseLastXY - mCanvasProperties->rubberStartPoint;
3143
3144 setSceneRect( -pnt.x(), -pnt.y(), viewport()->size().width(), viewport()->size().height() );
3145}
3146
3148{
3149 if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
3150 {
3152 bool allHandled = true;
3153 for ( const QgsMimeDataUtils::Uri &uri : lst )
3154 {
3155 bool handled = false;
3156 for ( QgsCustomDropHandler *handler : std::as_const( mDropHandlers ) )
3157 {
3158 if ( handler && handler->customUriProviderKey() == uri.providerKey )
3159 {
3160 if ( handler->handleCustomUriCanvasDrop( uri, this ) )
3161 {
3162 handled = true;
3163 break;
3164 }
3165 }
3166 }
3167 if ( !handled )
3168 allHandled = false;
3169 }
3170 if ( allHandled )
3171 event->accept();
3172 else
3173 event->ignore();
3174 }
3175 else
3176 {
3177 event->ignore();
3178 }
3179}
3180
3182{
3183 Q_UNUSED( event )
3184 updateDevicePixelFromScreen();
3185}
3186
3188{
3189 if ( !mBlockExtentChangedSignal )
3190 emit extentsChanged();
3191}
3192
3194{
3195 return mCanvasProperties->mouseLastXY;
3196}
3197
3198void QgsMapCanvas::setPreviewModeEnabled( bool previewEnabled )
3199{
3200 if ( !mPreviewEffect )
3201 {
3202 return;
3203 }
3204
3205 mPreviewEffect->setEnabled( previewEnabled );
3206}
3207
3209{
3210 if ( !mPreviewEffect )
3211 {
3212 return false;
3213 }
3214
3215 return mPreviewEffect->isEnabled();
3216}
3217
3219{
3220 if ( !mPreviewEffect )
3221 {
3222 return;
3223 }
3224
3225 mPreviewEffect->setMode( mode );
3226}
3227
3229{
3230 if ( !mPreviewEffect )
3231 {
3233 }
3234
3235 return mPreviewEffect->mode();
3236}
3237
3239{
3240 if ( !mSnappingUtils )
3241 {
3242 // associate a dummy instance, but better than null pointer
3243 QgsMapCanvas *c = const_cast<QgsMapCanvas *>( this );
3244 c->mSnappingUtils = new QgsMapCanvasSnappingUtils( c, c );
3245 }
3246 return mSnappingUtils;
3247}
3248
3250{
3251 mSnappingUtils = utils;
3252}
3253
3254void QgsMapCanvas::readProject( const QDomDocument &doc )
3255{
3256 QgsProject *project = qobject_cast<QgsProject *>( sender() );
3257
3258 QDomNodeList nodes = doc.elementsByTagName( QStringLiteral( "mapcanvas" ) );
3259 if ( nodes.count() )
3260 {
3261 QDomNode node = nodes.item( 0 );
3262
3263 // Search the specific MapCanvas node using the name
3264 if ( nodes.count() > 1 )
3265 {
3266 for ( int i = 0; i < nodes.size(); ++i )
3267 {
3268 QDomElement elementNode = nodes.at( i ).toElement();
3269
3270 if ( elementNode.hasAttribute( QStringLiteral( "name" ) ) && elementNode.attribute( QStringLiteral( "name" ) ) == objectName() )
3271 {
3272 node = nodes.at( i );
3273 break;
3274 }
3275 }
3276 }
3277
3278 QgsMapSettings tmpSettings;
3279 tmpSettings.readXml( node );
3280 if ( objectName() != QLatin1String( "theMapCanvas" ) )
3281 {
3282 // never manually set the crs for the main canvas - this is instead connected to the project CRS
3283 setDestinationCrs( tmpSettings.destinationCrs() );
3284 }
3285 if ( QgsProject::instance()->viewSettings()->restoreProjectExtentOnProjectLoad() && objectName() == QLatin1String( "theMapCanvas" ) )
3286 {
3288 }
3289 else
3290 {
3291 setExtent( tmpSettings.extent() );
3292 }
3293 setRotation( tmpSettings.rotation() );
3295
3296 clearExtentHistory(); // clear the extent history on project load
3297
3298 QDomElement elem = node.toElement();
3299 if ( elem.hasAttribute( QStringLiteral( "theme" ) ) )
3300 {
3301 if ( QgsProject::instance()->mapThemeCollection()->hasMapTheme( elem.attribute( QStringLiteral( "theme" ) ) ) )
3302 {
3303 setTheme( elem.attribute( QStringLiteral( "theme" ) ) );
3304 }
3305 }
3306 setAnnotationsVisible( elem.attribute( QStringLiteral( "annotationsVisible" ), QStringLiteral( "1" ) ).toInt() );
3307
3308 // restore canvas expression context
3309 const QDomNodeList scopeElements = elem.elementsByTagName( QStringLiteral( "expressionContextScope" ) );
3310 if ( scopeElements.size() > 0 )
3311 {
3312 const QDomElement scopeElement = scopeElements.at( 0 ).toElement();
3313 mExpressionContextScope.readXml( scopeElement, QgsReadWriteContext() );
3314 }
3315 }
3316 else
3317 {
3318 QgsDebugMsgLevel( QStringLiteral( "Couldn't read mapcanvas information from project" ), 2 );
3319 if ( !project->viewSettings()->defaultViewExtent().isNull() )
3320 {
3321 setReferencedExtent( project->viewSettings()->defaultViewExtent() );
3322 }
3323
3324 setRotation( project->viewSettings()->defaultRotation() );
3325 clearExtentHistory(); // clear the extent history on project load
3326 }
3327}
3328
3329void QgsMapCanvas::writeProject( QDomDocument &doc )
3330{
3331 // create node "mapcanvas" and call mMapRenderer->writeXml()
3332
3333 QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
3334 if ( !nl.count() )
3335 {
3336 QgsDebugError( QStringLiteral( "Unable to find qgis element in project file" ) );
3337 return;
3338 }
3339 QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
3340
3341 QDomElement mapcanvasNode = doc.createElement( QStringLiteral( "mapcanvas" ) );
3342 mapcanvasNode.setAttribute( QStringLiteral( "name" ), objectName() );
3343 if ( !mTheme.isEmpty() )
3344 mapcanvasNode.setAttribute( QStringLiteral( "theme" ), mTheme );
3345 mapcanvasNode.setAttribute( QStringLiteral( "annotationsVisible" ), mAnnotationsVisible );
3346 qgisNode.appendChild( mapcanvasNode );
3347
3348 mSettings.writeXml( mapcanvasNode, doc );
3349
3350 // store canvas expression context
3351 QDomElement scopeElement = doc.createElement( QStringLiteral( "expressionContextScope" ) );
3352 QgsExpressionContextScope tmpScope( mExpressionContextScope );
3353 tmpScope.removeVariable( QStringLiteral( "atlas_featurenumber" ) );
3354 tmpScope.removeVariable( QStringLiteral( "atlas_pagename" ) );
3355 tmpScope.removeVariable( QStringLiteral( "atlas_feature" ) );
3356 tmpScope.removeVariable( QStringLiteral( "atlas_featureid" ) );
3357 tmpScope.removeVariable( QStringLiteral( "atlas_geometry" ) );
3358 tmpScope.writeXml( scopeElement, doc, QgsReadWriteContext() );
3359 mapcanvasNode.appendChild( scopeElement );
3360
3361 // TODO: store only units, extent, projections, dest CRS
3362}
3363
3364void QgsMapCanvas::zoomByFactor( double scaleFactor, const QgsPointXY *center, bool ignoreScaleLock )
3365{
3366 if ( mScaleLocked && !ignoreScaleLock )
3367 {
3368 ScaleRestorer restorer( this );
3370 }
3371 else
3372 {
3374 r.scale( scaleFactor, center );
3375 setExtent( r, true );
3376 refresh();
3377 }
3378}
3379
3381{
3382 // Find out which layer it was that sent the signal.
3383 QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
3384 if ( layer )
3385 {
3386 emit selectionChanged( layer );
3387 refresh();
3388 }
3389}
3390
3391void QgsMapCanvas::dragEnterEvent( QDragEnterEvent *event )
3392{
3393 // By default graphics view delegates the drag events to graphics items.
3394 // But we do not want that and by ignoring the drag enter we let the
3395 // parent (e.g. QgisApp) to handle drops of map layers etc.
3396
3397 // so we ONLY accept the event if we know in advance that a custom drop handler
3398 // wants it
3399
3400 if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
3401 {
3403 bool allHandled = true;
3404 for ( const QgsMimeDataUtils::Uri &uri : lst )
3405 {
3406 bool handled = false;
3407 for ( QgsCustomDropHandler *handler : std::as_const( mDropHandlers ) )
3408 {
3409 if ( handler->canHandleCustomUriCanvasDrop( uri, this ) )
3410 {
3411 handled = true;
3412 break;
3413 }
3414 }
3415 if ( !handled )
3416 allHandled = false;
3417 }
3418 if ( allHandled )
3419 event->accept();
3420 else
3421 event->ignore();
3422 }
3423 else
3424 {
3425 event->ignore();
3426 }
3427}
3428
3430{
3431 if ( event->type() == QEvent::ToolTip && mMapTool && mMapTool->canvasToolTipEvent( qgis::down_cast<QHelpEvent *>( event ) ) )
3432 {
3433 return true;
3434 }
3435 return QGraphicsView::viewportEvent( event );
3436}
3437
3438void QgsMapCanvas::mapToolDestroyed()
3439{
3440 QgsDebugMsgLevel( QStringLiteral( "maptool destroyed" ), 2 );
3441 mMapTool = nullptr;
3442}
3443
3444bool QgsMapCanvas::event( QEvent *e )
3445{
3446 if ( e->type() == QEvent::Gesture )
3447 {
3448 if ( QTapAndHoldGesture *tapAndHoldGesture = qobject_cast<QTapAndHoldGesture *>( static_cast<QGestureEvent *>( e )->gesture( Qt::TapAndHoldGesture ) ) )
3449 {
3450 QPointF pos = tapAndHoldGesture->position();
3451 pos = mapFromGlobal( QPoint( pos.x(), pos.y() ) );
3452 QgsPointXY mapPoint = getCoordinateTransform()->toMapCoordinates( pos.x(), pos.y() );
3453 emit tapAndHoldGestureOccurred( mapPoint, tapAndHoldGesture );
3454 }
3455
3456 // call handler of current map tool
3457 if ( mMapTool )
3458 {
3459 return mMapTool->gestureEvent( static_cast<QGestureEvent *>( e ) );
3460 }
3461 }
3462
3463 // pass other events to base class
3464 return QGraphicsView::event( e );
3465}
3466
3468{
3469 // reload all layers in canvas
3470 const QList<QgsMapLayer *> layers = mapSettings().layers();
3471 for ( QgsMapLayer *layer : layers )
3472 {
3473 layer->reload();
3474 }
3475
3477}
3478
3480{
3481 // clear the cache
3482 clearCache();
3483
3484 // and then refresh
3485 refresh();
3486}
3487
3489{
3490 while ( mRefreshScheduled || mJob )
3491 {
3492 QgsApplication::processEvents();
3493 }
3494}
3495
3497{
3498 mSettings.setSegmentationTolerance( tolerance );
3499}
3500
3502{
3503 mSettings.setSegmentationToleranceType( type );
3504}
3505
3506QList<QgsMapCanvasAnnotationItem *> QgsMapCanvas::annotationItems() const
3507{
3508 QList<QgsMapCanvasAnnotationItem *> annotationItemList;
3509 const QList<QGraphicsItem *> items = mScene->items();
3510 for ( QGraphicsItem *gi : items )
3511 {
3512 QgsMapCanvasAnnotationItem *aItem = dynamic_cast<QgsMapCanvasAnnotationItem *>( gi );
3513 if ( aItem )
3514 {
3515 annotationItemList.push_back( aItem );
3516 }
3517 }
3518
3519 return annotationItemList;
3520}
3521
3523{
3524 mAnnotationsVisible = show;
3525 const QList<QgsMapCanvasAnnotationItem *> items = annotationItems();
3526 for ( QgsMapCanvasAnnotationItem *item : items )
3527 {
3528 item->setVisible( show );
3529 }
3530}
3531
3533{
3534 mSettings.setLabelingEngineSettings( settings );
3535}
3536
3538{
3539 return mSettings.labelingEngineSettings();
3540}
3541
3542void QgsMapCanvas::startPreviewJobs()
3543{
3544 stopPreviewJobs(); //just in case still running
3545 schedulePreviewJob( 0 );
3546}
3547
3548void QgsMapCanvas::startPreviewJob( int number )
3549{
3550 if ( number == 4 )
3551 number += 1;
3552
3553 int j = number / 3;
3554 int i = number % 3;
3555
3556 QgsMapSettings mapSettings = mSettings;
3557 mapSettings.setRotation( 0 );
3558 const QgsRectangle mapRect = mapSettings.visibleExtent();
3559 QgsPointXY jobCenter = mapRect.center();
3560 const double dx = ( i - 1 ) * mapRect.width();
3561 const double dy = ( 1 - j ) * mapRect.height();
3562 if ( !qgsDoubleNear( mSettings.rotation(), 0.0 ) )
3563 {
3564 const double radians = mSettings.rotation() * M_PI / 180;
3565 const double rdx = dx * cos( radians ) - dy * sin( radians );
3566 const double rdy = dy * cos( radians ) + dx * sin( radians );
3567 jobCenter.setX( jobCenter.x() + rdx );
3568 jobCenter.setY( jobCenter.y() + rdy );
3569 }
3570 else
3571 {
3572 jobCenter.setX( jobCenter.x() + dx );
3573 jobCenter.setY( jobCenter.y() + dy );
3574 }
3575 const QgsRectangle jobExtent = QgsRectangle::fromCenterAndSize( jobCenter, mapRect.width(), mapRect.height() );
3576
3577 //copy settings, only update extent
3578 QgsMapSettings jobSettings = mSettings;
3579 jobSettings.setExtent( jobExtent );
3580 jobSettings.setFlag( Qgis::MapSettingsFlag::DrawLabeling, false );
3582 // never profile preview jobs
3583 jobSettings.setFlag( Qgis::MapSettingsFlag::RecordProfile, false );
3584
3585 // truncate preview layers to fast layers
3586 const QList<QgsMapLayer *> layers = jobSettings.layers();
3587 QList<QgsMapLayer *> previewLayers;
3588 QgsDataProvider::PreviewContext context;
3590 for ( QgsMapLayer *layer : layers )
3591 {
3592 if ( layer->customProperty( QStringLiteral( "rendering/noPreviewJobs" ), false ).toBool() )
3593 {
3594 QgsDebugMsgLevel( QStringLiteral( "Layer %1 not rendered because it is explicitly blocked from preview jobs" ).arg( layer->id() ), 3 );
3595 continue;
3596 }
3597 context.lastRenderingTimeMs = mLastLayerRenderTime.value( layer->id(), 0 );
3598 QgsDataProvider *provider = layer->dataProvider();
3599 if ( provider && !provider->renderInPreview( context ) )
3600 {
3601 QgsDebugMsgLevel( QStringLiteral( "Layer %1 not rendered because it does not match the renderInPreview criterion %2" ).arg( layer->id() ).arg( mLastLayerRenderTime.value( layer->id() ) ), 3 );
3602 continue;
3603 }
3604
3605 previewLayers << layer;
3606 }
3608 && QgsProject::instance()->mainAnnotationLayer()->dataProvider()->renderInPreview( context ) )
3609 {
3610 previewLayers.insert( 0, QgsProject::instance()->mainAnnotationLayer() );
3611 }
3612 jobSettings.setLayers( filterLayersForRender( previewLayers ) );
3613
3614 QgsMapRendererQImageJob *job = new QgsMapRendererSequentialJob( jobSettings );
3615 job->setProperty( "number", number );
3616 mPreviewJobs.append( job );
3617 connect( job, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
3618 job->start();
3619}
3620
3621void QgsMapCanvas::stopPreviewJobs()
3622{
3623 mPreviewTimer.stop();
3624 for ( auto previewJob = mPreviewJobs.constBegin(); previewJob != mPreviewJobs.constEnd(); ++previewJob )
3625 {
3626 if ( *previewJob )
3627 {
3628 disconnect( *previewJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
3629 connect( *previewJob, &QgsMapRendererQImageJob::finished, *previewJob, &QgsMapRendererQImageJob::deleteLater );
3630 ( *previewJob )->cancelWithoutBlocking();
3631 }
3632 }
3633 mPreviewJobs.clear();
3634}
3635
3636void QgsMapCanvas::schedulePreviewJob( int number )
3637{
3638 mPreviewTimer.setSingleShot( true );
3639 mPreviewTimer.setInterval( Qgis::PREVIEW_JOB_DELAY_MS );
3640 disconnect( mPreviewTimerConnection );
3641 mPreviewTimerConnection = connect( &mPreviewTimer, &QTimer::timeout, this, [this, number]() {
3642 startPreviewJob( number );
3643 } );
3644 mPreviewTimer.start();
3645}
3646
3647bool QgsMapCanvas::panOperationInProgress()
3648{
3649 if ( mCanvasProperties->panSelectorDown )
3650 return true;
3651
3652 if ( QgsMapToolPan *panTool = qobject_cast<QgsMapToolPan *>( mMapTool ) )
3653 {
3654 if ( panTool->isDragging() )
3655 return true;
3656 }
3657
3658 return false;
3659}
3660
3661int QgsMapCanvas::nextZoomLevel( const QList<double> &resolutions, bool zoomIn ) const
3662{
3663 int resolutionLevel = -1;
3664 double currentResolution = mapUnitsPerPixel();
3665 int nResolutions = resolutions.size();
3666
3667 for ( int i = 0; i < nResolutions; ++i )
3668 {
3669 if ( qgsDoubleNear( resolutions[i], currentResolution, 0.0001 ) )
3670 {
3671 resolutionLevel = zoomIn ? ( i - 1 ) : ( i + 1 );
3672 break;
3673 }
3674 else if ( currentResolution <= resolutions[i] )
3675 {
3676 resolutionLevel = zoomIn ? ( i - 1 ) : i;
3677 break;
3678 }
3679 resolutionLevel = zoomIn ? i : i + 1;
3680 }
3681
3682 if ( resolutionLevel < 0 || resolutionLevel >= nResolutions )
3683 {
3684 return -1;
3685 }
3686 if ( zoomIn && resolutionLevel == nResolutions - 1 && resolutions[nResolutions - 1] < currentResolution / mWheelZoomFactor )
3687 {
3688 // Avoid jumping straight to last resolution when zoomed far out and zooming in
3689 return -1;
3690 }
3691 if ( !zoomIn && resolutionLevel == 0 && resolutions[0] > mWheelZoomFactor * currentResolution )
3692 {
3693 // Avoid jumping straight to first resolution when zoomed far in and zooming out
3694 return -1;
3695 }
3696 return resolutionLevel;
3697}
3698
3700{
3701 if ( !mZoomResolutions.isEmpty() )
3702 {
3703 int zoomLevel = nextZoomLevel( mZoomResolutions, true );
3704 if ( zoomLevel != -1 )
3705 {
3706 return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
3707 }
3708 }
3709 return 1 / mWheelZoomFactor;
3710}
3711
3713{
3714 if ( !mZoomResolutions.isEmpty() )
3715 {
3716 int zoomLevel = nextZoomLevel( mZoomResolutions, false );
3717 if ( zoomLevel != -1 )
3718 {
3719 return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
3720 }
3721 }
3722 return mWheelZoomFactor;
3723}
static const int PREVIEW_JOB_DELAY_MS
Delay between the scheduling of 2 preview jobs.
Definition qgis.h:6249
QFlags< MapSettingsFlag > MapSettingsFlags
Map settings flags.
Definition qgis.h:2738
@ MediumString
A medium-length string, recommended for general purpose use.
Definition qgis.h:2419
DistanceUnit
Units of distance.
Definition qgis.h:5013
@ Degrees
Degrees, for planar geographic CRS distance measurements.
Definition qgis.h:5020
@ ShowMainAnnotationLayer
The project's main annotation layer should be rendered in the canvas.
Definition qgis.h:3459
static const int MAXIMUM_LAYER_PREVIEW_TIME_MS
Maximum rendering time for a layer of a preview job.
Definition qgis.h:6252
@ Animated
Temporal navigation relies on frames within a datetime range.
Definition qgis.h:2536
@ Movie
Movie mode – behaves like a video player, with a fixed frame duration and no temporal range.
Definition qgis.h:2538
@ FixedRange
Temporal navigation relies on a fixed datetime range.
Definition qgis.h:2537
@ Disabled
Temporal navigation is disabled.
Definition qgis.h:2535
@ Warning
Warning message.
Definition qgis.h:158
@ AffectsLabeling
If present, indicates that the renderer will participate in the map labeling problem.
Definition qgis.h:829
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:358
@ Point
Points.
Definition qgis.h:359
@ Line
Lines.
Definition qgis.h:360
@ Polygon
Polygons.
Definition qgis.h:361
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:198
@ Plugin
Plugin based layer.
Definition qgis.h:193
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:199
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:196
@ Vector
Vector layer.
Definition qgis.h:191
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:195
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:194
@ Raster
Raster layer.
Definition qgis.h:192
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:197
@ YX
Northing/Easting (or Latitude/Longitude for geographic CRS).
Definition qgis.h:2405
@ View
Renderer used for displaying on screen.
Definition qgis.h:3446
QFlags< MapCanvasFlag > MapCanvasFlags
Flags controlling behavior of map canvases.
Definition qgis.h:3468
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
Definition qgis.h:2439
@ BallparkTransformsAreAppropriate
Indicates that approximate "ballpark" results are appropriate for this coordinate transform....
Definition qgis.h:2684
@ IgnoreImpossibleTransformations
Indicates that impossible transformations (such as those which attempt to transform between two diffe...
Definition qgis.h:2685
@ DrawEditingInfo
Enable drawing of vertex markers for layers in editing mode.
Definition qgis.h:2718
@ RenderPreviewJob
Render is a 'canvas preview' render, and shortcuts should be taken to ensure fast rendering.
Definition qgis.h:2727
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
Definition qgis.h:2725
@ RecordProfile
Enable run-time profiling while rendering.
Definition qgis.h:2734
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
Definition qgis.h:2722
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
Definition qgis.h:2726
@ Antialiasing
Enable anti-aliasing for map rendering.
Definition qgis.h:2717
@ DrawLabeling
Enable drawing of labels on top of the map.
Definition qgis.h:2721
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
Definition qgis.h:2731
Abstract base class for all 2D map controllers.
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
@ MaximumAngle
Maximum angle between generating radii (lines from arc center to output vertices).
virtual bool isEmpty() const
Returns true if the geometry is empty.
Represents a map layer containing a set of georeferenced annotations, e.g.
static QCursor getThemeCursor(Cursor cursor)
Helper to get a theme cursor.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsRuntimeProfiler * profiler()
Returns the application runtime profiler.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QgsCoordinateReferenceSystemRegistry * coordinateReferenceSystemRegistry()
Returns the application's coordinate reference system (CRS) registry, which handles known CRS definit...
void userCrsChanged(const QString &id)
Emitted whenever an existing user CRS definition is changed.
static Qgis::CoordinateOrder defaultCoordinateOrderForCrs(const QgsCoordinateReferenceSystem &crs)
Returns the default coordinate order to use for the specified crs.
static QString axisDirectionToAbbreviatedString(Qgis::CrsAxisDirection axis)
Returns a translated abbreviation representing an axis direction.
Represents a coordinate reference system (CRS).
void updateDefinition()
Updates the definition and parameters of the coordinate reference system to their latest values.
Handles coordinate transforms between two coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Abstract base class that may be implemented to handle new types of data to be dropped in QGIS.
virtual bool renderInPreview(const QgsDataProvider::PreviewContext &context)
Returns whether the layer must be rendered in preview jobs.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
QgsRange which stores a range of double values.
Definition qgsrange.h:233
QString what() const
Abstract interface for generating an expression context scope.
Single scope for storing variables and functions for use within a QgsExpressionContext.
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const
Writes scope variables to an XML element.
bool removeVariable(const QString &name)
Removes a variable from the context scope, if found.
void setVariable(const QString &name, const QVariant &value, bool isStatic=false)
Convenience method for setting a variable in the context scope by name name and value.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * atlasScope(const QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
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...
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setLimit(long long limit)
Set the maximum number of features to request.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsGeometry geometry
Definition qgsfeature.h:69
bool hasGeometry() const
Returns true if the feature has an associated geometry.
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
void remoteImageFetched(const QString &url)
Emitted when the cache has finished retrieving an image file from a remote url.
Stores global configuration for labeling engine.
Stores computed placement from labeling engine.
static void warning(const QString &msg)
Goes to qWarning.
An interactive map canvas item which displays a QgsAnnotation.
An interface for objects which block interactions with a QgsMapCanvas.
Interaction
Available interactions to block.
An abstract class for items that can be placed on the map canvas.
virtual void updatePosition()
called on changed extent or resize event to update position of the item
Snapping utils instance that is connected to a canvas and updates the configuration (map settings + c...
Deprecated to be deleted, stuff from here should be moved elsewhere.
QPoint mouseLastXY
Last seen point of the mouse.
bool panSelectorDown
Flag to indicate the pan selector key is held down by user.
CanvasProperties()=default
Constructor for CanvasProperties.
QPoint rubberStartPoint
Beginning point of a rubber band.
bool mouseButtonDown
Flag to indicate status of mouse button.
void setCurrentLayer(QgsMapLayer *layer)
void contextMenuAboutToShow(QMenu *menu, QgsMapMouseEvent *event)
Emitted before the map canvas context menu will be shown.
void zoomToProjectExtent()
Zoom to the full extent the project associated with this canvas.
void panToSelected(QgsMapLayer *layer=nullptr)
Pan to the selected features of current ayer keeping same extent.
void freeze(bool frozen=true)
Freezes/thaws the map canvas.
void enableAntiAliasing(bool flag)
used to determine if anti-aliasing is enabled or not
void zoomToSelected(QgsMapLayer *layer=nullptr)
Zoom to the extent of the selected features of provided map layer.
void setSnappingUtils(QgsSnappingUtils *utils)
Assign an instance of snapping utils to the map canvas.
bool isCachingEnabled() const
Check whether images of rendered layers are curerently being cached.
void zoomToFullExtent()
Zoom to the full extent of all layers currently visible in the canvas.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers that should be shown in the canvas.
void setProject(QgsProject *project)
Sets the project linked to this canvas.
const QgsRenderedItemResults * renderedItemResults(bool allowOutdatedResults=true) const
Gets access to the rendered item results (may be nullptr), which includes the results of rendering an...
void setMapController(QgsAbstract2DMapController *controller)
Sets the input controller device to use for controlling the canvas.
QColor selectionColor() const
Returns color for selected features.
bool event(QEvent *e) override
~QgsMapCanvas() override
void setCachingEnabled(bool enabled)
Set whether to cache images of rendered layers.
void mouseReleaseEvent(QMouseEvent *e) override
QgsDoubleRange zRange() const
Returns the range of z-values which will be visible in the map.
void setExtent(const QgsRectangle &r, bool magnified=false)
Sets the extent of the map canvas to the specified rectangle.
void setRenderFlag(bool flag)
Sets whether a user has disabled canvas renders via the GUI.
void selectionChanged(QgsMapLayer *layer)
Emitted when selection in any layer gets changed.
QList< QgsMapCanvasAnnotationItem * > annotationItems() const
Returns a list of all annotation items in the canvas.
void updateCanvasItemPositions()
called on resize or changed extent to notify canvas items to change their rectangle
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void extentsChanged()
Emitted when the extents of the map change.
QgsExpressionContextScope * defaultExpressionContextScope() const
Creates a new scope which contains default variables and functions relating to the map canvas.
void xyCoordinates(const QgsPointXY &p)
Emits current mouse position.
void setFlags(Qgis::MapCanvasFlags flags)
Sets flags which control how the map canvas behaves.
void stopRendering()
stop rendering (if there is any right now)
bool previewJobsEnabled
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets global labeling engine settings in the internal map settings.
QgsPointXY center() const
Gets map center, in geographical coordinates.
void showEvent(QShowEvent *event) override
int layerCount() const
Returns number of layers on the map.
void emitExtentsChanged()
Emits the extentsChanged signal when appropriate.
bool antiAliasingEnabled() const
true if antialiasing is enabled
void setPreviewMode(QgsPreviewEffect::PreviewMode mode)
Sets a preview mode for the map canvas.
QVector< T > layers() const
Returns a list of registered map layers with a specified layer type.
void layerStateChange()
This slot is connected to the visibility change of one or more layers.
QgsPreviewEffect::PreviewMode previewMode() const
Returns the current preview mode for the map canvas.
void zoomScale(double scale, bool ignoreScaleLock=false)
Zooms the canvas to a specific scale.
void zoomWithCenter(int x, int y, bool zoomIn)
Zooms in/out with a given center.
QPoint mouseLastXY()
returns last position of mouse cursor
void clearCache()
Make sure to remove any rendered images from cache (does nothing if cache is not enabled).
void tapAndHoldGestureOccurred(const QgsPointXY &mapPoint, QTapAndHoldGesture *gesture)
Emitted whenever a tap and hold gesture occurs at the specified map point.
const QgsDateTimeRange & temporalRange() const
Returns map canvas datetime range.
void setCanvasColor(const QColor &_newVal)
Write property of QColor bgColor.
void panDistanceBearingChanged(double distance, Qgis::DistanceUnit unit, double bearing)
Emitted whenever the distance or bearing of an in-progress panning operation is changed.
void zoomByFactor(double scaleFactor, const QgsPointXY *center=nullptr, bool ignoreScaleLock=false)
Zoom with the factor supplied.
const QgsTemporalController * temporalController() const
Gets access to the temporal controller that will be used to update the canvas temporal range.
void flashGeometries(const QList< QgsGeometry > &geometries, const QgsCoordinateReferenceSystem &crs=QgsCoordinateReferenceSystem(), const QColor &startColor=QColor(255, 0, 0, 255), const QColor &endColor=QColor(255, 0, 0, 0), int flashes=3, int duration=500)
Causes a set of geometries to flash within the canvas.
void setMapUpdateInterval(int timeMilliseconds)
Set how often map preview should be updated while it is being rendered (in milliseconds).
bool setReferencedExtent(const QgsReferencedRectangle &extent)
Sets the canvas to the specified extent.
void dragEnterEvent(QDragEnterEvent *e) override
QgsMapRendererCache * cache()
Returns the map renderer cache, if caching is enabled.
bool isDrawing()
Find out whether rendering is in progress.
void zRangeChanged()
Emitted when the map canvas z (elevation) range changes.
void keyPressEvent(QKeyEvent *e) override
void setZRange(const QgsDoubleRange &range)
Sets the range of z-values which will be visible in the map.
void clearExtentHistory()
Clears the list of extents and sets current extent as first item.
void zoomToPreviousExtent()
Zoom to the previous extent (view).
void enableMapTileRendering(bool flag)
sets map tile rendering flag
void panAction(QMouseEvent *event)
Called when mouse is moving and pan is activated.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the stored overrides of styles for rendering layers.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers shown within the map canvas.
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns global labeling engine settings from the internal map settings.
void scaleChanged(double scale)
Emitted when the scale of the map changes.
void mapToolSet(QgsMapTool *newTool, QgsMapTool *oldTool)
Emit map tool changed with the old tool.
void canvasColorChanged()
Emitted when canvas background color changes.
double zoomInFactor() const
Returns the zoom in factor.
void saveAsImage(const QString &fileName, QPixmap *QPixmap=nullptr, const QString &="PNG")
Save the contents of the map canvas to disk as an image.
void setSegmentationToleranceType(QgsAbstractGeometry::SegmentationToleranceType type)
Sets segmentation tolerance type (maximum angle or maximum difference between curve and approximation...
void setTemporalRange(const QgsDateTimeRange &range)
Set datetime range for the map canvas.
void moveCanvasContents(bool reset=false)
called when panning is in action, reset indicates end of panning
void magnificationChanged(double magnification)
Emitted when the scale of the map changes.
void zoomOut()
Zoom out with fixed factor.
void currentLayerChanged(QgsMapLayer *layer)
Emitted when the current layer is changed.
void setTemporalController(QgsTemporalController *controller)
Sets the temporal controller for this canvas.
void renderErrorOccurred(const QString &error, QgsMapLayer *layer)
Emitted whenever an error is encountered during a map render operation.
void addOverlayWidget(QWidget *widget, Qt::Edge edge)
Adds an overlay widget to the layout, which will be bound to the specified edge.
void waitWhileRendering()
Blocks until the rendering job has finished.
void mapRefreshCanceled()
Emitted when the pending map refresh has been canceled.
double magnificationFactor() const
Returns the magnification factor.
void writeProject(QDomDocument &)
called to write map canvas settings to project
void mousePressEvent(QMouseEvent *e) override
void updateScale()
Emits signal scaleChanged to update scale in main window.
void setMagnificationFactor(double factor, const QgsPointXY *center=nullptr)
Sets the factor of magnification to apply to the map canvas.
void refreshAllLayers()
Reload all layers (including refreshing layer properties from their data sources),...
void unsetMapTool(QgsMapTool *mapTool)
Unset the current map tool or last non zoom tool.
void panActionEnd(QPoint releasePoint)
Ends pan action and redraws the canvas.
void resizeEvent(QResizeEvent *e) override
double zoomOutFactor() const
Returns the zoom in factor.
void renderStarting()
Emitted when the canvas is about to be rendered.
void setMapSettingsFlags(Qgis::MapSettingsFlags flags)
Resets the flags for the canvas' map settings.
std::unique_ptr< CanvasProperties > mCanvasProperties
Handle pattern for implementation object.
void keyReleased(QKeyEvent *e)
Emit key release event.
QgsMapTool * mapTool() const
Returns the currently active tool.
void setWheelFactor(double factor)
Sets wheel zoom factor (should be greater than 1).
void setAnnotationsVisible(bool visible)
Sets whether annotations are visible in the canvas.
void layerStyleOverridesChanged()
Emitted when the configuration of overridden layer styles changes.
QgsMapCanvas(QWidget *parent=nullptr)
Constructor.
void panActionStart(QPoint releasePoint)
Starts a pan action.
void zoomNextStatusChanged(bool available)
Emitted when zoom next status changed.
void setPreviewJobsEnabled(bool enabled)
Sets whether canvas map preview jobs (low priority render jobs which render portions of the view just...
QgsRectangle fullExtent() const
Returns the combined extent for all layers on the map canvas.
void redrawAllLayers()
Clears all cached images and redraws all layers.
void keyReleaseEvent(QKeyEvent *e) override
bool isFrozen() const
Returns true if canvas is frozen.
void rotationChanged(double rotation)
Emitted when the rotation of the map changes.
void panToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter=true)
Centers canvas extent to feature ids.
void messageEmitted(const QString &title, const QString &message, Qgis::MessageLevel level=Qgis::MessageLevel::Info)
emit a message (usually to be displayed in a message bar)
void scaleLockChanged(bool locked)
Emitted when the scale locked state of the map changes.
const QgsLabelingResults * labelingResults(bool allowOutdatedResults=true) const
Gets access to the labeling results (may be nullptr).
void mouseMoveEvent(QMouseEvent *e) override
QgsRectangle projectExtent() const
Returns the associated project's full extent, in the canvas' CRS.
void setCenter(const QgsPointXY &center)
Set the center of the map canvas, in geographical coordinates.
void setParallelRenderingEnabled(bool enabled)
Set whether the layers are rendered in parallel or sequentially.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets destination coordinate reference system.
void flashFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, const QColor &startColor=QColor(255, 0, 0, 255), const QColor &endColor=QColor(255, 0, 0, 0), int flashes=3, int duration=500)
Causes a set of features with matching ids from a vector layer to flash within the canvas.
void installInteractionBlocker(QgsMapCanvasInteractionBlocker *blocker)
Installs an interaction blocker onto the canvas, which may prevent certain map canvas interactions fr...
bool isParallelRenderingEnabled() const
Check whether the layers are rendered in parallel or sequentially.
double scale() const
Returns the last reported scale of the canvas.
QgsSnappingUtils * snappingUtils() const
Returns snapping utility class that is associated with map canvas.
Qgis::DistanceUnit mapUnits() const
Convenience function for returning the current canvas map units.
double rotation() const
Gets the current map canvas rotation in clockwise degrees.
void temporalRangeChanged()
Emitted when the map canvas temporal range changes.
void paintEvent(QPaintEvent *e) override
void zoomLastStatusChanged(bool available)
Emitted when zoom last status changed.
void setSegmentationTolerance(double tolerance)
Sets the segmentation tolerance applied when rendering curved geometries.
void themeChanged(const QString &theme)
Emitted when the canvas has been assigned a different map theme.
void destinationCrsChanged()
Emitted when map CRS has changed.
void transformContextChanged()
Emitted when the canvas transform context is changed.
void keyPressed(QKeyEvent *e)
Emit key press event.
void setMapTool(QgsMapTool *mapTool, bool clean=false)
Sets the map tool currently being used on the canvas.
QColor canvasColor() const
Read property of QColor bgColor.
void mapCanvasRefreshed()
Emitted when canvas finished a refresh request.
int mapUpdateInterval() const
Find out how often map preview should be updated while it is being rendered (in milliseconds).
void setSelectionColor(const QColor &color)
Set color of selected vector features.
double mapUnitsPerPixel() const
Returns the mapUnitsPerPixel (map units per pixel) for the canvas.
void mouseDoubleClickEvent(QMouseEvent *e) override
void selectionChangedSlot()
Receives signal about selection change, and pass it on with layer info.
bool viewportEvent(QEvent *event) override
void setCustomDropHandlers(const QVector< QPointer< QgsCustomDropHandler > > &handlers)
Sets a list of custom drop handlers to use when drop events occur on the canvas.
void zoomToNextExtent()
Zoom to the next extent (view).
void layersChanged()
Emitted when a new set of layers has been received.
void zoomToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids)
Set canvas extent to the bounding box of a set of features.
void renderComplete(QPainter *painter)
Emitted when the canvas has rendered.
void zoomIn()
Zoom in with fixed factor.
QgsMapLayer * layer(int index)
Returns the map layer at position index in the layer stack.
void cancelJobs()
Cancel any rendering job, in a blocking way.
Qgis::MapCanvasFlags flags() const
Returns flags which control how the map canvas behaves.
bool allowInteraction(QgsMapCanvasInteractionBlocker::Interaction interaction) const
Returns true if the specified interaction is currently permitted on the canvas.
void wheelEvent(QWheelEvent *e) override
bool previewModeEnabled() const
Returns whether a preview mode is enabled for the map canvas.
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
void dropEvent(QDropEvent *event) override
void setPreviewModeEnabled(bool previewEnabled)
Enables a preview mode for the map canvas.
QgsProject * project()
Returns the project linked to this canvas.
void setScaleLocked(bool isLocked)
Lock the scale, so zooming can be performed using magnication.
void setRotation(double degrees)
Set the rotation of the map canvas in clockwise degrees.
void removeInteractionBlocker(QgsMapCanvasInteractionBlocker *blocker)
Removes an interaction blocker from the canvas.
void readProject(const QDomDocument &)
called to read map canvas settings from project
void setTheme(const QString &theme)
Sets a map theme to show in the canvas.
void zoomToFeatureExtent(QgsRectangle &rect)
Zooms to feature extent.
QMap< QString, QString > layerStyleOverrides() const
Returns the stored overrides of styles for layers.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
void refresh()
Repaints the canvas map.
QgsMapLayer * currentLayer()
returns current layer (set by legend widget)
@ FlagDontInvalidateCachedRendersWhenRangeChanges
Any cached rendering will not be invalidated when z range context is modified.
static QgsRectangle combinedExtent(const QList< QgsMapLayer * > &layers, const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext)
Returns the combined extent of a list of layers.
Base class for all map layer types.
Definition qgsmaplayer.h:80
void autoRefreshIntervalChanged(int interval)
Emitted when the auto refresh interval changes.
Qgis::LayerType type
Definition qgsmaplayer.h:90
void rendererChanged()
Signal emitted when renderer is changed.
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
A mouse event which is the result of a user interaction with a QgsMapCanvas.
Responsible for keeping a cache of rendered images resulting from a map rendering job.
void invalidateCacheForLayer(QgsMapLayer *layer)
Invalidates cached images which relate to the specified map layer.
Job implementation that renders everything sequentially using a custom painter.
void waitForFinished() override
Block until the job has finished.
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
void finished()
emitted when asynchronous rendering is finished (or canceled).
static const QgsSettingsEntryBool * settingsLogCanvasRefreshEvent
Settings entry log canvas refresh event.
void start()
Start the rendering job and immediately return.
QList< QgsMapRendererJob::Error > Errors
virtual QImage renderedImage()=0
Gets a preview/resulting image.
static QString worldFileContent(const QgsMapSettings &mapSettings)
Creates the content of a world file.
Contains configuration for rendering maps.
Qgis::DistanceUnit mapUnits() const
Returns the units of the map's geographical coordinates - used for scale calculation.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered in the map.
QPolygonF visiblePolygon() const
Returns the visible area as a polygon (may be rotated).
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
double scale() const
Returns the calculated map scale.
void setFrameRate(double rate)
Sets the frame rate of the map (in frames per second), for maps which are part of an animation.
QgsRectangle layerExtentToOutputExtent(const QgsMapLayer *layer, QgsRectangle extent) const
transform bounding box from layer's CRS to output CRS
const QgsMapToPixel & mapToPixel() const
double mapUnitsPerPixel() const
Returns the distance in geographical coordinates that equals to one pixel in the map.
QgsRectangle extent() const
Returns geographical coordinates of the rectangle that should be rendered.
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
QgsRectangle fullExtent() const
returns current extent of layer set
void setCurrentFrame(long long frame)
Sets the current frame of the map, for maps which are part of an animation.
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
double rotation() const
Returns the rotation of the resulting map image, in degrees clockwise.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected).
void readXml(QDomNode &node)
Restore the map settings from a XML node.
void mapThemesChanged()
Emitted when map themes within the collection are changed.
void mapThemeRenamed(const QString &name, const QString &newName)
Emitted when a map theme within the collection is renamed.
void mapThemeChanged(const QString &theme)
Emitted when a map theme changes definition.
Perform transforms between map coordinates and device coordinates.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
Abstract base class for all map tools.
Definition qgsmaptool.h:72
@ AllowZoomRect
Allow zooming by rectangle (by holding shift and dragging) while the tool is active.
Definition qgsmaptool.h:113
@ ShowContextMenu
Show a context menu when right-clicking with the tool (since QGIS 3.14). See populateContextMenu().
Definition qgsmaptool.h:114
virtual void deactivate()
called when map tool is being deactivated
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
static bool isUriList(const QMimeData *data)
QList< QgsMimeDataUtils::Uri > UriList
static UriList decodeUriList(const QMimeData *data)
A custom layout which can be used to overlay child widgets over a parent widget.
Represents a 2D point.
Definition qgspointxy.h:60
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition qgspointxy.h:186
void setY(double y)
Sets the y value of the point.
Definition qgspointxy.h:129
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:119
A graphics effect which can be applied to a widget to simulate various printing and color blindness m...
QgsReferencedRectangle fullExtent() const
Returns the full extent of the project, which represents the maximal limits of the project.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:109
static QgsProject * instance()
Returns the QgsProject singleton instance.
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void scaleMethodChanged()
Emitted when the project's scale method is changed.
void ellipsoidChanged(const QString &ellipsoid)
Emitted when the project ellipsoid is changed.
void projectColorsChanged()
Emitted whenever the project's color scheme has been changed.
void elevationShadingRendererChanged()
Emitted when the map shading renderer changes.
void readProject(const QDomDocument &document)
Emitted when a project is being read.
const QgsProjectViewSettings * viewSettings() const
Returns the project's view settings, which contains settings and properties relating to how a QgsProj...
void transformContextChanged()
Emitted when the project transformContext() is changed.
void writeProject(QDomDocument &document)
Emitted when the project is being written.
A container for the context for various read/write operations on objects.
A rectangle specified with double values.
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
static QgsRectangle fromCenterAndSize(const QgsPointXY &center, double width, double height)
Creates a new rectangle, given the specified center point and width and height.
QgsPointXY center
void setNull()
Mark a rectangle as being null (holding no spatial information).
A QgsRectangle with associated coordinate reference system.
Stores collated details of rendered items during a map rendering operation.
Responsible for drawing transient features (e.g.
void setWidth(double width)
Sets the width of the line.
void setSecondaryStrokeColor(const QColor &color)
Sets a secondary stroke color for the rubberband which will be drawn under the main stroke color.
@ ICON_CIRCLE
A circle is used to highlight points (○).
void setStrokeColor(const QColor &color)
Sets the stroke color for the rubberband.
QColor secondaryStrokeColor
void setIcon(IconType icon)
Sets the icon type to highlight point geometries.
void updatePosition() override
called on changed extent or resize event to update position of the item
void addGeometry(const QgsGeometry &geometry, QgsMapLayer *layer, bool doUpdate=true)
Adds the geometry of an existing feature to a rubberband This is useful for multi feature highlightin...
void setFillColor(const QColor &color)
Sets the fill color for the rubberband.
void clear(const QString &group="startup")
clear Clear all profile data.
Scoped object for saving and restoring a QPainter object's state.
Scoped object for logging of the runtime for a single operation or group of operations.
A utility class for dynamic handling of changes to screen properties.
void screenDpiChanged(double dpi)
Emitted whenever the screen dpi associated with the widget is changed.
static const QgsSettingsEntryBool * settingsRespectScreenDPI
Settings entry respect screen dpi.
Stores settings for use within QGIS.
Definition qgssettings.h:65
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
T enumValue(const QString &key, const T &defaultValue, const Section section=NoSection)
Returns the setting value for a setting based on an enum.
Contains configuration of snapping and can return answers to snapping queries.
void remoteSvgFetched(const QString &url)
Emitted when the cache has finished retrieving an SVG file from a remote url.
static double rendererFrameRate(const QgsFeatureRenderer *renderer)
Calculates the frame rate (in frames per second) at which the given renderer must be redrawn.
A controller base class for temporal objects, contains a signal for notifying updates of the objects ...
void updateTemporalRange(const QgsDateTimeRange &range)
Signals that a temporal range has changed and needs to be updated in all connected objects.
Implements a temporal controller based on a frame by frame navigation and animation.
void navigationModeChanged(Qgis::TemporalNavigationMode mode)
Emitted whenever the navigation mode changes.
@ FlagDontInvalidateCachedRendersWhenRangeChanges
Any cached rendering will not be invalidated when temporal range context is modified.
T begin() const
Returns the beginning of the range.
Definition qgsrange.h:446
T end() const
Returns the upper bound of the range.
Definition qgsrange.h:453
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
void release()
Releases the cursor override early (i.e.
Represents a vector layer which manages a vector based dataset.
Q_INVOKABLE QgsRectangle boundingBoxOfSelected() const
Returns the bounding box of the selected features. If there is no selection, QgsRectangle(0,...
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
Implements a map layer that is dedicated to rendering of vector tiles.
QList< QgsFeature > selectedFeatures() const
Returns the list of features currently selected in the layer.
void selectionChanged()
Emitted whenever the selected features in the layer are changed.
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
Represent a 2-dimensional vector.
Definition qgsvector.h:31
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
constexpr double CANVAS_MAGNIFICATION_MIN
Minimum magnification level allowed in map canvases.
Definition qgsguiutils.h:61
constexpr double CANVAS_MAGNIFICATION_MAX
Maximum magnification level allowed in map canvases.
Definition qgsguiutils.h:68
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6511
QSet< QgsFeatureId > QgsFeatureIds
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
#define QgsDebugError(str)
Definition qgslogger.h:57
QList< QgsMapLayer * > filterLayersForRender(const QList< QgsMapLayer * > &layers)
QgsTemporalRange< QDateTime > QgsDateTimeRange
QgsRange which stores a range of date times.
Definition qgsrange.h:761
double maxRenderingTimeMs
Default maximum allowable render time, in ms.
double lastRenderingTimeMs
Previous rendering time for the layer, in ms.