QGIS API Documentation  3.21.0-Master (909859188c)
qgsmapcanvas.cpp
Go to the documentation of this file.
1 /***************************************************************************
2 qgsmapcanvas.cpp - description
3 ------------------ -
4 begin : Sun Jun 30 2002
5 copyright : (C) 2002 by Gary E.Sherman
6 email : 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"
51 #include "qgsfeatureiterator.h"
52 #include "qgslogger.h"
53 #include "qgsmapcanvas.h"
54 #include "qgsmapcanvasmap.h"
56 #include "qgsmaplayer.h"
57 #include "qgsmapmouseevent.h"
58 #include "qgsmaptoolpan.h"
59 #include "qgsmaptoolzoom.h"
60 #include "qgsmaptopixel.h"
61 #include "qgsmapoverviewcanvas.h"
62 #include "qgsmaprenderercache.h"
64 #include "qgsmaprendererjob.h"
67 #include "qgsmapsettingsutils.h"
68 #include "qgsmessagelog.h"
69 #include "qgsmessageviewer.h"
70 #include "qgspallabeling.h"
71 #include "qgsproject.h"
72 #include "qgsrubberband.h"
73 #include "qgsvectorlayer.h"
74 #include "qgsmapthemecollection.h"
76 #include "qgssvgcache.h"
77 #include "qgsimagecache.h"
79 #include "qgsmimedatautils.h"
80 #include "qgscustomdrophandler.h"
81 #include "qgsreferencedgeometry.h"
82 #include "qgsprojectviewsettings.h"
86 #include "qgstemporalcontroller.h"
87 #include "qgsruntimeprofiler.h"
89 #include "qgsannotationlayer.h"
92 #include "qgslabelingresults.h"
93 #include "qgsmaplayerutils.h"
94 #include "qgssettingsregistrygui.h"
95 #include "qgsrendereditemresults.h"
96 
102 //TODO QGIS 4.0 - remove
104 {
105  public:
106 
110  CanvasProperties() = default;
111 
113  bool mouseButtonDown{ false };
114 
116  QPoint mouseLastXY;
117 
120 
122  bool panSelectorDown{ false };
123 };
124 
125 
126 
127 QgsMapCanvas::QgsMapCanvas( QWidget *parent )
128  : QGraphicsView( parent )
129  , mCanvasProperties( new CanvasProperties )
130  , mExpressionContextScope( tr( "Map Canvas" ) )
131 {
132  mScene = new QGraphicsScene();
133  setScene( mScene );
134  setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
135  setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
136  setMouseTracking( true );
137  setFocusPolicy( Qt::StrongFocus );
138 
139  mResizeTimer = new QTimer( this );
140  mResizeTimer->setSingleShot( true );
141  connect( mResizeTimer, &QTimer::timeout, this, &QgsMapCanvas::refresh );
142 
143  mRefreshTimer = new QTimer( this );
144  mRefreshTimer->setSingleShot( true );
145  connect( mRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::refreshMap );
146 
147  // create map canvas item which will show the map
148  mMap = new QgsMapCanvasMap( this );
149 
150  // project handling
152  this, &QgsMapCanvas::readProject );
155 
156  connect( QgsProject::instance()->mainAnnotationLayer(), &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
157  connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsMapCanvas::mapThemeChanged );
158  connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsMapCanvas::mapThemeRenamed );
159  connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemesChanged, this, &QgsMapCanvas::projectThemesChanged );
160 
161  {
162  QgsScopedRuntimeProfile profile( "Map settings initialization" );
166  mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
168  this, [ = ]
169  {
170  mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
171  refresh();
172  } );
175  this, [ = ]
176  {
177  mSettings.setTransformContext( QgsProject::instance()->transformContext() );
179  refresh();
180  } );
181 
183  {
186  if ( mSettings.destinationCrs() != crs )
187  {
188  // user crs has changed definition, refresh the map
189  setDestinationCrs( crs );
190  }
191  } );
192  }
193 
194  // refresh canvas when a remote svg/image has finished downloading
197  // refresh canvas when project color scheme is changed -- if layers use project colors, they need to be redrawn
199 
200  //segmentation parameters
201  QgsSettings settings;
202  double segmentationTolerance = settings.value( QStringLiteral( "qgis/segmentationTolerance" ), "0.01745" ).toDouble();
203  QgsAbstractGeometry::SegmentationToleranceType toleranceType = settings.enumValue( QStringLiteral( "qgis/segmentationToleranceType" ), QgsAbstractGeometry::MaximumAngle );
204  mSettings.setSegmentationTolerance( segmentationTolerance );
205  mSettings.setSegmentationToleranceType( toleranceType );
206 
207  mWheelZoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
208 
209  QSize s = viewport()->size();
210  mSettings.setOutputSize( s );
211 
212  setSceneRect( 0, 0, s.width(), s.height() );
213  mScene->setSceneRect( QRectF( 0, 0, s.width(), s.height() ) );
214 
215  moveCanvasContents( true );
216 
217  connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsMapCanvas::mapUpdateTimeout );
218  mMapUpdateTimer.setInterval( 250 );
219 
220 #ifdef Q_OS_WIN
221  // Enable touch event on Windows.
222  // Qt on Windows needs to be told it can take touch events or else it ignores them.
223  grabGesture( Qt::PinchGesture );
224  grabGesture( Qt::TapAndHoldGesture );
225  viewport()->setAttribute( Qt::WA_AcceptTouchEvents );
226 #endif
227 
228  mPreviewEffect = new QgsPreviewEffect( this );
229  viewport()->setGraphicsEffect( mPreviewEffect );
230 
231  mZoomCursor = QgsApplication::getThemeCursor( QgsApplication::Cursor::ZoomIn );
232 
233  connect( &mAutoRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::autoRefreshTriggered );
234 
236 
237  setInteractive( false );
238 
239  // make sure we have the same default in QgsMapSettings and the scene's background brush
240  // (by default map settings has white bg color, scene background brush is black)
241  setCanvasColor( mSettings.backgroundColor() );
242 
243  setTemporalRange( mSettings.temporalRange() );
244  refresh();
245 }
246 
247 
249 {
250  if ( mMapTool )
251  {
252  mMapTool->deactivate();
253  mMapTool = nullptr;
254  }
255  mLastNonZoomMapTool = nullptr;
256 
257  // rendering job may still end up writing into canvas map item
258  // so kill it before deleting canvas items
259  if ( mJob )
260  {
261  whileBlocking( mJob )->cancel();
262  delete mJob;
263  }
264 
265  QList< QgsMapRendererQImageJob * >::const_iterator previewJob = mPreviewJobs.constBegin();
266  for ( ; previewJob != mPreviewJobs.constEnd(); ++previewJob )
267  {
268  if ( *previewJob )
269  {
270  whileBlocking( *previewJob )->cancel();
271  delete *previewJob;
272  }
273  }
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 void QgsMapCanvas::setMagnificationFactor( double factor, const QgsPointXY *center )
286 {
287  // do not go higher or lower than min max magnification ratio
288  double magnifierMin = QgsGuiUtils::CANVAS_MAGNIFICATION_MIN;
289  double magnifierMax = QgsGuiUtils::CANVAS_MAGNIFICATION_MAX;
290  factor = std::clamp( factor, magnifierMin, magnifierMax );
291 
292  // the magnifier widget is in integer percent
293  if ( !qgsDoubleNear( factor, mSettings.magnificationFactor(), 0.01 ) )
294  {
295  mSettings.setMagnificationFactor( factor, center );
296  refresh();
297  emit magnificationChanged( factor );
298  }
299 }
300 
302 {
303  return mSettings.magnificationFactor();
304 }
305 
307 {
308  mSettings.setFlag( QgsMapSettings::Antialiasing, flag );
309 } // anti aliasing
310 
312 {
313  mSettings.setFlag( QgsMapSettings::RenderMapTile, flag );
314 }
315 
317 {
318  QList<QgsMapLayer *> layers = mapSettings().layers();
319  if ( index >= 0 && index < layers.size() )
320  return layers[index];
321  else
322  return nullptr;
323 }
324 
326 {
327  if ( mCurrentLayer == layer )
328  return;
329 
330  mCurrentLayer = layer;
331  emit currentLayerChanged( layer );
332 }
333 
334 double QgsMapCanvas::scale() const
335 {
336  return mapSettings().scale();
337 }
338 
340 {
341  return nullptr != mJob;
342 } // isDrawing
343 
344 // return the current coordinate transform based on the extents and
345 // device size
347 {
348  return &mapSettings().mapToPixel();
349 }
350 
351 void QgsMapCanvas::setLayers( const QList<QgsMapLayer *> &layers )
352 {
353  // following a theme => request denied!
354  if ( !mTheme.isEmpty() )
355  return;
356 
357  setLayersPrivate( layers );
358 }
359 
360 void QgsMapCanvas::setLayersPrivate( const QList<QgsMapLayer *> &layers )
361 {
362  QList<QgsMapLayer *> oldLayers = mSettings.layers();
363 
364  // update only if needed
365  if ( layers == oldLayers )
366  return;
367 
368  const auto constOldLayers = oldLayers;
369  for ( QgsMapLayer *layer : constOldLayers )
370  {
371  disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
372  disconnect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
373  if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
374  {
376  }
377  }
378 
379  mSettings.setLayers( layers );
380 
381  const auto constLayers = layers;
382  for ( QgsMapLayer *layer : constLayers )
383  {
384  if ( !layer )
385  continue;
386  connect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
387  connect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
388  if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
389  {
391  }
392  }
393 
394  QgsDebugMsgLevel( QStringLiteral( "Layers have changed, refreshing" ), 2 );
395  emit layersChanged();
396 
397  updateAutoRefreshTimer();
398  refresh();
399 }
400 
401 
403 {
404  return mSettings;
405 }
406 
408 {
409  if ( mSettings.destinationCrs() == crs )
410  return;
411 
412  // try to reproject current extent to the new one
413  QgsRectangle rect;
414  if ( !mSettings.visibleExtent().isEmpty() )
415  {
416  QgsCoordinateTransform transform( mSettings.destinationCrs(), crs, QgsProject::instance() );
417  transform.setBallparkTransformsAreAppropriate( true );
418  try
419  {
420  rect = transform.transformBoundingBox( mSettings.visibleExtent() );
421  }
422  catch ( QgsCsException &e )
423  {
424  Q_UNUSED( e )
425  QgsDebugMsg( QStringLiteral( "Transform error caught: %1" ).arg( e.what() ) );
426  }
427  }
428 
429  if ( !rect.isEmpty() )
430  {
431  // we will be manually calling updateCanvasItemPositions() later, AFTER setting the updating the mSettings destination CRS, and we don't
432  // want to do that twice!
433  mBlockItemPositionUpdates++;
434  setExtent( rect );
435  mBlockItemPositionUpdates--;
436  }
437 
438  mSettings.setDestinationCrs( crs );
439  updateScale();
441 
442  QgsDebugMsgLevel( QStringLiteral( "refreshing after destination CRS changed" ), 2 );
443  refresh();
444 
445  emit destinationCrsChanged();
446 }
447 
449 {
450  if ( mController )
452 
453  mController = controller;
455 }
456 
458 {
459  return mController;
460 }
461 
462 void QgsMapCanvas::setMapSettingsFlags( QgsMapSettings::Flags flags )
463 {
464  mSettings.setFlags( flags );
465  clearCache();
466  refresh();
467 }
468 
469 const QgsLabelingResults *QgsMapCanvas::labelingResults( bool allowOutdatedResults ) const
470 {
471  if ( !allowOutdatedResults && mLabelingResultsOutdated )
472  return nullptr;
473 
474  return mLabelingResults.get();
475 }
476 
477 const QgsRenderedItemResults *QgsMapCanvas::renderedItemResults( bool allowOutdatedResults ) const
478 {
479  if ( !allowOutdatedResults && mRenderedItemResultsOutdated )
480  return nullptr;
481 
482  return mRenderedItemResults.get();
483 }
484 
486 {
487  if ( enabled == isCachingEnabled() )
488  return;
489 
490  if ( mJob && mJob->isActive() )
491  {
492  // wait for the current rendering to finish, before touching the cache
493  mJob->waitForFinished();
494  }
495 
496  if ( enabled )
497  {
498  mCache = new QgsMapRendererCache;
499  }
500  else
501  {
502  delete mCache;
503  mCache = nullptr;
504  }
505  mPreviousRenderedItemResults.reset();
506 }
507 
509 {
510  return nullptr != mCache;
511 }
512 
514 {
515  if ( mCache )
516  mCache->clear();
517 
518  if ( mPreviousRenderedItemResults )
519  mPreviousRenderedItemResults.reset();
520  if ( mRenderedItemResults )
521  mRenderedItemResults.reset();
522 }
523 
525 {
526  mUseParallelRendering = enabled;
527 }
528 
530 {
531  return mUseParallelRendering;
532 }
533 
534 void QgsMapCanvas::setMapUpdateInterval( int timeMilliseconds )
535 {
536  mMapUpdateTimer.setInterval( timeMilliseconds );
537 }
538 
540 {
541  return mMapUpdateTimer.interval();
542 }
543 
544 
546 {
547  return mCurrentLayer;
548 }
549 
551 {
552  QgsExpressionContextScope *s = new QgsExpressionContextScope( QObject::tr( "Map Canvas" ) );
553  s->setVariable( QStringLiteral( "canvas_cursor_point" ), QgsGeometry::fromPointXY( cursorPoint() ), true );
554  return s;
555 }
556 
558 {
559  //build the expression context
560  QgsExpressionContext expressionContext;
561  expressionContext << QgsExpressionContextUtils::globalScope()
565  if ( QgsExpressionContextScopeGenerator *generator = dynamic_cast< QgsExpressionContextScopeGenerator * >( mController ) )
566  {
567  expressionContext << generator->createExpressionContextScope();
568  }
569  expressionContext << defaultExpressionContextScope()
570  << new QgsExpressionContextScope( mExpressionContextScope );
571  return expressionContext;
572 }
573 
575 {
576  if ( !mSettings.hasValidSettings() )
577  {
578  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh - invalid settings -> nothing to do" ), 2 );
579  return;
580  }
581 
582  if ( !mRenderFlag || mFrozen )
583  {
584  QgsDebugMsgLevel( QStringLiteral( "CANVAS render flag off" ), 2 );
585  return;
586  }
587 
588  if ( mRefreshScheduled )
589  {
590  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh already scheduled" ), 2 );
591  return;
592  }
593 
594  mRefreshScheduled = true;
595 
596  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh scheduling" ), 2 );
597 
598  // schedule a refresh
599  mRefreshTimer->start( 1 );
600 
601  mLabelingResultsOutdated = true;
602  mRenderedItemResultsOutdated = true;
603 }
604 
605 void QgsMapCanvas::refreshMap()
606 {
607  Q_ASSERT( mRefreshScheduled );
608 
609  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh!" ), 3 );
610 
611  stopRendering(); // if any...
612  stopPreviewJobs();
613 
615  mSettings.setPathResolver( QgsProject::instance()->pathResolver() );
616 
617  if ( !mTheme.isEmpty() )
618  {
619  // IMPORTANT: we MUST set the layer style overrides here! (At the time of writing this
620  // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
621  // current state of the style. If we had stored the style overrides earlier (such as in
622  // mapThemeChanged slot) then this xml could be out of date...
623  // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
624  // just return the style name, we can instead set the overrides in mapThemeChanged and not here
625  mSettings.setLayerStyleOverrides( QgsProject::instance()->mapThemeCollection()->mapThemeStyleOverrides( mTheme ) );
626  }
627 
628  // render main annotation layer above all other layers
629  QgsMapSettings renderSettings = mSettings;
630  QList<QgsMapLayer *> allLayers = renderSettings.layers();
631  allLayers.insert( 0, QgsProject::instance()->mainAnnotationLayer() );
632  renderSettings.setLayers( allLayers );
633 
634  // create the renderer job
635  Q_ASSERT( !mJob );
636  mJobCanceled = false;
637  if ( mUseParallelRendering )
638  mJob = new QgsMapRendererParallelJob( renderSettings );
639  else
640  mJob = new QgsMapRendererSequentialJob( renderSettings );
641 
642  connect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
643  mJob->setCache( mCache );
644  mJob->setLayerRenderingTimeHints( mLastLayerRenderTime );
645 
646  mJob->start();
647 
648  // from now on we can accept refresh requests again
649  // this must be reset only after the job has been started, because
650  // some providers (yes, it's you WCS and AMS!) during preparation
651  // do network requests and start an internal event loop, which may
652  // end up calling refresh() and would schedule another refresh,
653  // deleting the one we have just started.
654  mRefreshScheduled = false;
655 
656  mMapUpdateTimer.start();
657 
658  emit renderStarting();
659 }
660 
661 void QgsMapCanvas::mapThemeChanged( const QString &theme )
662 {
663  if ( theme == mTheme )
664  {
665  // set the canvas layers to match the new layers contained in the map theme
666  // NOTE: we do this when the theme layers change and not when we are refreshing the map
667  // as setLayers() sets up necessary connections to handle changes to the layers
668  setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
669  // IMPORTANT: we don't set the layer style overrides here! (At the time of writing this
670  // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
671  // current state of the style. If changes were made to the style then this xml
672  // snapshot goes out of sync...
673  // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
674  // just return the style name, we can instead set the overrides here and not in refreshMap()
675 
676  clearCache();
677  refresh();
678  }
679 }
680 
681 void QgsMapCanvas::mapThemeRenamed( const QString &theme, const QString &newTheme )
682 {
683  if ( mTheme.isEmpty() || theme != mTheme )
684  {
685  return;
686  }
687 
688  setTheme( newTheme );
689  refresh();
690 }
691 
692 void QgsMapCanvas::rendererJobFinished()
693 {
694  QgsDebugMsgLevel( QStringLiteral( "CANVAS finish! %1" ).arg( !mJobCanceled ), 2 );
695 
696  mMapUpdateTimer.stop();
697 
698  // TODO: would be better to show the errors in message bar
699  const auto constErrors = mJob->errors();
700  for ( const QgsMapRendererJob::Error &error : constErrors )
701  {
702  QgsMapLayer *layer = QgsProject::instance()->mapLayer( error.layerID );
703  emit renderErrorOccurred( error.message, layer );
704  QgsMessageLog::logMessage( error.layerID + " :: " + error.message, tr( "Rendering" ) );
705  }
706 
707  if ( !mJobCanceled )
708  {
709  // take labeling results before emitting renderComplete, so labeling map tools
710  // connected to signal work with correct results
711  if ( !mJob->usedCachedLabels() )
712  {
713  mLabelingResults.reset( mJob->takeLabelingResults() );
714  }
715  mLabelingResultsOutdated = false;
716 
717  std::unique_ptr< QgsRenderedItemResults > renderedItemResults( mJob->takeRenderedItemResults() );
718  // if a layer was redrawn from the cached version, we should copy any existing rendered item results from that layer
719  if ( mRenderedItemResults )
720  {
721  renderedItemResults->transferResults( mRenderedItemResults.get(), mJob->layersRedrawnFromCache() );
722  }
723  if ( mPreviousRenderedItemResults )
724  {
725  // also transfer any results from previous renders which happened before this
726  renderedItemResults->transferResults( mPreviousRenderedItemResults.get(), mJob->layersRedrawnFromCache() );
727  }
728 
729  if ( mCache && !mPreviousRenderedItemResults )
730  mPreviousRenderedItemResults = std::make_unique< QgsRenderedItemResults >( mJob->mapSettings().extent() );
731 
732  if ( mRenderedItemResults && mPreviousRenderedItemResults )
733  {
734  // for other layers which ARE present in the most recent rendered item results BUT were not part of this render, we
735  // store the results in a temporary store in case they are later switched back on and the layer's image is taken
736  // from the cache
737  mPreviousRenderedItemResults->transferResults( mRenderedItemResults.get() );
738  }
739  if ( mPreviousRenderedItemResults )
740  {
741  mPreviousRenderedItemResults->eraseResultsFromLayers( mJob->mapSettings().layerIds() );
742  }
743 
744  mRenderedItemResults = std::move( renderedItemResults );
745  mRenderedItemResultsOutdated = false;
746 
747  QImage img = mJob->renderedImage();
748 
749  // emit renderComplete to get our decorations drawn
750  QPainter p( &img );
751  emit renderComplete( &p );
752 
754  {
755  QString logMsg = tr( "Canvas refresh: %1 ms" ).arg( mJob->renderingTime() );
756  QgsMessageLog::logMessage( logMsg, tr( "Rendering" ) );
757  }
758 
759  if ( mDrawRenderingStats )
760  {
761  int w = img.width(), h = img.height();
762  QFont fnt = p.font();
763  fnt.setBold( true );
764  p.setFont( fnt );
765  int lh = p.fontMetrics().height() * 2;
766  QRect r( 0, h - lh, w, lh );
767  p.setPen( Qt::NoPen );
768  p.setBrush( QColor( 0, 0, 0, 110 ) );
769  p.drawRect( r );
770  p.setPen( Qt::white );
771  QString msg = QStringLiteral( "%1 :: %2 ms" ).arg( mUseParallelRendering ? QStringLiteral( "PARALLEL" ) : QStringLiteral( "SEQUENTIAL" ) ).arg( mJob->renderingTime() );
772  p.drawText( r, msg, QTextOption( Qt::AlignCenter ) );
773  }
774 
775  p.end();
776 
777  mMap->setContent( img, imageRect( img, mSettings ) );
778 
779  mLastLayerRenderTime.clear();
780  const auto times = mJob->perLayerRenderingTime();
781  for ( auto it = times.constBegin(); it != times.constEnd(); ++it )
782  {
783  mLastLayerRenderTime.insert( it.key()->id(), it.value() );
784  }
785  if ( mUsePreviewJobs && !mRefreshAfterJob )
786  startPreviewJobs();
787  }
788  else
789  {
790  mRefreshAfterJob = false;
791  }
792 
793  // now we are in a slot called from mJob - do not delete it immediately
794  // so the class is still valid when the execution returns to the class
795  mJob->deleteLater();
796  mJob = nullptr;
797 
798  emit mapCanvasRefreshed();
799 
800  if ( mRefreshAfterJob )
801  {
802  mRefreshAfterJob = false;
803  clearTemporalCache();
804  clearElevationCache();
805  refresh();
806  }
807 }
808 
809 void QgsMapCanvas::previewJobFinished()
810 {
811  QgsMapRendererQImageJob *job = qobject_cast<QgsMapRendererQImageJob *>( sender() );
812  Q_ASSERT( job );
813 
814  if ( mMap )
815  {
816  mMap->addPreviewImage( job->renderedImage(), job->mapSettings().extent() );
817  mPreviewJobs.removeAll( job );
818 
819  int number = job->property( "number" ).toInt();
820  if ( number < 8 )
821  {
822  startPreviewJob( number + 1 );
823  }
824 
825  delete job;
826  }
827 }
828 
829 QgsRectangle QgsMapCanvas::imageRect( const QImage &img, const QgsMapSettings &mapSettings )
830 {
831  // This is a hack to pass QgsMapCanvasItem::setRect what it
832  // expects (encoding of position and size of the item)
833  const QgsMapToPixel &m2p = mapSettings.mapToPixel();
834  QgsPointXY topLeft = m2p.toMapCoordinates( 0, 0 );
835 #ifdef QGISDEBUG
836  // do not assert this, since it might lead to crashes when changing screen while rendering
837  if ( img.devicePixelRatio() != mapSettings.devicePixelRatio() )
838  {
839  QgsLogger::warning( QStringLiteral( "The renderer map has a wrong device pixel ratio" ) );
840  }
841 #endif
842  double res = m2p.mapUnitsPerPixel() / img.devicePixelRatioF();
843  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + img.width()*res, topLeft.y() - img.height()*res );
844  return rect;
845 }
846 
848 {
849  return mUsePreviewJobs;
850 }
851 
853 {
854  mUsePreviewJobs = enabled;
855 }
856 
857 void QgsMapCanvas::setCustomDropHandlers( const QVector<QPointer<QgsCustomDropHandler> > &handlers )
858 {
859  mDropHandlers = handlers;
860 }
861 
862 void QgsMapCanvas::clearTemporalCache()
863 {
864  if ( mCache )
865  {
866  const QList<QgsMapLayer *> layerList = mapSettings().layers();
867  for ( QgsMapLayer *layer : layerList )
868  {
870  {
872  continue;
873 
874  mCache->invalidateCacheForLayer( layer );
875  }
876  }
877  }
878 }
879 
880 void QgsMapCanvas::clearElevationCache()
881 {
882  if ( mCache )
883  {
884  const QList<QgsMapLayer *> layerList = mapSettings().layers();
885  for ( QgsMapLayer *layer : layerList )
886  {
888  {
890  continue;
891 
892  mCache->invalidateCacheForLayer( layer );
893  }
894  }
895  }
896 }
897 
898 void QgsMapCanvas::showContextMenu( QgsMapMouseEvent *event )
899 {
900  const QgsPointXY mapPoint = event->originalMapPoint();
901 
902  QMenu menu;
903 
904  QMenu *copyCoordinateMenu = new QMenu( tr( "Copy Coordinate" ), &menu );
905  copyCoordinateMenu->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ) );
906 
907  auto addCoordinateFormat = [ &, this]( const QString identifier, const QgsCoordinateReferenceSystem & crs )
908  {
909  QgsCoordinateTransform ct( mSettings.destinationCrs(), crs, mSettings.transformContext() );
910  try
911  {
912  const QgsPointXY transformedPoint = ct.transform( mapPoint );
913 
914  // calculate precision based on visible map extent -- if user is zoomed in, we get better precision!
915  int displayPrecision = 0;
916  try
917  {
918  QgsRectangle extentReproj = ct.transformBoundingBox( extent() );
919  const double mapUnitsPerPixel = ( extentReproj.width() / width() + extentReproj.height() / height() ) * 0.5;
920  if ( mapUnitsPerPixel > 10 )
921  displayPrecision = 0;
922  else if ( mapUnitsPerPixel > 1 )
923  displayPrecision = 1;
924  else if ( mapUnitsPerPixel > 0.1 )
925  displayPrecision = 2;
926  else if ( mapUnitsPerPixel > 0.01 )
927  displayPrecision = 3;
928  else if ( mapUnitsPerPixel > 0.001 )
929  displayPrecision = 4;
930  else if ( mapUnitsPerPixel > 0.0001 )
931  displayPrecision = 5;
932  else if ( mapUnitsPerPixel > 0.00001 )
933  displayPrecision = 6;
934  else if ( mapUnitsPerPixel > 0.000001 )
935  displayPrecision = 7;
936  else if ( mapUnitsPerPixel > 0.0000001 )
937  displayPrecision = 8;
938  else
939  displayPrecision = 9;
940  }
941  catch ( QgsCsException & )
942  {
943  displayPrecision = crs.mapUnits() == QgsUnitTypes::DistanceDegrees ? 5 : 3;
944  }
945 
946  QAction *copyCoordinateAction = new QAction( QStringLiteral( "%3 (%1, %2)" ).arg(
947  QString::number( transformedPoint.x(), 'f', displayPrecision ),
948  QString::number( transformedPoint.y(), 'f', displayPrecision ),
949  identifier ), &menu );
950 
951  connect( copyCoordinateAction, &QAction::triggered, this, [displayPrecision, transformedPoint]
952  {
953  QClipboard *clipboard = QApplication::clipboard();
954 
955  const QString coordinates = QString::number( transformedPoint.x(), 'f', displayPrecision ) + ',' + QString::number( transformedPoint.y(), 'f', displayPrecision );
956 
957  //if we are on x11 system put text into selection ready for middle button pasting
958  if ( clipboard->supportsSelection() )
959  {
960  clipboard->setText( coordinates, QClipboard::Selection );
961  }
962  clipboard->setText( coordinates, QClipboard::Clipboard );
963 
964  } );
965  copyCoordinateMenu->addAction( copyCoordinateAction );
966  }
967  catch ( QgsCsException & )
968  {
969 
970  }
971  };
972 
973  addCoordinateFormat( tr( "Map CRS — %1" ).arg( mSettings.destinationCrs().userFriendlyIdentifier( QgsCoordinateReferenceSystem::ShortString ) ), mSettings.destinationCrs() );
974  if ( mSettings.destinationCrs() != QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) )
975  addCoordinateFormat( tr( "WGS84" ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
976 
977  QgsSettings settings;
978  const QString customCrsString = settings.value( QStringLiteral( "qgis/custom_coordinate_crs" ) ).toString();
979  if ( !customCrsString.isEmpty() )
980  {
981  QgsCoordinateReferenceSystem customCrs( customCrsString );
982  if ( customCrs != mSettings.destinationCrs() && customCrs != QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) )
983  {
984  addCoordinateFormat( customCrs.userFriendlyIdentifier( QgsCoordinateReferenceSystem::ShortString ), customCrs );
985  }
986  }
987  copyCoordinateMenu->addSeparator();
988  QAction *setCustomCrsAction = new QAction( tr( "Set Custom CRS…" ), &menu );
989  connect( setCustomCrsAction, &QAction::triggered, this, [ = ]
990  {
991  QgsProjectionSelectionDialog selector( this );
992  selector.setCrs( QgsCoordinateReferenceSystem( customCrsString ) );
993  if ( selector.exec() )
994  {
995  QgsSettings().setValue( QStringLiteral( "qgis/custom_coordinate_crs" ), selector.crs().authid().isEmpty() ? selector.crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) : selector.crs().authid() );
996  }
997  } );
998  copyCoordinateMenu->addAction( setCustomCrsAction );
999 
1000  menu.addMenu( copyCoordinateMenu );
1001 
1002  if ( mMapTool )
1003  if ( !mapTool()->populateContextMenuWithEvent( &menu, event ) )
1004  mMapTool->populateContextMenu( &menu );
1005 
1006  emit contextMenuAboutToShow( &menu, event );
1007 
1008  menu.exec( event->globalPos() );
1009 }
1010 
1011 void QgsMapCanvas::updateDevicePixelFromScreen()
1012 {
1013  mSettings.setDevicePixelRatio( devicePixelRatio() );
1014  // TODO: QGIS 4 -> always respect screen dpi
1015  if ( QgsSettingsRegistryGui::settingsRespectScreenDPI.value() )
1016  {
1017  if ( window()->windowHandle() )
1018  mSettings.setOutputDpi( window()->windowHandle()->screen()->physicalDotsPerInch() );
1019  }
1020  else
1021  {
1022  // Fallback: compatibility with QGIS <= 3.20; always assume low dpi screens
1023  mSettings.setOutputDpi( window()->windowHandle()->screen()->logicalDotsPerInch() );
1024  }
1025 }
1026 
1027 void QgsMapCanvas::setTemporalRange( const QgsDateTimeRange &dateTimeRange )
1028 {
1029  if ( temporalRange() == dateTimeRange )
1030  return;
1031 
1032  mSettings.setTemporalRange( dateTimeRange );
1033  mSettings.setIsTemporal( dateTimeRange.begin().isValid() || dateTimeRange.end().isValid() );
1034 
1035  emit temporalRangeChanged();
1036 
1037  // we need to discard any previously cached images which have temporal properties enabled, so that these will be updated when
1038  // the canvas is redrawn
1039  if ( !mJob )
1040  clearTemporalCache();
1041 
1042  autoRefreshTriggered();
1043 }
1044 
1045 const QgsDateTimeRange &QgsMapCanvas::temporalRange() const
1046 {
1047  return mSettings.temporalRange();
1048 }
1049 
1051 {
1052  mInteractionBlockers.append( blocker );
1053 }
1054 
1056 {
1057  mInteractionBlockers.removeAll( blocker );
1058 }
1059 
1061 {
1062  for ( const QgsMapCanvasInteractionBlocker *block : mInteractionBlockers )
1063  {
1064  if ( block->blockCanvasInteraction( interaction ) )
1065  return false;
1066  }
1067  return true;
1068 }
1069 
1070 void QgsMapCanvas::mapUpdateTimeout()
1071 {
1072  if ( mJob )
1073  {
1074  const QImage &img = mJob->renderedImage();
1075  mMap->setContent( img, imageRect( img, mSettings ) );
1076  }
1077 }
1078 
1080 {
1081  if ( mJob )
1082  {
1083  QgsDebugMsgLevel( QStringLiteral( "CANVAS stop rendering!" ), 2 );
1084  mJobCanceled = true;
1085  disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
1086  connect( mJob, &QgsMapRendererQImageJob::finished, mJob, &QgsMapRendererQImageJob::deleteLater );
1087  mJob->cancelWithoutBlocking();
1088  mJob = nullptr;
1089  emit mapRefreshCanceled();
1090  }
1091  stopPreviewJobs();
1092 }
1093 
1094 //the format defaults to "PNG" if not specified
1095 void QgsMapCanvas::saveAsImage( const QString &fileName, QPixmap *theQPixmap, const QString &format )
1096 {
1097  QPainter painter;
1098  QImage image;
1099 
1100  //
1101  //check if the optional QPaintDevice was supplied
1102  //
1103  if ( theQPixmap )
1104  {
1105  image = theQPixmap->toImage();
1106  painter.begin( &image );
1107 
1108  // render
1109  QgsMapRendererCustomPainterJob job( mSettings, &painter );
1110  job.start();
1111  job.waitForFinished();
1112  emit renderComplete( &painter );
1113  }
1114  else //use the map view
1115  {
1116  image = mMap->contentImage().copy();
1117  painter.begin( &image );
1118  }
1119 
1120  // draw annotations
1121  QStyleOptionGraphicsItem option;
1122  option.initFrom( this );
1123  QGraphicsItem *item = nullptr;
1124  QListIterator<QGraphicsItem *> i( items() );
1125  i.toBack();
1126  while ( i.hasPrevious() )
1127  {
1128  item = i.previous();
1129 
1130  if ( !( item && dynamic_cast< QgsMapCanvasAnnotationItem * >( item ) ) )
1131  {
1132  continue;
1133  }
1134 
1135  QgsScopedQPainterState painterState( &painter );
1136 
1137  QPointF itemScenePos = item->scenePos();
1138  painter.translate( itemScenePos.x(), itemScenePos.y() );
1139 
1140  item->paint( &painter, &option );
1141  }
1142 
1143  painter.end();
1144  image.save( fileName, format.toLocal8Bit().data() );
1145 
1146  QFileInfo myInfo = QFileInfo( fileName );
1147 
1148  // build the world file name
1149  QString outputSuffix = myInfo.suffix();
1150  QString myWorldFileName = myInfo.absolutePath() + '/' + myInfo.completeBaseName() + '.'
1151  + outputSuffix.at( 0 ) + outputSuffix.at( myInfo.suffix().size() - 1 ) + 'w';
1152  QFile myWorldFile( myWorldFileName );
1153  if ( !myWorldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) //don't use QIODevice::Text
1154  {
1155  return;
1156  }
1157  QTextStream myStream( &myWorldFile );
1159 }
1160 
1162 {
1163  return mapSettings().visibleExtent();
1164 }
1165 
1167 {
1168  return QgsMapLayerUtils::combinedExtent( mSettings.layers(), mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
1169 }
1170 
1172 {
1174  QgsCoordinateTransform ct( extent.crs(), mapSettings().destinationCrs(), mProject ? mProject->transformContext() : QgsProject::instance()->transformContext() );
1176  QgsRectangle rect;
1177  try
1178  {
1179  rect = ct.transformBoundingBox( extent );
1180  }
1181  catch ( QgsCsException & )
1182  {
1183  rect = mapSettings().fullExtent();
1184  }
1185 
1186  return rect;
1187 }
1188 
1189 void QgsMapCanvas::setExtent( const QgsRectangle &r, bool magnified )
1190 {
1191  QgsRectangle current = extent();
1192 
1193  if ( ( r == current ) && magnified )
1194  return;
1195 
1196  if ( r.isEmpty() )
1197  {
1198  if ( !mSettings.hasValidSettings() )
1199  {
1200  // we can't even just move the map center
1201  QgsDebugMsgLevel( QStringLiteral( "Empty extent - ignoring" ), 2 );
1202  return;
1203  }
1204 
1205  // ### QGIS 3: do not allow empty extent - require users to call setCenter() explicitly
1206  QgsDebugMsgLevel( QStringLiteral( "Empty extent - keeping old scale with new center!" ), 2 );
1207  setCenter( r.center() );
1208  }
1209  else
1210  {
1211  // If scale is locked we need to maintain the current scale, so we
1212  // - magnify and recenter the map
1213  // - restore locked scale
1214  if ( mScaleLocked && magnified )
1215  {
1216  ScaleRestorer restorer( this );
1217  const double ratio { extent().width() / extent().height() };
1218  const double factor { r.width() / r.height() > ratio ? extent().width() / r.width() : extent().height() / r.height() };
1219  const double scaleFactor { std::clamp( mSettings.magnificationFactor() * factor, QgsGuiUtils::CANVAS_MAGNIFICATION_MIN, QgsGuiUtils::CANVAS_MAGNIFICATION_MAX ) };
1220  const QgsPointXY newCenter { r.center() };
1221  mSettings.setMagnificationFactor( scaleFactor, &newCenter );
1222  emit magnificationChanged( scaleFactor );
1223  }
1224  else
1225  {
1226  mSettings.setExtent( r, magnified );
1227  }
1228  }
1229  emit extentsChanged();
1230  updateScale();
1231 
1232  //clear all extent items after current index
1233  for ( int i = mLastExtent.size() - 1; i > mLastExtentIndex; i-- )
1234  {
1235  mLastExtent.removeAt( i );
1236  }
1237 
1238  mLastExtent.append( extent() );
1239 
1240  // adjust history to no more than 100
1241  if ( mLastExtent.size() > 100 )
1242  {
1243  mLastExtent.removeAt( 0 );
1244  }
1245 
1246  // the last item is the current extent
1247  mLastExtentIndex = mLastExtent.size() - 1;
1248 
1249  // update controls' enabled state
1250  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1251  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1252 }
1253 
1255 {
1256  QgsRectangle canvasExtent = extent;
1257  if ( extent.crs() != mapSettings().destinationCrs() )
1258  {
1259  QgsCoordinateTransform ct( extent.crs(), mapSettings().destinationCrs(), QgsProject::instance() );
1260  canvasExtent = ct.transform( extent );
1261 
1262  if ( canvasExtent.isEmpty() )
1263  {
1264  return false;
1265  }
1266  }
1267 
1268  setExtent( canvasExtent, true );
1269  return true;
1270 }
1271 
1273 {
1274  const QgsRectangle r = mapSettings().extent();
1275  const double xMin = center.x() - r.width() / 2.0;
1276  const double yMin = center.y() - r.height() / 2.0;
1277  const QgsRectangle rect(
1278  xMin, yMin,
1279  xMin + r.width(), yMin + r.height()
1280  );
1281  if ( ! rect.isEmpty() )
1282  {
1283  setExtent( rect, true );
1284  }
1285 } // setCenter
1286 
1288 {
1290  return r.center();
1291 }
1292 
1293 QgsPointXY QgsMapCanvas::cursorPoint() const
1294 {
1295  return mCursorPoint;
1296 }
1297 
1299 {
1300  return mapSettings().rotation();
1301 } // rotation
1302 
1303 void QgsMapCanvas::setRotation( double degrees )
1304 {
1305  double current = rotation();
1306 
1307  if ( qgsDoubleNear( degrees, current ) )
1308  return;
1309 
1310  mSettings.setRotation( degrees );
1311  emit rotationChanged( degrees );
1312  emit extentsChanged(); // visible extent changes with rotation
1313 } // setRotation
1314 
1315 
1317 {
1318  emit scaleChanged( mapSettings().scale() );
1319 }
1320 
1322 {
1324  // If the full extent is an empty set, don't do the zoom
1325  if ( !extent.isEmpty() )
1326  {
1327  // Add a 5% margin around the full extent
1328  extent.scale( 1.05 );
1329  setExtent( extent );
1330  }
1331  refresh();
1332 }
1333 
1335 {
1337 
1338  // If the full extent is an empty set, don't do the zoom
1339  if ( !extent.isEmpty() )
1340  {
1341  // Add a 5% margin around the full extent
1342  extent.scale( 1.05 );
1343  setExtent( extent );
1344  }
1345  refresh();
1346 }
1347 
1349 {
1350  if ( mLastExtentIndex > 0 )
1351  {
1352  mLastExtentIndex--;
1353  mSettings.setExtent( mLastExtent[mLastExtentIndex] );
1354  emit extentsChanged();
1355  updateScale();
1356  refresh();
1357  // update controls' enabled state
1358  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1359  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1360  }
1361 
1362 } // zoomToPreviousExtent
1363 
1365 {
1366  if ( mLastExtentIndex < mLastExtent.size() - 1 )
1367  {
1368  mLastExtentIndex++;
1369  mSettings.setExtent( mLastExtent[mLastExtentIndex] );
1370  emit extentsChanged();
1371  updateScale();
1372  refresh();
1373  // update controls' enabled state
1374  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1375  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1376  }
1377 }// zoomToNextExtent
1378 
1380 {
1381  mLastExtent.clear(); // clear the zoom history list
1382  mLastExtent.append( extent() ) ; // set the current extent in the list
1383  mLastExtentIndex = mLastExtent.size() - 1;
1384  // update controls' enabled state
1385  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1386  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1387 }// clearExtentHistory
1388 
1389 QgsRectangle QgsMapCanvas::optimalExtentForPointLayer( QgsVectorLayer *layer, const QgsPointXY &center, int scaleFactor )
1390 {
1391  QgsRectangle rect( center, center );
1392 
1393  if ( layer->geometryType() == QgsWkbTypes::PointGeometry )
1394  {
1395  QgsPointXY centerLayerCoordinates = mSettings.mapToLayerCoordinates( layer, center );
1396  QgsRectangle extentRect = mSettings.mapToLayerCoordinates( layer, extent() ).scaled( 1.0 / scaleFactor, &centerLayerCoordinates );
1398  QgsFeatureIterator fit = layer->getFeatures( req );
1399  QgsFeature f;
1400  QgsPointXY closestPoint;
1401  double closestSquaredDistance = pow( extentRect.width(), 2.0 ) + pow( extentRect.height(), 2.0 );
1402  bool pointFound = false;
1403  while ( fit.nextFeature( f ) )
1404  {
1405  QgsPointXY point = f.geometry().asPoint();
1406  double sqrDist = point.sqrDist( centerLayerCoordinates );
1407  if ( sqrDist > closestSquaredDistance || sqrDist < 4 * std::numeric_limits<double>::epsilon() )
1408  continue;
1409  pointFound = true;
1410  closestPoint = point;
1411  closestSquaredDistance = sqrDist;
1412  }
1413  if ( pointFound )
1414  {
1415  // combine selected point with closest point and scale this rect
1416  rect.combineExtentWith( mSettings.layerToMapCoordinates( layer, closestPoint ) );
1417  rect.scale( scaleFactor, &center );
1418  }
1419  }
1420  return rect;
1421 }
1422 
1424 {
1425  QgsTemporaryCursorOverride cursorOverride( Qt::WaitCursor );
1426 
1427  if ( !layer )
1428  {
1429  // use current layer by default
1430  layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1431  }
1432 
1433  if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
1434  return;
1435 
1436  QgsRectangle rect = layer->boundingBoxOfSelected();
1437  if ( rect.isNull() )
1438  {
1439  cursorOverride.release();
1440  emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1441  return;
1442  }
1443 
1444  rect = mapSettings().layerExtentToOutputExtent( layer, rect );
1445 
1446  // zoom in if point cannot be distinguished from others
1447  // also check that rect is empty, as it might not in case of multi points
1448  if ( layer->geometryType() == QgsWkbTypes::PointGeometry && rect.isEmpty() )
1449  {
1450  rect = optimalExtentForPointLayer( layer, rect.center() );
1451  }
1452  zoomToFeatureExtent( rect );
1453 }
1454 
1455 void QgsMapCanvas::zoomToSelected( const QList<QgsMapLayer *> &layers )
1456 {
1457  QgsRectangle rect;
1458  rect.setMinimal();
1459  QgsRectangle selectionExtent;
1460  selectionExtent.setMinimal();
1461 
1462  for ( QgsMapLayer *mapLayer : layers )
1463  {
1464  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
1465 
1466  if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
1467  continue;
1468 
1469  rect = layer->boundingBoxOfSelected();
1470 
1471  if ( rect.isNull() )
1472  continue;
1473 
1474  rect = mapSettings().layerExtentToOutputExtent( layer, rect );
1475 
1476  if ( layer->geometryType() == QgsWkbTypes::PointGeometry && rect.isEmpty() )
1477  rect = optimalExtentForPointLayer( layer, rect.center() );
1478 
1479  selectionExtent.combineExtentWith( rect );
1480  }
1481 
1482  if ( selectionExtent.isNull() )
1483  {
1484  emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1485  return;
1486  }
1487 
1488  zoomToFeatureExtent( selectionExtent );
1489 }
1490 
1492 {
1493  return mSettings.zRange();
1494 }
1495 
1497 {
1498  if ( zRange() == range )
1499  return;
1500 
1501  mSettings.setZRange( range );
1502 
1503  emit zRangeChanged();
1504 
1505  // we need to discard any previously cached images which are elevation aware, so that these will be updated when
1506  // the canvas is redrawn
1507  if ( !mJob )
1508  clearElevationCache();
1509 
1510  autoRefreshTriggered();
1511 }
1512 
1514 {
1515  // no selected features, only one selected point feature
1516  //or two point features with the same x- or y-coordinates
1517  if ( rect.isEmpty() )
1518  {
1519  // zoom in
1520  QgsPointXY c = rect.center();
1521  rect = extent();
1522  rect.scale( 1.0, &c );
1523  }
1524  //zoom to an area
1525  else
1526  {
1527  // Expand rect to give a bit of space around the selected
1528  // objects so as to keep them clear of the map boundaries
1529  // The same 5% should apply to all margins.
1530  rect.scale( 1.05 );
1531  }
1532 
1533  setExtent( rect );
1534  refresh();
1535 }
1536 
1538 {
1539  if ( !layer )
1540  {
1541  return;
1542  }
1543 
1544  QgsRectangle bbox;
1545  QString errorMsg;
1546  if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
1547  {
1548  if ( bbox.isEmpty() )
1549  {
1550  bbox = optimalExtentForPointLayer( layer, bbox.center() );
1551  }
1552  zoomToFeatureExtent( bbox );
1553  }
1554  else
1555  {
1556  emit messageEmitted( tr( "Zoom to feature id failed" ), errorMsg, Qgis::MessageLevel::Warning );
1557  }
1558 
1559 }
1560 
1561 void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter )
1562 {
1563  if ( !layer )
1564  {
1565  return;
1566  }
1567 
1568  QgsRectangle bbox;
1569  QString errorMsg;
1570  if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
1571  {
1572  if ( alwaysRecenter || !mapSettings().extent().contains( bbox ) )
1573  setCenter( bbox.center() );
1574  refresh();
1575  }
1576  else
1577  {
1578  emit messageEmitted( tr( "Pan to feature id failed" ), errorMsg, Qgis::MessageLevel::Warning );
1579  }
1580 }
1581 
1582 bool QgsMapCanvas::boundingBoxOfFeatureIds( const QgsFeatureIds &ids, QgsVectorLayer *layer, QgsRectangle &bbox, QString &errorMsg ) const
1583 {
1584  QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
1585  bbox.setMinimal();
1586  QgsFeature fet;
1587  int featureCount = 0;
1588  errorMsg.clear();
1589 
1590  while ( it.nextFeature( fet ) )
1591  {
1592  QgsGeometry geom = fet.geometry();
1593  if ( geom.isNull() )
1594  {
1595  errorMsg = tr( "Feature does not have a geometry" );
1596  }
1597  else if ( geom.constGet()->isEmpty() )
1598  {
1599  errorMsg = tr( "Feature geometry is empty" );
1600  }
1601  if ( !errorMsg.isEmpty() )
1602  {
1603  return false;
1604  }
1606  bbox.combineExtentWith( r );
1607  featureCount++;
1608  }
1609 
1610  if ( featureCount != ids.count() )
1611  {
1612  errorMsg = tr( "Feature not found" );
1613  return false;
1614  }
1615 
1616  return true;
1617 }
1618 
1620 {
1621  if ( !layer )
1622  {
1623  // use current layer by default
1624  layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1625  }
1626 
1627  if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
1628  return;
1629 
1630  QgsRectangle rect = layer->boundingBoxOfSelected();
1631  if ( rect.isNull() )
1632  {
1633  emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1634  return;
1635  }
1636 
1637  rect = mapSettings().layerExtentToOutputExtent( layer, rect );
1638  setCenter( rect.center() );
1639  refresh();
1640 }
1641 
1642 void QgsMapCanvas::panToSelected( const QList<QgsMapLayer *> &layers )
1643 {
1644  QgsRectangle rect;
1645  rect.setMinimal();
1646  QgsRectangle selectionExtent;
1647  selectionExtent.setMinimal();
1648 
1649  for ( QgsMapLayer *mapLayer : layers )
1650  {
1651  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
1652 
1653  if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
1654  continue;
1655 
1656  rect = layer->boundingBoxOfSelected();
1657 
1658  if ( rect.isNull() )
1659  continue;
1660 
1661  rect = mapSettings().layerExtentToOutputExtent( layer, rect );
1662 
1663  if ( layer->geometryType() == QgsWkbTypes::PointGeometry && rect.isEmpty() )
1664  rect = optimalExtentForPointLayer( layer, rect.center() );
1665 
1666  selectionExtent.combineExtentWith( rect );
1667  }
1668 
1669  if ( selectionExtent.isNull() )
1670  {
1671  emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1672  return;
1673  }
1674 
1675  setCenter( selectionExtent.center() );
1676  refresh();
1677 }
1678 
1680  const QColor &color1, const QColor &color2,
1681  int flashes, int duration )
1682 {
1683  if ( !layer )
1684  {
1685  return;
1686  }
1687 
1688  QList< QgsGeometry > geoms;
1689 
1690  QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
1691  QgsFeature fet;
1692  while ( it.nextFeature( fet ) )
1693  {
1694  if ( !fet.hasGeometry() )
1695  continue;
1696  geoms << fet.geometry();
1697  }
1698 
1699  flashGeometries( geoms, layer->crs(), color1, color2, flashes, duration );
1700 }
1701 
1702 void QgsMapCanvas::flashGeometries( const QList<QgsGeometry> &geometries, const QgsCoordinateReferenceSystem &crs, const QColor &color1, const QColor &color2, int flashes, int duration )
1703 {
1704  if ( geometries.isEmpty() )
1705  return;
1706 
1707  QgsWkbTypes::GeometryType geomType = QgsWkbTypes::geometryType( geometries.at( 0 ).wkbType() );
1708  QgsRubberBand *rb = new QgsRubberBand( this, geomType );
1709  for ( const QgsGeometry &geom : geometries )
1710  rb->addGeometry( geom, crs, false );
1711  rb->updatePosition();
1712  rb->update();
1713 
1714  if ( geomType == QgsWkbTypes::LineGeometry || geomType == QgsWkbTypes::PointGeometry )
1715  {
1716  rb->setWidth( 2 );
1717  rb->setSecondaryStrokeColor( QColor( 255, 255, 255 ) );
1718  }
1719  if ( geomType == QgsWkbTypes::PointGeometry )
1721 
1722  QColor startColor = color1;
1723  if ( !startColor.isValid() )
1724  {
1725  if ( geomType == QgsWkbTypes::PolygonGeometry )
1726  {
1727  startColor = rb->fillColor();
1728  }
1729  else
1730  {
1731  startColor = rb->strokeColor();
1732  }
1733  startColor.setAlpha( 255 );
1734  }
1735  QColor endColor = color2;
1736  if ( !endColor.isValid() )
1737  {
1738  endColor = startColor;
1739  endColor.setAlpha( 0 );
1740  }
1741 
1742 
1743  QVariantAnimation *animation = new QVariantAnimation( this );
1744  connect( animation, &QVariantAnimation::finished, this, [animation, rb]
1745  {
1746  animation->deleteLater();
1747  delete rb;
1748  } );
1749  connect( animation, &QPropertyAnimation::valueChanged, this, [rb, geomType]( const QVariant & value )
1750  {
1751  QColor c = value.value<QColor>();
1752  if ( geomType == QgsWkbTypes::PolygonGeometry )
1753  {
1754  rb->setFillColor( c );
1755  }
1756  else
1757  {
1758  rb->setStrokeColor( c );
1759  QColor c = rb->secondaryStrokeColor();
1760  c.setAlpha( c.alpha() );
1761  rb->setSecondaryStrokeColor( c );
1762  }
1763  rb->update();
1764  } );
1765 
1766  animation->setDuration( duration * flashes );
1767  animation->setStartValue( endColor );
1768  double midStep = 0.2 / flashes;
1769  for ( int i = 0; i < flashes; ++i )
1770  {
1771  double start = static_cast< double >( i ) / flashes;
1772  animation->setKeyValueAt( start + midStep, startColor );
1773  double end = static_cast< double >( i + 1 ) / flashes;
1774  if ( !qgsDoubleNear( end, 1.0 ) )
1775  animation->setKeyValueAt( end, endColor );
1776  }
1777  animation->setEndValue( endColor );
1778  animation->start();
1779 }
1780 
1781 void QgsMapCanvas::keyPressEvent( QKeyEvent *e )
1782 {
1783  if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )
1784  {
1785  emit keyPressed( e );
1786  return;
1787  }
1788 
1789  // Don't want to interfer with mouse events
1790  if ( ! mCanvasProperties->mouseButtonDown )
1791  {
1792  // this is backwards, but we can't change now without breaking api because
1793  // forever QgsMapTools have had to explicitly mark events as ignored in order to
1794  // indicate that they've consumed the event and that the default behavior should not
1795  // be applied..!
1796  e->accept();
1797  if ( mMapTool )
1798  {
1799  mMapTool->keyPressEvent( e );
1800  if ( !e->isAccepted() ) // map tool consumed event
1801  return;
1802  }
1803 
1804  QgsRectangle currentExtent = mapSettings().visibleExtent();
1805  double dx = std::fabs( currentExtent.width() / 4 );
1806  double dy = std::fabs( currentExtent.height() / 4 );
1807 
1808  switch ( e->key() )
1809  {
1810  case Qt::Key_Left:
1811  QgsDebugMsgLevel( QStringLiteral( "Pan left" ), 2 );
1812  setCenter( center() - QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
1813  refresh();
1814  break;
1815 
1816  case Qt::Key_Right:
1817  QgsDebugMsgLevel( QStringLiteral( "Pan right" ), 2 );
1818  setCenter( center() + QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
1819  refresh();
1820  break;
1821 
1822  case Qt::Key_Up:
1823  QgsDebugMsgLevel( QStringLiteral( "Pan up" ), 2 );
1824  setCenter( center() + QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
1825  refresh();
1826  break;
1827 
1828  case Qt::Key_Down:
1829  QgsDebugMsgLevel( QStringLiteral( "Pan down" ), 2 );
1830  setCenter( center() - QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
1831  refresh();
1832  break;
1833 
1834  case Qt::Key_Space:
1835  QgsDebugMsgLevel( QStringLiteral( "Pressing pan selector" ), 2 );
1836 
1837  //mCanvasProperties->dragging = true;
1838  if ( ! e->isAutoRepeat() )
1839  {
1840  QApplication::setOverrideCursor( Qt::ClosedHandCursor );
1841  mCanvasProperties->panSelectorDown = true;
1842  panActionStart( mCanvasProperties->mouseLastXY );
1843  }
1844  break;
1845 
1846  case Qt::Key_PageUp:
1847  QgsDebugMsgLevel( QStringLiteral( "Zoom in" ), 2 );
1848  zoomIn();
1849  break;
1850 
1851  case Qt::Key_PageDown:
1852  QgsDebugMsgLevel( QStringLiteral( "Zoom out" ), 2 );
1853  zoomOut();
1854  break;
1855 
1856 #if 0
1857  case Qt::Key_P:
1858  mUseParallelRendering = !mUseParallelRendering;
1859  refresh();
1860  break;
1861 
1862  case Qt::Key_S:
1863  mDrawRenderingStats = !mDrawRenderingStats;
1864  refresh();
1865  break;
1866 #endif
1867 
1868  default:
1869  // Pass it on
1870  if ( !mMapTool )
1871  {
1872  e->ignore();
1873  QgsDebugMsgLevel( "Ignoring key: " + QString::number( e->key() ), 2 );
1874  }
1875  }
1876  }
1877 
1878  emit keyPressed( e );
1879 }
1880 
1881 void QgsMapCanvas::keyReleaseEvent( QKeyEvent *e )
1882 {
1883  QgsDebugMsgLevel( QStringLiteral( "keyRelease event" ), 2 );
1884 
1885  switch ( e->key() )
1886  {
1887  case Qt::Key_Space:
1888  if ( !e->isAutoRepeat() && mCanvasProperties->panSelectorDown )
1889  {
1890  QgsDebugMsgLevel( QStringLiteral( "Releasing pan selector" ), 2 );
1891  QApplication::restoreOverrideCursor();
1892  mCanvasProperties->panSelectorDown = false;
1893  panActionEnd( mCanvasProperties->mouseLastXY );
1894  }
1895  break;
1896 
1897  default:
1898  // Pass it on
1899  if ( mMapTool )
1900  {
1901  mMapTool->keyReleaseEvent( e );
1902  }
1903  else e->ignore();
1904 
1905  QgsDebugMsgLevel( "Ignoring key release: " + QString::number( e->key() ), 2 );
1906  }
1907 
1908  emit keyReleased( e );
1909 
1910 } //keyReleaseEvent()
1911 
1912 
1914 {
1915  // call handler of current map tool
1916  if ( mMapTool )
1917  {
1918  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
1919  mMapTool->canvasDoubleClickEvent( me.get() );
1920  }
1921 }// mouseDoubleClickEvent
1922 
1923 
1924 void QgsMapCanvas::beginZoomRect( QPoint pos )
1925 {
1926  mZoomRect.setRect( 0, 0, 0, 0 );
1927  QApplication::setOverrideCursor( mZoomCursor );
1928  mZoomDragging = true;
1929  mZoomRubberBand.reset( new QgsRubberBand( this, QgsWkbTypes::PolygonGeometry ) );
1930  QColor color( Qt::blue );
1931  color.setAlpha( 63 );
1932  mZoomRubberBand->setColor( color );
1933  mZoomRect.setTopLeft( pos );
1934 }
1935 
1936 void QgsMapCanvas::endZoomRect( QPoint pos )
1937 {
1938  mZoomDragging = false;
1939  mZoomRubberBand.reset( nullptr );
1940  QApplication::restoreOverrideCursor();
1941 
1942  // store the rectangle
1943  mZoomRect.setRight( pos.x() );
1944  mZoomRect.setBottom( pos.y() );
1945 
1946  if ( mZoomRect.width() < 5 && mZoomRect.height() < 5 )
1947  {
1948  //probably a mistake - would result in huge zoom!
1949  return;
1950  }
1951 
1952  //account for bottom right -> top left dragging
1953  mZoomRect = mZoomRect.normalized();
1954 
1955  // set center and zoom
1956  const QSize &zoomRectSize = mZoomRect.size();
1957  const QSize &canvasSize = mSettings.outputSize();
1958  double sfx = static_cast< double >( zoomRectSize.width() ) / canvasSize.width();
1959  double sfy = static_cast< double >( zoomRectSize.height() ) / canvasSize.height();
1960  double sf = std::max( sfx, sfy );
1961 
1962  QgsPointXY c = mSettings.mapToPixel().toMapCoordinates( mZoomRect.center() );
1963 
1964  zoomByFactor( sf, &c );
1965  refresh();
1966 }
1967 
1968 void QgsMapCanvas::mousePressEvent( QMouseEvent *e )
1969 {
1970  // use shift+middle mouse button for zooming, map tools won't receive any events in that case
1971  if ( e->button() == Qt::MiddleButton &&
1972  e->modifiers() & Qt::ShiftModifier )
1973  {
1974  beginZoomRect( e->pos() );
1975  return;
1976  }
1977  //use middle mouse button for panning, map tools won't receive any events in that case
1978  else if ( e->button() == Qt::MiddleButton )
1979  {
1980  mCanvasProperties->panSelectorDown = true;
1981  panActionStart( mCanvasProperties->mouseLastXY );
1982  }
1983  else
1984  {
1985  // call handler of current map tool
1986  if ( mMapTool )
1987  {
1988  if ( mMapTool->flags() & QgsMapTool::AllowZoomRect && e->button() == Qt::LeftButton
1989  && e->modifiers() & Qt::ShiftModifier )
1990  {
1991  beginZoomRect( e->pos() );
1992  return;
1993  }
1994  else if ( mMapTool->flags() & QgsMapTool::ShowContextMenu && e->button() == Qt::RightButton )
1995  {
1996  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
1997  showContextMenu( me.get() );
1998  return;
1999  }
2000  else
2001  {
2002  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2003  mMapTool->canvasPressEvent( me.get() );
2004  }
2005  }
2006  }
2007 
2008  if ( mCanvasProperties->panSelectorDown )
2009  {
2010  return;
2011  }
2012 
2013  mCanvasProperties->mouseButtonDown = true;
2014  mCanvasProperties->rubberStartPoint = e->pos();
2015 }
2016 
2017 void QgsMapCanvas::mouseReleaseEvent( QMouseEvent *e )
2018 {
2019  // if using shift+middle mouse button for zooming, end zooming and return
2020  if ( mZoomDragging &&
2021  e->button() == Qt::MiddleButton )
2022  {
2023  endZoomRect( e->pos() );
2024  return;
2025  }
2026  //use middle mouse button for panning, map tools won't receive any events in that case
2027  else if ( e->button() == Qt::MiddleButton )
2028  {
2029  mCanvasProperties->panSelectorDown = false;
2030  panActionEnd( mCanvasProperties->mouseLastXY );
2031  }
2032  else if ( e->button() == Qt::BackButton )
2033  {
2035  return;
2036  }
2037  else if ( e->button() == Qt::ForwardButton )
2038  {
2039  zoomToNextExtent();
2040  return;
2041  }
2042  else
2043  {
2044  if ( mZoomDragging && e->button() == Qt::LeftButton )
2045  {
2046  endZoomRect( e->pos() );
2047  return;
2048  }
2049 
2050  // call handler of current map tool
2051  if ( mMapTool )
2052  {
2053  // right button was pressed in zoom tool? return to previous non zoom tool
2054  if ( e->button() == Qt::RightButton && mMapTool->flags() & QgsMapTool::Transient )
2055  {
2056  QgsDebugMsgLevel( QStringLiteral( "Right click in map tool zoom or pan, last tool is %1." ).arg(
2057  mLastNonZoomMapTool ? QStringLiteral( "not null" ) : QStringLiteral( "null" ) ), 2 );
2058 
2059  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
2060 
2061  // change to older non-zoom tool
2062  if ( mLastNonZoomMapTool
2063  && ( !( mLastNonZoomMapTool->flags() & QgsMapTool::EditTool )
2064  || ( vlayer && vlayer->isEditable() ) ) )
2065  {
2066  QgsMapTool *t = mLastNonZoomMapTool;
2067  mLastNonZoomMapTool = nullptr;
2068  setMapTool( t );
2069  }
2070  return;
2071  }
2072  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2073  mMapTool->canvasReleaseEvent( me.get() );
2074  }
2075  }
2076 
2077 
2078  mCanvasProperties->mouseButtonDown = false;
2079 
2080  if ( mCanvasProperties->panSelectorDown )
2081  return;
2082 
2083 }
2084 
2085 void QgsMapCanvas::resizeEvent( QResizeEvent *e )
2086 {
2087  QGraphicsView::resizeEvent( e );
2088  mResizeTimer->start( 500 ); // in charge of refreshing canvas
2089 
2090  double oldScale = mSettings.scale();
2091  QSize lastSize = viewport()->size();
2092  mSettings.setOutputSize( lastSize );
2093 
2094  mScene->setSceneRect( QRectF( 0, 0, lastSize.width(), lastSize.height() ) );
2095 
2096  moveCanvasContents( true );
2097 
2098  if ( mScaleLocked )
2099  {
2100  double scaleFactor = oldScale / mSettings.scale();
2101  QgsRectangle r = mSettings.extent();
2102  QgsPointXY center = r.center();
2103  r.scale( scaleFactor, &center );
2104  mSettings.setExtent( r );
2105  }
2106  else
2107  {
2108  updateScale();
2109  }
2110 
2111  emit extentsChanged();
2112 }
2113 
2114 void QgsMapCanvas::paintEvent( QPaintEvent *e )
2115 {
2116  // no custom event handling anymore
2117 
2118  QGraphicsView::paintEvent( e );
2119 } // paintEvent
2120 
2122 {
2123  if ( mBlockItemPositionUpdates )
2124  return;
2125 
2126  const QList<QGraphicsItem *> items = mScene->items();
2127  for ( QGraphicsItem *gi : items )
2128  {
2129  QgsMapCanvasItem *item = dynamic_cast<QgsMapCanvasItem *>( gi );
2130 
2131  if ( item )
2132  {
2133  item->updatePosition();
2134  }
2135  }
2136 }
2137 
2138 
2139 void QgsMapCanvas::wheelEvent( QWheelEvent *e )
2140 {
2141  // Zoom the map canvas in response to a mouse wheel event. Moving the
2142  // wheel forward (away) from the user zooms in
2143 
2144  QgsDebugMsgLevel( "Wheel event delta " + QString::number( e->angleDelta().y() ), 2 );
2145 
2146  if ( mMapTool )
2147  {
2148  mMapTool->wheelEvent( e );
2149  if ( e->isAccepted() )
2150  return;
2151  }
2152 
2153  if ( e->angleDelta().y() == 0 )
2154  {
2155  e->accept();
2156  return;
2157  }
2158 
2159  double zoomFactor = e->angleDelta().y() > 0 ? 1. / zoomInFactor() : zoomOutFactor();
2160 
2161  // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
2162  zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( e->angleDelta().y() );
2163 
2164  if ( e->modifiers() & Qt::ControlModifier )
2165  {
2166  //holding ctrl while wheel zooming results in a finer zoom
2167  zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
2168  }
2169 
2170  double signedWheelFactor = e->angleDelta().y() > 0 ? 1 / zoomFactor : zoomFactor;
2171 
2172  // zoom map to mouse cursor by scaling
2173  QgsPointXY oldCenter = center();
2174 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
2175  QgsPointXY mousePos( getCoordinateTransform()->toMapCoordinates( e->pos().x(), e->pos().y() ) );
2176 #else
2177  QgsPointXY mousePos( getCoordinateTransform()->toMapCoordinates( e->position().x(), e->position().y() ) );
2178 #endif
2179  QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * signedWheelFactor ),
2180  mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * signedWheelFactor ) );
2181 
2182  zoomByFactor( signedWheelFactor, &newCenter );
2183  e->accept();
2184 }
2185 
2186 void QgsMapCanvas::setWheelFactor( double factor )
2187 {
2188  mWheelZoomFactor = factor;
2189 }
2190 
2192 {
2193  // magnification is alreday handled in zoomByFactor
2195 }
2196 
2198 {
2199  // magnification is alreday handled in zoomByFactor
2201 }
2202 
2203 void QgsMapCanvas::zoomScale( double newScale, bool ignoreScaleLock )
2204 {
2205  zoomByFactor( newScale / scale(), nullptr, ignoreScaleLock );
2206 }
2207 
2208 void QgsMapCanvas::zoomWithCenter( int x, int y, bool zoomIn )
2209 {
2210  double scaleFactor = ( zoomIn ? zoomInFactor() : zoomOutFactor() );
2211 
2212  // transform the mouse pos to map coordinates
2214 
2215  if ( mScaleLocked )
2216  {
2217  ScaleRestorer restorer( this );
2219  }
2220  else
2221  {
2223  r.scale( scaleFactor, &center );
2224  setExtent( r, true );
2225  refresh();
2226  }
2227 }
2228 
2229 void QgsMapCanvas::setScaleLocked( bool isLocked )
2230 {
2231  if ( mScaleLocked != isLocked )
2232  {
2233  mScaleLocked = isLocked;
2234  emit scaleLockChanged( mScaleLocked );
2235  }
2236 }
2237 
2238 void QgsMapCanvas::mouseMoveEvent( QMouseEvent *e )
2239 {
2240  mCanvasProperties->mouseLastXY = e->pos();
2241 
2242  if ( mCanvasProperties->panSelectorDown )
2243  {
2244  panAction( e );
2245  }
2246  else if ( mZoomDragging )
2247  {
2248  mZoomRect.setBottomRight( e->pos() );
2249  mZoomRubberBand->setToCanvasRectangle( mZoomRect );
2250  mZoomRubberBand->show();
2251  }
2252  else
2253  {
2254  // call handler of current map tool
2255  if ( mMapTool )
2256  {
2257  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2258  mMapTool->canvasMoveEvent( me.get() );
2259  }
2260  }
2261 
2262  // show x y on status bar (if we are mid pan operation, then the cursor point hasn't changed!)
2263  if ( !panOperationInProgress() )
2264  {
2265  mCursorPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->mouseLastXY );
2266  emit xyCoordinates( mCursorPoint );
2267  }
2268 }
2269 
2270 void QgsMapCanvas::setMapTool( QgsMapTool *tool, bool clean )
2271 {
2272  if ( !tool )
2273  return;
2274 
2275  if ( mMapTool )
2276  {
2277  if ( clean )
2278  mMapTool->clean();
2279 
2280  disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2281  mMapTool->deactivate();
2282  }
2283 
2284  if ( ( tool->flags() & QgsMapTool::Transient )
2285  && mMapTool && !( mMapTool->flags() & QgsMapTool::Transient ) )
2286  {
2287  // if zoom or pan tool will be active, save old tool
2288  // to bring it back on right click
2289  // (but only if it wasn't also zoom or pan tool)
2290  mLastNonZoomMapTool = mMapTool;
2291  }
2292  else
2293  {
2294  mLastNonZoomMapTool = nullptr;
2295  }
2296 
2297  QgsMapTool *oldTool = mMapTool;
2298 
2299  // set new map tool and activate it
2300  mMapTool = tool;
2301  emit mapToolSet( mMapTool, oldTool );
2302  if ( mMapTool )
2303  {
2304  connect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2305  mMapTool->activate();
2306  }
2307 
2308 } // setMapTool
2309 
2311 {
2312  if ( mMapTool && mMapTool == tool )
2313  {
2314  disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2315  QgsMapTool *oldTool = mMapTool;
2316  mMapTool = nullptr;
2317  oldTool->deactivate();
2318  emit mapToolSet( nullptr, oldTool );
2319  setCursor( Qt::ArrowCursor );
2320  }
2321 
2322  if ( mLastNonZoomMapTool && mLastNonZoomMapTool == tool )
2323  {
2324  mLastNonZoomMapTool = nullptr;
2325  }
2326 }
2327 
2329 {
2330  mProject = project;
2331 }
2332 
2333 void QgsMapCanvas::setCanvasColor( const QColor &color )
2334 {
2335  if ( canvasColor() == color )
2336  return;
2337 
2338  // background of map's pixmap
2339  mSettings.setBackgroundColor( color );
2340 
2341  // background of the QGraphicsView
2342  QBrush bgBrush( color );
2343  setBackgroundBrush( bgBrush );
2344 #if 0
2345  QPalette palette;
2346  palette.setColor( backgroundRole(), color );
2347  setPalette( palette );
2348 #endif
2349 
2350  // background of QGraphicsScene
2351  mScene->setBackgroundBrush( bgBrush );
2352 
2353  refresh();
2354 
2355  emit canvasColorChanged();
2356 }
2357 
2359 {
2360  return mScene->backgroundBrush().color();
2361 }
2362 
2363 void QgsMapCanvas::setSelectionColor( const QColor &color )
2364 {
2365  if ( mSettings.selectionColor() == color )
2366  return;
2367 
2368  mSettings.setSelectionColor( color );
2369 
2370  if ( mCache )
2371  {
2372  bool hasSelectedFeatures = false;
2373  const auto layers = mSettings.layers();
2374  for ( QgsMapLayer *layer : layers )
2375  {
2376  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
2377  if ( vlayer && vlayer->selectedFeatureCount() )
2378  {
2379  hasSelectedFeatures = true;
2380  break;
2381  }
2382  }
2383 
2384  if ( hasSelectedFeatures )
2385  {
2386  mCache->clear();
2387  refresh();
2388  }
2389  }
2390 }
2391 
2393 {
2394  return mSettings.selectionColor();
2395 }
2396 
2398 {
2399  return mapSettings().layers().size();
2400 } // layerCount
2401 
2402 
2403 QList<QgsMapLayer *> QgsMapCanvas::layers() const
2404 {
2405  return mapSettings().layers();
2406 }
2407 
2409 {
2410  // called when a layer has changed visibility setting
2411  refresh();
2412 }
2413 
2414 void QgsMapCanvas::freeze( bool frozen )
2415 {
2416  mFrozen = frozen;
2417 }
2418 
2420 {
2421  return mFrozen;
2422 }
2423 
2425 {
2426  return mapSettings().mapUnitsPerPixel();
2427 }
2428 
2430 {
2431  return mapSettings().mapUnits();
2432 }
2433 
2434 QMap<QString, QString> QgsMapCanvas::layerStyleOverrides() const
2435 {
2436  return mSettings.layerStyleOverrides();
2437 }
2438 
2439 void QgsMapCanvas::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
2440 {
2441  if ( overrides == mSettings.layerStyleOverrides() )
2442  return;
2443 
2444  mSettings.setLayerStyleOverrides( overrides );
2445  clearCache();
2447 }
2448 
2449 void QgsMapCanvas::setTheme( const QString &theme )
2450 {
2451  if ( mTheme == theme )
2452  return;
2453 
2454  clearCache();
2455  if ( theme.isEmpty() || !QgsProject::instance()->mapThemeCollection()->hasMapTheme( theme ) )
2456  {
2457  mTheme.clear();
2458  mSettings.setLayerStyleOverrides( QMap< QString, QString>() );
2459  setLayers( QgsProject::instance()->mapThemeCollection()->masterVisibleLayers() );
2460  emit themeChanged( QString() );
2461  }
2462  else
2463  {
2464  mTheme = theme;
2465  setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
2466  emit themeChanged( theme );
2467  }
2468 }
2469 
2471 {
2472  mRenderFlag = flag;
2473 
2474  if ( mRenderFlag )
2475  {
2476  refresh();
2477  }
2478  else
2479  stopRendering();
2480 }
2481 
2482 #if 0
2483 void QgsMapCanvas::connectNotify( const char *signal )
2484 {
2485  Q_UNUSED( signal )
2486  QgsDebugMsg( "QgsMapCanvas connected to " + QString( signal ) );
2487 } //connectNotify
2488 #endif
2489 
2490 void QgsMapCanvas::layerRepaintRequested( bool deferred )
2491 {
2492  if ( !deferred )
2493  refresh();
2494 }
2495 
2496 void QgsMapCanvas::autoRefreshTriggered()
2497 {
2498  if ( mJob )
2499  {
2500  // canvas is currently being redrawn, so we defer the last requested
2501  // auto refresh until current rendering job finishes
2502  mRefreshAfterJob = true;
2503  return;
2504  }
2505 
2506  refresh();
2507 }
2508 
2509 void QgsMapCanvas::updateAutoRefreshTimer()
2510 {
2511  // min auto refresh interval stores the smallest interval between layer auto refreshes. We automatically
2512  // trigger a map refresh on this minimum interval
2513  int minAutoRefreshInterval = -1;
2514  const auto layers = mSettings.layers();
2515  for ( QgsMapLayer *layer : layers )
2516  {
2518  minAutoRefreshInterval = minAutoRefreshInterval > 0 ? std::min( layer->autoRefreshInterval(), minAutoRefreshInterval ) : layer->autoRefreshInterval();
2519  }
2520 
2521  if ( minAutoRefreshInterval > 0 )
2522  {
2523  mAutoRefreshTimer.setInterval( minAutoRefreshInterval );
2524  mAutoRefreshTimer.start();
2525  }
2526  else
2527  {
2528  mAutoRefreshTimer.stop();
2529  }
2530 }
2531 
2532 void QgsMapCanvas::projectThemesChanged()
2533 {
2534  if ( mTheme.isEmpty() )
2535  return;
2536 
2537  if ( !QgsProject::instance()->mapThemeCollection()->hasMapTheme( mTheme ) )
2538  {
2539  // theme has been removed - stop following
2540  setTheme( QString() );
2541  }
2542 
2543 }
2544 
2546 {
2547  return mMapTool;
2548 }
2549 
2551 {
2552  return mProject;
2553 }
2554 
2555 void QgsMapCanvas::panActionEnd( QPoint releasePoint )
2556 {
2557  // move map image and other items to standard position
2558  moveCanvasContents( true ); // true means reset
2559 
2560  // use start and end box points to calculate the extent
2561  QgsPointXY start = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
2562  QgsPointXY end = getCoordinateTransform()->toMapCoordinates( releasePoint );
2563 
2564  // modify the center
2565  double dx = end.x() - start.x();
2566  double dy = end.y() - start.y();
2567  QgsPointXY c = center();
2568  c.set( c.x() - dx, c.y() - dy );
2569  setCenter( c );
2570 
2571  refresh();
2572 }
2573 
2574 void QgsMapCanvas::panActionStart( QPoint releasePoint )
2575 {
2576  mCanvasProperties->rubberStartPoint = releasePoint;
2577 
2578  mDa = QgsDistanceArea();
2579  mDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
2580  mDa.setSourceCrs( mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
2581 }
2582 
2583 void QgsMapCanvas::panAction( QMouseEvent *e )
2584 {
2585  Q_UNUSED( e )
2586 
2587  QgsPointXY currentMapPoint = getCoordinateTransform()->toMapCoordinates( e->pos() );
2588  QgsPointXY startMapPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
2589  emit panDistanceBearingChanged( mDa.measureLine( currentMapPoint, startMapPoint ), mDa.lengthUnits(), mDa.bearing( currentMapPoint, startMapPoint ) * 180 / M_PI );
2590 
2591  // move all map canvas items
2593 }
2594 
2596 {
2597  QPoint pnt( 0, 0 );
2598  if ( !reset )
2599  pnt += mCanvasProperties->mouseLastXY - mCanvasProperties->rubberStartPoint;
2600 
2601  setSceneRect( -pnt.x(), -pnt.y(), viewport()->size().width(), viewport()->size().height() );
2602 }
2603 
2604 void QgsMapCanvas::dropEvent( QDropEvent *event )
2605 {
2606  if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
2607  {
2609  bool allHandled = true;
2610  for ( const QgsMimeDataUtils::Uri &uri : lst )
2611  {
2612  bool handled = false;
2613  for ( QgsCustomDropHandler *handler : std::as_const( mDropHandlers ) )
2614  {
2615  if ( handler && handler->customUriProviderKey() == uri.providerKey )
2616  {
2617  if ( handler->handleCustomUriCanvasDrop( uri, this ) )
2618  {
2619  handled = true;
2620  break;
2621  }
2622  }
2623  }
2624  if ( !handled )
2625  allHandled = false;
2626  }
2627  if ( allHandled )
2628  event->accept();
2629  else
2630  event->ignore();
2631  }
2632  else
2633  {
2634  event->ignore();
2635  }
2636 }
2637 
2638 void QgsMapCanvas::showEvent( QShowEvent *event )
2639 {
2640  Q_UNUSED( event )
2641  updateDevicePixelFromScreen();
2642  // keep device pixel ratio up to date on screen or resolution change
2643  if ( window()->windowHandle() )
2644  {
2645  connect( window()->windowHandle(), &QWindow::screenChanged, this, [ = ]( QScreen * )
2646  {
2647  disconnect( mScreenDpiChangedConnection );
2648  mScreenDpiChangedConnection = connect( window()->windowHandle()->screen(), &QScreen::physicalDotsPerInchChanged, this, &QgsMapCanvas::updateDevicePixelFromScreen );
2649  updateDevicePixelFromScreen();
2650  } );
2651 
2652  mScreenDpiChangedConnection = connect( window()->windowHandle()->screen(), &QScreen::physicalDotsPerInchChanged, this, &QgsMapCanvas::updateDevicePixelFromScreen );
2653  }
2654 }
2655 
2657 {
2658  return mCanvasProperties->mouseLastXY;
2659 }
2660 
2661 void QgsMapCanvas::setPreviewModeEnabled( bool previewEnabled )
2662 {
2663  if ( !mPreviewEffect )
2664  {
2665  return;
2666  }
2667 
2668  mPreviewEffect->setEnabled( previewEnabled );
2669 }
2670 
2672 {
2673  if ( !mPreviewEffect )
2674  {
2675  return false;
2676  }
2677 
2678  return mPreviewEffect->isEnabled();
2679 }
2680 
2682 {
2683  if ( !mPreviewEffect )
2684  {
2685  return;
2686  }
2687 
2688  mPreviewEffect->setMode( mode );
2689 }
2690 
2692 {
2693  if ( !mPreviewEffect )
2694  {
2696  }
2697 
2698  return mPreviewEffect->mode();
2699 }
2700 
2702 {
2703  if ( !mSnappingUtils )
2704  {
2705  // associate a dummy instance, but better than null pointer
2706  QgsMapCanvas *c = const_cast<QgsMapCanvas *>( this );
2707  c->mSnappingUtils = new QgsMapCanvasSnappingUtils( c, c );
2708  }
2709  return mSnappingUtils;
2710 }
2711 
2713 {
2714  mSnappingUtils = utils;
2715 }
2716 
2717 void QgsMapCanvas::readProject( const QDomDocument &doc )
2718 {
2719  QgsProject *project = qobject_cast< QgsProject * >( sender() );
2720 
2721  QDomNodeList nodes = doc.elementsByTagName( QStringLiteral( "mapcanvas" ) );
2722  if ( nodes.count() )
2723  {
2724  QDomNode node = nodes.item( 0 );
2725 
2726  // Search the specific MapCanvas node using the name
2727  if ( nodes.count() > 1 )
2728  {
2729  for ( int i = 0; i < nodes.size(); ++i )
2730  {
2731  QDomElement elementNode = nodes.at( i ).toElement();
2732 
2733  if ( elementNode.hasAttribute( QStringLiteral( "name" ) ) && elementNode.attribute( QStringLiteral( "name" ) ) == objectName() )
2734  {
2735  node = nodes.at( i );
2736  break;
2737  }
2738  }
2739  }
2740 
2741  QgsMapSettings tmpSettings;
2742  tmpSettings.readXml( node );
2743  if ( objectName() != QLatin1String( "theMapCanvas" ) )
2744  {
2745  // never manually set the crs for the main canvas - this is instead connected to the project CRS
2746  setDestinationCrs( tmpSettings.destinationCrs() );
2747  }
2748  setExtent( tmpSettings.extent() );
2749  setRotation( tmpSettings.rotation() );
2751 
2752  clearExtentHistory(); // clear the extent history on project load
2753 
2754  QDomElement elem = node.toElement();
2755  if ( elem.hasAttribute( QStringLiteral( "theme" ) ) )
2756  {
2757  if ( QgsProject::instance()->mapThemeCollection()->hasMapTheme( elem.attribute( QStringLiteral( "theme" ) ) ) )
2758  {
2759  setTheme( elem.attribute( QStringLiteral( "theme" ) ) );
2760  }
2761  }
2762  setAnnotationsVisible( elem.attribute( QStringLiteral( "annotationsVisible" ), QStringLiteral( "1" ) ).toInt() );
2763 
2764  // restore canvas expression context
2765  const QDomNodeList scopeElements = elem.elementsByTagName( QStringLiteral( "expressionContextScope" ) );
2766  if ( scopeElements.size() > 0 )
2767  {
2768  const QDomElement scopeElement = scopeElements.at( 0 ).toElement();
2769  mExpressionContextScope.readXml( scopeElement, QgsReadWriteContext() );
2770  }
2771  }
2772  else
2773  {
2774  QgsDebugMsg( QStringLiteral( "Couldn't read mapcanvas information from project" ) );
2776  {
2778  clearExtentHistory(); // clear the extent history on project load
2779  }
2780  }
2781 }
2782 
2783 void QgsMapCanvas::writeProject( QDomDocument &doc )
2784 {
2785  // create node "mapcanvas" and call mMapRenderer->writeXml()
2786 
2787  QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
2788  if ( !nl.count() )
2789  {
2790  QgsDebugMsg( QStringLiteral( "Unable to find qgis element in project file" ) );
2791  return;
2792  }
2793  QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
2794 
2795  QDomElement mapcanvasNode = doc.createElement( QStringLiteral( "mapcanvas" ) );
2796  mapcanvasNode.setAttribute( QStringLiteral( "name" ), objectName() );
2797  if ( !mTheme.isEmpty() )
2798  mapcanvasNode.setAttribute( QStringLiteral( "theme" ), mTheme );
2799  mapcanvasNode.setAttribute( QStringLiteral( "annotationsVisible" ), mAnnotationsVisible );
2800  qgisNode.appendChild( mapcanvasNode );
2801 
2802  mSettings.writeXml( mapcanvasNode, doc );
2803 
2804  // store canvas expression context
2805  QDomElement scopeElement = doc.createElement( QStringLiteral( "expressionContextScope" ) );
2806  QgsExpressionContextScope tmpScope( mExpressionContextScope );
2807  tmpScope.removeVariable( QStringLiteral( "atlas_featurenumber" ) );
2808  tmpScope.removeVariable( QStringLiteral( "atlas_pagename" ) );
2809  tmpScope.removeVariable( QStringLiteral( "atlas_feature" ) );
2810  tmpScope.removeVariable( QStringLiteral( "atlas_featureid" ) );
2811  tmpScope.removeVariable( QStringLiteral( "atlas_geometry" ) );
2812  tmpScope.writeXml( scopeElement, doc, QgsReadWriteContext() );
2813  mapcanvasNode.appendChild( scopeElement );
2814 
2815  // TODO: store only units, extent, projections, dest CRS
2816 }
2817 
2818 void QgsMapCanvas::zoomByFactor( double scaleFactor, const QgsPointXY *center, bool ignoreScaleLock )
2819 {
2820  if ( mScaleLocked && !ignoreScaleLock )
2821  {
2822  ScaleRestorer restorer( this );
2824  }
2825  else
2826  {
2828  r.scale( scaleFactor, center );
2829  setExtent( r, true );
2830  refresh();
2831  }
2832 }
2833 
2835 {
2836  // Find out which layer it was that sent the signal.
2837  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( sender() );
2838  if ( layer )
2839  {
2840  emit selectionChanged( layer );
2841  refresh();
2842  }
2843 }
2844 
2845 void QgsMapCanvas::dragEnterEvent( QDragEnterEvent *event )
2846 {
2847  // By default graphics view delegates the drag events to graphics items.
2848  // But we do not want that and by ignoring the drag enter we let the
2849  // parent (e.g. QgisApp) to handle drops of map layers etc.
2850 
2851  // so we ONLY accept the event if we know in advance that a custom drop handler
2852  // wants it
2853 
2854  if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
2855  {
2857  bool allHandled = true;
2858  for ( const QgsMimeDataUtils::Uri &uri : lst )
2859  {
2860  bool handled = false;
2861  for ( QgsCustomDropHandler *handler : std::as_const( mDropHandlers ) )
2862  {
2863  if ( handler->canHandleCustomUriCanvasDrop( uri, this ) )
2864  {
2865  handled = true;
2866  break;
2867  }
2868  }
2869  if ( !handled )
2870  allHandled = false;
2871  }
2872  if ( allHandled )
2873  event->accept();
2874  else
2875  event->ignore();
2876  }
2877  else
2878  {
2879  event->ignore();
2880  }
2881 }
2882 
2883 bool QgsMapCanvas::viewportEvent( QEvent *event )
2884 {
2885  if ( event->type() == QEvent::ToolTip && mMapTool && mMapTool->canvasToolTipEvent( qgis::down_cast<QHelpEvent *>( event ) ) )
2886  {
2887  return true;
2888  }
2889  return QGraphicsView::viewportEvent( event );
2890 }
2891 
2892 void QgsMapCanvas::mapToolDestroyed()
2893 {
2894  QgsDebugMsgLevel( QStringLiteral( "maptool destroyed" ), 2 );
2895  mMapTool = nullptr;
2896 }
2897 
2898 bool QgsMapCanvas::event( QEvent *e )
2899 {
2900  if ( !QTouchDevice::devices().empty() )
2901  {
2902  if ( e->type() == QEvent::Gesture )
2903  {
2904  if ( QTapAndHoldGesture *tapAndHoldGesture = qobject_cast< QTapAndHoldGesture * >( static_cast<QGestureEvent *>( e )->gesture( Qt::TapAndHoldGesture ) ) )
2905  {
2906  QPointF pos = tapAndHoldGesture->position();
2907  pos = mapFromGlobal( QPoint( pos.x(), pos.y() ) );
2908  QgsPointXY mapPoint = getCoordinateTransform()->toMapCoordinates( pos.x(), pos.y() );
2909  emit tapAndHoldGestureOccurred( mapPoint, tapAndHoldGesture );
2910  }
2911 
2912  // call handler of current map tool
2913  if ( mMapTool )
2914  {
2915  return mMapTool->gestureEvent( static_cast<QGestureEvent *>( e ) );
2916  }
2917  }
2918  }
2919 
2920  // pass other events to base class
2921  return QGraphicsView::event( e );
2922 }
2923 
2925 {
2926  // reload all layers in canvas
2927  const QList<QgsMapLayer *> layers = mapSettings().layers();
2928  for ( QgsMapLayer *layer : layers )
2929  {
2930  layer->reload();
2931  }
2932 
2933  redrawAllLayers();
2934 }
2935 
2937 {
2938  // clear the cache
2939  clearCache();
2940 
2941  // and then refresh
2942  refresh();
2943 }
2944 
2946 {
2947  while ( mRefreshScheduled || mJob )
2948  {
2949  QgsApplication::processEvents();
2950  }
2951 }
2952 
2954 {
2955  mSettings.setSegmentationTolerance( tolerance );
2956 }
2957 
2959 {
2960  mSettings.setSegmentationToleranceType( type );
2961 }
2962 
2963 QList<QgsMapCanvasAnnotationItem *> QgsMapCanvas::annotationItems() const
2964 {
2965  QList<QgsMapCanvasAnnotationItem *> annotationItemList;
2966  const QList<QGraphicsItem *> items = mScene->items();
2967  for ( QGraphicsItem *gi : items )
2968  {
2969  QgsMapCanvasAnnotationItem *aItem = dynamic_cast< QgsMapCanvasAnnotationItem *>( gi );
2970  if ( aItem )
2971  {
2972  annotationItemList.push_back( aItem );
2973  }
2974  }
2975 
2976  return annotationItemList;
2977 }
2978 
2980 {
2981  mAnnotationsVisible = show;
2982  const QList<QgsMapCanvasAnnotationItem *> items = annotationItems();
2983  for ( QgsMapCanvasAnnotationItem *item : items )
2984  {
2985  item->setVisible( show );
2986  }
2987 }
2988 
2990 {
2991  mSettings.setLabelingEngineSettings( settings );
2992 }
2993 
2995 {
2996  return mSettings.labelingEngineSettings();
2997 }
2998 
2999 void QgsMapCanvas::startPreviewJobs()
3000 {
3001  stopPreviewJobs(); //just in case still running
3002 
3003  //canvas preview jobs aren't compatible with rotation
3004  // TODO fix this
3005  if ( !qgsDoubleNear( mSettings.rotation(), 0.0 ) )
3006  return;
3007 
3008  schedulePreviewJob( 0 );
3009 }
3010 
3011 void QgsMapCanvas::startPreviewJob( int number )
3012 {
3013  QgsRectangle mapRect = mSettings.visibleExtent();
3014 
3015  if ( number == 4 )
3016  number += 1;
3017 
3018  int j = number / 3;
3019  int i = number % 3;
3020 
3021  //copy settings, only update extent
3022  QgsMapSettings jobSettings = mSettings;
3023 
3024  double dx = ( i - 1 ) * mapRect.width();
3025  double dy = ( 1 - j ) * mapRect.height();
3026  QgsRectangle jobExtent = mapRect;
3027 
3028  jobExtent.setXMaximum( jobExtent.xMaximum() + dx );
3029  jobExtent.setXMinimum( jobExtent.xMinimum() + dx );
3030  jobExtent.setYMaximum( jobExtent.yMaximum() + dy );
3031  jobExtent.setYMinimum( jobExtent.yMinimum() + dy );
3032 
3033  jobSettings.setExtent( jobExtent );
3034  jobSettings.setFlag( QgsMapSettings::DrawLabeling, false );
3035  jobSettings.setFlag( QgsMapSettings::RenderPreviewJob, true );
3036 
3037  // truncate preview layers to fast layers
3038  const QList<QgsMapLayer *> layers = jobSettings.layers();
3039  QList< QgsMapLayer * > previewLayers;
3041  context.maxRenderingTimeMs = MAXIMUM_LAYER_PREVIEW_TIME_MS;
3042  for ( QgsMapLayer *layer : layers )
3043  {
3044  context.lastRenderingTimeMs = mLastLayerRenderTime.value( layer->id(), 0 );
3045  QgsDataProvider *provider = layer->dataProvider();
3046  if ( provider && !provider->renderInPreview( context ) )
3047  {
3048  QgsDebugMsgLevel( QStringLiteral( "Layer %1 not rendered because it does not match the renderInPreview criterion %2" ).arg( layer->id() ).arg( mLastLayerRenderTime.value( layer->id() ) ), 3 );
3049  continue;
3050  }
3051 
3052  previewLayers << layer;
3053  }
3054  jobSettings.setLayers( previewLayers );
3055 
3056  QgsMapRendererQImageJob *job = new QgsMapRendererSequentialJob( jobSettings );
3057  job->setProperty( "number", number );
3058  mPreviewJobs.append( job );
3059  connect( job, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
3060  job->start();
3061 }
3062 
3063 void QgsMapCanvas::stopPreviewJobs()
3064 {
3065  mPreviewTimer.stop();
3066  const auto previewJobs = mPreviewJobs;
3067  for ( auto previewJob : previewJobs )
3068  {
3069  if ( previewJob )
3070  {
3071  disconnect( previewJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
3072  connect( previewJob, &QgsMapRendererQImageJob::finished, previewJob, &QgsMapRendererQImageJob::deleteLater );
3073  previewJob->cancelWithoutBlocking();
3074  }
3075  }
3076  mPreviewJobs.clear();
3077 }
3078 
3079 void QgsMapCanvas::schedulePreviewJob( int number )
3080 {
3081  mPreviewTimer.setSingleShot( true );
3082  mPreviewTimer.setInterval( PREVIEW_JOB_DELAY_MS );
3083  disconnect( mPreviewTimerConnection );
3084  mPreviewTimerConnection = connect( &mPreviewTimer, &QTimer::timeout, this, [ = ]()
3085  {
3086  startPreviewJob( number );
3087  } );
3088  mPreviewTimer.start();
3089 }
3090 
3091 bool QgsMapCanvas::panOperationInProgress()
3092 {
3093  if ( mCanvasProperties->panSelectorDown )
3094  return true;
3095 
3096  if ( QgsMapToolPan *panTool = qobject_cast< QgsMapToolPan *>( mMapTool ) )
3097  {
3098  if ( panTool->isDragging() )
3099  return true;
3100  }
3101 
3102  return false;
3103 }
3104 
3105 int QgsMapCanvas::nextZoomLevel( const QList<double> &resolutions, bool zoomIn ) const
3106 {
3107  int resolutionLevel = -1;
3108  double currentResolution = mapUnitsPerPixel();
3109 
3110  for ( int i = 0, n = resolutions.size(); i < n; ++i )
3111  {
3112  if ( qgsDoubleNear( resolutions[i], currentResolution, 0.0001 ) )
3113  {
3114  resolutionLevel = zoomIn ? ( i - 1 ) : ( i + 1 );
3115  break;
3116  }
3117  else if ( currentResolution <= resolutions[i] )
3118  {
3119  resolutionLevel = zoomIn ? ( i - 1 ) : i;
3120  break;
3121  }
3122  }
3123  return ( resolutionLevel < 0 || resolutionLevel >= resolutions.size() ) ? -1 : resolutionLevel;
3124 }
3125 
3127 {
3128  if ( !mZoomResolutions.isEmpty() )
3129  {
3130  int zoomLevel = nextZoomLevel( mZoomResolutions, true );
3131  if ( zoomLevel != -1 )
3132  {
3133  return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
3134  }
3135  }
3136  return 1 / mWheelZoomFactor;
3137 }
3138 
3140 {
3141  if ( !mZoomResolutions.isEmpty() )
3142  {
3143  int zoomLevel = nextZoomLevel( mZoomResolutions, false );
3144  if ( zoomLevel != -1 )
3145  {
3146  return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
3147  }
3148  }
3149  return mWheelZoomFactor;
3150 }
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
@ MaximumAngle
Maximum angle between generating radii (lines from arc center to output vertices)
virtual bool isEmpty() const
Returns true if the geometry is empty.
static QCursor getThemeCursor(Cursor cursor)
Helper to get a theme cursor.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QgsCoordinateReferenceSystemRegistry * coordinateReferenceSystemRegistry()
Returns the application's coordinate reference system (CRS) registry, which handles known CRS definit...
void userCrsChanged(const QString &id)
Emitted whenever an existing user CRS definition is changed.
This class represents a coordinate reference system (CRS).
@ ShortString
A heavily abbreviated string, for use when a compact representation is required.
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,...
Q_GADGET QgsUnitTypes::DistanceUnit mapUnits
Class for doing transforms between two map coordinate systems.
QgsPointXY transform(const QgsPointXY &point, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, TransformDirection direction=ForwardTransform, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Abstract base class that may be implemented to handle new types of data to be dropped in QGIS.
Abstract base class for spatial data provider implementations.
virtual bool renderInPreview(const QgsDataProvider::PreviewContext &context)
Returns whether the layer must be rendered in preview jobs.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
double bearing(const QgsPointXY &p1, const QgsPointXY &p2) const
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.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
QgsUnitTypes::DistanceUnit lengthUnits() const
Returns the units of distance for length calculations made by this object.
QgsRange which stores a range of double values.
Definition: qgsrange.h:203
QString what() const
Definition: qgsexception.h:48
Abstract interface for generating an expression context scope.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads scope variables from an XML element.
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const
Writes scope variables to an XML element.
bool removeVariable(const QString &name)
Removes a variable from the context scope, if found.
void setVariable(const QString &name, const QVariant &value, bool isStatic=false)
Convenience method for setting a variable in the context scope by name name and value.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * atlasScope(const QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setLimit(long long limit)
Set the maximum number of features to request.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:205
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Q_GADGET bool isNull
Definition: qgsgeometry.h:126
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
static QgsGeometry fromPointXY(const QgsPointXY &point) SIP_HOLDGIL
Creates a new geometry from a QgsPointXY object.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
void remoteImageFetched(const QString &url)
Emitted when the cache has finished retrieving an image file from a remote url.
Stores global configuration for labeling engine.
Class that stores computed placement from labeling engine.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
An interactive map canvas item which displays a QgsAnnotation.
An interface for objects which block interactions with a QgsMapCanvas.
Interaction
Available interactions to block.
An abstract class for items that can be placed on the map canvas.
virtual void updatePosition()
called on changed extent or resize event to update position of the item
Snapping utils instance that is connected to a canvas and updates the configuration (map settings + c...
Deprecated to be deleted, stuff from here should be moved elsewhere.
QPoint mouseLastXY
Last seen point of the mouse.
bool panSelectorDown
Flag to indicate the pan selector key is held down by user.
CanvasProperties()=default
Constructor for CanvasProperties.
QPoint rubberStartPoint
Beginning point of a rubber band.
bool mouseButtonDown
Flag to indicate status of mouse button.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:87
void setCurrentLayer(QgsMapLayer *layer)
void setCustomDropHandlers(const QVector< QPointer< QgsCustomDropHandler >> &handlers)
Sets a list of custom drop handlers to use when drop events occur on the canvas.
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.
QgsUnitTypes::DistanceUnit mapUnits() const
Convenience function for returning the current canvas map units.
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 setSnappingUtils(QgsSnappingUtils *utils)
Assign an instance of snapping utils to the map canvas.
bool isCachingEnabled() const
Check whether images of rendered layers are curerently being cached.
void zoomToFullExtent()
Zoom to the full extent of all layers currently visible in the canvas.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers that should be shown in the canvas.
void setProject(QgsProject *project)
Sets the project linked to this canvas.
const QgsRenderedItemResults * renderedItemResults(bool allowOutdatedResults=true) const
Gets access to the rendered item results (may be nullptr), which includes the results of rendering an...
QColor selectionColor() const
Returns color for selected features.
bool event(QEvent *e) override
~QgsMapCanvas() override
void setCachingEnabled(bool enabled)
Set whether to cache images of rendered layers.
void mouseReleaseEvent(QMouseEvent *e) override
QgsDoubleRange zRange() const
Returns the range of z-values which will be visible in the map.
void setExtent(const QgsRectangle &r, bool magnified=false)
Sets the extent of the map canvas to the specified rectangle.
void setRenderFlag(bool flag)
Sets whether a user has disabled canvas renders via the GUI.
QList< QgsMapCanvasAnnotationItem * > annotationItems() const
Returns a list of all annotation items in the canvas.
void updateCanvasItemPositions()
called on resize or changed extent to notify canvas items to change their rectangle
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void extentsChanged()
Emitted when the extents of the map change.
QgsExpressionContextScope * defaultExpressionContextScope() const
Creates a new scope which contains default variables and functions relating to the map canvas.
void xyCoordinates(const QgsPointXY &p)
Emits current mouse position.
void stopRendering()
stop rendering (if there is any right now)
bool previewJobsEnabled
Definition: qgsmapcanvas.h:100
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 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.
QList< QgsMapLayer * > layers() const
Returns the list of layers shown within the map canvas.
void zoomByFactor(double scaleFactor, const QgsPointXY *center=nullptr, bool ignoreScaleLock=false)
Zoom with the factor supplied.
const QgsTemporalController * temporalController() const
Gets access to the temporal controller that will be used to update the canvas temporal range.
void flashGeometries(const QList< QgsGeometry > &geometries, const QgsCoordinateReferenceSystem &crs=QgsCoordinateReferenceSystem(), const QColor &startColor=QColor(255, 0, 0, 255), const QColor &endColor=QColor(255, 0, 0, 0), int flashes=3, int duration=500)
Causes a set of geometries to flash within the canvas.
void setMapUpdateInterval(int timeMilliseconds)
Set how often map preview should be updated while it is being rendered (in milliseconds)
void rotationChanged(double)
Emitted when the rotation of the map changes.
void selectionChanged(QgsVectorLayer *layer)
Emitted when selection in any layer gets changed.
void dragEnterEvent(QDragEnterEvent *e) override
bool isDrawing()
Find out whether rendering is in progress.
void zRangeChanged()
Emitted when the map canvas z (elevation) range changes.
void keyPressEvent(QKeyEvent *e) override
void setZRange(const QgsDoubleRange &range)
Sets the range of z-values which will be visible in the map.
void clearExtentHistory()
Clears the list of extents and sets current extent as first item.
void zoomToPreviousExtent()
Zoom to the previous extent (view)
void enableMapTileRendering(bool flag)
sets map tile rendering flag
void panAction(QMouseEvent *event)
Called when mouse is moving and pan is activated.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the stored overrides of styles for rendering layers.
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 panToSelected(QgsVectorLayer *layer=nullptr)
Pan to the selected features of current (vector) layer keeping same extent.
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 setMapSettingsFlags(QgsMapSettings::Flags flags)
Resets the flags for the canvas' map settings.
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.
std::unique_ptr< CanvasProperties > mCanvasProperties
Handle pattern for implementation object.
void keyReleased(QKeyEvent *e)
Emit key release event.
void setWheelFactor(double factor)
Sets wheel zoom factor (should be greater than 1)
void setAnnotationsVisible(bool visible)
Sets whether annotations are visible in the canvas.
void layerStyleOverridesChanged()
Emitted when the configuration of overridden layer styles changes.
QgsMapCanvas(QWidget *parent=nullptr)
Constructor.
void panActionStart(QPoint releasePoint)
Starts a pan action.
void setPreviewJobsEnabled(bool enabled)
Sets whether canvas map preview jobs (low priority render jobs which render portions of the view just...
bool setReferencedExtent(const QgsReferencedRectangle &extent) SIP_THROW(QgsCsException)
Sets the canvas to the specified extent.
QgsRectangle fullExtent() const
Returns the combined extent for all layers on the map canvas.
void redrawAllLayers()
Clears all cached images and redraws all layers.
void keyReleaseEvent(QKeyEvent *e) override
bool isFrozen() const
Returns true if canvas is frozen.
void panToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter=true)
Centers canvas extent to feature ids.
void scaleChanged(double)
Emitted when the scale of the map changes.
void scaleLockChanged(bool locked)
Emitted when the scale locked state of the map changes.
const QgsLabelingResults * labelingResults(bool allowOutdatedResults=true) const
Gets access to the labeling results (may be nullptr).
void mouseMoveEvent(QMouseEvent *e) override
QgsRectangle projectExtent() const
Returns the associated project's full extent, in the canvas' CRS.
void setCenter(const QgsPointXY &center)
Set the center of the map canvas, in geographical coordinates.
void setParallelRenderingEnabled(bool enabled)
Set whether the layers are rendered in parallel or sequentially.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets destination coordinate reference system.
void flashFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, const QColor &startColor=QColor(255, 0, 0, 255), const QColor &endColor=QColor(255, 0, 0, 0), int flashes=3, int duration=500)
Causes a set of features with matching ids from a vector layer to flash within the canvas.
void installInteractionBlocker(QgsMapCanvasInteractionBlocker *blocker)
Installs an interaction blocker onto the canvas, which may prevent certain map canvas interactions fr...
bool isParallelRenderingEnabled() const
Check whether the layers are rendered in parallel or sequentially.
void panDistanceBearingChanged(double distance, QgsUnitTypes::DistanceUnit unit, double bearing)
Emitted whenever the distance or bearing of an in-progress panning operation is changed.
double scale() const
Returns the last reported scale of the canvas.
QgsSnappingUtils * snappingUtils() const
Returns snapping utility class that is associated with map canvas.
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 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.
bool allowInteraction(QgsMapCanvasInteractionBlocker::Interaction interaction) const
Returns true if the specified interaction is currently permitted on the canvas.
void wheelEvent(QWheelEvent *e) override
bool previewModeEnabled() const
Returns whether a preview mode is enabled for the map canvas.
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
void dropEvent(QDropEvent *event) override
void setPreviewModeEnabled(bool previewEnabled)
Enables a preview mode for the map canvas.
QgsProject * project()
Returns the project linked to this canvas.
QString theme
Definition: qgsmapcanvas.h:99
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 zoomToSelected(QgsVectorLayer *layer=nullptr)
Zoom to the extent of the selected features of provided (vector) layer.
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:72
virtual bool isSpatial() const
Returns true if the layer is considered a spatial layer, ie it has some form of geometry associated w...
void autoRefreshIntervalChanged(int interval)
Emitted when the auto refresh interval changes.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:78
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
bool hasAutoRefreshEnabled() const
Returns true if auto refresh is enabled for the layer.
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Definition: qgsmaplayer.h:1492
int autoRefreshInterval
Definition: qgsmaplayer.h:76
virtual Q_INVOKABLE QgsDataProvider * dataProvider()
Returns the layer's data provider, it may be nullptr.
virtual Q_INVOKABLE void reload()
Synchronises with changes in the datasource.
Definition: qgsmaplayer.h:533
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1485
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.
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.
virtual QgsLabelingResults * takeLabelingResults()=0
Gets pointer to internal labeling engine (in order to get access to the results).
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...
static const QgsSettingsEntryBool settingsLogCanvasRefreshEvent
Settings entry log canvas refresh event.
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
void finished()
emitted when asynchronous rendering is finished (or canceled).
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.
virtual bool isActive() const =0
Tell whether the rendering job is currently running in background.
void setLayerRenderingTimeHints(const QHash< QString, int > &hints)
Sets approximate render times (in ms) for map layers.
QgsRenderedItemResults * takeRenderedItemResults()
Takes the rendered item results from the map render job and returns them.
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 writeXml(QDomNode &node, QDomDocument &doc)
QgsPointXY layerToMapCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from layer's CRS to output CRS
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.
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.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
QStringList layerIds() const
Returns the list of layer IDs which will be rendered in the map.
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
@ Antialiasing
Enable anti-aliasing for map rendering.
@ 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.
@ DrawLabeling
Enable drawing of labels on top of the map.
double magnificationFactor() const
Returns the magnification factor.
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.
double mapUnitsPerPixel() const
Returns the distance in geographical coordinates that equals to one pixel in the map.
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.
QgsUnitTypes::DistanceUnit mapUnits() const
Returns the units of the map's geographical coordinates - used for scale calculation.
const QgsMapToPixel & mapToPixel() const
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.
QList< QgsMapLayer * > layers() const
Returns the list of layers which will be rendered in the map.
void setPathResolver(const QgsPathResolver &resolver)
Sets the path resolver for conversion between relative and absolute paths during rendering operations...
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns the global configuration of the labeling engine.
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 setFlag(Flag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
void setFlags(QgsMapSettings::Flags flags)
Sets combination of flags that will be used for rendering.
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 setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
void readXml(QDomNode &node)
void setMagnificationFactor(double factor, const QgsPointXY *center=nullptr)
Set the magnification factor.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
void mapThemesChanged()
Emitted when map themes within the collection are changed.
void mapThemeRenamed(const QString &name, const QString &newName)
Emitted when a map theme within the collection is renamed.
bool hasMapTheme(const QString &name) const
Returns whether a map theme with a matching name exists.
void mapThemeChanged(const QString &theme)
Emitted when a map theme changes definition.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns current map units per pixel.
QgsPointXY toMapCoordinates(int x, int y) const
Transform device coordinates to map (world) coordinates.
A map tool for panning the map.
Definition: qgsmaptoolpan.h:33
Abstract base class for all map tools.
Definition: qgsmaptool.h:66
virtual void populateContextMenu(QMenu *menu)
Allows the tool to populate and customize the given menu, prior to showing it in response to a right-...
Definition: qgsmaptool.cpp:239
virtual bool canvasToolTipEvent(QHelpEvent *e)
Tooltip event for overriding.
Definition: qgsmaptool.cpp:199
virtual void canvasDoubleClickEvent(QgsMapMouseEvent *e)
Mouse double-click event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:163
virtual bool populateContextMenuWithEvent(QMenu *menu, QgsMapMouseEvent *event)
Allows the tool to populate and customize the given menu, prior to showing it in response to a right-...
Definition: qgsmaptool.cpp:245
virtual void canvasPressEvent(QgsMapMouseEvent *e)
Mouse press event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:168
virtual void canvasMoveEvent(QgsMapMouseEvent *e)
Mouse move event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:158
virtual void keyPressEvent(QKeyEvent *e)
Key event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:183
virtual void keyReleaseEvent(QKeyEvent *e)
Key event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:188
virtual Flags flags() const
Returns the flags for the map tool.
Definition: qgsmaptool.h:108
virtual void canvasReleaseEvent(QgsMapMouseEvent *e)
Mouse release event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:173
virtual void wheelEvent(QWheelEvent *e)
Mouse wheel event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:178
@ AllowZoomRect
Allow zooming by rectangle (by holding shift and dragging) while the tool is active.
Definition: qgsmaptool.h:99
@ EditTool
Map tool is an edit tool, which can only be used when layer is editable.
Definition: qgsmaptool.h:98
@ ShowContextMenu
Show a context menu when right-clicking with the tool (since QGIS 3.14). See populateContextMenu().
Definition: qgsmaptool.h:100
virtual void clean()
convenient method to clean members
Definition: qgsmaptool.cpp:110
virtual void activate()
called when set as currently active map tool
Definition: qgsmaptool.cpp:84
virtual bool gestureEvent(QGestureEvent *e)
gesture event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:193
virtual void deactivate()
called when map tool is being deactivated
Definition: qgsmaptool.cpp:100
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static bool isUriList(const QMimeData *data)
QList< QgsMimeDataUtils::Uri > UriList
static UriList decodeUriList(const QMimeData *data)
A class to represent a 2D point.
Definition: qgspointxy.h:59
double sqrDist(double x, double y) const SIP_HOLDGIL
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:190
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
A graphics effect which can be applied to a widget to simulate various printing and color blindness m...
void setMode(PreviewMode mode)
Sets the mode for the preview effect, which controls how the effect modifies a widgets appearance.
PreviewMode mode() const
Returns the mode used for the preview effect.
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:101
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:467
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:109
void projectColorsChanged()
Emitted whenever the project's color scheme has been changed.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:107
void readProject(const QDomDocument &)
Emitted when a project is being read.
void writeProject(QDomDocument &)
Emitted when the project is being written.
const QgsProjectViewSettings * viewSettings() const
Returns the project's view settings, which contains settings and properties relating to how a QgsProj...
void transformContextChanged()
Emitted when the project transformContext() is changed.
A generic dialog to prompt the user for a Coordinate Reference System.
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:256
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
void setMinimal() SIP_HOLDGIL
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:172
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
A QgsRectangle with associated coordinate reference system.
Stores collated details of rendered items during a map rendering operation.
void transferResults(QgsRenderedItemResults *other, const QStringList &layerIds)
Transfers all results from an other QgsRenderedItemResults object where the items have layer IDs matc...
A class for drawing transient features (e.g.
Definition: qgsrubberband.h:51
QColor strokeColor
Definition: qgsrubberband.h:69
void setWidth(int width)
Sets the width of the line.
void setSecondaryStrokeColor(const QColor &color)
Sets a secondary stroke color for the rubberband which will be drawn under the main stroke color.
@ ICON_CIRCLE
A circle is used to highlight points (○)
QColor fillColor
Definition: qgsrubberband.h:68
void setStrokeColor(const QColor &color)
Sets the stroke color for the rubberband.
QColor secondaryStrokeColor
Definition: qgsrubberband.h:71
void setIcon(IconType icon)
Sets the icon type to highlight point geometries.
void updatePosition() override
called on changed extent or resize event to update position of the item
void addGeometry(const QgsGeometry &geometry, QgsMapLayer *layer, bool doUpdate=true)
Adds the geometry of an existing feature to a rubberband This is useful for multi feature highlightin...
void setFillColor(const QColor &color)
Sets the fill color for the rubberband.
Scoped object for saving and restoring a QPainter object's state.
Scoped object for logging of the runtime for a single operation or group of operations.
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.
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.
bool isActive() const
Returns true if the temporal property is active.
virtual QgsTemporalProperty::Flags flags() const
Returns flags associated to the temporal property.
@ FlagDontInvalidateCachedRendersWhenRangeChanges
Any cached rendering will not be invalidated when temporal range context is modified.
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
void setIsTemporal(bool enabled)
Sets whether the temporal range is enabled (i.e.
void setTemporalRange(const QgsDateTimeRange &range)
Sets the temporal range for the object.
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
Definition: qgsguiutils.h:221
void release()
Releases the cursor override early (i.e.
DistanceUnit
Units of distance.
Definition: qgsunittypes.h:68
@ DistanceDegrees
Degrees, for planar geographic CRS distance measurements.
Definition: qgsunittypes.h:75
Represents a vector layer which manages a vector based data sets.
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.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
A class to represent a vector.
Definition: qgsvector.h:30
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:938
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:141
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:1108
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:1047
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
Stores settings related to the context in which a preview job runs.
double maxRenderingTimeMs
Default maximum allowable render time, in ms.
double lastRenderingTimeMs
Previous rendering time for the layer, in ms.