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