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