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