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