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