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