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