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