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