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