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