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