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