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