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