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