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