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