QGIS API Documentation 3.40.0-Bratislava (b56115d8743)
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 <cmath>
19
20#include <QtGlobal>
21#include <QApplication>
22#include <QCursor>
23#include <QDir>
24#include <QFile>
25#include <QGraphicsItem>
26#include <QGraphicsScene>
27#include <QGraphicsView>
28#include <QKeyEvent>
29#include <QPainter>
30#include <QPaintEvent>
31#include <QPixmap>
32#include <QRect>
33#include <QTextStream>
34#include <QResizeEvent>
35#include <QScreen>
36#include <QString>
37#include <QStringList>
38#include <QWheelEvent>
39#include <QWindow>
40#include <QMenu>
41#include <QClipboard>
42#include <QVariantAnimation>
43#include <QPropertyAnimation>
44
45#include "qgis.h"
46#include "qgssettings.h"
48#include "qgsapplication.h"
49#include "qgsexception.h"
50#include "qgsfeatureiterator.h"
51#include "qgsgrouplayer.h"
52#include "qgslogger.h"
53#include "qgsmapcanvas.h"
54#include "qgsmapcanvasmap.h"
56#include "qgsmaplayer.h"
57#include "qgsmapmouseevent.h"
58#include "qgsmaptoolpan.h"
59#include "qgsmaptopixel.h"
60#include "qgsmaprenderercache.h"
62#include "qgsmaprendererjob.h"
65#include "qgsmapsettingsutils.h"
66#include "qgsmessagelog.h"
67#include "qgsproject.h"
68#include "qgsrubberband.h"
69#include "qgsvectorlayer.h"
73#include "qgssvgcache.h"
74#include "qgsimagecache.h"
76#include "qgsmimedatautils.h"
82#include "qgsruntimeprofiler.h"
84#include "qgsannotationlayer.h"
87#include "qgslabelingresults.h"
88#include "qgsmaplayerutils.h"
92#include "qgssymbollayerutils.h"
93#include "qgsvectortilelayer.h"
94#include "qgsscreenhelper.h"
95#include "qgs2dmapcontroller.h"
97
103//TODO QGIS 4.0 - remove
105{
106 public:
107
111 CanvasProperties() = default;
112
114 bool mouseButtonDown{ false };
115
118
121
123 bool panSelectorDown{ false };
124};
125
126
127
129 : QGraphicsView( parent )
130 , mCanvasProperties( new CanvasProperties )
131 , mExpressionContextScope( tr( "Map Canvas" ) )
132{
133 mScene = new QGraphicsScene();
134 mLayout = new QgsOverlayWidgetLayout();
135 setLayout( mLayout );
136
137 setScene( mScene );
138 setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
139 setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
140 setMouseTracking( true );
141 setFocusPolicy( Qt::StrongFocus );
142
143 mScreenHelper = new QgsScreenHelper( this );
144 connect( mScreenHelper, &QgsScreenHelper::screenDpiChanged, this, &QgsMapCanvas::updateDevicePixelFromScreen );
145
146 mResizeTimer = new QTimer( this );
147 mResizeTimer->setSingleShot( true );
148 connect( mResizeTimer, &QTimer::timeout, this, &QgsMapCanvas::refresh );
149
150 mRefreshTimer = new QTimer( this );
151 mRefreshTimer->setSingleShot( true );
152 connect( mRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::refreshMap );
153
154 // create map canvas item which will show the map
155 mMap = new QgsMapCanvasMap( this );
156
157 // project handling
162
163 connect( QgsProject::instance()->mainAnnotationLayer(), &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
164 connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsMapCanvas::mapThemeChanged );
165 connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsMapCanvas::mapThemeRenamed );
166 connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemesChanged, this, &QgsMapCanvas::projectThemesChanged );
167
168 {
169 QgsScopedRuntimeProfile profile( "Map settings initialization" );
173 mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
175 this, [ = ]
176 {
177 mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
178 refresh();
179 } );
182 this, [ = ]
183 {
184 mSettings.setTransformContext( QgsProject::instance()->transformContext() );
186 refresh();
187 } );
188
190 {
193 if ( mSettings.destinationCrs() != crs )
194 {
195 // user crs has changed definition, refresh the map
196 setDestinationCrs( crs );
197 }
198 } );
199 }
200
201 // refresh canvas when a remote svg/image has finished downloading
204 // refresh canvas when project color scheme is changed -- if layers use project colors, they need to be redrawn
206
207 //segmentation parameters
208 QgsSettings settings;
209 double segmentationTolerance = settings.value( QStringLiteral( "qgis/segmentationTolerance" ), "0.01745" ).toDouble();
210 QgsAbstractGeometry::SegmentationToleranceType toleranceType = settings.enumValue( QStringLiteral( "qgis/segmentationToleranceType" ), QgsAbstractGeometry::MaximumAngle );
211 mSettings.setSegmentationTolerance( segmentationTolerance );
212 mSettings.setSegmentationToleranceType( toleranceType );
213
214 mWheelZoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
215
216 QSize s = viewport()->size();
217 mSettings.setOutputSize( s );
218
220
221 setSceneRect( 0, 0, s.width(), s.height() );
222 mScene->setSceneRect( QRectF( 0, 0, s.width(), s.height() ) );
223
224 moveCanvasContents( true );
225
226 connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsMapCanvas::mapUpdateTimeout );
227 mMapUpdateTimer.setInterval( 250 );
228
229#ifdef Q_OS_WIN
230 // Enable touch event on Windows.
231 // Qt on Windows needs to be told it can take touch events or else it ignores them.
232 grabGesture( Qt::PinchGesture );
233 grabGesture( Qt::TapAndHoldGesture );
234 viewport()->setAttribute( Qt::WA_AcceptTouchEvents );
235#endif
236
237 mPreviewEffect = new QgsPreviewEffect( this );
238 viewport()->setGraphicsEffect( mPreviewEffect );
239
241
242 connect( &mAutoRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::autoRefreshTriggered );
243
245
246 setInteractive( false );
247
248 // make sure we have the same default in QgsMapSettings and the scene's background brush
249 // (by default map settings has white bg color, scene background brush is black)
250 setCanvasColor( mSettings.backgroundColor() );
251
252 setTemporalRange( mSettings.temporalRange() );
253 refresh();
254}
255
256
258{
259 if ( mMapTool )
260 {
261 mMapTool->deactivate();
262 disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
263 mMapTool = nullptr;
264 }
265
266 // we also clear the canvas pointer for all child map tools. We're now in a partially destroyed state and it's
267 // no longer safe for map tools to try to cleanup things in the canvas during their destruction (such as removing
268 // associated canvas items)
269 // NOTE -- it may be better to just delete the map tool children here upfront?
270 const QList< QgsMapTool * > tools = findChildren< QgsMapTool *>();
271 for ( QgsMapTool *tool : tools )
272 {
273 tool->mCanvas = nullptr;
274 }
275
276 cancelJobs();
277
278 // delete canvas items prior to deleting the canvas
279 // because they might try to update canvas when it's
280 // already being destructed, ends with segfault
281 qDeleteAll( mScene->items() );
282
283 mScene->deleteLater(); // crashes in python tests on windows
284
285 delete mCache;
286}
287
288void QgsMapCanvas::addOverlayWidget( QWidget *widget, Qt::Edge edge )
289{
290 mLayout->addWidget( widget, edge );
291}
292
294{
295 // rendering job may still end up writing into canvas map item
296 // so kill it before deleting canvas items
297 if ( mJob )
298 {
299 whileBlocking( mJob )->cancel();
300 delete mJob;
301 mJob = nullptr;
302 }
303
304 for ( auto previewJob = mPreviewJobs.constBegin(); previewJob != mPreviewJobs.constEnd(); ++previewJob )
305 {
306 if ( *previewJob )
307 {
308 whileBlocking( *previewJob )->cancel();
309 delete *previewJob;
310 }
311 }
312 mPreviewJobs.clear();
313}
314
315void QgsMapCanvas::setMagnificationFactor( double factor, const QgsPointXY *center )
316{
317 // do not go higher or lower than min max magnification ratio
318 double magnifierMin = QgsGuiUtils::CANVAS_MAGNIFICATION_MIN;
319 double magnifierMax = QgsGuiUtils::CANVAS_MAGNIFICATION_MAX;
320 factor = std::clamp( factor, magnifierMin, magnifierMax );
321
322 // the magnifier widget is in integer percent
323 if ( !qgsDoubleNear( factor, mSettings.magnificationFactor(), 0.01 ) )
324 {
325 mSettings.setMagnificationFactor( factor, center );
326 refresh();
327 emit magnificationChanged( factor );
328 }
329}
330
332{
333 return mSettings.magnificationFactor();
334}
335
341
346
351
353{
354 QList<QgsMapLayer *> layers = mapSettings().layers();
355 if ( index >= 0 && index < layers.size() )
356 return layers[index];
357 else
358 return nullptr;
359}
360
361QgsMapLayer *QgsMapCanvas::layer( const QString &id )
362{
363 // first check for layers from canvas map settings
364 const QList<QgsMapLayer *> layers = mapSettings().layers();
365 for ( QgsMapLayer *layer : layers )
366 {
367 if ( layer && layer->id() == id )
368 return layer;
369 }
370
371 // else fallback to searching project layers
372 // TODO: allow a specific project to be associated with a canvas!
373 return QgsProject::instance()->mapLayer( id );
374}
375
377{
378 if ( mCurrentLayer == layer )
379 return;
380
381 mCurrentLayer = layer;
383}
384
386{
387 return mapSettings().scale();
388}
389
391{
392 return nullptr != mJob;
393} // isDrawing
394
395// return the current coordinate transform based on the extents and
396// device size
401
402void QgsMapCanvas::setLayers( const QList<QgsMapLayer *> &layers )
403{
404 // following a theme => request denied!
405 if ( !mTheme.isEmpty() )
406 return;
407
408 setLayersPrivate( layers );
409}
410
412{
413 mFlags = flags;
414}
415
417{
418 return mFlags;
419}
420
421void QgsMapCanvas::setLayersPrivate( const QList<QgsMapLayer *> &layers )
422{
423 const QList<QgsMapLayer *> oldLayers = mSettings.layers();
424
425 // update only if needed
426 if ( layers == oldLayers )
427 return;
428
429 for ( QgsMapLayer *layer : oldLayers )
430 {
431 disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
432 disconnect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
433 switch ( layer->type() )
434 {
436 {
437 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
439 disconnect( vlayer, &QgsVectorLayer::rendererChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
440 break;
441 }
442
444 {
445 QgsVectorTileLayer *vtlayer = qobject_cast<QgsVectorTileLayer *>( layer );
447 break;
448 }
449
457 break;
458 }
459 }
460
461 mSettings.setLayers( layers );
462
463 for ( QgsMapLayer *layer : std::as_const( layers ) )
464 {
465 if ( !layer )
466 continue;
467 connect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
468 connect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
469
470 switch ( layer->type() )
471 {
473 {
474 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
476 connect( vlayer, &QgsVectorLayer::rendererChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
477 break;
478 }
479
481 {
482 QgsVectorTileLayer *vtlayer = qobject_cast<QgsVectorTileLayer *>( layer );
484 break;
485 }
486
494 break;
495 }
496 }
497
498 QgsDebugMsgLevel( QStringLiteral( "Layers have changed, refreshing" ), 2 );
499 emit layersChanged();
500
501 updateAutoRefreshTimer();
502 refresh();
503}
504
505
507{
508 return mSettings;
509}
510
512{
513 return mSettings;
514}
515
517{
518 if ( mSettings.destinationCrs() == crs )
519 return;
520
521 // try to reproject current extent to the new one
522 QgsRectangle rect;
523 if ( !mSettings.visibleExtent().isEmpty() )
524 {
525 const QgsCoordinateTransform transform( mSettings.destinationCrs(), crs, QgsProject::instance(),
528 try
529 {
530 rect = transform.transformBoundingBox( mSettings.visibleExtent() );
531 }
532 catch ( QgsCsException &e )
533 {
534 Q_UNUSED( e )
535 QgsDebugError( QStringLiteral( "Transform error caught: %1" ).arg( e.what() ) );
536 }
537 }
538
539 // defer extent and scale changed signals until we've correctly
540 // set the destination crs, otherwise slots which connect to these signals
541 // may retrieve an outdated CRS for the map canvas
542 mBlockExtentChangedSignal++;
543 mBlockScaleChangedSignal++;
544
545 if ( !rect.isEmpty() )
546 {
547 // we will be manually calling updateCanvasItemPositions() later, AFTER setting the updating the mSettings destination CRS, and we don't
548 // want to do that twice!
549 mBlockItemPositionUpdates++;
550 setExtent( rect );
551 mBlockItemPositionUpdates--;
552 }
553
554 mBlockExtentChangedSignal--;
555 mBlockScaleChangedSignal--;
556
557 mSettings.setDestinationCrs( crs );
558 updateScale();
560
562
563 QgsDebugMsgLevel( QStringLiteral( "refreshing after destination CRS changed" ), 2 );
564 refresh();
565
567}
568
570{
571 if ( mController )
573 if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast< QgsTemporalNavigationObject * >( mController ) )
574 {
575 disconnect( temporalNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsMapCanvas::temporalControllerModeChanged );
576
577 // clear any existing animation settings from map settings. We don't do this on every render, as a 3rd party plugin
578 // might be in control of these!
579 mSettings.setFrameRate( -1 );
580 mSettings.setCurrentFrame( -1 );
581 }
582
583 mController = controller;
585 if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast< QgsTemporalNavigationObject * >( mController ) )
586 connect( temporalNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsMapCanvas::temporalControllerModeChanged );
587}
588
589void QgsMapCanvas::temporalControllerModeChanged()
590{
591 if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast< QgsTemporalNavigationObject * >( mController ) )
592 {
593 switch ( temporalNavigationObject->navigationMode() )
594 {
597 mSettings.setFrameRate( temporalNavigationObject->framesPerSecond() );
598 mSettings.setCurrentFrame( temporalNavigationObject->currentFrameNumber() );
599 break;
600
603 // clear any existing animation settings from map settings. We don't do this on every render, as a 3rd party plugin
604 // might be in control of these!
605 mSettings.setFrameRate( -1 );
606 mSettings.setCurrentFrame( -1 );
607 break;
608 }
609 }
610}
611
613{
614 return mController;
615}
616
618{
619 mSettings.setFlags( flags );
620 clearCache();
621 refresh();
622}
623
624const QgsLabelingResults *QgsMapCanvas::labelingResults( bool allowOutdatedResults ) const
625{
626 if ( !allowOutdatedResults && mLabelingResultsOutdated )
627 return nullptr;
628
629 return mLabelingResults.get();
630}
631
632const QgsRenderedItemResults *QgsMapCanvas::renderedItemResults( bool allowOutdatedResults ) const
633{
634 if ( !allowOutdatedResults && mRenderedItemResultsOutdated )
635 return nullptr;
636
637 return mRenderedItemResults.get();
638}
639
641{
642 if ( enabled == isCachingEnabled() )
643 return;
644
645 if ( mJob && mJob->isActive() )
646 {
647 // wait for the current rendering to finish, before touching the cache
648 mJob->waitForFinished();
649 }
650
651 if ( enabled )
652 {
653 mCache = new QgsMapRendererCache;
654 }
655 else
656 {
657 delete mCache;
658 mCache = nullptr;
659 }
660 mPreviousRenderedItemResults.reset();
661}
662
664{
665 return nullptr != mCache;
666}
667
669{
670 if ( mCache )
671 mCache->clear();
672
673 if ( mPreviousRenderedItemResults )
674 mPreviousRenderedItemResults.reset();
675 if ( mRenderedItemResults )
676 mRenderedItemResults.reset();
677}
678
680{
681 return mCache;
682}
683
685{
686 mUseParallelRendering = enabled;
687}
688
690{
691 return mUseParallelRendering;
692}
693
694void QgsMapCanvas::setMapUpdateInterval( int timeMilliseconds )
695{
696 mMapUpdateTimer.setInterval( timeMilliseconds );
697}
698
700{
701 return mMapUpdateTimer.interval();
702}
703
704
706{
707 return mCurrentLayer;
708}
709
711{
712 QgsExpressionContextScope *s = new QgsExpressionContextScope( QObject::tr( "Map Canvas" ) );
713 s->setVariable( QStringLiteral( "canvas_cursor_point" ), QgsGeometry::fromPointXY( cursorPoint() ), true );
714 return s;
715}
716
718{
719 //build the expression context
720 QgsExpressionContext expressionContext;
721 expressionContext << QgsExpressionContextUtils::globalScope()
725 if ( QgsExpressionContextScopeGenerator *generator = dynamic_cast< QgsExpressionContextScopeGenerator * >( mController ) )
726 {
727 expressionContext << generator->createExpressionContextScope();
728 }
729 expressionContext << defaultExpressionContextScope()
730 << new QgsExpressionContextScope( mExpressionContextScope );
731 return expressionContext;
732}
733
735{
736 if ( !mSettings.hasValidSettings() )
737 {
738 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh - invalid settings -> nothing to do" ), 2 );
739 return;
740 }
741
742 if ( !mRenderFlag || mFrozen )
743 {
744 QgsDebugMsgLevel( QStringLiteral( "CANVAS render flag off" ), 2 );
745 return;
746 }
747
748 if ( mRefreshScheduled )
749 {
750 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh already scheduled" ), 2 );
751 return;
752 }
753
754 mRefreshScheduled = true;
755
756 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh scheduling" ), 2 );
757
758 // schedule a refresh
759 mRefreshTimer->start( 1 );
760
761 mLabelingResultsOutdated = true;
762 mRenderedItemResultsOutdated = true;
763}
764
765QList< QgsMapLayer * > filterLayersForRender( const QList< QgsMapLayer * > &layers )
766{
767 QList<QgsMapLayer *> filteredLayers;
768 for ( QgsMapLayer *layer : layers )
769 {
770 if ( QgsAnnotationLayer *annotationLayer = qobject_cast< QgsAnnotationLayer * >( layer ) )
771 {
772 if ( QgsMapLayer *linkedLayer = annotationLayer->linkedVisibilityLayer() )
773 {
774 if ( !layers.contains( linkedLayer ) )
775 continue;
776 }
777 }
778 filteredLayers.append( layer );
779 }
780 return filteredLayers;
781}
782
783void QgsMapCanvas::refreshMap()
784{
785 Q_ASSERT( mRefreshScheduled );
786
787 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh!" ), 3 );
788
789 stopRendering(); // if any...
790 stopPreviewJobs();
791
792 if ( mCacheInvalidations.testFlag( CacheInvalidationType::Temporal ) )
793 {
794 clearTemporalCache();
795 mCacheInvalidations &= ~( static_cast< int >( CacheInvalidationType::Temporal ) );
796 }
797 if ( mCacheInvalidations.testFlag( CacheInvalidationType::Elevation ) )
798 {
799 clearElevationCache();
800 mCacheInvalidations &= ~( static_cast< int >( CacheInvalidationType::Elevation ) );
801 }
802
804
805 // if using the temporal controller in animation mode, get the frame settings from that
806 if ( QgsTemporalNavigationObject *temporalNavigationObject = dynamic_cast < QgsTemporalNavigationObject * >( mController ) )
807 {
808 switch ( temporalNavigationObject->navigationMode() )
809 {
812 mSettings.setFrameRate( temporalNavigationObject->framesPerSecond() );
813 mSettings.setCurrentFrame( temporalNavigationObject->currentFrameNumber() );
814 break;
815
818 break;
819 }
820 }
821
823
824 if ( !mTheme.isEmpty() )
825 {
826 // IMPORTANT: we MUST set the layer style overrides here! (At the time of writing this
827 // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
828 // current state of the style. If we had stored the style overrides earlier (such as in
829 // mapThemeChanged slot) then this xml could be out of date...
830 // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
831 // just return the style name, we can instead set the overrides in mapThemeChanged and not here
833 }
834
835 // render main annotation layer above all other layers
836 QgsMapSettings renderSettings = mSettings;
837 QList<QgsMapLayer *> allLayers = renderSettings.layers();
839 allLayers.insert( 0, QgsProject::instance()->mainAnnotationLayer() );
840
841 renderSettings.setLayers( filterLayersForRender( allLayers ) );
842
843 // create the renderer job
844
845 QgsApplication::profiler()->clear( QStringLiteral( "rendering" ) );
846
847 Q_ASSERT( !mJob );
848 mJobCanceled = false;
849 if ( mUseParallelRendering )
850 mJob = new QgsMapRendererParallelJob( renderSettings );
851 else
852 mJob = new QgsMapRendererSequentialJob( renderSettings );
853
854 connect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
855 mJob->setCache( mCache );
856 mJob->setLayerRenderingTimeHints( mLastLayerRenderTime );
857
858 mJob->start();
859
860 // from now on we can accept refresh requests again
861 // this must be reset only after the job has been started, because
862 // some providers (yes, it's you WCS and AMS!) during preparation
863 // do network requests and start an internal event loop, which may
864 // end up calling refresh() and would schedule another refresh,
865 // deleting the one we have just started.
866 mRefreshScheduled = false;
867
868 mMapUpdateTimer.start();
869
870 emit renderStarting();
871}
872
873void QgsMapCanvas::mapThemeChanged( const QString &theme )
874{
875 if ( theme == mTheme )
876 {
877 // set the canvas layers to match the new layers contained in the map theme
878 // NOTE: we do this when the theme layers change and not when we are refreshing the map
879 // as setLayers() sets up necessary connections to handle changes to the layers
880 setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
881 // IMPORTANT: we don't set the layer style overrides here! (At the time of writing this
882 // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
883 // current state of the style. If changes were made to the style then this xml
884 // snapshot goes out of sync...
885 // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
886 // just return the style name, we can instead set the overrides here and not in refreshMap()
887
888 clearCache();
889 refresh();
890 }
891}
892
893void QgsMapCanvas::mapThemeRenamed( const QString &theme, const QString &newTheme )
894{
895 if ( mTheme.isEmpty() || theme != mTheme )
896 {
897 return;
898 }
899
900 setTheme( newTheme );
901 refresh();
902}
903
904void QgsMapCanvas::rendererJobFinished()
905{
906 QgsDebugMsgLevel( QStringLiteral( "CANVAS finish! %1" ).arg( !mJobCanceled ), 2 );
907
908 mMapUpdateTimer.stop();
909
910 notifyRendererErrors( mJob->errors() );
911
912 if ( !mJobCanceled )
913 {
914 // take labeling results before emitting renderComplete, so labeling map tools
915 // connected to signal work with correct results
916 if ( !mJob->usedCachedLabels() )
917 {
918 mLabelingResults.reset( mJob->takeLabelingResults() );
919 }
920 mLabelingResultsOutdated = false;
921
922 std::unique_ptr< QgsRenderedItemResults > renderedItemResults( mJob->takeRenderedItemResults() );
923 // if a layer was redrawn from the cached version, we should copy any existing rendered item results from that layer
924 if ( mRenderedItemResults )
925 {
926 renderedItemResults->transferResults( mRenderedItemResults.get(), mJob->layersRedrawnFromCache() );
927 }
928 if ( mPreviousRenderedItemResults )
929 {
930 // also transfer any results from previous renders which happened before this
931 renderedItemResults->transferResults( mPreviousRenderedItemResults.get(), mJob->layersRedrawnFromCache() );
932 }
933
934 if ( mCache && !mPreviousRenderedItemResults )
935 mPreviousRenderedItemResults = std::make_unique< QgsRenderedItemResults >( mJob->mapSettings().extent() );
936
937 if ( mRenderedItemResults && mPreviousRenderedItemResults )
938 {
939 // for other layers which ARE present in the most recent rendered item results BUT were not part of this render, we
940 // store the results in a temporary store in case they are later switched back on and the layer's image is taken
941 // from the cache
942 mPreviousRenderedItemResults->transferResults( mRenderedItemResults.get() );
943 }
944 if ( mPreviousRenderedItemResults )
945 {
946 mPreviousRenderedItemResults->eraseResultsFromLayers( mJob->mapSettings().layerIds() );
947 }
948
949 mRenderedItemResults = std::move( renderedItemResults );
950 mRenderedItemResultsOutdated = false;
951
952 QImage img = mJob->renderedImage();
953
954 // emit renderComplete to get our decorations drawn
955 QPainter p( &img );
956 emit renderComplete( &p );
957
959 {
960 QString logMsg = tr( "Canvas refresh: %1 ms" ).arg( mJob->renderingTime() );
961 QgsMessageLog::logMessage( logMsg, tr( "Rendering" ) );
962 }
963
964 if ( mDrawRenderingStats )
965 {
966 int w = img.width(), h = img.height();
967 QFont fnt = p.font();
968 fnt.setBold( true );
969 p.setFont( fnt );
970 int lh = p.fontMetrics().height() * 2;
971 QRect r( 0, h - lh, w, lh );
972 p.setPen( Qt::NoPen );
973 p.setBrush( QColor( 0, 0, 0, 110 ) );
974 p.drawRect( r );
975 p.setPen( Qt::white );
976 QString msg = QStringLiteral( "%1 :: %2 ms" ).arg( mUseParallelRendering ? QStringLiteral( "PARALLEL" ) : QStringLiteral( "SEQUENTIAL" ) ).arg( mJob->renderingTime() );
977 p.drawText( r, msg, QTextOption( Qt::AlignCenter ) );
978 }
979
980 p.end();
981
982 mMap->setContent( img, imageRect( img, mSettings ) );
983
984 mLastLayerRenderTime.clear();
985 const auto times = mJob->perLayerRenderingTime();
986 for ( auto it = times.constBegin(); it != times.constEnd(); ++it )
987 {
988 mLastLayerRenderTime.insert( it.key()->id(), it.value() );
989 }
990 if ( mUsePreviewJobs && !mRefreshAfterJob )
991 startPreviewJobs();
992 }
993 else
994 {
995 mRefreshAfterJob = false;
996 }
997
998 // now we are in a slot called from mJob - do not delete it immediately
999 // so the class is still valid when the execution returns to the class
1000 mJob->deleteLater();
1001 mJob = nullptr;
1002
1003 emit mapCanvasRefreshed();
1004
1005 if ( mRefreshAfterJob )
1006 {
1007 mRefreshAfterJob = false;
1008 refresh();
1009 }
1010}
1011
1012void QgsMapCanvas::previewJobFinished()
1013{
1014 QgsMapRendererQImageJob *job = qobject_cast<QgsMapRendererQImageJob *>( sender() );
1015 Q_ASSERT( job );
1016
1017 if ( mMap )
1018 {
1019 mMap->addPreviewImage( job->renderedImage(), job->mapSettings().extent() );
1020 mPreviewJobs.removeAll( job );
1021
1022 int number = job->property( "number" ).toInt();
1023 if ( number < 8 )
1024 {
1025 startPreviewJob( number + 1 );
1026 }
1027
1028 delete job;
1029 }
1030}
1031
1032QgsRectangle QgsMapCanvas::imageRect( const QImage &img, const QgsMapSettings &mapSettings )
1033{
1034 // This is a hack to pass QgsMapCanvasItem::setRect what it
1035 // expects (encoding of position and size of the item)
1036 const QgsMapToPixel &m2p = mapSettings.mapToPixel();
1037 QgsPointXY topLeft = m2p.toMapCoordinates( 0, 0 );
1038#ifdef QGISDEBUG
1039 // do not assert this, since it might lead to crashes when changing screen while rendering
1040 if ( img.devicePixelRatio() != mapSettings.devicePixelRatio() )
1041 {
1042 QgsLogger::warning( QStringLiteral( "The renderer map has a wrong device pixel ratio" ) );
1043 }
1044#endif
1045 double res = m2p.mapUnitsPerPixel() / img.devicePixelRatioF();
1046 QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + img.width()*res, topLeft.y() - img.height()*res );
1047 return rect;
1048}
1049
1051{
1052 return mUsePreviewJobs;
1053}
1054
1056{
1057 mUsePreviewJobs = enabled;
1058}
1059
1060void QgsMapCanvas::setCustomDropHandlers( const QVector<QPointer<QgsCustomDropHandler> > &handlers )
1061{
1062 mDropHandlers = handlers;
1063}
1064
1065void QgsMapCanvas::clearTemporalCache()
1066{
1067 if ( mCache )
1068 {
1069 bool invalidateLabels = false;
1070 const QList<QgsMapLayer *> layerList = mapSettings().layers();
1071 for ( QgsMapLayer *layer : layerList )
1072 {
1073 bool alreadyInvalidatedThisLayer = false;
1074 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
1075 {
1076 if ( vl->renderer() && QgsSymbolLayerUtils::rendererFrameRate( vl->renderer() ) > -1 )
1077 {
1078 // layer has an animated symbol assigned, so we have to redraw it regardless of whether
1079 // or not it has temporal settings
1080 mCache->invalidateCacheForLayer( layer );
1081 alreadyInvalidatedThisLayer = true;
1082 // we can't shortcut and "continue" here, as we still need to check whether the layer
1083 // will cause label invalidation using the logic below
1084 }
1085 }
1086
1088 {
1089 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
1090 {
1091 if ( vl->labelsEnabled() || vl->diagramsEnabled() || ( vl->renderer() && vl->renderer()->flags().testFlag( Qgis::FeatureRendererFlag::AffectsLabeling ) ) )
1092 invalidateLabels = true;
1093 }
1094
1096 continue;
1097
1098 if ( !alreadyInvalidatedThisLayer )
1099 {
1100 mCache->invalidateCacheForLayer( layer );
1101 }
1102 }
1103 else if ( QgsGroupLayer *gl = qobject_cast<QgsGroupLayer *>( layer ) )
1104 {
1105 const QList<QgsMapLayer *> childLayerList = gl->childLayers();
1106 for ( QgsMapLayer *childLayer : childLayerList )
1107 {
1108 if ( childLayer->temporalProperties() && childLayer->temporalProperties()->isActive() )
1109 {
1110 if ( childLayer->temporalProperties()->flags() & QgsTemporalProperty::FlagDontInvalidateCachedRendersWhenRangeChanges )
1111 continue;
1112
1113 mCache->invalidateCacheForLayer( layer );
1114 break;
1115 }
1116 }
1117 }
1118 }
1119
1120 if ( invalidateLabels )
1121 {
1122 mCache->clearCacheImage( QStringLiteral( "_labels_" ) );
1123 mCache->clearCacheImage( QStringLiteral( "_preview_labels_" ) );
1124 }
1125 }
1126}
1127
1128void QgsMapCanvas::clearElevationCache()
1129{
1130 if ( mCache )
1131 {
1132 bool invalidateLabels = false;
1133 const QList<QgsMapLayer *> layerList = mapSettings().layers();
1134 for ( QgsMapLayer *layer : layerList )
1135 {
1137 {
1138 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
1139 {
1140 if ( vl->labelsEnabled() || vl->diagramsEnabled() || ( vl->renderer() && vl->renderer()->flags().testFlag( Qgis::FeatureRendererFlag::AffectsLabeling ) ) )
1141 invalidateLabels = true;
1142 }
1143
1145 continue;
1146
1147 mCache->invalidateCacheForLayer( layer );
1148 }
1149 else if ( QgsGroupLayer *gl = qobject_cast<QgsGroupLayer *>( layer ) )
1150 {
1151 const QList<QgsMapLayer *> childLayerList = gl->childLayers();
1152 for ( QgsMapLayer *childLayer : childLayerList )
1153 {
1154 if ( childLayer->elevationProperties() && childLayer->elevationProperties()->hasElevation() )
1155 {
1156 if ( childLayer->elevationProperties()->flags() & QgsMapLayerElevationProperties::FlagDontInvalidateCachedRendersWhenRangeChanges )
1157 continue;
1158
1159 mCache->invalidateCacheForLayer( layer );
1160 break;
1161 }
1162 }
1163 }
1164 }
1165
1166 if ( invalidateLabels )
1167 {
1168 mCache->clearCacheImage( QStringLiteral( "_labels_" ) );
1169 mCache->clearCacheImage( QStringLiteral( "_preview_labels_" ) );
1170 }
1171 }
1172}
1173
1174void QgsMapCanvas::showContextMenu( QgsMapMouseEvent *event )
1175{
1176 const QgsPointXY mapPoint = event->originalMapPoint();
1177
1178 QMenu menu;
1179
1180 QMenu *copyCoordinateMenu = new QMenu( tr( "Copy Coordinate" ), &menu );
1181 copyCoordinateMenu->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ) );
1182
1183 auto addCoordinateFormat = [ &, this]( const QString identifier, const QgsCoordinateReferenceSystem & crs )
1184 {
1185 const QgsCoordinateTransform ct( mSettings.destinationCrs(), crs, mSettings.transformContext() );
1186 try
1187 {
1188 const QgsPointXY transformedPoint = ct.transform( mapPoint );
1189
1190 // calculate precision based on visible map extent -- if user is zoomed in, we get better precision!
1191 int displayPrecision = 0;
1192 try
1193 {
1194 QgsCoordinateTransform extentTransform = ct;
1195 extentTransform.setBallparkTransformsAreAppropriate( true );
1196 QgsRectangle extentReproj = extentTransform.transformBoundingBox( extent() );
1197 const double mapUnitsPerPixel = ( extentReproj.width() / width() + extentReproj.height() / height() ) * 0.5;
1198 if ( mapUnitsPerPixel > 10 )
1199 displayPrecision = 0;
1200 else if ( mapUnitsPerPixel > 1 )
1201 displayPrecision = 1;
1202 else if ( mapUnitsPerPixel > 0.1 )
1203 displayPrecision = 2;
1204 else if ( mapUnitsPerPixel > 0.01 )
1205 displayPrecision = 3;
1206 else if ( mapUnitsPerPixel > 0.001 )
1207 displayPrecision = 4;
1208 else if ( mapUnitsPerPixel > 0.0001 )
1209 displayPrecision = 5;
1210 else if ( mapUnitsPerPixel > 0.00001 )
1211 displayPrecision = 6;
1212 else if ( mapUnitsPerPixel > 0.000001 )
1213 displayPrecision = 7;
1214 else if ( mapUnitsPerPixel > 0.0000001 )
1215 displayPrecision = 8;
1216 else
1217 displayPrecision = 9;
1218 }
1219 catch ( QgsCsException & )
1220 {
1221 displayPrecision = crs.mapUnits() == Qgis::DistanceUnit::Degrees ? 5 : 3;
1222 }
1223
1224 const QList< Qgis::CrsAxisDirection > axisList = crs.axisOrdering();
1225 QString firstSuffix;
1226 QString secondSuffix;
1227 if ( axisList.size() >= 2 )
1228 {
1231 }
1232
1233 QString firstNumber;
1234 QString secondNumber;
1236 {
1237 firstNumber = QString::number( transformedPoint.y(), 'f', displayPrecision );
1238 secondNumber = QString::number( transformedPoint.x(), 'f', displayPrecision );
1239 }
1240 else
1241 {
1242 firstNumber = QString::number( transformedPoint.x(), 'f', displayPrecision );
1243 secondNumber = QString::number( transformedPoint.y(), 'f', displayPrecision );
1244 }
1245
1246 QAction *copyCoordinateAction = new QAction( QStringLiteral( "%5 (%1%2, %3%4)" ).arg(
1247 firstNumber, firstSuffix, secondNumber, secondSuffix, identifier ), &menu );
1248
1249 connect( copyCoordinateAction, &QAction::triggered, this, [firstNumber, secondNumber, transformedPoint]
1250 {
1251 QClipboard *clipboard = QApplication::clipboard();
1252
1253 const QString coordinates = firstNumber + ',' + secondNumber;
1254
1255 //if we are on x11 system put text into selection ready for middle button pasting
1256 if ( clipboard->supportsSelection() )
1257 {
1258 clipboard->setText( coordinates, QClipboard::Selection );
1259 }
1260 clipboard->setText( coordinates, QClipboard::Clipboard );
1261
1262 } );
1263 copyCoordinateMenu->addAction( copyCoordinateAction );
1264 }
1265 catch ( QgsCsException & )
1266 {
1267
1268 }
1269 };
1270
1271 addCoordinateFormat( tr( "Map CRS — %1" ).arg( mSettings.destinationCrs().userFriendlyIdentifier( Qgis::CrsIdentifierType::MediumString ) ), mSettings.destinationCrs() );
1272 QgsCoordinateReferenceSystem wgs84( QStringLiteral( "EPSG:4326" ) );
1273 if ( mSettings.destinationCrs() != wgs84 )
1274 addCoordinateFormat( wgs84.userFriendlyIdentifier( Qgis::CrsIdentifierType::MediumString ), wgs84 );
1275
1276 QgsSettings settings;
1277 const QString customCrsString = settings.value( QStringLiteral( "qgis/custom_coordinate_crs" ) ).toString();
1278 if ( !customCrsString.isEmpty() )
1279 {
1280 QgsCoordinateReferenceSystem customCrs( customCrsString );
1281 if ( customCrs != mSettings.destinationCrs() && customCrs != QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) )
1282 {
1283 addCoordinateFormat( customCrs.userFriendlyIdentifier( Qgis::CrsIdentifierType::MediumString ), customCrs );
1284 }
1285 }
1286 copyCoordinateMenu->addSeparator();
1287 QAction *setCustomCrsAction = new QAction( tr( "Set Custom CRS…" ), &menu );
1288 connect( setCustomCrsAction, &QAction::triggered, this, [ = ]
1289 {
1290 QgsProjectionSelectionDialog selector( this );
1291 selector.setCrs( QgsCoordinateReferenceSystem( customCrsString ) );
1292 if ( selector.exec() )
1293 {
1294 QgsSettings().setValue( QStringLiteral( "qgis/custom_coordinate_crs" ), selector.crs().authid().isEmpty() ? selector.crs().toWkt( Qgis::CrsWktVariant::Preferred ) : selector.crs().authid() );
1295 }
1296 } );
1297 copyCoordinateMenu->addAction( setCustomCrsAction );
1298
1299 menu.addMenu( copyCoordinateMenu );
1300
1301 if ( mMapTool )
1302 if ( !mapTool()->populateContextMenuWithEvent( &menu, event ) )
1303 mMapTool->populateContextMenu( &menu );
1304
1305 emit contextMenuAboutToShow( &menu, event );
1306
1307 if ( !menu.isEmpty() ) // menu can be empty after populateContextMenu() and contextMenuAboutToShow()
1308 menu.exec( event->globalPos() );
1309}
1310
1311void QgsMapCanvas::notifyRendererErrors( const QgsMapRendererJob::Errors &errors )
1312{
1313 const QDateTime currentTime = QDateTime::currentDateTime();
1314
1315 // remove errors too old
1316 for ( const QgsMapRendererJob::Error &error : errors )
1317 {
1318 const QString errorKey = error.layerID + ':' + error.message;
1319 if ( mRendererErrors.contains( errorKey ) )
1320 {
1321 const QDateTime sameErrorTime = mRendererErrors.value( errorKey );
1322
1323 if ( sameErrorTime.secsTo( currentTime ) < 60 )
1324 continue;
1325 }
1326
1327 mRendererErrors[errorKey] = currentTime;
1328
1329 if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( error.layerID ) )
1330 emit renderErrorOccurred( error.message, layer );
1331 }
1332}
1333
1334void QgsMapCanvas::updateDevicePixelFromScreen()
1335{
1336 mSettings.setDevicePixelRatio( static_cast<float>( devicePixelRatioF() ) );
1337 // TODO: QGIS 4 -> always respect screen dpi
1339 {
1340 if ( window()->windowHandle() )
1341 {
1342 mSettings.setOutputDpi( window()->windowHandle()->screen()->physicalDotsPerInch() );
1343 mSettings.setDpiTarget( window()->windowHandle()->screen()->physicalDotsPerInch() );
1344 }
1345 }
1346 else
1347 {
1348 // Fallback: compatibility with QGIS <= 3.20; always assume low dpi screens
1349 mSettings.setOutputDpi( window()->windowHandle()->screen()->logicalDotsPerInch() );
1350 mSettings.setDpiTarget( window()->windowHandle()->screen()->logicalDotsPerInch() );
1351 }
1352 refresh();
1353}
1354
1355void QgsMapCanvas::onElevationShadingRendererChanged()
1356{
1357 if ( !mProject )
1358 return;
1359 bool wasDeactivated = !mSettings.elevationShadingRenderer().isActive();
1361 if ( mCache && wasDeactivated )
1362 mCache->clear();
1363 refresh();
1364}
1365
1367{
1368 if ( temporalRange() == dateTimeRange )
1369 return;
1370
1371 mSettings.setTemporalRange( dateTimeRange );
1372 mSettings.setIsTemporal( dateTimeRange.begin().isValid() || dateTimeRange.end().isValid() );
1373
1374 emit temporalRangeChanged();
1375
1376 // we need to discard any previously cached images which have temporal properties enabled, so that these will be updated when
1377 // the canvas is redrawn
1378 mCacheInvalidations |= CacheInvalidationType::Temporal;
1379
1380 autoRefreshTriggered();
1381}
1382
1384{
1385 return mSettings.temporalRange();
1386}
1387
1389{
1390 mInteractionBlockers.append( blocker );
1391}
1392
1394{
1395 mInteractionBlockers.removeAll( blocker );
1396}
1397
1399{
1400 for ( const QgsMapCanvasInteractionBlocker *block : mInteractionBlockers )
1401 {
1402 if ( block->blockCanvasInteraction( interaction ) )
1403 return false;
1404 }
1405 return true;
1406}
1407
1409{
1410 if ( mMapController )
1411 {
1412 delete mMapController;
1413 mMapController = nullptr;
1414 }
1415
1416 if ( !controller )
1417 return;
1418
1419 mMapController = controller;
1420 mMapController->setParent( this );
1421
1422#if 0
1423 // connect high level signals to the canvas, e.g.
1424 connect( mMapController, &QgsAbstract2DMapController::zoomMap, this, [ = ]( double factor ) { zoomByFactor( factor ); } );
1425#endif
1426}
1427
1428void QgsMapCanvas::mapUpdateTimeout()
1429{
1430 if ( mJob )
1431 {
1432 const QImage &img = mJob->renderedImage();
1433 mMap->setContent( img, imageRect( img, mSettings ) );
1434 }
1435}
1436
1438{
1439 if ( mJob )
1440 {
1441 QgsDebugMsgLevel( QStringLiteral( "CANVAS stop rendering!" ), 2 );
1442 mJobCanceled = true;
1443 disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
1444 connect( mJob, &QgsMapRendererQImageJob::finished, mJob, &QgsMapRendererQImageJob::deleteLater );
1445 mJob->cancelWithoutBlocking();
1446 mJob = nullptr;
1447 emit mapRefreshCanceled();
1448 }
1449 stopPreviewJobs();
1450}
1451
1452//the format defaults to "PNG" if not specified
1453void QgsMapCanvas::saveAsImage( const QString &fileName, QPixmap *theQPixmap, const QString &format )
1454{
1455 QPainter painter;
1456 QImage image;
1457
1458 //
1459 //check if the optional QPaintDevice was supplied
1460 //
1461 if ( theQPixmap )
1462 {
1463 image = theQPixmap->toImage();
1464 painter.begin( &image );
1465
1466 // render
1467 QgsMapRendererCustomPainterJob job( mSettings, &painter );
1468 job.start();
1469 job.waitForFinished();
1470 emit renderComplete( &painter );
1471 }
1472 else //use the map view
1473 {
1474 image = mMap->contentImage().copy();
1475 painter.begin( &image );
1476 }
1477
1478 // draw annotations
1479 QStyleOptionGraphicsItem option;
1480 option.initFrom( this );
1481 QGraphicsItem *item = nullptr;
1482 QListIterator<QGraphicsItem *> i( items() );
1483 i.toBack();
1484 while ( i.hasPrevious() )
1485 {
1486 item = i.previous();
1487
1488 if ( !( item && dynamic_cast< QgsMapCanvasAnnotationItem * >( item ) ) )
1489 {
1490 continue;
1491 }
1492
1493 QgsScopedQPainterState painterState( &painter );
1494
1495 QPointF itemScenePos = item->scenePos();
1496 painter.translate( itemScenePos.x(), itemScenePos.y() );
1497
1498 item->paint( &painter, &option );
1499 }
1500
1501 painter.end();
1502 image.save( fileName, format.toLocal8Bit().data() );
1503
1504 QFileInfo myInfo = QFileInfo( fileName );
1505
1506 // build the world file name
1507 QString outputSuffix = myInfo.suffix();
1508 QString myWorldFileName = myInfo.absolutePath() + '/' + myInfo.completeBaseName() + '.'
1509 + outputSuffix.at( 0 ) + outputSuffix.at( myInfo.suffix().size() - 1 ) + 'w';
1510 QFile myWorldFile( myWorldFileName );
1511 if ( !myWorldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) //don't use QIODevice::Text
1512 {
1513 return;
1514 }
1515 QTextStream myStream( &myWorldFile );
1517}
1518
1520{
1521 return mapSettings().visibleExtent();
1522}
1523
1525{
1526 return QgsMapLayerUtils::combinedExtent( mSettings.layers(), mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
1527}
1528
1530{
1532 QgsCoordinateTransform ct( extent.crs(), mapSettings().destinationCrs(), mProject ? mProject->transformContext() : QgsProject::instance()->transformContext() );
1534 QgsRectangle rect;
1535 try
1536 {
1537 rect = ct.transformBoundingBox( extent );
1538 }
1539 catch ( QgsCsException & )
1540 {
1541 rect = mapSettings().fullExtent();
1542 }
1543
1544 return rect;
1545}
1546
1547void QgsMapCanvas::setExtent( const QgsRectangle &r, bool magnified )
1548{
1549 QgsRectangle current = extent();
1550
1551 if ( ( r == current ) && magnified )
1552 return;
1553
1554 if ( r.isEmpty() )
1555 {
1556 if ( !mSettings.hasValidSettings() )
1557 {
1558 // we can't even just move the map center
1559 QgsDebugMsgLevel( QStringLiteral( "Empty extent - ignoring" ), 2 );
1560 return;
1561 }
1562
1563 // ### QGIS 3: do not allow empty extent - require users to call setCenter() explicitly
1564 QgsDebugMsgLevel( QStringLiteral( "Empty extent - keeping old scale with new center!" ), 2 );
1565
1566 setCenter( r.center() );
1567 }
1568 else
1569 {
1570 // If scale is locked we need to maintain the current scale, so we
1571 // - magnify and recenter the map
1572 // - restore locked scale
1573 if ( mScaleLocked && magnified )
1574 {
1575 ScaleRestorer restorer( this );
1576 const double ratio { mapSettings().extent().width() / mapSettings().extent().height() };
1577 const double factor { r.width() / r.height() > ratio ? mapSettings().extent().width() / r.width() : mapSettings().extent().height() / r.height() };
1578 const double scaleFactor { std::clamp( mSettings.magnificationFactor() * factor, QgsGuiUtils::CANVAS_MAGNIFICATION_MIN, QgsGuiUtils::CANVAS_MAGNIFICATION_MAX ) };
1579 const QgsPointXY newCenter { r.center() };
1580 mSettings.setMagnificationFactor( scaleFactor, &newCenter );
1581 emit magnificationChanged( scaleFactor );
1582 }
1583 else
1584 {
1585 mSettings.setExtent( r, magnified );
1586 }
1587 }
1589 updateScale();
1590
1591 //clear all extent items after current index
1592 for ( int i = mLastExtent.size() - 1; i > mLastExtentIndex; i-- )
1593 {
1594 mLastExtent.removeAt( i );
1595 }
1596
1597 if ( !mLastExtent.isEmpty() && mLastExtent.last() != mSettings.extent() )
1598 {
1599 mLastExtent.append( mSettings.extent() );
1600 }
1601
1602 // adjust history to no more than 100
1603 if ( mLastExtent.size() > 100 )
1604 {
1605 mLastExtent.removeAt( 0 );
1606 }
1607
1608 // the last item is the current extent
1609 mLastExtentIndex = mLastExtent.size() - 1;
1610
1611 // update controls' enabled state
1612 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1613 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1614}
1615
1617{
1618 QgsRectangle canvasExtent = extent;
1619 if ( extent.crs() != mapSettings().destinationCrs() )
1620 {
1621 QgsCoordinateTransform ct( extent.crs(), mapSettings().destinationCrs(), QgsProject::instance() );
1623 canvasExtent = ct.transformBoundingBox( extent );
1624
1625 if ( canvasExtent.isEmpty() )
1626 {
1627 return false;
1628 }
1629 }
1630
1631 setExtent( canvasExtent, true );
1632 return true;
1633}
1634
1636{
1637 const QgsRectangle r = mapSettings().extent();
1638 const double xMin = center.x() - r.width() / 2.0;
1639 const double yMin = center.y() - r.height() / 2.0;
1640 const QgsRectangle rect(
1641 xMin, yMin,
1642 xMin + r.width(), yMin + r.height()
1643 );
1644 if ( ! rect.isEmpty() )
1645 {
1646 setExtent( rect, true );
1647 }
1648} // setCenter
1649
1651{
1653 return r.center();
1654}
1655
1656QgsPointXY QgsMapCanvas::cursorPoint() const
1657{
1658 return mCursorPoint;
1659}
1660
1662{
1663 return mapSettings().rotation();
1664}
1665
1666void QgsMapCanvas::setRotation( double degrees )
1667{
1668 double current = rotation();
1669
1670 if ( qgsDoubleNear( degrees, current ) )
1671 return;
1672
1673 mSettings.setRotation( degrees );
1674 emit rotationChanged( degrees );
1675 emitExtentsChanged(); // visible extent changes with rotation
1676}
1677
1679{
1680 if ( !mBlockScaleChangedSignal )
1681 emit scaleChanged( mapSettings().scale() );
1682}
1683
1685{
1687 // If the full extent is an empty set, don't do the zoom
1688 if ( !extent.isEmpty() )
1689 {
1690 // Add a 5% margin around the full extent
1691 extent.scale( 1.05 );
1692 setExtent( extent, true );
1693 }
1694 refresh();
1695}
1696
1698{
1700
1701 // If the full extent is an empty set, don't do the zoom
1702 if ( !extent.isEmpty() )
1703 {
1704 // Add a 5% margin around the full extent
1705 extent.scale( 1.05 );
1706 setExtent( extent, true );
1707 }
1708 refresh();
1709}
1710
1712{
1713 if ( mLastExtentIndex > 0 )
1714 {
1715 mLastExtentIndex--;
1716 mSettings.setExtent( mLastExtent[mLastExtentIndex] );
1718 updateScale();
1719 refresh();
1720 // update controls' enabled state
1721 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1722 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1723 }
1724
1725} // zoomToPreviousExtent
1726
1728{
1729 if ( mLastExtentIndex < mLastExtent.size() - 1 )
1730 {
1731 mLastExtentIndex++;
1732 mSettings.setExtent( mLastExtent[mLastExtentIndex] );
1734 updateScale();
1735 refresh();
1736 // update controls' enabled state
1737 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1738 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1739 }
1740}// zoomToNextExtent
1741
1743{
1744 mLastExtent.clear(); // clear the zoom history list
1745 mLastExtent.append( mSettings.extent() ) ; // set the current extent in the list
1746 mLastExtentIndex = mLastExtent.size() - 1;
1747 // update controls' enabled state
1748 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1749 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1750}// clearExtentHistory
1751
1752QgsRectangle QgsMapCanvas::optimalExtentForPointLayer( QgsVectorLayer *layer, const QgsPointXY &center, int scaleFactor )
1753{
1754 QgsRectangle rect( center, center );
1755
1756 if ( layer->geometryType() == Qgis::GeometryType::Point )
1757 {
1758 QgsPointXY centerLayerCoordinates = mSettings.mapToLayerCoordinates( layer, center );
1759 QgsRectangle extentRect = mSettings.mapToLayerCoordinates( layer, extent() ).scaled( 1.0 / scaleFactor, &centerLayerCoordinates );
1761 QgsFeatureIterator fit = layer->getFeatures( req );
1762 QgsFeature f;
1763 QgsPointXY closestPoint;
1764 double closestSquaredDistance = pow( extentRect.width(), 2.0 ) + pow( extentRect.height(), 2.0 );
1765 bool pointFound = false;
1766 while ( fit.nextFeature( f ) )
1767 {
1768 QgsPointXY point = f.geometry().asPoint();
1769 double sqrDist = point.sqrDist( centerLayerCoordinates );
1770 if ( sqrDist > closestSquaredDistance || sqrDist < 4 * std::numeric_limits<double>::epsilon() )
1771 continue;
1772 pointFound = true;
1773 closestPoint = point;
1774 closestSquaredDistance = sqrDist;
1775 }
1776 if ( pointFound )
1777 {
1778 // combine selected point with closest point and scale this rect
1779 rect.combineExtentWith( mSettings.layerToMapCoordinates( layer, closestPoint ) );
1780 rect.scale( scaleFactor, &center );
1781 }
1782 }
1783 return rect;
1784}
1785
1787{
1788 QgsTemporaryCursorOverride cursorOverride( Qt::WaitCursor );
1789
1790 if ( !layer )
1791 {
1792 // use current layer by default
1793 layer = mCurrentLayer;
1794 }
1795
1796 if ( !layer || !layer->isSpatial() )
1797 return;
1798
1799 QgsRectangle rect;
1800
1801 switch ( layer->type() )
1802 {
1804 {
1805 QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer );
1806 if ( vlayer->selectedFeatureCount() == 0 )
1807 return;
1808
1809 rect = vlayer->boundingBoxOfSelected();
1810 if ( rect.isNull() )
1811 {
1812 cursorOverride.release();
1813 emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1814 return;
1815 }
1816
1818
1819 // zoom in if point cannot be distinguished from others
1820 // also check that rect is empty, as it might not in case of multi points
1821 if ( vlayer->geometryType() == Qgis::GeometryType::Point && rect.isEmpty() )
1822 {
1823 rect = optimalExtentForPointLayer( vlayer, rect.center() );
1824 }
1825 break;
1826 }
1827
1829 {
1830 QgsVectorTileLayer *vtLayer = qobject_cast< QgsVectorTileLayer * >( layer );
1831 if ( vtLayer->selectedFeatureCount() == 0 )
1832 return;
1833
1834 const QList< QgsFeature > selectedFeatures = vtLayer->selectedFeatures();
1835 for ( const QgsFeature &feature : selectedFeatures )
1836 {
1837 if ( !feature.hasGeometry() )
1838 continue;
1839
1840 rect.combineExtentWith( feature.geometry().boundingBox() );
1841 }
1842
1843 if ( rect.isNull() )
1844 {
1845 cursorOverride.release();
1846 emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1847 return;
1848 }
1849
1851 break;
1852 }
1853
1861 return; // not supported
1862 }
1863
1864 zoomToFeatureExtent( rect );
1865}
1866
1867void QgsMapCanvas::zoomToSelected( const QList<QgsMapLayer *> &layers )
1868{
1869 QgsRectangle rect;
1870 rect.setNull();
1871 QgsRectangle selectionExtent;
1872 selectionExtent.setNull();
1873
1874 for ( QgsMapLayer *mapLayer : layers )
1875 {
1876 if ( !mapLayer || !mapLayer->isSpatial() )
1877 continue;
1878
1879 switch ( mapLayer->type() )
1880 {
1882 {
1883 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
1884
1885 if ( layer->selectedFeatureCount() == 0 )
1886 continue;
1887
1888 rect = layer->boundingBoxOfSelected();
1889
1890 if ( rect.isNull() )
1891 continue;
1892
1894
1895 if ( layer->geometryType() == Qgis::GeometryType::Point && rect.isEmpty() )
1896 rect = optimalExtentForPointLayer( layer, rect.center() );
1897
1898 selectionExtent.combineExtentWith( rect );
1899 break;
1900 }
1901
1903 {
1904 QgsVectorTileLayer *vtLayer = qobject_cast< QgsVectorTileLayer * >( mapLayer );
1905 if ( vtLayer->selectedFeatureCount() == 0 )
1906 continue;
1907
1908 const QList< QgsFeature > selectedFeatures = vtLayer->selectedFeatures();
1909 QgsRectangle rect;
1910 for ( const QgsFeature &feature : selectedFeatures )
1911 {
1912 if ( !feature.hasGeometry() )
1913 continue;
1914
1915 rect.combineExtentWith( feature.geometry().boundingBox() );
1916 }
1917
1918 rect = mapSettings().layerExtentToOutputExtent( vtLayer, rect );
1919 selectionExtent.combineExtentWith( rect );
1920 break;
1921 }
1922
1930 break;
1931 }
1932 }
1933
1934 if ( selectionExtent.isNull() )
1935 {
1936 emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1937 return;
1938 }
1939
1940 zoomToFeatureExtent( selectionExtent );
1941}
1942
1944{
1945 return mSettings.zRange();
1946}
1947
1949{
1950 if ( zRange() == range )
1951 return;
1952
1953 mSettings.setZRange( range );
1954
1955 emit zRangeChanged();
1956
1957 // we need to discard any previously cached images which are elevation aware, so that these will be updated when
1958 // the canvas is redrawn
1959 mCacheInvalidations |= CacheInvalidationType::Elevation;
1960
1961 autoRefreshTriggered();
1962}
1963
1965{
1966 // no selected features, only one selected point feature
1967 //or two point features with the same x- or y-coordinates
1968 if ( rect.isEmpty() )
1969 {
1970 // zoom in
1971 QgsPointXY c = rect.center();
1972 rect = extent();
1973 rect.scale( 1.0, &c );
1974 }
1975 //zoom to an area
1976 else
1977 {
1978 // Expand rect to give a bit of space around the selected
1979 // objects so as to keep them clear of the map boundaries
1980 // The same 5% should apply to all margins.
1981 rect.scale( 1.05 );
1982 }
1983
1984 setExtent( rect );
1985 refresh();
1986}
1987
1989{
1990 if ( !layer )
1991 {
1992 return;
1993 }
1994
1995 QgsRectangle bbox;
1996 QString errorMsg;
1997 if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
1998 {
1999 if ( bbox.isEmpty() )
2000 {
2001 bbox = optimalExtentForPointLayer( layer, bbox.center() );
2002 }
2003 zoomToFeatureExtent( bbox );
2004 }
2005 else
2006 {
2007 emit messageEmitted( tr( "Zoom to feature id failed" ), errorMsg, Qgis::MessageLevel::Warning );
2008 }
2009
2010}
2011
2012void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter )
2013{
2014 if ( !layer )
2015 {
2016 return;
2017 }
2018
2019 QgsRectangle bbox;
2020 QString errorMsg;
2021 if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
2022 {
2023 if ( alwaysRecenter || !mapSettings().extent().contains( bbox ) )
2024 setCenter( bbox.center() );
2025 refresh();
2026 }
2027 else
2028 {
2029 emit messageEmitted( tr( "Pan to feature id failed" ), errorMsg, Qgis::MessageLevel::Warning );
2030 }
2031}
2032
2033bool QgsMapCanvas::boundingBoxOfFeatureIds( const QgsFeatureIds &ids, QgsVectorLayer *layer, QgsRectangle &bbox, QString &errorMsg ) const
2034{
2035 QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
2036 bbox.setNull();
2037 QgsFeature fet;
2038 int featureCount = 0;
2039 errorMsg.clear();
2040
2041 while ( it.nextFeature( fet ) )
2042 {
2043 QgsGeometry geom = fet.geometry();
2044 if ( geom.isNull() )
2045 {
2046 errorMsg = tr( "Feature does not have a geometry" );
2047 }
2048 else if ( geom.constGet()->isEmpty() )
2049 {
2050 errorMsg = tr( "Feature geometry is empty" );
2051 }
2052 if ( !errorMsg.isEmpty() )
2053 {
2054 return false;
2055 }
2057 bbox.combineExtentWith( r );
2058 featureCount++;
2059 }
2060
2061 if ( featureCount != ids.count() )
2062 {
2063 errorMsg = tr( "Feature not found" );
2064 return false;
2065 }
2066
2067 return true;
2068}
2069
2071{
2072 if ( !layer )
2073 {
2074 // use current layer by default
2075 layer = mCurrentLayer;
2076 }
2077 if ( !layer || !layer->isSpatial() )
2078 return;
2079
2080 QgsRectangle rect;
2081 switch ( layer->type() )
2082 {
2084 {
2085 QgsVectorLayer *vLayer = qobject_cast< QgsVectorLayer * >( layer );
2086 if ( vLayer->selectedFeatureCount() == 0 )
2087 return;
2088
2089 rect = vLayer->boundingBoxOfSelected();
2090 break;
2091 }
2093 {
2094 QgsVectorTileLayer *vtLayer = qobject_cast< QgsVectorTileLayer * >( layer );
2095 if ( vtLayer->selectedFeatureCount() == 0 )
2096 return;
2097
2098 const QList< QgsFeature > selectedFeatures = vtLayer->selectedFeatures();
2099 for ( const QgsFeature &feature : selectedFeatures )
2100 {
2101 if ( !feature.hasGeometry() )
2102 continue;
2103
2104 rect.combineExtentWith( feature.geometry().boundingBox() );
2105 }
2106 break;
2107 }
2108
2116 return;
2117 }
2118
2119 if ( rect.isNull() )
2120 {
2121 emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
2122 return;
2123 }
2124
2126 setCenter( rect.center() );
2127 refresh();
2128}
2129
2130void QgsMapCanvas::panToSelected( const QList<QgsMapLayer *> &layers )
2131{
2132 QgsRectangle selectionExtent;
2133 selectionExtent.setNull();
2134
2135 for ( QgsMapLayer *mapLayer : layers )
2136 {
2137 if ( !mapLayer || !mapLayer->isSpatial() )
2138 continue;
2139
2140 QgsRectangle rect;
2141 switch ( mapLayer->type() )
2142 {
2144 {
2145 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
2146 if ( layer->selectedFeatureCount() == 0 )
2147 continue;
2148
2149 rect = layer->boundingBoxOfSelected();
2150
2151 if ( rect.isNull() )
2152 continue;
2153
2155
2156 if ( layer->geometryType() == Qgis::GeometryType::Point && rect.isEmpty() )
2157 rect = optimalExtentForPointLayer( layer, rect.center() );
2158 break;
2159 }
2160
2162 {
2163 QgsVectorTileLayer *vtLayer = qobject_cast< QgsVectorTileLayer * >( mapLayer );
2164 if ( vtLayer->selectedFeatureCount() == 0 )
2165 continue;
2166
2167 const QList< QgsFeature > selectedFeatures = vtLayer->selectedFeatures();
2168 for ( const QgsFeature &feature : selectedFeatures )
2169 {
2170 if ( !feature.hasGeometry() )
2171 continue;
2172
2173 rect.combineExtentWith( feature.geometry().boundingBox() );
2174 }
2175
2176 rect = mapSettings().layerExtentToOutputExtent( vtLayer, rect );
2177 break;
2178 }
2179
2187 continue;
2188 }
2189
2190 selectionExtent.combineExtentWith( rect );
2191 }
2192
2193 if ( selectionExtent.isNull() )
2194 {
2195 emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
2196 return;
2197 }
2198
2199 setCenter( selectionExtent.center() );
2200 refresh();
2201}
2202
2204 const QColor &color1, const QColor &color2,
2205 int flashes, int duration )
2206{
2207 if ( !layer )
2208 {
2209 return;
2210 }
2211
2212 QList< QgsGeometry > geoms;
2213
2214 QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
2215 QgsFeature fet;
2216 while ( it.nextFeature( fet ) )
2217 {
2218 if ( !fet.hasGeometry() )
2219 continue;
2220 geoms << fet.geometry();
2221 }
2222
2223 flashGeometries( geoms, layer->crs(), color1, color2, flashes, duration );
2224}
2225
2226void QgsMapCanvas::flashGeometries( const QList<QgsGeometry> &geometries, const QgsCoordinateReferenceSystem &crs, const QColor &color1, const QColor &color2, int flashes, int duration )
2227{
2228 if ( geometries.isEmpty() )
2229 return;
2230
2231 Qgis::GeometryType geomType = QgsWkbTypes::geometryType( geometries.at( 0 ).wkbType() );
2232 QgsRubberBand *rb = new QgsRubberBand( this, geomType );
2233 for ( const QgsGeometry &geom : geometries )
2234 rb->addGeometry( geom, crs, false );
2235 rb->updatePosition();
2236 rb->update();
2237
2238 if ( geomType == Qgis::GeometryType::Line || geomType == Qgis::GeometryType::Point )
2239 {
2240 rb->setWidth( 2 );
2241 rb->setSecondaryStrokeColor( QColor( 255, 255, 255 ) );
2242 }
2243 if ( geomType == Qgis::GeometryType::Point )
2245
2246 QColor startColor = color1;
2247 if ( !startColor.isValid() )
2248 {
2249 if ( geomType == Qgis::GeometryType::Polygon )
2250 {
2251 startColor = rb->fillColor();
2252 }
2253 else
2254 {
2255 startColor = rb->strokeColor();
2256 }
2257 startColor.setAlpha( 255 );
2258 }
2259 QColor endColor = color2;
2260 if ( !endColor.isValid() )
2261 {
2262 endColor = startColor;
2263 endColor.setAlpha( 0 );
2264 }
2265
2266
2267 QVariantAnimation *animation = new QVariantAnimation( this );
2268 connect( animation, &QVariantAnimation::finished, this, [animation, rb]
2269 {
2270 animation->deleteLater();
2271 delete rb;
2272 } );
2273 connect( animation, &QPropertyAnimation::valueChanged, this, [rb, geomType]( const QVariant & value )
2274 {
2275 QColor c = value.value<QColor>();
2276 if ( geomType == Qgis::GeometryType::Polygon )
2277 {
2278 rb->setFillColor( c );
2279 }
2280 else
2281 {
2282 rb->setStrokeColor( c );
2283 QColor c = rb->secondaryStrokeColor();
2284 c.setAlpha( c.alpha() );
2286 }
2287 rb->update();
2288 } );
2289
2290 animation->setDuration( duration * flashes );
2291 animation->setStartValue( endColor );
2292 double midStep = 0.2 / flashes;
2293 for ( int i = 0; i < flashes; ++i )
2294 {
2295 double start = static_cast< double >( i ) / flashes;
2296 animation->setKeyValueAt( start + midStep, startColor );
2297 double end = static_cast< double >( i + 1 ) / flashes;
2298 if ( !qgsDoubleNear( end, 1.0 ) )
2299 animation->setKeyValueAt( end, endColor );
2300 }
2301 animation->setEndValue( endColor );
2302 animation->start();
2303}
2304
2306{
2307 if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )
2308 {
2309 emit keyPressed( e );
2310 return;
2311 }
2312
2313 // Don't want to interfer with mouse events
2314 if ( ! mCanvasProperties->mouseButtonDown )
2315 {
2316 // this is backwards, but we can't change now without breaking api because
2317 // forever QgsMapTools have had to explicitly mark events as ignored in order to
2318 // indicate that they've consumed the event and that the default behavior should not
2319 // be applied..!
2320 e->accept();
2321 if ( mMapTool )
2322 {
2323 mMapTool->keyPressEvent( e );
2324 if ( !e->isAccepted() ) // map tool consumed event
2325 return;
2326 }
2327
2328 QgsRectangle currentExtent = mapSettings().visibleExtent();
2329 double dx = std::fabs( currentExtent.width() / 4 );
2330 double dy = std::fabs( currentExtent.height() / 4 );
2331
2332 switch ( e->key() )
2333 {
2334 case Qt::Key_Left:
2335 QgsDebugMsgLevel( QStringLiteral( "Pan left" ), 2 );
2336 setCenter( center() - QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
2337 refresh();
2338 break;
2339
2340 case Qt::Key_Right:
2341 QgsDebugMsgLevel( QStringLiteral( "Pan right" ), 2 );
2342 setCenter( center() + QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
2343 refresh();
2344 break;
2345
2346 case Qt::Key_Up:
2347 QgsDebugMsgLevel( QStringLiteral( "Pan up" ), 2 );
2348 setCenter( center() + QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
2349 refresh();
2350 break;
2351
2352 case Qt::Key_Down:
2353 QgsDebugMsgLevel( QStringLiteral( "Pan down" ), 2 );
2354 setCenter( center() - QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
2355 refresh();
2356 break;
2357
2358 case Qt::Key_Space:
2359 QgsDebugMsgLevel( QStringLiteral( "Pressing pan selector" ), 2 );
2360
2361 //mCanvasProperties->dragging = true;
2362 if ( ! e->isAutoRepeat() )
2363 {
2364 mTemporaryCursorOverride.reset( new QgsTemporaryCursorOverride( Qt::ClosedHandCursor ) );
2365 mCanvasProperties->panSelectorDown = true;
2366 panActionStart( mCanvasProperties->mouseLastXY );
2367 }
2368 break;
2369
2370 case Qt::Key_PageUp:
2371 QgsDebugMsgLevel( QStringLiteral( "Zoom in" ), 2 );
2372 zoomIn();
2373 break;
2374
2375 case Qt::Key_PageDown:
2376 QgsDebugMsgLevel( QStringLiteral( "Zoom out" ), 2 );
2377 zoomOut();
2378 break;
2379
2380#if 0
2381 case Qt::Key_P:
2382 mUseParallelRendering = !mUseParallelRendering;
2383 refresh();
2384 break;
2385
2386 case Qt::Key_S:
2387 mDrawRenderingStats = !mDrawRenderingStats;
2388 refresh();
2389 break;
2390#endif
2391
2392 default:
2393 // Pass it on
2394 if ( !mMapTool )
2395 {
2396 e->ignore();
2397 QgsDebugMsgLevel( "Ignoring key: " + QString::number( e->key() ), 2 );
2398 }
2399 }
2400 }
2401
2402 emit keyPressed( e );
2403}
2404
2406{
2407 QgsDebugMsgLevel( QStringLiteral( "keyRelease event" ), 2 );
2408
2409 switch ( e->key() )
2410 {
2411 case Qt::Key_Space:
2412 if ( !e->isAutoRepeat() && mCanvasProperties->panSelectorDown )
2413 {
2414 QgsDebugMsgLevel( QStringLiteral( "Releasing pan selector" ), 2 );
2415 mTemporaryCursorOverride.reset();
2416 mCanvasProperties->panSelectorDown = false;
2417 panActionEnd( mCanvasProperties->mouseLastXY );
2418 }
2419 break;
2420
2421 default:
2422 // Pass it on
2423 if ( mMapTool )
2424 {
2425 mMapTool->keyReleaseEvent( e );
2426 }
2427 else e->ignore();
2428
2429 QgsDebugMsgLevel( "Ignoring key release: " + QString::number( e->key() ), 2 );
2430 }
2431
2432 emit keyReleased( e );
2433
2434} //keyReleaseEvent()
2435
2436
2438{
2439 // call handler of current map tool
2440 if ( mMapTool )
2441 {
2442 std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2443 mMapTool->canvasDoubleClickEvent( me.get() );
2444 }
2445}// mouseDoubleClickEvent
2446
2447
2448void QgsMapCanvas::beginZoomRect( QPoint pos )
2449{
2450 mZoomRect.setRect( 0, 0, 0, 0 );
2451 mTemporaryCursorOverride.reset( new QgsTemporaryCursorOverride( mZoomCursor ) );
2452 mZoomDragging = true;
2453 mZoomRubberBand.reset( new QgsRubberBand( this, Qgis::GeometryType::Polygon ) );
2454 QColor color( Qt::blue );
2455 color.setAlpha( 63 );
2456 mZoomRubberBand->setColor( color );
2457 mZoomRect.setTopLeft( pos );
2458}
2459
2460void QgsMapCanvas::stopZoomRect()
2461{
2462 if ( mZoomDragging )
2463 {
2464 mZoomDragging = false;
2465 mZoomRubberBand.reset( nullptr );
2466 mTemporaryCursorOverride.reset();
2467 }
2468}
2469
2470void QgsMapCanvas::endZoomRect( QPoint pos )
2471{
2472 stopZoomRect();
2473
2474 // store the rectangle
2475 mZoomRect.setRight( pos.x() );
2476 mZoomRect.setBottom( pos.y() );
2477
2478 //account for bottom right -> top left dragging
2479 mZoomRect = mZoomRect.normalized();
2480
2481 if ( mZoomRect.width() < 5 && mZoomRect.height() < 5 )
2482 {
2483 //probably a mistake - would result in huge zoom!
2484 return;
2485 }
2486
2487 // set center and zoom
2488 const QSize &zoomRectSize = mZoomRect.size();
2489 const QSize &canvasSize = mSettings.outputSize();
2490 double sfx = static_cast< double >( zoomRectSize.width() ) / canvasSize.width();
2491 double sfy = static_cast< double >( zoomRectSize.height() ) / canvasSize.height();
2492 double sf = std::max( sfx, sfy );
2493
2494 QgsPointXY c = mSettings.mapToPixel().toMapCoordinates( mZoomRect.center() );
2495
2496 zoomByFactor( sf, &c );
2497 refresh();
2498}
2499
2500void QgsMapCanvas::startPan()
2501{
2502 if ( !mCanvasProperties->panSelectorDown )
2503 {
2504 mCanvasProperties->panSelectorDown = true;
2505 mTemporaryCursorOverride.reset( new QgsTemporaryCursorOverride( Qt::ClosedHandCursor ) );
2506 panActionStart( mCanvasProperties->mouseLastXY );
2507 }
2508}
2509
2510void QgsMapCanvas::stopPan()
2511{
2512 if ( mCanvasProperties->panSelectorDown )
2513 {
2514 mCanvasProperties->panSelectorDown = false;
2515 mTemporaryCursorOverride.reset();
2516 panActionEnd( mCanvasProperties->mouseLastXY );
2517 }
2518}
2519
2520void QgsMapCanvas::mousePressEvent( QMouseEvent *e )
2521{
2522 // use shift+middle mouse button for zooming, map tools won't receive any events in that case
2523 if ( e->button() == Qt::MiddleButton &&
2524 e->modifiers() & Qt::ShiftModifier )
2525 {
2526 beginZoomRect( e->pos() );
2527 return;
2528 }
2529 //use middle mouse button for panning, map tools won't receive any events in that case
2530 else if ( e->button() == Qt::MiddleButton )
2531 {
2532 startPan();
2533 }
2534 else
2535 {
2536 // If doing a middle-button-click, followed by a right-button-click,
2537 // cancel the pan or zoomRect action started above.
2538 stopPan();
2539 stopZoomRect();
2540
2541 // call handler of current map tool
2542 if ( mMapTool )
2543 {
2544 if ( mMapTool->flags() & QgsMapTool::AllowZoomRect && e->button() == Qt::LeftButton
2545 && e->modifiers() & Qt::ShiftModifier )
2546 {
2547 beginZoomRect( e->pos() );
2548 return;
2549 }
2550 else if ( mMapTool->flags() & QgsMapTool::ShowContextMenu && e->button() == Qt::RightButton )
2551 {
2552 std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2553 showContextMenu( me.get() );
2554 return;
2555 }
2556 else
2557 {
2558 std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2559 mMapTool->canvasPressEvent( me.get() );
2560 }
2561 }
2562 }
2563
2564 if ( mCanvasProperties->panSelectorDown )
2565 {
2566 return;
2567 }
2568
2569 mCanvasProperties->mouseButtonDown = true;
2570 mCanvasProperties->rubberStartPoint = e->pos();
2571}
2572
2574{
2575 // if using shift+middle mouse button for zooming, end zooming and return
2576 if ( mZoomDragging &&
2577 e->button() == Qt::MiddleButton )
2578 {
2579 endZoomRect( e->pos() );
2580 return;
2581 }
2582 //use middle mouse button for panning, map tools won't receive any events in that case
2583 else if ( e->button() == Qt::MiddleButton )
2584 {
2585 stopPan();
2586 }
2587 else if ( e->button() == Qt::BackButton )
2588 {
2590 return;
2591 }
2592 else if ( e->button() == Qt::ForwardButton )
2593 {
2595 return;
2596 }
2597 else
2598 {
2599 if ( mZoomDragging && e->button() == Qt::LeftButton )
2600 {
2601 endZoomRect( e->pos() );
2602 return;
2603 }
2604
2605 // call handler of current map tool
2606 if ( mMapTool )
2607 {
2608 std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2609 mMapTool->canvasReleaseEvent( me.get() );
2610 }
2611 }
2612
2613
2614 mCanvasProperties->mouseButtonDown = false;
2615
2616 if ( mCanvasProperties->panSelectorDown )
2617 return;
2618
2619}
2620
2621void QgsMapCanvas::resizeEvent( QResizeEvent *e )
2622{
2623 QGraphicsView::resizeEvent( e );
2624 mResizeTimer->start( 500 ); // in charge of refreshing canvas
2625
2626 double oldScale = mSettings.scale();
2627 QSize lastSize = viewport()->size();
2628 mSettings.setOutputSize( lastSize );
2629
2630 mScene->setSceneRect( QRectF( 0, 0, lastSize.width(), lastSize.height() ) );
2631
2632 moveCanvasContents( true );
2633
2634 if ( mScaleLocked )
2635 {
2636 double scaleFactor = oldScale / mSettings.scale();
2637 QgsRectangle r = mSettings.extent();
2638 QgsPointXY center = r.center();
2639 r.scale( scaleFactor, &center );
2640 mSettings.setExtent( r );
2641 }
2642 else
2643 {
2644 updateScale();
2645 }
2646
2648}
2649
2650void QgsMapCanvas::paintEvent( QPaintEvent *e )
2651{
2652 // no custom event handling anymore
2653
2654 QGraphicsView::paintEvent( e );
2655} // paintEvent
2656
2658{
2659 if ( mBlockItemPositionUpdates )
2660 return;
2661
2662 const QList<QGraphicsItem *> items = mScene->items();
2663 for ( QGraphicsItem *gi : items )
2664 {
2665 QgsMapCanvasItem *item = dynamic_cast<QgsMapCanvasItem *>( gi );
2666
2667 if ( item )
2668 {
2669 item->updatePosition();
2670 }
2671 }
2672}
2673
2674
2675void QgsMapCanvas::wheelEvent( QWheelEvent *e )
2676{
2677 // Zoom the map canvas in response to a mouse wheel event. Moving the
2678 // wheel forward (away) from the user zooms in
2679
2680 QgsDebugMsgLevel( "Wheel event delta " + QString::number( e->angleDelta().y() ), 2 );
2681
2682 if ( mMapTool )
2683 {
2684 mMapTool->wheelEvent( e );
2685 if ( e->isAccepted() )
2686 return;
2687 }
2688
2689 if ( e->angleDelta().y() == 0 )
2690 {
2691 e->accept();
2692 return;
2693 }
2694
2695 QgsSettings settings;
2696 bool reverseZoom = settings.value( QStringLiteral( "qgis/reverse_wheel_zoom" ), false ).toBool();
2697 bool zoomIn = reverseZoom ? e->angleDelta().y() < 0 : e->angleDelta().y() > 0;
2698 double zoomFactor = zoomIn ? 1. / zoomInFactor() : zoomOutFactor();
2699
2700 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
2701 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( e->angleDelta().y() );
2702
2703 if ( e->modifiers() & Qt::ControlModifier )
2704 {
2705 //holding ctrl while wheel zooming results in a finer zoom
2706 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
2707 }
2708
2709 double signedWheelFactor = zoomIn ? 1 / zoomFactor : zoomFactor;
2710
2711 // zoom map to mouse cursor by scaling
2712 QgsPointXY oldCenter = center();
2713 QgsPointXY mousePos( getCoordinateTransform()->toMapCoordinates( e->position().x(), e->position().y() ) );
2714 QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * signedWheelFactor ),
2715 mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * signedWheelFactor ) );
2716
2717 zoomByFactor( signedWheelFactor, &newCenter );
2718 e->accept();
2719}
2720
2721void QgsMapCanvas::setWheelFactor( double factor )
2722{
2723 mWheelZoomFactor = std::max( factor, 1.01 );
2724}
2725
2727{
2728 // magnification is already handled in zoomByFactor
2730}
2731
2733{
2734 // magnification is already handled in zoomByFactor
2736}
2737
2738void QgsMapCanvas::zoomScale( double newScale, bool ignoreScaleLock )
2739{
2740 zoomByFactor( newScale / scale(), nullptr, ignoreScaleLock );
2741}
2742
2743void QgsMapCanvas::zoomWithCenter( int x, int y, bool zoomIn )
2744{
2745 double scaleFactor = ( zoomIn ? zoomInFactor() : zoomOutFactor() );
2746
2747 // transform the mouse pos to map coordinates
2749
2750 if ( mScaleLocked )
2751 {
2752 ScaleRestorer restorer( this );
2754 }
2755 else
2756 {
2757 zoomByFactor( scaleFactor, &center );
2758 }
2759}
2760
2761void QgsMapCanvas::setScaleLocked( bool isLocked )
2762{
2763 if ( mScaleLocked != isLocked )
2764 {
2765 mScaleLocked = isLocked;
2766 emit scaleLockChanged( mScaleLocked );
2767 }
2768}
2769
2770void QgsMapCanvas::mouseMoveEvent( QMouseEvent *e )
2771{
2772 mCanvasProperties->mouseLastXY = e->pos();
2773
2774 if ( mCanvasProperties->panSelectorDown )
2775 {
2776 panAction( e );
2777 }
2778 else if ( mZoomDragging )
2779 {
2780 mZoomRect.setBottomRight( e->pos() );
2781 mZoomRubberBand->setToCanvasRectangle( mZoomRect );
2782 mZoomRubberBand->show();
2783 }
2784 else
2785 {
2786 // call handler of current map tool
2787 if ( mMapTool )
2788 {
2789 std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2790 mMapTool->canvasMoveEvent( me.get() );
2791 }
2792 }
2793
2794 // show x y on status bar (if we are mid pan operation, then the cursor point hasn't changed!)
2795 if ( !panOperationInProgress() )
2796 {
2797 mCursorPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->mouseLastXY );
2798 emit xyCoordinates( mCursorPoint );
2799 }
2800}
2801
2802void QgsMapCanvas::setMapTool( QgsMapTool *tool, bool clean )
2803{
2804 if ( !tool )
2805 return;
2806
2807 if ( tool == mMapTool )
2808 {
2809 mMapTool->reactivate();
2810 return;
2811 }
2812
2813 if ( mMapTool )
2814 {
2815 if ( clean )
2816 mMapTool->clean();
2817
2818 disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2819 mMapTool->deactivate();
2820 }
2821
2822 QgsMapTool *oldTool = mMapTool;
2823
2824 // set new map tool and activate it
2825 mMapTool = tool;
2826 emit mapToolSet( mMapTool, oldTool );
2827 if ( mMapTool )
2828 {
2829 connect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2830 mMapTool->activate();
2831 }
2832
2833} // setMapTool
2834
2836{
2837 if ( mMapTool && mMapTool == tool )
2838 {
2839 disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2840 QgsMapTool *oldTool = mMapTool;
2841 mMapTool = nullptr;
2842 oldTool->deactivate();
2843 emit mapToolSet( nullptr, oldTool );
2844 setCursor( Qt::ArrowCursor );
2845 }
2846}
2847
2849{
2850 if ( mProject )
2851 disconnect( mProject, &QgsProject::elevationShadingRendererChanged, this, &QgsMapCanvas::onElevationShadingRendererChanged );
2852
2853 mProject = project;
2854
2855 if ( mProject )
2856 connect( mProject, &QgsProject::elevationShadingRendererChanged, this, &QgsMapCanvas::onElevationShadingRendererChanged );
2857}
2858
2859void QgsMapCanvas::setCanvasColor( const QColor &color )
2860{
2861 if ( canvasColor() == color )
2862 return;
2863
2864 // background of map's pixmap
2865 mSettings.setBackgroundColor( color );
2866
2867 // background of the QGraphicsView
2868 QBrush bgBrush( color );
2869 setBackgroundBrush( bgBrush );
2870#if 0
2871 QPalette palette;
2872 palette.setColor( backgroundRole(), color );
2873 setPalette( palette );
2874#endif
2875
2876 // background of QGraphicsScene
2877 mScene->setBackgroundBrush( bgBrush );
2878
2879 refresh();
2880
2881 emit canvasColorChanged();
2882}
2883
2885{
2886 return mScene->backgroundBrush().color();
2887}
2888
2889void QgsMapCanvas::setSelectionColor( const QColor &color )
2890{
2891 if ( mSettings.selectionColor() == color )
2892 return;
2893
2894 mSettings.setSelectionColor( color );
2895
2896 if ( mCache )
2897 {
2898 bool hasSelectedFeatures = false;
2899 const auto layers = mSettings.layers();
2900 for ( QgsMapLayer *layer : layers )
2901 {
2902 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
2903 if ( vlayer && vlayer->selectedFeatureCount() )
2904 {
2905 hasSelectedFeatures = true;
2906 break;
2907 }
2908 }
2909
2910 if ( hasSelectedFeatures )
2911 {
2912 mCache->clear();
2913 refresh();
2914 }
2915 }
2916}
2917
2919{
2920 return mSettings.selectionColor();
2921}
2922
2924{
2925 return mapSettings().layers().size();
2926}
2927
2928QList<QgsMapLayer *> QgsMapCanvas::layers( bool expandGroupLayers ) const
2929{
2930 return mapSettings().layers( expandGroupLayers );
2931}
2932
2934{
2935 // called when a layer has changed visibility setting
2936 refresh();
2937}
2938
2939void QgsMapCanvas::freeze( bool frozen )
2940{
2941 mFrozen = frozen;
2942}
2943
2945{
2946 return mFrozen;
2947}
2948
2950{
2951 return mapSettings().mapUnitsPerPixel();
2952}
2953
2958
2959QMap<QString, QString> QgsMapCanvas::layerStyleOverrides() const
2960{
2961 return mSettings.layerStyleOverrides();
2962}
2963
2964void QgsMapCanvas::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
2965{
2966 if ( overrides == mSettings.layerStyleOverrides() )
2967 return;
2968
2969 mSettings.setLayerStyleOverrides( overrides );
2970 clearCache();
2972}
2973
2974void QgsMapCanvas::setTheme( const QString &theme )
2975{
2976 if ( mTheme == theme )
2977 return;
2978
2979 clearCache();
2980 if ( theme.isEmpty() || !QgsProject::instance()->mapThemeCollection()->hasMapTheme( theme ) )
2981 {
2982 mTheme.clear();
2983 mSettings.setLayerStyleOverrides( QMap< QString, QString>() );
2984 setLayers( QgsProject::instance()->mapThemeCollection()->masterVisibleLayers() );
2985 emit themeChanged( QString() );
2986 }
2987 else
2988 {
2989 mTheme = theme;
2990 setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
2991 emit themeChanged( theme );
2992 }
2993}
2994
2996{
2997 mRenderFlag = flag;
2998
2999 if ( mRenderFlag )
3000 {
3001 refresh();
3002 }
3003 else
3004 stopRendering();
3005}
3006
3007#if 0
3008void QgsMapCanvas::connectNotify( const char *signal )
3009{
3010 Q_UNUSED( signal )
3011 QgsDebugMsgLevel( "QgsMapCanvas connected to " + QString( signal ), 2 );
3012} //connectNotify
3013#endif
3014
3015void QgsMapCanvas::layerRepaintRequested( bool deferred )
3016{
3017 if ( !deferred )
3018 refresh();
3019}
3020
3021void QgsMapCanvas::autoRefreshTriggered()
3022{
3023 if ( mJob )
3024 {
3025 // canvas is currently being redrawn, so we defer the last requested
3026 // auto refresh until current rendering job finishes
3027 mRefreshAfterJob = true;
3028 return;
3029 }
3030
3031 refresh();
3032}
3033
3034void QgsMapCanvas::updateAutoRefreshTimer()
3035{
3036 // min auto refresh interval stores the smallest interval between layer auto refreshes. We automatically
3037 // trigger a map refresh on this minimum interval
3038 int minAutoRefreshInterval = -1;
3039 const auto layers = mSettings.layers();
3040 for ( QgsMapLayer *layer : layers )
3041 {
3042 int layerRefreshInterval = 0;
3043
3045 {
3046 layerRefreshInterval = layer->autoRefreshInterval();
3047 }
3048 else if ( QgsVectorLayer *vectorLayer = qobject_cast< QgsVectorLayer * >( layer ) )
3049 {
3050 if ( const QgsFeatureRenderer *renderer = vectorLayer->renderer() )
3051 {
3052 const double rendererRefreshRate = QgsSymbolLayerUtils::rendererFrameRate( renderer );
3053 if ( rendererRefreshRate > 0 )
3054 {
3055 layerRefreshInterval = 1000 / rendererRefreshRate;
3056 }
3057 }
3058 }
3059
3060 if ( layerRefreshInterval == 0 )
3061 continue;
3062
3063 minAutoRefreshInterval = minAutoRefreshInterval > 0 ? std::min( layerRefreshInterval, minAutoRefreshInterval ) : layerRefreshInterval;
3064 }
3065
3066 if ( minAutoRefreshInterval > 0 )
3067 {
3068 mAutoRefreshTimer.setInterval( minAutoRefreshInterval );
3069 mAutoRefreshTimer.start();
3070 }
3071 else
3072 {
3073 mAutoRefreshTimer.stop();
3074 }
3075}
3076
3077void QgsMapCanvas::projectThemesChanged()
3078{
3079 if ( mTheme.isEmpty() )
3080 return;
3081
3082 if ( !QgsProject::instance()->mapThemeCollection()->hasMapTheme( mTheme ) )
3083 {
3084 // theme has been removed - stop following
3085 setTheme( QString() );
3086 }
3087
3088}
3089
3091{
3092 return mMapTool;
3093}
3094
3096{
3097 return mProject;
3098}
3099
3100void QgsMapCanvas::panActionEnd( QPoint releasePoint )
3101{
3102 // move map image and other items to standard position
3103 moveCanvasContents( true ); // true means reset
3104
3105 // use start and end box points to calculate the extent
3107 QgsPointXY end = getCoordinateTransform()->toMapCoordinates( releasePoint );
3108
3109 // modify the center
3110 double dx = end.x() - start.x();
3111 double dy = end.y() - start.y();
3112 QgsPointXY c = center();
3113 c.set( c.x() - dx, c.y() - dy );
3114 setCenter( c );
3115
3116 refresh();
3117}
3118
3119void QgsMapCanvas::panActionStart( QPoint releasePoint )
3120{
3121 mCanvasProperties->rubberStartPoint = releasePoint;
3122
3123 mDa = QgsDistanceArea();
3124 mDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
3125 mDa.setSourceCrs( mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
3126}
3127
3128void QgsMapCanvas::panAction( QMouseEvent *e )
3129{
3130 Q_UNUSED( e )
3131
3132 QgsPointXY currentMapPoint = getCoordinateTransform()->toMapCoordinates( e->pos() );
3133 QgsPointXY startMapPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
3134 try
3135 {
3136 emit panDistanceBearingChanged( mDa.measureLine( currentMapPoint, startMapPoint ), mDa.lengthUnits(), mDa.bearing( currentMapPoint, startMapPoint ) * 180 / M_PI );
3137 }
3138 catch ( QgsCsException & )
3139 {}
3140
3141 // move all map canvas items
3143}
3144
3146{
3147 QPoint pnt( 0, 0 );
3148 if ( !reset )
3149 pnt += mCanvasProperties->mouseLastXY - mCanvasProperties->rubberStartPoint;
3150
3151 setSceneRect( -pnt.x(), -pnt.y(), viewport()->size().width(), viewport()->size().height() );
3152}
3153
3154void QgsMapCanvas::dropEvent( QDropEvent *event )
3155{
3156 if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
3157 {
3159 bool allHandled = true;
3160 for ( const QgsMimeDataUtils::Uri &uri : lst )
3161 {
3162 bool handled = false;
3163 for ( QgsCustomDropHandler *handler : std::as_const( mDropHandlers ) )
3164 {
3165 if ( handler && handler->customUriProviderKey() == uri.providerKey )
3166 {
3167 if ( handler->handleCustomUriCanvasDrop( uri, this ) )
3168 {
3169 handled = true;
3170 break;
3171 }
3172 }
3173 }
3174 if ( !handled )
3175 allHandled = false;
3176 }
3177 if ( allHandled )
3178 event->accept();
3179 else
3180 event->ignore();
3181 }
3182 else
3183 {
3184 event->ignore();
3185 }
3186}
3187
3188void QgsMapCanvas::showEvent( QShowEvent *event )
3189{
3190 Q_UNUSED( event )
3191 updateDevicePixelFromScreen();
3192}
3193
3195{
3196 if ( !mBlockExtentChangedSignal )
3197 emit extentsChanged();
3198}
3199
3201{
3202 return mCanvasProperties->mouseLastXY;
3203}
3204
3205void QgsMapCanvas::setPreviewModeEnabled( bool previewEnabled )
3206{
3207 if ( !mPreviewEffect )
3208 {
3209 return;
3210 }
3211
3212 mPreviewEffect->setEnabled( previewEnabled );
3213}
3214
3216{
3217 if ( !mPreviewEffect )
3218 {
3219 return false;
3220 }
3221
3222 return mPreviewEffect->isEnabled();
3223}
3224
3226{
3227 if ( !mPreviewEffect )
3228 {
3229 return;
3230 }
3231
3232 mPreviewEffect->setMode( mode );
3233}
3234
3236{
3237 if ( !mPreviewEffect )
3238 {
3240 }
3241
3242 return mPreviewEffect->mode();
3243}
3244
3246{
3247 if ( !mSnappingUtils )
3248 {
3249 // associate a dummy instance, but better than null pointer
3250 QgsMapCanvas *c = const_cast<QgsMapCanvas *>( this );
3251 c->mSnappingUtils = new QgsMapCanvasSnappingUtils( c, c );
3252 }
3253 return mSnappingUtils;
3254}
3255
3257{
3258 mSnappingUtils = utils;
3259}
3260
3261void QgsMapCanvas::readProject( const QDomDocument &doc )
3262{
3263 QgsProject *project = qobject_cast< QgsProject * >( sender() );
3264
3265 QDomNodeList nodes = doc.elementsByTagName( QStringLiteral( "mapcanvas" ) );
3266 if ( nodes.count() )
3267 {
3268 QDomNode node = nodes.item( 0 );
3269
3270 // Search the specific MapCanvas node using the name
3271 if ( nodes.count() > 1 )
3272 {
3273 for ( int i = 0; i < nodes.size(); ++i )
3274 {
3275 QDomElement elementNode = nodes.at( i ).toElement();
3276
3277 if ( elementNode.hasAttribute( QStringLiteral( "name" ) ) && elementNode.attribute( QStringLiteral( "name" ) ) == objectName() )
3278 {
3279 node = nodes.at( i );
3280 break;
3281 }
3282 }
3283 }
3284
3285 QgsMapSettings tmpSettings;
3286 tmpSettings.readXml( node );
3287 if ( objectName() != QLatin1String( "theMapCanvas" ) )
3288 {
3289 // never manually set the crs for the main canvas - this is instead connected to the project CRS
3290 setDestinationCrs( tmpSettings.destinationCrs() );
3291 }
3292 setExtent( tmpSettings.extent() );
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 );
3320 {
3322 }
3323
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
3429bool QgsMapCanvas::viewportEvent( QEvent *event )
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
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
3541
3542void QgsMapCanvas::startPreviewJobs()
3543{
3544 stopPreviewJobs(); //just in case still running
3545
3546 //canvas preview jobs aren't compatible with rotation
3547 // TODO fix this
3548 if ( !qgsDoubleNear( mSettings.rotation(), 0.0 ) )
3549 return;
3550
3551 schedulePreviewJob( 0 );
3552}
3553
3554void QgsMapCanvas::startPreviewJob( int number )
3555{
3556 QgsRectangle mapRect = mSettings.visibleExtent();
3557
3558 if ( number == 4 )
3559 number += 1;
3560
3561 int j = number / 3;
3562 int i = number % 3;
3563
3564 //copy settings, only update extent
3565 QgsMapSettings jobSettings = mSettings;
3566
3567 double dx = ( i - 1 ) * mapRect.width();
3568 double dy = ( 1 - j ) * mapRect.height();
3569 QgsRectangle jobExtent = mapRect;
3570
3571 jobExtent.setXMaximum( jobExtent.xMaximum() + dx );
3572 jobExtent.setXMinimum( jobExtent.xMinimum() + dx );
3573 jobExtent.setYMaximum( jobExtent.yMaximum() + dy );
3574 jobExtent.setYMinimum( jobExtent.yMinimum() + dy );
3575
3576 jobSettings.setExtent( jobExtent );
3577 jobSettings.setFlag( Qgis::MapSettingsFlag::DrawLabeling, false );
3579 // never profile preview jobs
3580 jobSettings.setFlag( Qgis::MapSettingsFlag::RecordProfile, false );
3581
3582 // truncate preview layers to fast layers
3583 const QList<QgsMapLayer *> layers = jobSettings.layers();
3584 QList< QgsMapLayer * > previewLayers;
3586 context.maxRenderingTimeMs = MAXIMUM_LAYER_PREVIEW_TIME_MS;
3587 for ( QgsMapLayer *layer : layers )
3588 {
3589 if ( layer->customProperty( QStringLiteral( "rendering/noPreviewJobs" ), false ).toBool() )
3590 {
3591 QgsDebugMsgLevel( QStringLiteral( "Layer %1 not rendered because it is explicitly blocked from preview jobs" ).arg( layer->id() ), 3 );
3592 continue;
3593 }
3594 context.lastRenderingTimeMs = mLastLayerRenderTime.value( layer->id(), 0 );
3595 QgsDataProvider *provider = layer->dataProvider();
3596 if ( provider && !provider->renderInPreview( context ) )
3597 {
3598 QgsDebugMsgLevel( QStringLiteral( "Layer %1 not rendered because it does not match the renderInPreview criterion %2" ).arg( layer->id() ).arg( mLastLayerRenderTime.value( layer->id() ) ), 3 );
3599 continue;
3600 }
3601
3602 previewLayers << layer;
3603 }
3605 && QgsProject::instance()->mainAnnotationLayer()->dataProvider()->renderInPreview( context ) )
3606 {
3607 previewLayers.insert( 0, QgsProject::instance()->mainAnnotationLayer() );
3608 }
3609 jobSettings.setLayers( filterLayersForRender( previewLayers ) );
3610
3611 QgsMapRendererQImageJob *job = new QgsMapRendererSequentialJob( jobSettings );
3612 job->setProperty( "number", number );
3613 mPreviewJobs.append( job );
3614 connect( job, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
3615 job->start();
3616}
3617
3618void QgsMapCanvas::stopPreviewJobs()
3619{
3620 mPreviewTimer.stop();
3621 for ( auto previewJob = mPreviewJobs.constBegin(); previewJob != mPreviewJobs.constEnd(); ++previewJob )
3622 {
3623 if ( *previewJob )
3624 {
3625 disconnect( *previewJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
3626 connect( *previewJob, &QgsMapRendererQImageJob::finished, *previewJob, &QgsMapRendererQImageJob::deleteLater );
3627 ( *previewJob )->cancelWithoutBlocking();
3628 }
3629 }
3630 mPreviewJobs.clear();
3631}
3632
3633void QgsMapCanvas::schedulePreviewJob( int number )
3634{
3635 mPreviewTimer.setSingleShot( true );
3636 mPreviewTimer.setInterval( PREVIEW_JOB_DELAY_MS );
3637 disconnect( mPreviewTimerConnection );
3638 mPreviewTimerConnection = connect( &mPreviewTimer, &QTimer::timeout, this, [ = ]()
3639 {
3640 startPreviewJob( number );
3641 } );
3642 mPreviewTimer.start();
3643}
3644
3645bool QgsMapCanvas::panOperationInProgress()
3646{
3647 if ( mCanvasProperties->panSelectorDown )
3648 return true;
3649
3650 if ( QgsMapToolPan *panTool = qobject_cast< QgsMapToolPan *>( mMapTool ) )
3651 {
3652 if ( panTool->isDragging() )
3653 return true;
3654 }
3655
3656 return false;
3657}
3658
3659int QgsMapCanvas::nextZoomLevel( const QList<double> &resolutions, bool zoomIn ) const
3660{
3661 int resolutionLevel = -1;
3662 double currentResolution = mapUnitsPerPixel();
3663 int nResolutions = resolutions.size();
3664
3665 for ( int i = 0; i < nResolutions; ++i )
3666 {
3667 if ( qgsDoubleNear( resolutions[i], currentResolution, 0.0001 ) )
3668 {
3669 resolutionLevel = zoomIn ? ( i - 1 ) : ( i + 1 );
3670 break;
3671 }
3672 else if ( currentResolution <= resolutions[i] )
3673 {
3674 resolutionLevel = zoomIn ? ( i - 1 ) : i;
3675 break;
3676 }
3677 resolutionLevel = zoomIn ? i : i + 1;
3678 }
3679
3680 if ( resolutionLevel < 0 || resolutionLevel >= nResolutions )
3681 {
3682 return -1;
3683 }
3684 if ( zoomIn && resolutionLevel == nResolutions - 1 && resolutions[nResolutions - 1] < currentResolution / mWheelZoomFactor )
3685 {
3686 // Avoid jumping straight to last resolution when zoomed far out and zooming in
3687 return -1;
3688 }
3689 if ( !zoomIn && resolutionLevel == 0 && resolutions[0] > mWheelZoomFactor * currentResolution )
3690 {
3691 // Avoid jumping straight to first resolution when zoomed far in and zooming out
3692 return -1;
3693 }
3694 return resolutionLevel;
3695}
3696
3698{
3699 if ( !mZoomResolutions.isEmpty() )
3700 {
3701 int zoomLevel = nextZoomLevel( mZoomResolutions, true );
3702 if ( zoomLevel != -1 )
3703 {
3704 return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
3705 }
3706 }
3707 return 1 / mWheelZoomFactor;
3708}
3709
3711{
3712 if ( !mZoomResolutions.isEmpty() )
3713 {
3714 int zoomLevel = nextZoomLevel( mZoomResolutions, false );
3715 if ( zoomLevel != -1 )
3716 {
3717 return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
3718 }
3719 }
3720 return mWheelZoomFactor;
3721}
QFlags< MapSettingsFlag > MapSettingsFlags
Map settings flags.
Definition qgis.h:2556
@ MediumString
A medium-length string, recommended for general purpose use.
DistanceUnit
Units of distance.
Definition qgis.h:4669
@ Degrees
Degrees, for planar geographic CRS distance measurements.
@ ShowMainAnnotationLayer
The project's main annotation layer should be rendered in the canvas.
@ Animated
Temporal navigation relies on frames within a datetime range.
@ Movie
Movie mode – behaves like a video player, with a fixed frame duration and no temporal range.
@ FixedRange
Temporal navigation relies on a fixed datetime range.
@ Disabled
Temporal navigation is disabled.
@ Warning
Warning message.
Definition qgis.h:156
@ AffectsLabeling
If present, indicates that the renderer will participate in the map labeling problem.
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:337
@ Polygon
Polygons.
@ Group
Composite group layer. Added in QGIS 3.24.
@ Plugin
Plugin based layer.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ Vector
Vector layer.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ Mesh
Mesh layer. Added in QGIS 3.2.
@ Raster
Raster layer.
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
@ YX
Northing/Easting (or Latitude/Longitude for geographic CRS)
@ View
Renderer used for displaying on screen.
QFlags< MapCanvasFlag > MapCanvasFlags
Flags controlling behavior of map canvases.
Definition qgis.h:3211
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
@ BallparkTransformsAreAppropriate
Indicates that approximate "ballpark" results are appropriate for this coordinate transform....
@ IgnoreImpossibleTransformations
Indicates that impossible transformations (such as those which attempt to transform between two diffe...
@ DrawEditingInfo
Enable drawing of vertex markers for layers in editing mode.
@ RenderPreviewJob
Render is a 'canvas preview' render, and shortcuts should be taken to ensure fast rendering.
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ RecordProfile
Enable run-time profiling while rendering.
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
@ Antialiasing
Enable anti-aliasing for map rendering.
@ DrawLabeling
Enable drawing of labels on top of the map.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
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.
This class represents a coordinate reference system (CRS).
QString userFriendlyIdentifier(Qgis::CrsIdentifierType type=Qgis::CrsIdentifierType::MediumString) const
Returns a user friendly identifier for the CRS.
void updateDefinition()
Updates the definition and parameters of the coordinate reference system to their latest values.
QList< Qgis::CrsAxisDirection > axisOrdering() const
Returns an ordered list of the axis directions reflecting the native axis order for the CRS.
Class for doing transforms between two map 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.
Abstract base class for spatial data provider implementations.
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.
double bearing(const QgsPointXY &p1, const QgsPointXY &p2) const
Computes the bearing (in radians) between two points.
double measureLine(const QVector< QgsPointXY > &points) const
Measures the length of a line with multiple segments.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
Qgis::DistanceUnit lengthUnits() const
Returns the units of distance for length calculations made by this object.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
QgsRange which stores a range of double values.
Definition qgsrange.h:231
bool isActive() const
Returns whether this shading renderer is active.
QString what() const
Abstract interface for generating an expression context scope.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads scope variables from an XML element.
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.
Abstract base class for all 2D vector feature renderers.
This class 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.
A map layer which consists of a set of child layers, where all component layers are rendered as a sin...
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.
Class that 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.
Map canvas is a class for displaying all GIS data types on a canvas.
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)
Freeze/thaw 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.
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)
virtual QgsMapLayerElevationProperties::Flags flags() const
Returns flags associated to the elevation properties.
@ FlagDontInvalidateCachedRendersWhenRangeChanges
Any cached rendering will not be invalidated when z range context is modified.
virtual bool hasElevation() const
Returns true if the layer has an elevation or z component.
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:76
virtual bool isSpatial() const
Returns true if the layer is considered a spatial layer, ie it has some form of geometry associated w...
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
void autoRefreshIntervalChanged(int interval)
Emitted when the auto refresh interval changes.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:83
Q_DECL_DEPRECATED bool hasAutoRefreshEnabled() const
Returns true if auto refresh is enabled for the layer.
QString id
Definition qgsmaplayer.h:79
Qgis::LayerType type
Definition qgsmaplayer.h:86
void rendererChanged()
Signal emitted when renderer is changed.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
int autoRefreshInterval
Definition qgsmaplayer.h:81
virtual Q_INVOKABLE QgsDataProvider * dataProvider()
Returns the layer's data provider, it may be nullptr.
virtual Q_INVOKABLE void reload()
Synchronises with changes in the datasource.
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
This class is responsible for keeping cache of rendered images resulting from a map rendering job.
void clear()
Invalidates the cache contents, clearing all cached images.
void invalidateCacheForLayer(QgsMapLayer *layer)
Invalidates cached images which relate to the specified map layer.
void clearCacheImage(const QString &cacheKey)
Removes an image from the cache with matching cacheKey.
Job implementation that renders everything sequentially using a custom painter.
void waitForFinished() override
Block until the job has finished.
virtual void waitForFinished()=0
Block until the job has finished.
void setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
QHash< QgsMapLayer *, int > perLayerRenderingTime() const
Returns the render time (in ms) per layer.
virtual bool usedCachedLabels() const =0
Returns true if the render job was able to use a cached labeling solution.
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
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.
int renderingTime() const
Returns the total time it took to finish the job (in milliseconds).
QStringList layersRedrawnFromCache() const
Returns a list of the layer IDs for all layers which were redrawn from cached images.
QList< QgsMapRendererJob::Error > Errors
QgsRenderedItemResults * takeRenderedItemResults()
Takes the rendered item results from the map render job and returns them.
virtual bool isActive() const =0
Tell whether the rendering job is currently running in background.
virtual QgsLabelingResults * takeLabelingResults()=0
Gets pointer to internal labeling engine (in order to get access to the results).
virtual void cancel()=0
Stop the rendering job - does not return until the job has terminated.
void setLayerRenderingTimeHints(const QHash< QString, int > &hints)
Sets approximate render times (in ms) for map layers.
virtual void cancelWithoutBlocking()=0
Triggers cancellation of the rendering job without blocking.
Job implementation that renders all layers in parallel.
Intermediate base class adding functionality that allows client to query the rendered image.
virtual QImage renderedImage()=0
Gets a preview/resulting image.
Job implementation that renders everything sequentially in one thread.
static QString worldFileContent(const QgsMapSettings &mapSettings)
Creates the content of a world file.
The QgsMapSettings class contains configuration for rendering of the map.
void setElevationShadingRenderer(const QgsElevationShadingRenderer &renderer)
Sets the shading renderer used to render shading on the entire map.
Qgis::DistanceUnit mapUnits() const
Returns the units of the map's geographical coordinates - used for scale calculation.
void writeXml(QDomNode &node, QDomDocument &doc)
Writes the map settings to an XML node.
QgsPointXY layerToMapCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from layer's CRS to output CRS
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns the global configuration of the labeling engine.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered in the map.
void setSelectionColor(const QColor &color)
Sets the color that is used for drawing of selected vector features.
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.
void setFlags(Qgis::MapSettingsFlags flags)
Sets combination of flags that will be used for rendering.
QgsRectangle layerExtentToOutputExtent(const QgsMapLayer *layer, QgsRectangle extent) const
transform bounding box from layer's CRS to output CRS
QgsDoubleRange zRange() const
Returns the range of z-values which will be visible in the map.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
void setDpiTarget(double dpi)
Sets the target dpi (dots per inch) to be taken into consideration when rendering.
double magnificationFactor() const
Returns the magnification factor.
QStringList layerIds(bool expandGroupLayers=false) const
Returns the list of layer IDs which will be rendered in the map.
void setDevicePixelRatio(float dpr)
Sets the device pixel ratio.
void setZRange(const QgsDoubleRange &range)
Sets the range of z-values which will be visible in the map.
QColor backgroundColor() const
Returns the background color of the map.
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
const QgsMapToPixel & mapToPixel() const
double mapUnitsPerPixel() const
Returns the distance in geographical coordinates that equals to one pixel in the map.
void setRendererUsage(Qgis::RendererUsage rendererUsage)
Sets the rendering usage.
float devicePixelRatio() const
Returns the device pixel ratio.
QSize outputSize() const
Returns the size of the resulting map image, in pixels.
QgsRectangle extent() const
Returns geographical coordinates of the rectangle that should be rendered.
void setSegmentationTolerance(double tolerance)
Sets the segmentation tolerance applied when rendering curved geometries.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the map of map layer style overrides (key: layer ID, value: style name) where a different style ...
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
QColor selectionColor() const
Returns the color that is used for drawing of selected vector features.
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
QgsRectangle fullExtent() const
returns current extent of layer set
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
void setRotation(double rotation)
Sets the rotation of the resulting map image, in degrees clockwise.
void setCurrentFrame(long long frame)
Sets the current frame of the map, for maps which are part of an animation.
const QgsElevationShadingRenderer & elevationShadingRenderer() const
Returns the shading renderer used to render shading on the entire map.
void setPathResolver(const QgsPathResolver &resolver)
Sets the path resolver for conversion between relative and absolute paths during rendering operations...
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
QMap< QString, QString > layerStyleOverrides() const
Returns the map of map layer style overrides (key: layer ID, value: style name) where a different sty...
double rotation() const
Returns the rotation of the resulting map image, in degrees clockwise.
bool hasValidSettings() const
Check whether the map settings are valid and can be used for rendering.
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
void setSegmentationToleranceType(QgsAbstractGeometry::SegmentationToleranceType type)
Sets segmentation tolerance type (maximum angle or maximum difference between curve and approximation...
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 setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
void readXml(QDomNode &node)
Restore the map settings from a XML node.
void setMagnificationFactor(double factor, const QgsPointXY *center=nullptr)
Set the magnification factor.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
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.
bool hasMapTheme(const QString &name) const
Returns whether a map theme with a matching name exists.
QMap< QString, QString > mapThemeStyleOverrides(const QString &name)
Gets layer style overrides (for QgsMapSettings) of the visible layers for given map theme.
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.
A map tool for panning the map.
Abstract base class for all map tools.
Definition qgsmaptool.h:71
virtual void populateContextMenu(QMenu *menu)
Allows the tool to populate and customize the given menu, prior to showing it in response to a right-...
virtual bool canvasToolTipEvent(QHelpEvent *e)
Tooltip event for overriding.
virtual void canvasDoubleClickEvent(QgsMapMouseEvent *e)
Mouse double-click event for overriding. Default implementation does nothing.
virtual bool populateContextMenuWithEvent(QMenu *menu, QgsMapMouseEvent *event)
Allows the tool to populate and customize the given menu, prior to showing it in response to a right-...
virtual void canvasPressEvent(QgsMapMouseEvent *e)
Mouse press event for overriding. Default implementation does nothing.
virtual void canvasMoveEvent(QgsMapMouseEvent *e)
Mouse move event for overriding. Default implementation does nothing.
virtual void keyPressEvent(QKeyEvent *e)
Key event for overriding. Default implementation does nothing.
virtual void keyReleaseEvent(QKeyEvent *e)
Key event for overriding. Default implementation does nothing.
virtual Flags flags() const
Returns the flags for the map tool.
Definition qgsmaptool.h:121
virtual void canvasReleaseEvent(QgsMapMouseEvent *e)
Mouse release event for overriding. Default implementation does nothing.
virtual void wheelEvent(QWheelEvent *e)
Mouse wheel event for overriding. Default implementation does nothing.
@ 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 reactivate()
Called when the map tool is being activated while it is already active.
virtual void clean()
convenient method to clean members
virtual void activate()
called when set as currently active map tool
virtual bool gestureEvent(QGestureEvent *e)
gesture event for overriding. Default implementation does nothing.
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)
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.
void addWidget(QWidget *widget, Qt::Edge edge)
Adds a widget to the layout, which will be bound to the specified edge.
A class to represent 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
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
A graphics effect which can be applied to a widget to simulate various printing and color blindness m...
void setMode(PreviewMode mode)
Sets the mode for the preview effect, which controls how the effect modifies a widgets appearance.
PreviewMode mode() const
Returns the mode used for the preview effect.
double defaultRotation() const
Returns the default map rotation (in clockwise degrees) for maps in the project.
QgsReferencedRectangle defaultViewExtent() const
Returns the default view extent, which should be used as the initial map extent when this project is ...
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:107
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.
QgsPathResolver pathResolver() const
Returns path resolver object with considering whether the project uses absolute or relative paths and...
QgsAnnotationLayer * mainAnnotationLayer()
Returns the main annotation layer associated with the project.
void ellipsoidChanged(const QString &ellipsoid)
Emitted when the project ellipsoid is changed.
QgsMapThemeCollection * mapThemeCollection
Definition qgsproject.h:115
void projectColorsChanged()
Emitted whenever the project's color scheme has been changed.
QgsCoordinateTransformContext transformContext
Definition qgsproject.h:113
QgsElevationShadingRenderer elevationShadingRenderer() const
Returns the elevation shading renderer used for map shading.
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 generic dialog to prompt the user for a Coordinate Reference System.
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
void setYMinimum(double y)
Set the minimum y value.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
void setXMinimum(double x)
Set the minimum x value.
double width() const
Returns the width of the rectangle.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
bool isNull() const
Test if the rectangle is null (holding no spatial information).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
void setYMaximum(double y)
Set the maximum y value.
QgsPointXY center() const
Returns the center point of the rectangle.
void setXMaximum(double x)
Set the maximum x value.
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
bool isEmpty() const
Returns true if the rectangle has no area.
double height() const
Returns the height of the rectangle.
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.
void transferResults(QgsRenderedItemResults *other, const QStringList &layerIds)
Transfers all results from an other QgsRenderedItemResults object where the items have layer IDs matc...
A class 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.
This class is a composition of two QSettings instances:
Definition qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
T enumValue(const QString &key, const T &defaultValue, const Section section=NoSection)
Returns the setting value for a setting based on an enum.
This class has all the 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.
bool isActive() const
Returns true if the temporal property is active.
virtual QgsTemporalProperty::Flags flags() const
Returns flags associated to the temporal property.
@ FlagDontInvalidateCachedRendersWhenRangeChanges
Any cached rendering will not be invalidated when temporal range context is modified.
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
void setIsTemporal(bool enabled)
Sets whether the temporal range is enabled (i.e.
void setTemporalRange(const QgsDateTimeRange &range)
Sets the temporal range for the object.
T begin() const
Returns the beginning of the range.
Definition qgsrange.h:444
T end() const
Returns the upper bound of the range.
Definition qgsrange.h:451
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 data sets.
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.
A class to represent a vector.
Definition qgsvector.h:30
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:60
constexpr double CANVAS_MAGNIFICATION_MAX
Maximum magnification level allowed in map canvases.
Definition qgsguiutils.h:67
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:5917
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:5821
QSet< QgsFeatureId > QgsFeatureIds
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
QList< QgsMapLayer * > filterLayersForRender(const QList< QgsMapLayer * > &layers)
const QgsCoordinateReferenceSystem & crs
Stores settings related to the context in which a preview job runs.
double maxRenderingTimeMs
Default maximum allowable render time, in ms.
double lastRenderingTimeMs
Previous rendering time for the layer, in ms.