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