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