QGIS API Documentation  3.13.0-Master (b73bd58cfb)
qgslayoutitemmap.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutitemmap.cpp
3  ---------------------
4  begin : July 2017
5  copyright : (C) 2017 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 /***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include "qgslayoutitemmap.h"
18 #include "qgslayout.h"
19 #include "qgslayoutrendercontext.h"
20 #include "qgslayoutreportcontext.h"
21 #include "qgslayoututils.h"
22 #include "qgslayoutmodel.h"
23 #include "qgsmapthemecollection.h"
24 #include "qgsannotationmanager.h"
25 #include "qgsannotation.h"
26 #include "qgsmapsettingsutils.h"
27 #include "qgslayertree.h"
28 #include "qgsmaplayerref.h"
29 #include "qgsmaplayerlistutils.h"
31 #include "qgsvectorlayer.h"
32 #include "qgsexpressioncontext.h"
33 #include "qgsapplication.h"
35 #include "qgsstyleentityvisitor.h"
36 
37 #include <QPainter>
38 #include <QStyleOptionGraphicsItem>
39 
41  : QgsLayoutItem( layout )
42 {
43  mBackgroundUpdateTimer = new QTimer( this );
44  mBackgroundUpdateTimer->setSingleShot( true );
45  connect( mBackgroundUpdateTimer, &QTimer::timeout, this, &QgsLayoutItemMap::recreateCachedImageInBackground );
46 
47  assignFreeId();
48 
49  setCacheMode( QGraphicsItem::NoCache );
50 
51  connect( this, &QgsLayoutItem::sizePositionChanged, this, [ = ]
52  {
53  shapeChanged();
54  } );
55 
56  mGridStack = qgis::make_unique< QgsLayoutItemMapGridStack >( this );
57  mOverviewStack = qgis::make_unique< QgsLayoutItemMapOverviewStack >( this );
58 
59  if ( layout )
60  connectUpdateSlot();
61 }
62 
64 {
65  if ( mPainterJob )
66  {
67  disconnect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
69  mPainterJob->cancel(); // blocks
70  mPainter->end();
71  }
72 }
73 
75 {
77 }
78 
80 {
81  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemMap.svg" ) );
82 }
83 
84 QgsLayoutItem::Flags QgsLayoutItemMap::itemFlags() const
85 {
87 }
88 
90 {
91  if ( !mLayout )
92  return;
93 
94  QList<QgsLayoutItemMap *> mapsList;
95  mLayout->layoutItems( mapsList );
96 
97  int maxId = -1;
98  bool used = false;
99  for ( QgsLayoutItemMap *map : qgis::as_const( mapsList ) )
100  {
101  if ( map == this )
102  continue;
103 
104  if ( map->mMapId == mMapId )
105  used = true;
106 
107  maxId = std::max( maxId, map->mMapId );
108  }
109  if ( used )
110  {
111  mMapId = maxId + 1;
112  mLayout->itemsModel()->updateItemDisplayName( this );
113  }
114  updateToolTip();
115 }
116 
118 {
119  if ( !QgsLayoutItem::id().isEmpty() )
120  {
121  return QgsLayoutItem::id();
122  }
123 
124  return tr( "Map %1" ).arg( mMapId );
125 }
126 
128 {
129  return new QgsLayoutItemMap( layout );
130 }
131 
133 {
135 
136  mCachedLayerStyleOverridesPresetName.clear();
137 
138  invalidateCache();
139 
140  updateAtlasFeature();
141 }
142 
144 {
145  if ( rect().isEmpty() )
146  return 0;
147 
148  QgsScaleCalculator calculator;
149  calculator.setMapUnits( crs().mapUnits() );
150  calculator.setDpi( 25.4 ); //Using mm
151  double widthInMm = mLayout->convertFromLayoutUnits( rect().width(), QgsUnitTypes::LayoutMillimeters ).length();
152  return calculator.calculate( extent(), widthInMm );
153 }
154 
155 void QgsLayoutItemMap::setScale( double scaleDenominator, bool forceUpdate )
156 {
157  double currentScaleDenominator = scale();
158 
159  if ( qgsDoubleNear( scaleDenominator, currentScaleDenominator ) || qgsDoubleNear( scaleDenominator, 0.0 ) )
160  {
161  return;
162  }
163 
164  double scaleRatio = scaleDenominator / currentScaleDenominator;
165  mExtent.scale( scaleRatio );
166 
167  if ( mAtlasDriven && mAtlasScalingMode == Fixed )
168  {
169  //if map is atlas controlled and set to fixed scaling mode, then scale changes should be treated as permanent
170  //and also apply to the map's original extent (see #9602)
171  //we can't use the scaleRatio calculated earlier, as the scale can vary depending on extent for geographic coordinate systems
172  QgsScaleCalculator calculator;
173  calculator.setMapUnits( crs().mapUnits() );
174  calculator.setDpi( 25.4 ); //QGraphicsView units are mm
175  scaleRatio = scaleDenominator / calculator.calculate( mExtent, rect().width() );
176  mExtent.scale( scaleRatio );
177  }
178 
179  invalidateCache();
180  if ( forceUpdate )
181  {
182  emit changed();
183  update();
184  }
185  emit extentChanged();
186 }
187 
189 {
190  if ( mExtent == extent )
191  {
192  return;
193  }
194  mExtent = extent;
195 
196  //recalculate data defined scale and extents, since that may override extent
197  refreshMapExtents();
198 
199  //adjust height
200  QRectF currentRect = rect();
201 
202  double newHeight = currentRect.width() * mExtent.height() / mExtent.width();
203 
204  attemptSetSceneRect( QRectF( pos().x(), pos().y(), currentRect.width(), newHeight ) );
205  update();
206 }
207 
209 {
210  QgsRectangle newExtent = extent;
211  QgsRectangle currentExtent = mExtent;
212  //Make sure the width/height ratio is the same as the current layout map extent.
213  //This is to keep the map item frame size fixed
214  double currentWidthHeightRatio = 1.0;
215  if ( !currentExtent.isNull() )
216  currentWidthHeightRatio = currentExtent.width() / currentExtent.height();
217  else
218  currentWidthHeightRatio = rect().width() / rect().height();
219  double newWidthHeightRatio = newExtent.width() / newExtent.height();
220 
221  if ( currentWidthHeightRatio < newWidthHeightRatio )
222  {
223  //enlarge height of new extent, ensuring the map center stays the same
224  double newHeight = newExtent.width() / currentWidthHeightRatio;
225  double deltaHeight = newHeight - newExtent.height();
226  newExtent.setYMinimum( newExtent.yMinimum() - deltaHeight / 2 );
227  newExtent.setYMaximum( newExtent.yMaximum() + deltaHeight / 2 );
228  }
229  else
230  {
231  //enlarge width of new extent, ensuring the map center stays the same
232  double newWidth = currentWidthHeightRatio * newExtent.height();
233  double deltaWidth = newWidth - newExtent.width();
234  newExtent.setXMinimum( newExtent.xMinimum() - deltaWidth / 2 );
235  newExtent.setXMaximum( newExtent.xMaximum() + deltaWidth / 2 );
236  }
237 
238  if ( mExtent == newExtent )
239  {
240  return;
241  }
242  mExtent = newExtent;
243 
244  //recalculate data defined scale and extents, since that may override extent
245  refreshMapExtents();
246 
247  invalidateCache();
248  emit changed();
249  emit extentChanged();
250 }
251 
253 {
254  return mExtent;
255 }
256 
258 {
259  QPolygonF poly;
260  mapPolygon( mExtent, poly );
261  return poly;
262 }
263 
265 {
266  if ( mCrs.isValid() )
267  return mCrs;
268  else if ( mLayout && mLayout->project() )
269  return mLayout->project()->crs();
271 }
272 
274 {
275  mCrs = crs;
276 }
277 
278 QList<QgsMapLayer *> QgsLayoutItemMap::layers() const
279 {
280  return _qgis_listRefToRaw( mLayers );
281 }
282 
283 void QgsLayoutItemMap::setLayers( const QList<QgsMapLayer *> &layers )
284 {
285  mLayers = _qgis_listRawToRef( layers );
286 }
287 
288 void QgsLayoutItemMap::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
289 {
290  if ( overrides == mLayerStyleOverrides )
291  return;
292 
293  mLayerStyleOverrides = overrides;
294  emit layerStyleOverridesChanged(); // associated legends may listen to this
295 
296 }
297 
299 {
300  mLayerStyleOverrides.clear();
301  for ( const QgsMapLayerRef &layerRef : qgis::as_const( mLayers ) )
302  {
303  if ( QgsMapLayer *layer = layerRef.get() )
304  {
305  QgsMapLayerStyle style;
306  style.readFromLayer( layer );
307  mLayerStyleOverrides.insert( layer->id(), style.xmlData() );
308  }
309  }
310 }
311 
313 {
314  if ( mFollowVisibilityPreset == follow )
315  return;
316 
317  mFollowVisibilityPreset = follow;
318  if ( !mFollowVisibilityPresetName.isEmpty() )
319  emit themeChanged( mFollowVisibilityPreset ? mFollowVisibilityPresetName : QString() );
320 }
321 
323 {
324  if ( name == mFollowVisibilityPresetName )
325  return;
326 
327  mFollowVisibilityPresetName = name;
328  if ( mFollowVisibilityPreset )
329  emit themeChanged( mFollowVisibilityPresetName );
330 }
331 
332 void QgsLayoutItemMap::moveContent( double dx, double dy )
333 {
334  mLastRenderedImageOffsetX -= dx;
335  mLastRenderedImageOffsetY -= dy;
336  if ( !mDrawing )
337  {
338  transformShift( dx, dy );
339  mExtent.setXMinimum( mExtent.xMinimum() + dx );
340  mExtent.setXMaximum( mExtent.xMaximum() + dx );
341  mExtent.setYMinimum( mExtent.yMinimum() + dy );
342  mExtent.setYMaximum( mExtent.yMaximum() + dy );
343 
344  //in case data defined extents are set, these override the calculated values
345  refreshMapExtents();
346 
347  invalidateCache();
348  emit changed();
349  emit extentChanged();
350  }
351 }
352 
353 void QgsLayoutItemMap::zoomContent( double factor, QPointF point )
354 {
355  if ( mDrawing )
356  {
357  return;
358  }
359 
360  //find out map coordinates of position
361  double mapX = mExtent.xMinimum() + ( point.x() / rect().width() ) * ( mExtent.xMaximum() - mExtent.xMinimum() );
362  double mapY = mExtent.yMinimum() + ( 1 - ( point.y() / rect().height() ) ) * ( mExtent.yMaximum() - mExtent.yMinimum() );
363 
364  //find out new center point
365  double centerX = ( mExtent.xMaximum() + mExtent.xMinimum() ) / 2;
366  double centerY = ( mExtent.yMaximum() + mExtent.yMinimum() ) / 2;
367 
368  centerX = mapX + ( centerX - mapX ) * ( 1.0 / factor );
369  centerY = mapY + ( centerY - mapY ) * ( 1.0 / factor );
370 
371  double newIntervalX, newIntervalY;
372 
373  if ( factor > 0 )
374  {
375  newIntervalX = ( mExtent.xMaximum() - mExtent.xMinimum() ) / factor;
376  newIntervalY = ( mExtent.yMaximum() - mExtent.yMinimum() ) / factor;
377  }
378  else //no need to zoom
379  {
380  return;
381  }
382 
383  mExtent.setXMaximum( centerX + newIntervalX / 2 );
384  mExtent.setXMinimum( centerX - newIntervalX / 2 );
385  mExtent.setYMaximum( centerY + newIntervalY / 2 );
386  mExtent.setYMinimum( centerY - newIntervalY / 2 );
387 
388  if ( mAtlasDriven && mAtlasScalingMode == Fixed )
389  {
390  //if map is atlas controlled and set to fixed scaling mode, then scale changes should be treated as permanent
391  //and also apply to the map's original extent (see #9602)
392  //we can't use the scaleRatio calculated earlier, as the scale can vary depending on extent for geographic coordinate systems
393  QgsScaleCalculator calculator;
394  calculator.setMapUnits( crs().mapUnits() );
395  calculator.setDpi( 25.4 ); //QGraphicsView units are mm
396  double scaleRatio = scale() / calculator.calculate( mExtent, rect().width() );
397  mExtent.scale( scaleRatio );
398  }
399 
400  //recalculate data defined scale and extents, since that may override zoom
401  refreshMapExtents();
402 
403  invalidateCache();
404  emit changed();
405  emit extentChanged();
406 }
407 
409 {
410  const QList< QgsMapLayer * > layers = layersToRender();
411  for ( QgsMapLayer *layer : layers )
412  {
413  if ( layer->dataProvider() && layer->providerType() == QLatin1String( "wms" ) )
414  {
415  return true;
416  }
417  }
418  return false;
419 }
420 
422 {
424  return true;
425 
426  // we MUST force the whole layout to render as a raster if any map item
427  // uses blend modes, and we are not drawing on a solid opaque background
428  // because in this case the map item needs to be rendered as a raster, but
429  // it also needs to interact with items below it
430  if ( !containsAdvancedEffects() )
431  return false;
432 
433  // TODO layer transparency is probably ok to allow without forcing rasterization
434 
435  if ( hasBackground() && qgsDoubleNear( backgroundColor().alphaF(), 1.0 ) )
436  return false;
437 
438  return true;
439 }
440 
442 {
444  return true;
445 
446  //check easy things first
447 
448  //overviews
449  if ( mOverviewStack->containsAdvancedEffects() )
450  {
451  return true;
452  }
453 
454  //grids
455  if ( mGridStack->containsAdvancedEffects() )
456  {
457  return true;
458  }
459 
460  QgsMapSettings ms;
461  ms.setLayers( layersToRender() );
462  return ( !QgsMapSettingsUtils::containsAdvancedEffects( ms ).isEmpty() );
463 }
464 
465 void QgsLayoutItemMap::setMapRotation( double rotation )
466 {
467  mMapRotation = rotation;
468  mEvaluatedMapRotation = mMapRotation;
469  invalidateCache();
470  emit mapRotationChanged( rotation );
471  emit changed();
472 }
473 
475 {
476  return valueType == QgsLayoutObject::EvaluatedValue ? mEvaluatedMapRotation : mMapRotation;
477 
478 }
479 
481 {
482  mAtlasDriven = enabled;
483 
484  if ( !enabled )
485  {
486  //if not enabling the atlas, we still need to refresh the map extents
487  //so that data defined extents and scale are recalculated
488  refreshMapExtents();
489  }
490 }
491 
493 {
494  if ( valueType == QgsLayoutObject::EvaluatedValue )
495  {
496  //evaluate data defined atlas margin
497 
498  //start with user specified margin
499  double margin = mAtlasMargin;
501 
502  bool ok = false;
503  double ddMargin = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapAtlasMargin, context, 0.0, &ok );
504  if ( ok )
505  {
506  //divide by 100 to convert to 0 -> 1.0 range
507  margin = ddMargin / 100;
508  }
509  return margin;
510  }
511  else
512  {
513  return mAtlasMargin;
514  }
515 }
516 
518 {
519  if ( mGridStack->size() < 1 )
520  {
521  QgsLayoutItemMapGrid *grid = new QgsLayoutItemMapGrid( tr( "Grid %1" ).arg( 1 ), this );
522  mGridStack->addGrid( grid );
523  }
524  return mGridStack->grid( 0 );
525 }
526 
528 {
529  if ( mOverviewStack->size() < 1 )
530  {
531  QgsLayoutItemMapOverview *overview = new QgsLayoutItemMapOverview( tr( "Overview %1" ).arg( 1 ), this );
532  mOverviewStack->addOverview( overview );
533  }
534  return mOverviewStack->overview( 0 );
535 }
536 
538 {
539 }
540 
541 bool QgsLayoutItemMap::writePropertiesToElement( QDomElement &mapElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
542 {
543  if ( mKeepLayerSet )
544  {
545  mapElem.setAttribute( QStringLiteral( "keepLayerSet" ), QStringLiteral( "true" ) );
546  }
547  else
548  {
549  mapElem.setAttribute( QStringLiteral( "keepLayerSet" ), QStringLiteral( "false" ) );
550  }
551 
552  if ( mDrawAnnotations )
553  {
554  mapElem.setAttribute( QStringLiteral( "drawCanvasItems" ), QStringLiteral( "true" ) );
555  }
556  else
557  {
558  mapElem.setAttribute( QStringLiteral( "drawCanvasItems" ), QStringLiteral( "false" ) );
559  }
560 
561  //extent
562  QDomElement extentElem = doc.createElement( QStringLiteral( "Extent" ) );
563  extentElem.setAttribute( QStringLiteral( "xmin" ), qgsDoubleToString( mExtent.xMinimum() ) );
564  extentElem.setAttribute( QStringLiteral( "xmax" ), qgsDoubleToString( mExtent.xMaximum() ) );
565  extentElem.setAttribute( QStringLiteral( "ymin" ), qgsDoubleToString( mExtent.yMinimum() ) );
566  extentElem.setAttribute( QStringLiteral( "ymax" ), qgsDoubleToString( mExtent.yMaximum() ) );
567  mapElem.appendChild( extentElem );
568 
569  if ( mCrs.isValid() )
570  {
571  QDomElement crsElem = doc.createElement( QStringLiteral( "crs" ) );
572  mCrs.writeXml( crsElem, doc );
573  mapElem.appendChild( crsElem );
574  }
575 
576  // follow map theme
577  mapElem.setAttribute( QStringLiteral( "followPreset" ), mFollowVisibilityPreset ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
578  mapElem.setAttribute( QStringLiteral( "followPresetName" ), mFollowVisibilityPresetName );
579 
580  //map rotation
581  mapElem.setAttribute( QStringLiteral( "mapRotation" ), QString::number( mMapRotation ) );
582 
583  //layer set
584  QDomElement layerSetElem = doc.createElement( QStringLiteral( "LayerSet" ) );
585  for ( const QgsMapLayerRef &layerRef : mLayers )
586  {
587  if ( !layerRef )
588  continue;
589  QDomElement layerElem = doc.createElement( QStringLiteral( "Layer" ) );
590  QDomText layerIdText = doc.createTextNode( layerRef.layerId );
591  layerElem.appendChild( layerIdText );
592 
593  layerElem.setAttribute( QStringLiteral( "name" ), layerRef.name );
594  layerElem.setAttribute( QStringLiteral( "source" ), layerRef.source );
595  layerElem.setAttribute( QStringLiteral( "provider" ), layerRef.provider );
596 
597  layerSetElem.appendChild( layerElem );
598  }
599  mapElem.appendChild( layerSetElem );
600 
601  // override styles
602  if ( mKeepLayerStyles )
603  {
604  QDomElement stylesElem = doc.createElement( QStringLiteral( "LayerStyles" ) );
605  for ( auto styleIt = mLayerStyleOverrides.constBegin(); styleIt != mLayerStyleOverrides.constEnd(); ++styleIt )
606  {
607  QDomElement styleElem = doc.createElement( QStringLiteral( "LayerStyle" ) );
608 
609  QgsMapLayerRef ref( styleIt.key() );
610  ref.resolve( mLayout->project() );
611 
612  styleElem.setAttribute( QStringLiteral( "layerid" ), ref.layerId );
613  styleElem.setAttribute( QStringLiteral( "name" ), ref.name );
614  styleElem.setAttribute( QStringLiteral( "source" ), ref.source );
615  styleElem.setAttribute( QStringLiteral( "provider" ), ref.provider );
616 
617  QgsMapLayerStyle style( styleIt.value() );
618  style.writeXml( styleElem );
619  stylesElem.appendChild( styleElem );
620  }
621  mapElem.appendChild( stylesElem );
622  }
623 
624  //grids
625  mGridStack->writeXml( mapElem, doc, context );
626 
627  //overviews
628  mOverviewStack->writeXml( mapElem, doc, context );
629 
630  //atlas
631  QDomElement atlasElem = doc.createElement( QStringLiteral( "AtlasMap" ) );
632  atlasElem.setAttribute( QStringLiteral( "atlasDriven" ), mAtlasDriven );
633  atlasElem.setAttribute( QStringLiteral( "scalingMode" ), mAtlasScalingMode );
634  atlasElem.setAttribute( QStringLiteral( "margin" ), qgsDoubleToString( mAtlasMargin ) );
635  mapElem.appendChild( atlasElem );
636 
637  mapElem.setAttribute( QStringLiteral( "labelMargin" ), mLabelMargin.encodeMeasurement() );
638  mapElem.setAttribute( QStringLiteral( "mapFlags" ), static_cast< int>( mMapFlags ) );
639 
640  QDomElement labelBlockingItemsElem = doc.createElement( QStringLiteral( "labelBlockingItems" ) );
641  for ( const auto &item : qgis::as_const( mBlockingLabelItems ) )
642  {
643  if ( !item )
644  continue;
645 
646  QDomElement blockingItemElem = doc.createElement( QStringLiteral( "item" ) );
647  blockingItemElem.setAttribute( QStringLiteral( "uuid" ), item->uuid() );
648  labelBlockingItemsElem.appendChild( blockingItemElem );
649  }
650  mapElem.appendChild( labelBlockingItemsElem );
651 
652  return true;
653 }
654 
655 bool QgsLayoutItemMap::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
656 {
657  mUpdatesEnabled = false;
658 
659  //extent
660  QDomNodeList extentNodeList = itemElem.elementsByTagName( QStringLiteral( "Extent" ) );
661  if ( !extentNodeList.isEmpty() )
662  {
663  QDomElement extentElem = extentNodeList.at( 0 ).toElement();
664  double xmin, xmax, ymin, ymax;
665  xmin = extentElem.attribute( QStringLiteral( "xmin" ) ).toDouble();
666  xmax = extentElem.attribute( QStringLiteral( "xmax" ) ).toDouble();
667  ymin = extentElem.attribute( QStringLiteral( "ymin" ) ).toDouble();
668  ymax = extentElem.attribute( QStringLiteral( "ymax" ) ).toDouble();
669  setExtent( QgsRectangle( xmin, ymin, xmax, ymax ) );
670  }
671 
672  QDomNodeList crsNodeList = itemElem.elementsByTagName( QStringLiteral( "crs" ) );
673  if ( !crsNodeList.isEmpty() )
674  {
675  QDomElement crsElem = crsNodeList.at( 0 ).toElement();
676  mCrs.readXml( crsElem );
677  }
678  else
679  {
681  }
682 
683  //map rotation
684  mMapRotation = itemElem.attribute( QStringLiteral( "mapRotation" ), QStringLiteral( "0" ) ).toDouble();
685  mEvaluatedMapRotation = mMapRotation;
686 
687  // follow map theme
688  mFollowVisibilityPreset = itemElem.attribute( QStringLiteral( "followPreset" ) ).compare( QLatin1String( "true" ) ) == 0;
689  mFollowVisibilityPresetName = itemElem.attribute( QStringLiteral( "followPresetName" ) );
690 
691  //mKeepLayerSet flag
692  QString keepLayerSetFlag = itemElem.attribute( QStringLiteral( "keepLayerSet" ) );
693  if ( keepLayerSetFlag.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
694  {
695  mKeepLayerSet = true;
696  }
697  else
698  {
699  mKeepLayerSet = false;
700  }
701 
702  QString drawCanvasItemsFlag = itemElem.attribute( QStringLiteral( "drawCanvasItems" ), QStringLiteral( "true" ) );
703  if ( drawCanvasItemsFlag.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
704  {
705  mDrawAnnotations = true;
706  }
707  else
708  {
709  mDrawAnnotations = false;
710  }
711 
712  mLayerStyleOverrides.clear();
713 
714  //mLayers
715  mLayers.clear();
716  QDomNodeList layerSetNodeList = itemElem.elementsByTagName( QStringLiteral( "LayerSet" ) );
717  if ( !layerSetNodeList.isEmpty() )
718  {
719  QDomElement layerSetElem = layerSetNodeList.at( 0 ).toElement();
720  QDomNodeList layerIdNodeList = layerSetElem.elementsByTagName( QStringLiteral( "Layer" ) );
721  mLayers.reserve( layerIdNodeList.size() );
722  for ( int i = 0; i < layerIdNodeList.size(); ++i )
723  {
724  QDomElement layerElem = layerIdNodeList.at( i ).toElement();
725  QString layerId = layerElem.text();
726  QString layerName = layerElem.attribute( QStringLiteral( "name" ) );
727  QString layerSource = layerElem.attribute( QStringLiteral( "source" ) );
728  QString layerProvider = layerElem.attribute( QStringLiteral( "provider" ) );
729 
730  QgsMapLayerRef ref( layerId, layerName, layerSource, layerProvider );
731  ref.resolveWeakly( mLayout->project() );
732  mLayers << ref;
733  }
734  }
735 
736  // override styles
737  QDomNodeList layerStylesNodeList = itemElem.elementsByTagName( QStringLiteral( "LayerStyles" ) );
738  mKeepLayerStyles = !layerStylesNodeList.isEmpty();
739  if ( mKeepLayerStyles )
740  {
741  QDomElement layerStylesElem = layerStylesNodeList.at( 0 ).toElement();
742  QDomNodeList layerStyleNodeList = layerStylesElem.elementsByTagName( QStringLiteral( "LayerStyle" ) );
743  for ( int i = 0; i < layerStyleNodeList.size(); ++i )
744  {
745  const QDomElement &layerStyleElement = layerStyleNodeList.at( i ).toElement();
746  QString layerId = layerStyleElement.attribute( QStringLiteral( "layerid" ) );
747  QString layerName = layerStyleElement.attribute( QStringLiteral( "name" ) );
748  QString layerSource = layerStyleElement.attribute( QStringLiteral( "source" ) );
749  QString layerProvider = layerStyleElement.attribute( QStringLiteral( "provider" ) );
750  QgsMapLayerRef ref( layerId, layerName, layerSource, layerProvider );
751  ref.resolveWeakly( mLayout->project() );
752 
753  QgsMapLayerStyle style;
754  style.readXml( layerStyleElement );
755  mLayerStyleOverrides.insert( ref.layerId, style.xmlData() );
756  }
757  }
758 
759  mDrawing = false;
760  mNumCachedLayers = 0;
761  mCacheInvalidated = true;
762 
763  //overviews
764  mOverviewStack->readXml( itemElem, doc, context );
765 
766  //grids
767  mGridStack->readXml( itemElem, doc, context );
768 
769  //atlas
770  QDomNodeList atlasNodeList = itemElem.elementsByTagName( QStringLiteral( "AtlasMap" ) );
771  if ( !atlasNodeList.isEmpty() )
772  {
773  QDomElement atlasElem = atlasNodeList.at( 0 ).toElement();
774  mAtlasDriven = ( atlasElem.attribute( QStringLiteral( "atlasDriven" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
775  if ( atlasElem.hasAttribute( QStringLiteral( "fixedScale" ) ) ) // deprecated XML
776  {
777  mAtlasScalingMode = ( atlasElem.attribute( QStringLiteral( "fixedScale" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) ) ? Fixed : Auto;
778  }
779  else if ( atlasElem.hasAttribute( QStringLiteral( "scalingMode" ) ) )
780  {
781  mAtlasScalingMode = static_cast<AtlasScalingMode>( atlasElem.attribute( QStringLiteral( "scalingMode" ) ).toInt() );
782  }
783  mAtlasMargin = atlasElem.attribute( QStringLiteral( "margin" ), QStringLiteral( "0.1" ) ).toDouble();
784  }
785 
786  setLabelMargin( QgsLayoutMeasurement::decodeMeasurement( itemElem.attribute( QStringLiteral( "labelMargin" ), QStringLiteral( "0" ) ) ) );
787 
788  mMapFlags = static_cast< MapItemFlags>( itemElem.attribute( QStringLiteral( "mapFlags" ), nullptr ).toInt() );
789 
790  // label blocking items
791  mBlockingLabelItems.clear();
792  mBlockingLabelItemUuids.clear();
793  QDomNodeList labelBlockingNodeList = itemElem.elementsByTagName( QStringLiteral( "labelBlockingItems" ) );
794  if ( !labelBlockingNodeList.isEmpty() )
795  {
796  QDomElement blockingItems = labelBlockingNodeList.at( 0 ).toElement();
797  QDomNodeList labelBlockingNodeList = blockingItems.childNodes();
798  for ( int i = 0; i < labelBlockingNodeList.size(); ++i )
799  {
800  const QDomElement &itemBlockingElement = labelBlockingNodeList.at( i ).toElement();
801  const QString itemUuid = itemBlockingElement.attribute( QStringLiteral( "uuid" ) );
802  mBlockingLabelItemUuids << itemUuid;
803  }
804  }
805 
807 
808  mUpdatesEnabled = true;
809  return true;
810 }
811 
812 void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem *style, QWidget * )
813 {
814  if ( !mLayout || !painter || !painter->device() || !mUpdatesEnabled )
815  {
816  return;
817  }
818  if ( !shouldDrawItem() )
819  {
820  return;
821  }
822 
823  QRectF thisPaintRect = rect();
824  if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
825  return;
826 
827  //TODO - try to reduce the amount of duplicate code here!
828 
829  if ( mLayout->renderContext().isPreviewRender() )
830  {
831  painter->save();
832  painter->setClipRect( thisPaintRect );
833  if ( !mCacheFinalImage || mCacheFinalImage->isNull() )
834  {
835  // No initial render available - so draw some preview text alerting user
836  painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
837  painter->drawRect( thisPaintRect );
838  painter->setBrush( Qt::NoBrush );
839  QFont messageFont;
840  messageFont.setPointSize( 12 );
841  painter->setFont( messageFont );
842  painter->setPen( QColor( 255, 255, 255, 255 ) );
843  painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering map" ) );
844  if ( mPainterJob && mCacheInvalidated && !mDrawingPreview )
845  {
846  // current job was invalidated - start a new one
847  mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( style );
848  mBackgroundUpdateTimer->start( 1 );
849  }
850  else if ( !mPainterJob && !mDrawingPreview )
851  {
852  // this is the map's very first paint - trigger a cache update
853  mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( style );
854  mBackgroundUpdateTimer->start( 1 );
855  }
856  }
857  else
858  {
859  if ( mCacheInvalidated && !mDrawingPreview )
860  {
861  // cache was invalidated - trigger a background update
862  mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( style );
863  mBackgroundUpdateTimer->start( 1 );
864  }
865 
866  //Background color is already included in cached image, so no need to draw
867 
868  double imagePixelWidth = mCacheFinalImage->width(); //how many pixels of the image are for the map extent?
869  double scale = rect().width() / imagePixelWidth;
870 
871  painter->save();
872 
873  painter->translate( mLastRenderedImageOffsetX + mXOffset, mLastRenderedImageOffsetY + mYOffset );
874  painter->scale( scale, scale );
875  painter->drawImage( 0, 0, *mCacheFinalImage );
876 
877  //restore rotation
878  painter->restore();
879  }
880 
881  painter->setClipRect( thisPaintRect, Qt::NoClip );
882 
883  mOverviewStack->drawItems( painter, false );
884  mGridStack->drawItems( painter );
885  drawAnnotations( painter );
886  drawMapFrame( painter );
887  painter->restore();
888  }
889  else
890  {
891  if ( mDrawing )
892  return;
893 
894  mDrawing = true;
895  QPaintDevice *paintDevice = painter->device();
896  if ( !paintDevice )
897  return;
898 
899  QgsRectangle cExtent = extent();
900  QSizeF size( cExtent.width() * mapUnitsToLayoutUnits(), cExtent.height() * mapUnitsToLayoutUnits() );
901 
902  if ( containsAdvancedEffects() && ( !mLayout || !( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagForceVectorOutput ) ) )
903  {
904  // rasterize
905  double destinationDpi = QgsLayoutUtils::scaleFactorFromItemStyle( style ) * 25.4;
906  double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, QgsUnitTypes::LayoutInches ).length() : 1;
907  int widthInPixels = static_cast< int >( std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi ) );
908  int heightInPixels = static_cast< int >( std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi ) );
909  QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
910 
911  image.fill( Qt::transparent );
912  image.setDotsPerMeterX( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
913  image.setDotsPerMeterY( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
914  double dotsPerMM = destinationDpi / 25.4;
915  QPainter p( &image );
916 
917  QPointF tl = -boundingRect().topLeft();
918  QRect imagePaintRect( static_cast< int >( std::round( tl.x() * dotsPerMM ) ),
919  static_cast< int >( std::round( tl.y() * dotsPerMM ) ),
920  static_cast< int >( std::round( thisPaintRect.width() * dotsPerMM ) ),
921  static_cast< int >( std::round( thisPaintRect.height() * dotsPerMM ) ) );
922  p.setClipRect( imagePaintRect );
923 
924  p.translate( imagePaintRect.topLeft() );
925 
926  // Fill with background color - must be drawn onto the flattened image
927  // so that layers with opacity or blend modes can correctly interact with it
928  if ( shouldDrawPart( Background ) )
929  {
930  p.scale( dotsPerMM, dotsPerMM );
931  drawMapBackground( &p );
932  p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
933  }
934 
935  drawMap( &p, cExtent, imagePaintRect.size(), image.logicalDpiX() );
936 
937  // important - all other items, overviews, grids etc must be rendered to the
938  // flattened image, in case these have blend modes must need to interact
939  // with the map
940  p.scale( dotsPerMM, dotsPerMM );
941 
942  if ( shouldDrawPart( OverviewMapExtent ) )
943  {
944  mOverviewStack->drawItems( &p, false );
945  }
946  if ( shouldDrawPart( Grid ) )
947  {
948  mGridStack->drawItems( &p );
949  }
950  drawAnnotations( &p );
951 
952  painter->save();
953  painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
954  painter->drawImage( QPointF( -tl.x()* dotsPerMM, -tl.y() * dotsPerMM ), image );
955  painter->scale( dotsPerMM, dotsPerMM );
956  painter->restore();
957  }
958  else
959  {
960  // Fill with background color
961  if ( shouldDrawPart( Background ) )
962  {
963  drawMapBackground( painter );
964  }
965 
966  painter->save();
967  painter->setClipRect( thisPaintRect );
968 
969  if ( shouldDrawPart( Layer ) && !qgsDoubleNear( size.width(), 0.0 ) && !qgsDoubleNear( size.height(), 0.0 ) )
970  {
971  painter->save();
972  painter->translate( mXOffset, mYOffset );
973 
974  double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
975  size *= dotsPerMM; // output size will be in dots (pixels)
976  painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
977 
978  if ( mCurrentExportPart != NotLayered )
979  {
980  if ( !mStagedRendererJob )
981  {
982  createStagedRenderJob( cExtent, size, paintDevice->logicalDpiX() );
983  }
984 
985  mStagedRendererJob->renderCurrentPart( painter );
986  }
987  else
988  {
989  drawMap( painter, cExtent, size, paintDevice->logicalDpiX() );
990  }
991 
992  painter->restore();
993  }
994 
995  painter->setClipRect( thisPaintRect, Qt::NoClip );
996 
997  if ( shouldDrawPart( OverviewMapExtent ) )
998  {
999  mOverviewStack->drawItems( painter, false );
1000  }
1001  if ( shouldDrawPart( Grid ) )
1002  {
1003  mGridStack->drawItems( painter );
1004  }
1005  drawAnnotations( painter );
1006  painter->restore();
1007  }
1008 
1009  if ( shouldDrawPart( Frame ) )
1010  {
1011  drawMapFrame( painter );
1012  }
1013 
1014  mDrawing = false;
1015  }
1016 }
1017 
1019 {
1020  const int layerCount = layersToRender().length();
1021  return ( hasBackground() ? 1 : 0 )
1022  + ( layerCount + ( layerCount ? 1 : 0 ) ) // +1 for label layer, if labels present
1023  + ( mGridStack->hasEnabledItems() ? 1 : 0 )
1024  + ( mOverviewStack->hasEnabledItems() ? 1 : 0 )
1025  + ( frameEnabled() ? 1 : 0 );
1026 }
1027 
1029 {
1030  mCurrentExportPart = Start;
1031  // only follow export themes if the map isn't set to follow a fixed theme
1032  mExportThemes = !mFollowVisibilityPreset ? mLayout->renderContext().exportThemes() : QStringList();
1033  mExportThemeIt = mExportThemes.begin();
1034 }
1035 
1037 {
1038  mCurrentExportPart = NotLayered;
1039  mExportThemes.clear();
1040  mExportThemeIt = mExportThemes.begin();
1041 }
1042 
1044 {
1045  switch ( mCurrentExportPart )
1046  {
1047  case Start:
1048  if ( hasBackground() )
1049  {
1050  mCurrentExportPart = Background;
1051  return true;
1052  }
1053  FALLTHROUGH
1054 
1055  case Background:
1056  mCurrentExportPart = Layer;
1057  return true;
1058 
1059  case Layer:
1060  if ( mStagedRendererJob )
1061  {
1062  if ( mStagedRendererJob->nextPart() )
1063  return true;
1064  else
1065  mStagedRendererJob.reset(); // no more map layer parts
1066  }
1067 
1068  if ( mExportThemeIt != mExportThemes.end() && ++mExportThemeIt != mExportThemes.end() )
1069  {
1070  // move to next theme and continue exporting map layers
1071  return true;
1072  }
1073 
1074  if ( mGridStack->hasEnabledItems() )
1075  {
1076  mCurrentExportPart = Grid;
1077  return true;
1078  }
1079  FALLTHROUGH
1080 
1081  case Grid:
1082  for ( int i = 0; i < mOverviewStack->size(); ++i )
1083  {
1084  QgsLayoutItemMapItem *item = mOverviewStack->item( i );
1086  {
1087  mCurrentExportPart = OverviewMapExtent;
1088  return true;
1089  }
1090  }
1091  FALLTHROUGH
1092 
1093  case OverviewMapExtent:
1094  if ( frameEnabled() )
1095  {
1096  mCurrentExportPart = Frame;
1097  return true;
1098  }
1099 
1100  FALLTHROUGH
1101 
1102  case Frame:
1103  if ( isSelected() && !mLayout->renderContext().isPreviewRender() )
1104  {
1105  mCurrentExportPart = SelectionBoxes;
1106  return true;
1107  }
1108  FALLTHROUGH
1109 
1110  case SelectionBoxes:
1111  mCurrentExportPart = End;
1112  return false;
1113 
1114  case End:
1115  return false;
1116 
1117  case NotLayered:
1118  return false;
1119  }
1120  return false;
1121 }
1122 
1124 {
1125  return ItemContainsSubLayers;
1126 }
1127 
1129 {
1130  ExportLayerDetail detail;
1131 
1132  switch ( mCurrentExportPart )
1133  {
1134  case Start:
1135  break;
1136 
1137  case Background:
1138  detail.name = tr( "%1: Background" ).arg( displayName() );
1139  return detail;
1140 
1141  case Layer:
1142  if ( !mExportThemes.empty() && mExportThemeIt != mExportThemes.end() )
1143  detail.mapTheme = *mExportThemeIt;
1144 
1145  if ( mStagedRendererJob )
1146  {
1147  switch ( mStagedRendererJob->currentStage() )
1148  {
1150  {
1151  detail.mapLayerId = mStagedRendererJob->currentLayerId();
1152  if ( const QgsMapLayer *layer = mLayout->project()->mapLayer( detail.mapLayerId ) )
1153  {
1154  if ( !detail.mapTheme.isEmpty() )
1155  detail.name = QStringLiteral( "%1 (%2): %3" ).arg( displayName(), detail.mapTheme, layer->name() );
1156  else
1157  detail.name = QStringLiteral( "%1: %2" ).arg( displayName(), layer->name() );
1158  }
1159  else
1160  {
1161  // might be an item based layer
1162  const QList<QgsLayoutItemMapOverview *> res = mOverviewStack->asList();
1163  for ( QgsLayoutItemMapOverview *item : res )
1164  {
1165  if ( !item || !item->enabled() || item->stackingPosition() == QgsLayoutItemMapItem::StackAboveMapLabels )
1166  continue;
1167 
1168  if ( item->mapLayer() && detail.mapLayerId == item->mapLayer()->id() )
1169  {
1170  if ( !detail.mapTheme.isEmpty() )
1171  detail.name = QStringLiteral( "%1 (%2): %3" ).arg( displayName(), detail.mapTheme, item->mapLayer()->name() );
1172  else
1173  detail.name = QStringLiteral( "%1: %2" ).arg( displayName(), item->mapLayer()->name() );
1174  break;
1175  }
1176  }
1177  }
1178  return detail;
1179  }
1180 
1182  detail.mapLayerId = mStagedRendererJob->currentLayerId();
1183  if ( const QgsMapLayer *layer = mLayout->project()->mapLayer( detail.mapLayerId ) )
1184  {
1185  if ( !detail.mapTheme.isEmpty() )
1186  detail.name = QStringLiteral( "%1 (%2): %3 (Labels)" ).arg( displayName(), detail.mapTheme, layer->name() );
1187  else
1188  detail.name = tr( "%1: %2 (Labels)" ).arg( displayName(), layer->name() );
1189  }
1190  else
1191  {
1192  if ( !detail.mapTheme.isEmpty() )
1193  detail.name = tr( "%1 (%2): Labels" ).arg( displayName(), detail.mapTheme );
1194  else
1195  detail.name = tr( "%1: Labels" ).arg( displayName() );
1196  }
1197  return detail;
1198 
1200  break;
1201  }
1202  }
1203  else
1204  {
1205  // we must be on the first layer, not having had a chance to create the render job yet
1206  const QList< QgsMapLayer * > layers = layersToRender();
1207  if ( !layers.isEmpty() )
1208  {
1209  const QgsMapLayer *layer = layers.constLast();
1210  if ( !detail.mapTheme.isEmpty() )
1211  detail.name = QStringLiteral( "%1 (%2): %3" ).arg( displayName(), detail.mapTheme, layer->name() );
1212  else
1213  detail.name = QStringLiteral( "%1: %2" ).arg( displayName(), layer->name() );
1214  detail.mapLayerId = layer->id();
1215  }
1216  }
1217  break;
1218 
1219  case Grid:
1220  detail.name = tr( "%1: Grids" ).arg( displayName() );
1221  return detail;
1222 
1223  case OverviewMapExtent:
1224  detail.name = tr( "%1: Overviews" ).arg( displayName() );
1225  return detail;
1226 
1227  case Frame:
1228  detail.name = tr( "%1: Frame" ).arg( displayName() );
1229  return detail;
1230 
1231  case SelectionBoxes:
1232  case End:
1233  case NotLayered:
1234  break;
1235  }
1236 
1237  return detail;
1238 }
1239 
1241 {
1244 }
1245 
1246 void QgsLayoutItemMap::drawMap( QPainter *painter, const QgsRectangle &extent, QSizeF size, double dpi )
1247 {
1248  if ( !painter )
1249  {
1250  return;
1251  }
1252  if ( qgsDoubleNear( size.width(), 0.0 ) || qgsDoubleNear( size.height(), 0.0 ) )
1253  {
1254  //don't attempt to draw if size is invalid
1255  return;
1256  }
1257 
1258  // render
1259  QgsMapSettings ms( mapSettings( extent, size, dpi, true ) );
1260  if ( shouldDrawPart( OverviewMapExtent ) )
1261  {
1262  ms.setLayers( mOverviewStack->modifyMapLayerList( ms.layers() ) );
1263  }
1264 
1265  QgsMapRendererCustomPainterJob job( ms, painter );
1266  // Render the map in this thread. This is done because of problems
1267  // with printing to printer on Windows (printing to PDF is fine though).
1268  // Raster images were not displayed - see #10599
1269  job.renderSynchronously();
1270 
1271  mRenderingErrors = job.errors();
1272 }
1273 
1274 void QgsLayoutItemMap::recreateCachedImageInBackground()
1275 {
1276  if ( mPainterJob )
1277  {
1278  disconnect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
1279  QgsMapRendererCustomPainterJob *oldJob = mPainterJob.release();
1280  QPainter *oldPainter = mPainter.release();
1281  QImage *oldImage = mCacheRenderingImage.release();
1282  connect( oldJob, &QgsMapRendererCustomPainterJob::finished, this, [oldPainter, oldJob, oldImage]
1283  {
1284  oldJob->deleteLater();
1285  delete oldPainter;
1286  delete oldImage;
1287  } );
1288  oldJob->cancelWithoutBlocking();
1289  }
1290  else
1291  {
1292  mCacheRenderingImage.reset( nullptr );
1293  emit backgroundTaskCountChanged( 1 );
1294  }
1295 
1296  Q_ASSERT( !mPainterJob );
1297  Q_ASSERT( !mPainter );
1298  Q_ASSERT( !mCacheRenderingImage );
1299 
1300  QgsRectangle ext = extent();
1301  double widthLayoutUnits = ext.width() * mapUnitsToLayoutUnits();
1302  double heightLayoutUnits = ext.height() * mapUnitsToLayoutUnits();
1303 
1304  int w = static_cast< int >( std::round( widthLayoutUnits * mPreviewScaleFactor ) );
1305  int h = static_cast< int >( std::round( heightLayoutUnits * mPreviewScaleFactor ) );
1306 
1307  // limit size of image for better performance
1308  if ( w > 5000 || h > 5000 )
1309  {
1310  if ( w > h )
1311  {
1312  w = 5000;
1313  h = static_cast< int>( std::round( w * heightLayoutUnits / widthLayoutUnits ) );
1314  }
1315  else
1316  {
1317  h = 5000;
1318  w = static_cast< int >( std::round( h * widthLayoutUnits / heightLayoutUnits ) );
1319  }
1320  }
1321 
1322  if ( w <= 0 || h <= 0 )
1323  return;
1324 
1325  mCacheRenderingImage.reset( new QImage( w, h, QImage::Format_ARGB32 ) );
1326 
1327  // set DPI of the image
1328  mCacheRenderingImage->setDotsPerMeterX( static_cast< int >( std::round( 1000 * w / widthLayoutUnits ) ) );
1329  mCacheRenderingImage->setDotsPerMeterY( static_cast< int >( std::round( 1000 * h / heightLayoutUnits ) ) );
1330 
1331  if ( hasBackground() )
1332  {
1333  //Initially fill image with specified background color. This ensures that layers with blend modes will
1334  //preview correctly
1335  mCacheRenderingImage->fill( backgroundColor().rgba() );
1336  }
1337  else
1338  {
1339  //no background, but start with empty fill to avoid artifacts
1340  mCacheRenderingImage->fill( QColor( 255, 255, 255, 0 ).rgba() );
1341  }
1342 
1343  mCacheInvalidated = false;
1344  mPainter.reset( new QPainter( mCacheRenderingImage.get() ) );
1345  QgsMapSettings settings( mapSettings( ext, QSizeF( w, h ), mCacheRenderingImage->logicalDpiX(), true ) );
1346 
1347  if ( shouldDrawPart( OverviewMapExtent ) )
1348  {
1349  settings.setLayers( mOverviewStack->modifyMapLayerList( settings.layers() ) );
1350  }
1351 
1352  mPainterJob.reset( new QgsMapRendererCustomPainterJob( settings, mPainter.get() ) );
1353  connect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
1354  mPainterJob->start();
1355 
1356  // from now on we can accept refresh requests again
1357  // this must be reset only after the job has been started, because
1358  // some providers (yes, it's you WCS and AMS!) during preparation
1359  // do network requests and start an internal event loop, which may
1360  // end up calling refresh() and would schedule another refresh,
1361  // deleting the one we have just started.
1362 
1363  // ^^ that comment was directly copied from a similar fix in QgsMapCanvas. And
1364  // with little surprise, both those providers are still badly behaved and causing
1365  // annoying bugs for us to deal with...
1366  mDrawingPreview = false;
1367 }
1368 
1369 QgsLayoutItemMap::MapItemFlags QgsLayoutItemMap::mapFlags() const
1370 {
1371  return mMapFlags;
1372 }
1373 
1374 void QgsLayoutItemMap::setMapFlags( QgsLayoutItemMap::MapItemFlags mapFlags )
1375 {
1376  mMapFlags = mapFlags;
1377 }
1378 
1379 QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, double dpi, bool includeLayerSettings ) const
1380 {
1381  QgsExpressionContext expressionContext = createExpressionContext();
1382  QgsCoordinateReferenceSystem renderCrs = crs();
1383 
1384  QgsMapSettings jobMapSettings;
1385  jobMapSettings.setDestinationCrs( renderCrs );
1386  jobMapSettings.setExtent( extent );
1387  jobMapSettings.setOutputSize( size.toSize() );
1388  jobMapSettings.setOutputDpi( dpi );
1389  jobMapSettings.setBackgroundColor( Qt::transparent );
1390  jobMapSettings.setRotation( mEvaluatedMapRotation );
1391  if ( mLayout )
1392  jobMapSettings.setEllipsoid( mLayout->project()->ellipsoid() );
1393 
1394  if ( includeLayerSettings )
1395  {
1396  //set layers to render
1397  QList<QgsMapLayer *> layers = layersToRender( &expressionContext );
1398  jobMapSettings.setLayers( layers );
1399  jobMapSettings.setLayerStyleOverrides( layerStyleOverridesToRender( expressionContext ) );
1400  }
1401 
1402  if ( !mLayout->renderContext().isPreviewRender() )
1403  {
1404  //if outputting layout, we disable optimisations like layer simplification by default, UNLESS the context specifically tells us to use them
1405  jobMapSettings.setFlag( QgsMapSettings::UseRenderingOptimization, mLayout->renderContext().simplifyMethod().simplifyHints() != QgsVectorSimplifyMethod::NoSimplification );
1406  jobMapSettings.setSimplifyMethod( mLayout->renderContext().simplifyMethod() );
1407  }
1408  else
1409  {
1410  // preview render - always use optimization
1411  jobMapSettings.setFlag( QgsMapSettings::UseRenderingOptimization, true );
1412  }
1413 
1414  jobMapSettings.setExpressionContext( expressionContext );
1415 
1416  // layout-specific overrides of flags
1417  jobMapSettings.setFlag( QgsMapSettings::ForceVectorOutput, true ); // force vector output (no caching of marker images etc.)
1418  jobMapSettings.setFlag( QgsMapSettings::Antialiasing, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
1419  jobMapSettings.setFlag( QgsMapSettings::DrawEditingInfo, false );
1420  jobMapSettings.setSelectionColor( mLayout->renderContext().selectionColor() );
1421  jobMapSettings.setFlag( QgsMapSettings::DrawSelection, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagDrawSelection );
1424  jobMapSettings.setTransformContext( mLayout->project()->transformContext() );
1425  jobMapSettings.setPathResolver( mLayout->project()->pathResolver() );
1426 
1427  QgsLabelingEngineSettings labelSettings = mLayout->project()->labelingEngineSettings();
1428 
1429  // override project "show partial labels" setting with this map's setting
1432  jobMapSettings.setLabelingEngineSettings( labelSettings );
1433 
1434  // override the default text render format inherited from the labeling engine settings using the layout's render context setting
1435  jobMapSettings.setTextRenderFormat( mLayout->renderContext().textRenderFormat() );
1436 
1437  if ( mEvaluatedLabelMargin.length() > 0 )
1438  {
1439  QPolygonF visiblePoly = jobMapSettings.visiblePolygon();
1440  visiblePoly.append( visiblePoly.at( 0 ) ); //close polygon
1441  const double layoutLabelMargin = mLayout->convertToLayoutUnits( mEvaluatedLabelMargin );
1442  const double layoutLabelMarginInMapUnits = layoutLabelMargin / rect().width() * jobMapSettings.extent().width();
1443  QgsGeometry mapBoundaryGeom = QgsGeometry::fromQPolygonF( visiblePoly );
1444  mapBoundaryGeom = mapBoundaryGeom.buffer( -layoutLabelMarginInMapUnits, 0 );
1445  jobMapSettings.setLabelBoundaryGeometry( mapBoundaryGeom );
1446  }
1447 
1448  if ( !mBlockingLabelItems.isEmpty() )
1449  {
1450  jobMapSettings.setLabelBlockingRegions( createLabelBlockingRegions( jobMapSettings ) );
1451  }
1452 
1453  for ( QgsRenderedFeatureHandlerInterface *handler : qgis::as_const( mRenderedFeatureHandlers ) )
1454  {
1455  jobMapSettings.addRenderedFeatureHandler( handler );
1456  }
1457 
1458  return jobMapSettings;
1459 }
1460 
1462 {
1463  assignFreeId();
1464 
1465  mBlockingLabelItems.clear();
1466  for ( const QString &uuid : qgis::as_const( mBlockingLabelItemUuids ) )
1467  {
1468  QgsLayoutItem *item = mLayout->itemByUuid( uuid, true );
1469  if ( item )
1470  {
1471  addLabelBlockingItem( item );
1472  }
1473  }
1474 
1475  mOverviewStack->finalizeRestoreFromXml();
1476  mGridStack->finalizeRestoreFromXml();
1477 }
1478 
1479 void QgsLayoutItemMap::setMoveContentPreviewOffset( double xOffset, double yOffset )
1480 {
1481  mXOffset = xOffset;
1482  mYOffset = yOffset;
1483 }
1484 
1486 {
1487  return mCurrentRectangle;
1488 }
1489 
1491 {
1493 
1494  //Can't utilize QgsExpressionContextUtils::mapSettingsScope as we don't always
1495  //have a QgsMapSettings object available when the context is required, so we manually
1496  //add the same variables here
1497  QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Map Settings" ) );
1498 
1499  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_id" ), id(), true ) );
1500  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_rotation" ), mMapRotation, true ) );
1501  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_scale" ), scale(), true ) );
1502 
1503  QgsRectangle currentExtent( extent() );
1504  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent" ), QVariant::fromValue( QgsGeometry::fromRect( currentExtent ) ), true ) );
1505  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_width" ), currentExtent.width(), true ) );
1506  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_height" ), currentExtent.height(), true ) );
1507  QgsGeometry centerPoint = QgsGeometry::fromPointXY( currentExtent.center() );
1508  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_center" ), QVariant::fromValue( centerPoint ), true ) );
1509 
1510  QgsCoordinateReferenceSystem mapCrs = crs();
1511  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs" ), mapCrs.authid(), true ) );
1512  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_definition" ), mapCrs.toProj(), true ) );
1513  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_description" ), mapCrs.description(), true ) );
1514  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_units" ), QgsUnitTypes::toString( mapCrs.mapUnits() ), true ) );
1515  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_acronym" ), mapCrs.projectionAcronym(), true ) );
1516  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_ellipsoid" ), mapCrs.ellipsoidAcronym(), true ) );
1517  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_proj4" ), mapCrs.toProj(), true ) );
1518  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_wkt" ), mapCrs.toWkt( QgsCoordinateReferenceSystem::WKT2_2018 ), true ) );
1519 
1520  QVariantList layersIds;
1521  QVariantList layers;
1522  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layer_ids" ), layersIds, true ) );
1523  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layers" ), layers, true ) );
1524 
1525  context.appendScope( scope );
1526 
1527  // The scope map_layer_ids and map_layers variables have been added to the context, only now we can
1528  // call layersToRender (just in case layersToRender relies on evaluating an expression which uses
1529  // other variables contained within the map settings scope
1530  const QList<QgsMapLayer *> layersInMap = layersToRender( &context );
1531 
1532  layersIds.reserve( layersInMap.count() );
1533  layers.reserve( layersInMap.count() );
1534  for ( QgsMapLayer *layer : layersInMap )
1535  {
1536  layersIds << layer->id();
1537  layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( layer ) );
1538  }
1539  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layer_ids" ), layersIds, true ) );
1540  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layers" ), layers, true ) );
1541 
1542  scope->addFunction( QStringLiteral( "is_layer_visible" ), new QgsExpressionContextUtils::GetLayerVisibility( layersInMap, scale() ) );
1543 
1544  // maybe one day we'll populate these with real values!
1545  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_start_time" ), QVariant(), true ) );
1546  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_end_time" ), QVariant(), true ) );
1547  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_interval" ), QVariant(), true ) );
1548 
1549  return context;
1550 }
1551 
1553 {
1554  double extentWidth = extent().width();
1555  if ( extentWidth <= 0 )
1556  {
1557  return 1;
1558  }
1559  return rect().width() / extentWidth;
1560 }
1561 
1563 {
1564  double dx = mXOffset;
1565  double dy = mYOffset;
1566  transformShift( dx, dy );
1567  QPolygonF poly = visibleExtentPolygon();
1568  poly.translate( -dx, -dy );
1569  return poly;
1570 }
1571 
1573 {
1574  if ( !mBlockingLabelItems.contains( item ) )
1575  mBlockingLabelItems.append( item );
1576 
1577  connect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemMap::invalidateCache, Qt::UniqueConnection );
1578 }
1579 
1581 {
1582  mBlockingLabelItems.removeAll( item );
1583  if ( item )
1585 }
1586 
1588 {
1589  return mBlockingLabelItems.contains( item );
1590 }
1591 
1593 {
1594  // NOTE: if visitEnter returns false it means "don't visit the item", not "abort all further visitations"
1596  return true;
1597 
1598  if ( mOverviewStack )
1599  {
1600  for ( int i = 0; i < mOverviewStack->size(); ++i )
1601  {
1602  if ( mOverviewStack->item( i )->accept( visitor ) )
1603  return false;
1604  }
1605  }
1606 
1607  if ( mGridStack )
1608  {
1609  for ( int i = 0; i < mGridStack->size(); ++i )
1610  {
1611  if ( mGridStack->item( i )->accept( visitor ) )
1612  return false;
1613  }
1614  }
1615 
1617  return false;
1618 
1619  return true;
1620 }
1621 
1623 {
1624  mRenderedFeatureHandlers.append( handler );
1625 }
1626 
1628 {
1629  mRenderedFeatureHandlers.removeAll( handler );
1630 }
1631 
1632 QPointF QgsLayoutItemMap::mapToItemCoords( QPointF mapCoords ) const
1633 {
1634  QPolygonF mapPoly = transformedMapPolygon();
1635  if ( mapPoly.empty() )
1636  {
1637  return QPointF( 0, 0 );
1638  }
1639 
1640  QgsRectangle tExtent = transformedExtent();
1641  QgsPointXY rotationPoint( ( tExtent.xMaximum() + tExtent.xMinimum() ) / 2.0, ( tExtent.yMaximum() + tExtent.yMinimum() ) / 2.0 );
1642  double dx = mapCoords.x() - rotationPoint.x();
1643  double dy = mapCoords.y() - rotationPoint.y();
1644  QgsLayoutUtils::rotate( -mEvaluatedMapRotation, dx, dy );
1645  QgsPointXY backRotatedCoords( rotationPoint.x() + dx, rotationPoint.y() + dy );
1646 
1647  QgsRectangle unrotatedExtent = transformedExtent();
1648  double xItem = rect().width() * ( backRotatedCoords.x() - unrotatedExtent.xMinimum() ) / unrotatedExtent.width();
1649  double yItem = rect().height() * ( 1 - ( backRotatedCoords.y() - unrotatedExtent.yMinimum() ) / unrotatedExtent.height() );
1650  return QPointF( xItem, yItem );
1651 }
1652 
1654 {
1656  QgsRectangle newExtent = mExtent;
1657  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
1658  {
1659  extent = newExtent;
1660  }
1661  else
1662  {
1663  QPolygonF poly;
1664  mapPolygon( newExtent, poly );
1665  QRectF bRect = poly.boundingRect();
1666  extent.setXMinimum( bRect.left() );
1667  extent.setXMaximum( bRect.right() );
1668  extent.setYMinimum( bRect.top() );
1669  extent.setYMaximum( bRect.bottom() );
1670  }
1671  return extent;
1672 }
1673 
1675 {
1676  if ( mDrawing )
1677  return;
1678 
1679  mCacheInvalidated = true;
1680  update();
1681 }
1682 
1684 {
1685  QRectF rectangle = rect();
1686  double frameExtension = frameEnabled() ? pen().widthF() / 2.0 : 0.0;
1687 
1688  double topExtension = 0.0;
1689  double rightExtension = 0.0;
1690  double bottomExtension = 0.0;
1691  double leftExtension = 0.0;
1692 
1693  if ( mGridStack )
1694  mGridStack->calculateMaxGridExtension( topExtension, rightExtension, bottomExtension, leftExtension );
1695 
1696  topExtension = std::max( topExtension, frameExtension );
1697  rightExtension = std::max( rightExtension, frameExtension );
1698  bottomExtension = std::max( bottomExtension, frameExtension );
1699  leftExtension = std::max( leftExtension, frameExtension );
1700 
1701  rectangle.setLeft( rectangle.left() - leftExtension );
1702  rectangle.setRight( rectangle.right() + rightExtension );
1703  rectangle.setTop( rectangle.top() - topExtension );
1704  rectangle.setBottom( rectangle.bottom() + bottomExtension );
1705  if ( rectangle != mCurrentRectangle )
1706  {
1707  prepareGeometryChange();
1708  mCurrentRectangle = rectangle;
1709  }
1710 }
1711 
1713 {
1715  if ( property == QgsLayoutObject::MapCrs || property == QgsLayoutObject::AllProperties )
1716  {
1717  bool ok;
1718  QString crsVar = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapCrs, context, QString(), &ok );
1719  if ( ok && QgsCoordinateReferenceSystem( crsVar ).isValid() )
1720  mCrs = QgsCoordinateReferenceSystem( crsVar );
1721  }
1722  //updates data defined properties and redraws item to match
1723  if ( property == QgsLayoutObject::MapRotation || property == QgsLayoutObject::MapScale ||
1724  property == QgsLayoutObject::MapXMin || property == QgsLayoutObject::MapYMin ||
1725  property == QgsLayoutObject::MapXMax || property == QgsLayoutObject::MapYMax ||
1726  property == QgsLayoutObject::MapAtlasMargin ||
1727  property == QgsLayoutObject::AllProperties )
1728  {
1729  QgsRectangle beforeExtent = mExtent;
1730  refreshMapExtents( &context );
1731  emit changed();
1732  if ( mExtent != beforeExtent )
1733  {
1734  emit extentChanged();
1735  }
1736  }
1737  if ( property == QgsLayoutObject::MapLabelMargin || property == QgsLayoutObject::AllProperties )
1738  {
1739  refreshLabelMargin( false );
1740  }
1741  if ( property == QgsLayoutObject::MapStylePreset || property == QgsLayoutObject::AllProperties )
1742  {
1743  const QString previousTheme = mLastEvaluatedThemeName.isEmpty() ? mFollowVisibilityPresetName : mLastEvaluatedThemeName;
1744  mLastEvaluatedThemeName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, mFollowVisibilityPresetName );
1745  if ( mLastEvaluatedThemeName != previousTheme )
1746  emit themeChanged( mLastEvaluatedThemeName );
1747  }
1748 
1749  //force redraw
1750  mCacheInvalidated = true;
1751 
1753 }
1754 
1755 void QgsLayoutItemMap::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
1756 {
1757  if ( !mLayers.isEmpty() || mLayerStyleOverrides.isEmpty() )
1758  {
1759  for ( QgsMapLayer *layer : layers )
1760  {
1761  mLayerStyleOverrides.remove( layer->id() );
1762  }
1763  _qgis_removeLayers( mLayers, layers );
1764  }
1765 }
1766 
1767 void QgsLayoutItemMap::painterJobFinished()
1768 {
1769  mPainter->end();
1770  mPainterJob.reset( nullptr );
1771  mPainter.reset( nullptr );
1772  mCacheFinalImage = std::move( mCacheRenderingImage );
1773  mLastRenderedImageOffsetX = 0;
1774  mLastRenderedImageOffsetY = 0;
1775  emit backgroundTaskCountChanged( 0 );
1776  update();
1777 }
1778 
1779 void QgsLayoutItemMap::shapeChanged()
1780 {
1781  // keep center as center
1782  QgsPointXY oldCenter = mExtent.center();
1783 
1784  double w = rect().width();
1785  double h = rect().height();
1786 
1787  // keep same width as before
1788  double newWidth = mExtent.width();
1789  // but scale height to match item's aspect ratio
1790  double newHeight = newWidth * h / w;
1791 
1792  mExtent = QgsRectangle::fromCenterAndSize( oldCenter, newWidth, newHeight );
1793 
1794  //recalculate data defined scale and extents
1795  refreshMapExtents();
1797  invalidateCache();
1798  emit changed();
1799  emit extentChanged();
1800 }
1801 
1802 void QgsLayoutItemMap::mapThemeChanged( const QString &theme )
1803 {
1804  if ( theme == mCachedLayerStyleOverridesPresetName )
1805  mCachedLayerStyleOverridesPresetName.clear(); // force cache regeneration at next redraw
1806 }
1807 
1808 void QgsLayoutItemMap::connectUpdateSlot()
1809 {
1810  //connect signal from layer registry to update in case of new or deleted layers
1811  QgsProject *project = mLayout->project();
1812  if ( project )
1813  {
1814  // handles updating the stored layer state BEFORE the layers are removed
1815  connect( project, static_cast < void ( QgsProject::* )( const QList<QgsMapLayer *>& layers ) > ( &QgsProject::layersWillBeRemoved ),
1816  this, &QgsLayoutItemMap::layersAboutToBeRemoved );
1817  // redraws the map AFTER layers are removed
1818  connect( project->layerTreeRoot(), &QgsLayerTree::layerOrderChanged, this, [ = ]
1819  {
1820  if ( layers().isEmpty() )
1821  {
1822  //using project layers, and layer order has changed
1823  invalidateCache();
1824  }
1825  } );
1826 
1827  connect( project, &QgsProject::crsChanged, this, [ = ]
1828  {
1829  if ( !mCrs.isValid() )
1830  {
1831  //using project CRS, which just changed....
1832  invalidateCache();
1833  }
1834  } );
1835 
1836  // If project colors change, we need to redraw the map, as layer symbols may rely on project colors
1837  connect( project, &QgsProject::projectColorsChanged, this, [ = ]
1838  {
1839  invalidateCache();
1840  } );
1841  }
1843  connect( &mLayout->renderContext(), &QgsLayoutRenderContext::predefinedScalesChanged, this, [ = ]
1844  {
1845  if ( mAtlasScalingMode == Predefined )
1846  updateAtlasFeature();
1847  } );
1848 
1849  connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsLayoutItemMap::mapThemeChanged );
1850 }
1851 
1853 {
1854  QPolygonF thisExtent = visibleExtentPolygon();
1855  QTransform mapTransform;
1856  QPolygonF thisRectPoly = QPolygonF( QRectF( 0, 0, rect().width(), rect().height() ) );
1857  //workaround QT Bug #21329
1858  thisRectPoly.pop_back();
1859  thisExtent.pop_back();
1860 
1861  QPolygonF thisItemPolyInLayout = mapToScene( thisRectPoly );
1862 
1863  //create transform from layout coordinates to map coordinates
1864  QTransform::quadToQuad( thisItemPolyInLayout, thisExtent, mapTransform );
1865  return mapTransform;
1866 }
1867 
1868 QList<QgsLabelBlockingRegion> QgsLayoutItemMap::createLabelBlockingRegions( const QgsMapSettings & ) const
1869 {
1870  const QTransform mapTransform = layoutToMapCoordsTransform();
1871  QList< QgsLabelBlockingRegion > blockers;
1872  blockers.reserve( mBlockingLabelItems.count() );
1873  for ( const auto &item : qgis::as_const( mBlockingLabelItems ) )
1874  {
1875  // invisible items don't block labels!
1876  if ( !item )
1877  continue;
1878 
1879  // layout items may be temporarily hidden during layered exports
1880  if ( item->property( "wasVisible" ).isValid() )
1881  {
1882  if ( !item->property( "wasVisible" ).toBool() )
1883  continue;
1884  }
1885  else if ( !item->isVisible() )
1886  continue;
1887 
1888  QPolygonF itemRectInMapCoordinates = mapTransform.map( item->mapToScene( item->rect() ) );
1889  itemRectInMapCoordinates.append( itemRectInMapCoordinates.at( 0 ) ); //close polygon
1890  QgsGeometry blockingRegion = QgsGeometry::fromQPolygonF( itemRectInMapCoordinates );
1891  blockers << QgsLabelBlockingRegion( blockingRegion );
1892  }
1893  return blockers;
1894 }
1895 
1897 {
1898  return mLabelMargin;
1899 }
1900 
1902 {
1903  mLabelMargin = margin;
1904  refreshLabelMargin( false );
1905 }
1906 
1907 void QgsLayoutItemMap::updateToolTip()
1908 {
1909  setToolTip( displayName() );
1910 }
1911 
1912 QString QgsLayoutItemMap::themeToRender( const QgsExpressionContext &context ) const
1913 {
1914  QString presetName;
1915 
1916  if ( mFollowVisibilityPreset )
1917  {
1918  presetName = mFollowVisibilityPresetName;
1919  // preset name can be overridden by data-defined one
1920  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
1921  }
1922  else if ( !mExportThemes.empty() && mExportThemeIt != mExportThemes.end() )
1923  presetName = *mExportThemeIt;
1924  return presetName;
1925 }
1926 
1927 QList<QgsMapLayer *> QgsLayoutItemMap::layersToRender( const QgsExpressionContext *context ) const
1928 {
1929  QgsExpressionContext scopedContext;
1930  if ( !context )
1931  scopedContext = createExpressionContext();
1932  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
1933 
1934  QList<QgsMapLayer *> renderLayers;
1935 
1936  QString presetName = themeToRender( *evalContext );
1937  if ( !presetName.isEmpty() )
1938  {
1939  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
1940  renderLayers = mLayout->project()->mapThemeCollection()->mapThemeVisibleLayers( presetName );
1941  else // fallback to using map canvas layers
1942  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
1943  }
1944  else if ( !layers().isEmpty() )
1945  {
1946  renderLayers = layers();
1947  }
1948  else
1949  {
1950  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
1951  }
1952 
1953  bool ok = false;
1954  QString ddLayers = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapLayers, *evalContext, QString(), &ok );
1955  if ( ok )
1956  {
1957  renderLayers.clear();
1958 
1959  const QStringList layerNames = ddLayers.split( '|' );
1960  //need to convert layer names to layer ids
1961  for ( const QString &name : layerNames )
1962  {
1963  const QList< QgsMapLayer * > matchingLayers = mLayout->project()->mapLayersByName( name );
1964  for ( QgsMapLayer *layer : matchingLayers )
1965  {
1966  renderLayers << layer;
1967  }
1968  }
1969  }
1970 
1971  //remove atlas coverage layer if required
1972  if ( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagHideCoverageLayer )
1973  {
1974  //hiding coverage layer
1975  int removeAt = renderLayers.indexOf( mLayout->reportContext().layer() );
1976  if ( removeAt != -1 )
1977  {
1978  renderLayers.removeAt( removeAt );
1979  }
1980  }
1981 
1982  // remove any invalid layers
1983  renderLayers.erase( std::remove_if( renderLayers.begin(), renderLayers.end(), []( QgsMapLayer * layer )
1984  {
1985  return !layer || !layer->isValid();
1986  } ), renderLayers.end() );
1987 
1988  return renderLayers;
1989 }
1990 
1991 QMap<QString, QString> QgsLayoutItemMap::layerStyleOverridesToRender( const QgsExpressionContext &context ) const
1992 {
1993  QString presetName = themeToRender( context );
1994  if ( !presetName.isEmpty() )
1995  {
1996  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
1997  {
1998  if ( presetName != mCachedLayerStyleOverridesPresetName )
1999  {
2000  // have to regenerate cache of style overrides
2001  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
2002  mCachedLayerStyleOverridesPresetName = presetName;
2003  }
2004 
2005  return mCachedPresetLayerStyleOverrides;
2006  }
2007  else
2008  return QMap<QString, QString>();
2009  }
2010  else if ( mFollowVisibilityPreset )
2011  {
2012  QString presetName = mFollowVisibilityPresetName;
2013  // data defined preset name?
2014  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
2015  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
2016  {
2017  if ( presetName.isEmpty() || presetName != mCachedLayerStyleOverridesPresetName )
2018  {
2019  // have to regenerate cache of style overrides
2020  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
2021  mCachedLayerStyleOverridesPresetName = presetName;
2022  }
2023 
2024  return mCachedPresetLayerStyleOverrides;
2025  }
2026  else
2027  return QMap<QString, QString>();
2028  }
2029  else if ( mKeepLayerStyles )
2030  {
2031  return mLayerStyleOverrides;
2032  }
2033  else
2034  {
2035  return QMap<QString, QString>();
2036  }
2037 }
2038 
2039 QgsRectangle QgsLayoutItemMap::transformedExtent() const
2040 {
2041  double dx = mXOffset;
2042  double dy = mYOffset;
2043  transformShift( dx, dy );
2044  return QgsRectangle( mExtent.xMinimum() - dx, mExtent.yMinimum() - dy, mExtent.xMaximum() - dx, mExtent.yMaximum() - dy );
2045 }
2046 
2047 void QgsLayoutItemMap::mapPolygon( const QgsRectangle &extent, QPolygonF &poly ) const
2048 {
2049  poly.clear();
2050  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
2051  {
2052  poly << QPointF( extent.xMinimum(), extent.yMaximum() );
2053  poly << QPointF( extent.xMaximum(), extent.yMaximum() );
2054  poly << QPointF( extent.xMaximum(), extent.yMinimum() );
2055  poly << QPointF( extent.xMinimum(), extent.yMinimum() );
2056  //ensure polygon is closed by readding first point
2057  poly << QPointF( poly.at( 0 ) );
2058  return;
2059  }
2060 
2061  //there is rotation
2062  QgsPointXY rotationPoint( ( extent.xMaximum() + extent.xMinimum() ) / 2.0, ( extent.yMaximum() + extent.yMinimum() ) / 2.0 );
2063  double dx, dy; //x-, y- shift from rotation point to corner point
2064 
2065  //top left point
2066  dx = rotationPoint.x() - extent.xMinimum();
2067  dy = rotationPoint.y() - extent.yMaximum();
2068  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2069  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2070 
2071  //top right point
2072  dx = rotationPoint.x() - extent.xMaximum();
2073  dy = rotationPoint.y() - extent.yMaximum();
2074  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2075  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2076 
2077  //bottom right point
2078  dx = rotationPoint.x() - extent.xMaximum();
2079  dy = rotationPoint.y() - extent.yMinimum();
2080  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2081  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2082 
2083  //bottom left point
2084  dx = rotationPoint.x() - extent.xMinimum();
2085  dy = rotationPoint.y() - extent.yMinimum();
2086  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2087  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2088 
2089  //ensure polygon is closed by readding first point
2090  poly << QPointF( poly.at( 0 ) );
2091 }
2092 
2093 void QgsLayoutItemMap::transformShift( double &xShift, double &yShift ) const
2094 {
2095  double mmToMapUnits = 1.0 / mapUnitsToLayoutUnits();
2096  double dxScaled = xShift * mmToMapUnits;
2097  double dyScaled = - yShift * mmToMapUnits;
2098 
2099  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dxScaled, dyScaled );
2100 
2101  xShift = dxScaled;
2102  yShift = dyScaled;
2103 }
2104 
2105 void QgsLayoutItemMap::drawAnnotations( QPainter *painter )
2106 {
2107  if ( !mLayout || !mLayout->project() || !mDrawAnnotations )
2108  {
2109  return;
2110  }
2111 
2112  const QList< QgsAnnotation * > annotations = mLayout->project()->annotationManager()->annotations();
2113  if ( annotations.isEmpty() )
2114  return;
2115 
2117  rc.setForceVectorOutput( true );
2119  QList< QgsMapLayer * > layers = layersToRender( &rc.expressionContext() );
2120 
2121  for ( QgsAnnotation *annotation : annotations )
2122  {
2123  if ( !annotation || !annotation->isVisible() )
2124  {
2125  continue;
2126  }
2127  if ( annotation->mapLayer() && !layers.contains( annotation->mapLayer() ) )
2128  continue;
2129 
2130  drawAnnotation( annotation, rc );
2131  }
2132 }
2133 
2134 void QgsLayoutItemMap::drawAnnotation( const QgsAnnotation *annotation, QgsRenderContext &context )
2135 {
2136  if ( !annotation || !annotation->isVisible() || !context.painter() || !context.painter()->device() )
2137  {
2138  return;
2139  }
2140 
2141  context.painter()->save();
2142  context.painter()->setRenderHint( QPainter::Antialiasing, context.flags() & QgsRenderContext::Antialiasing );
2143 
2144  double itemX, itemY;
2145  if ( annotation->hasFixedMapPosition() )
2146  {
2147  QPointF mapPos = layoutMapPosForItem( annotation );
2148  itemX = mapPos.x();
2149  itemY = mapPos.y();
2150  }
2151  else
2152  {
2153  itemX = annotation->relativePosition().x() * rect().width();
2154  itemY = annotation->relativePosition().y() * rect().height();
2155  }
2156  context.painter()->translate( itemX, itemY );
2157 
2158  //setup painter scaling to dots so that symbology is drawn to scale
2159  double dotsPerMM = context.painter()->device()->logicalDpiX() / 25.4;
2160  context.painter()->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
2161 
2162  annotation->render( context );
2163  context.painter()->restore();
2164 }
2165 
2166 QPointF QgsLayoutItemMap::layoutMapPosForItem( const QgsAnnotation *annotation ) const
2167 {
2168  if ( !annotation )
2169  return QPointF( 0, 0 );
2170 
2171  double mapX = 0.0;
2172  double mapY = 0.0;
2173 
2174  mapX = annotation->mapPosition().x();
2175  mapY = annotation->mapPosition().y();
2176  QgsCoordinateReferenceSystem annotationCrs = annotation->mapPositionCrs();
2177 
2178  if ( annotationCrs != crs() )
2179  {
2180  //need to reproject
2181  QgsCoordinateTransform t( annotationCrs, crs(), mLayout->project() );
2182  double z = 0.0;
2183  try
2184  {
2185  t.transformInPlace( mapX, mapY, z );
2186  }
2187  catch ( const QgsCsException & )
2188  {
2189  }
2190  }
2191 
2192  return mapToItemCoords( QPointF( mapX, mapY ) );
2193 }
2194 
2195 void QgsLayoutItemMap::drawMapFrame( QPainter *p )
2196 {
2197  if ( frameEnabled() && p )
2198  {
2199  p->save();
2200  p->setPen( pen() );
2201  p->setBrush( Qt::NoBrush );
2202  p->setRenderHint( QPainter::Antialiasing, true );
2203  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
2204  p->restore();
2205  }
2206 }
2207 
2208 void QgsLayoutItemMap::drawMapBackground( QPainter *p )
2209 {
2210  if ( hasBackground() && p )
2211  {
2212  p->save();
2213  p->setBrush( brush() );//this causes a problem in atlas generation
2214  p->setPen( Qt::NoPen );
2215  p->setRenderHint( QPainter::Antialiasing, true );
2216  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
2217  p->restore();
2218  }
2219 }
2220 
2221 bool QgsLayoutItemMap::shouldDrawPart( QgsLayoutItemMap::PartType part ) const
2222 {
2223  if ( mCurrentExportPart == NotLayered )
2224  {
2225  //all parts of the map are visible
2226  return true;
2227  }
2228 
2229  switch ( part )
2230  {
2231  case NotLayered:
2232  return true;
2233 
2234  case Start:
2235  return false;
2236 
2237  case Background:
2238  return mCurrentExportPart == Background && hasBackground();
2239 
2240  case Layer:
2241  return mCurrentExportPart == Layer;
2242 
2243  case Grid:
2244  return mCurrentExportPart == Grid && mGridStack->hasEnabledItems();
2245 
2246  case OverviewMapExtent:
2247  return mCurrentExportPart == OverviewMapExtent && mOverviewStack->hasEnabledItems();
2248 
2249  case Frame:
2250  return mCurrentExportPart == Frame && frameEnabled();
2251 
2252  case SelectionBoxes:
2253  return mCurrentExportPart == SelectionBoxes && isSelected();
2254 
2255  case End:
2256  return false;
2257  }
2258 
2259  return false;
2260 }
2261 
2262 void QgsLayoutItemMap::refreshMapExtents( const QgsExpressionContext *context )
2263 {
2264  QgsExpressionContext scopedContext;
2265  if ( !context )
2266  scopedContext = createExpressionContext();
2267 
2268  bool ok = false;
2269  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
2270 
2271 
2272  //data defined map extents set?
2273  QgsRectangle newExtent = extent();
2274  bool useDdXMin = false;
2275  bool useDdXMax = false;
2276  bool useDdYMin = false;
2277  bool useDdYMax = false;
2278  double minXD = 0;
2279  double minYD = 0;
2280  double maxXD = 0;
2281  double maxYD = 0;
2282 
2283  minXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMin, *evalContext, 0.0, &ok );
2284  if ( ok )
2285  {
2286  useDdXMin = true;
2287  newExtent.setXMinimum( minXD );
2288  }
2289  minYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMin, *evalContext, 0.0, &ok );
2290  if ( ok )
2291  {
2292  useDdYMin = true;
2293  newExtent.setYMinimum( minYD );
2294  }
2295  maxXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMax, *evalContext, 0.0, &ok );
2296  if ( ok )
2297  {
2298  useDdXMax = true;
2299  newExtent.setXMaximum( maxXD );
2300  }
2301  maxYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMax, *evalContext, 0.0, &ok );
2302  if ( ok )
2303  {
2304  useDdYMax = true;
2305  newExtent.setYMaximum( maxYD );
2306  }
2307 
2308  if ( newExtent != mExtent )
2309  {
2310  //calculate new extents to fit data defined extents
2311 
2312  //Make sure the width/height ratio is the same as in current map extent.
2313  //This is to keep the map item frame and the page layout fixed
2314  double currentWidthHeightRatio = mExtent.width() / mExtent.height();
2315  double newWidthHeightRatio = newExtent.width() / newExtent.height();
2316 
2317  if ( currentWidthHeightRatio < newWidthHeightRatio )
2318  {
2319  //enlarge height of new extent, ensuring the map center stays the same
2320  double newHeight = newExtent.width() / currentWidthHeightRatio;
2321  double deltaHeight = newHeight - newExtent.height();
2322  newExtent.setYMinimum( newExtent.yMinimum() - deltaHeight / 2 );
2323  newExtent.setYMaximum( newExtent.yMaximum() + deltaHeight / 2 );
2324  }
2325  else
2326  {
2327  //enlarge width of new extent, ensuring the map center stays the same
2328  double newWidth = currentWidthHeightRatio * newExtent.height();
2329  double deltaWidth = newWidth - newExtent.width();
2330  newExtent.setXMinimum( newExtent.xMinimum() - deltaWidth / 2 );
2331  newExtent.setXMaximum( newExtent.xMaximum() + deltaWidth / 2 );
2332  }
2333 
2334  mExtent = newExtent;
2335  }
2336 
2337  //now refresh scale, as this potentially overrides extents
2338 
2339  //data defined map scale set?
2340  double scaleD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapScale, *evalContext, 0.0, &ok );
2341  if ( ok )
2342  {
2343  setScale( scaleD, false );
2344  newExtent = mExtent;
2345  }
2346 
2347  if ( useDdXMax || useDdXMin || useDdYMax || useDdYMin )
2348  {
2349  //if only one of min/max was set for either x or y, then make sure our extent is locked on that value
2350  //as we can do this without altering the scale
2351  if ( useDdXMin && !useDdXMax )
2352  {
2353  double xMax = mExtent.xMaximum() - ( mExtent.xMinimum() - minXD );
2354  newExtent.setXMinimum( minXD );
2355  newExtent.setXMaximum( xMax );
2356  }
2357  else if ( !useDdXMin && useDdXMax )
2358  {
2359  double xMin = mExtent.xMinimum() - ( mExtent.xMaximum() - maxXD );
2360  newExtent.setXMinimum( xMin );
2361  newExtent.setXMaximum( maxXD );
2362  }
2363  if ( useDdYMin && !useDdYMax )
2364  {
2365  double yMax = mExtent.yMaximum() - ( mExtent.yMinimum() - minYD );
2366  newExtent.setYMinimum( minYD );
2367  newExtent.setYMaximum( yMax );
2368  }
2369  else if ( !useDdYMin && useDdYMax )
2370  {
2371  double yMin = mExtent.yMinimum() - ( mExtent.yMaximum() - maxYD );
2372  newExtent.setYMinimum( yMin );
2373  newExtent.setYMaximum( maxYD );
2374  }
2375 
2376  if ( newExtent != mExtent )
2377  {
2378  mExtent = newExtent;
2379  }
2380  }
2381 
2382  //lastly, map rotation overrides all
2383  double mapRotation = mMapRotation;
2384 
2385  //data defined map rotation set?
2386  mapRotation = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapRotation, *evalContext, mapRotation );
2387 
2388  if ( !qgsDoubleNear( mEvaluatedMapRotation, mapRotation ) )
2389  {
2390  mEvaluatedMapRotation = mapRotation;
2391  emit mapRotationChanged( mapRotation );
2392  }
2393 }
2394 
2395 void QgsLayoutItemMap::refreshLabelMargin( bool updateItem )
2396 {
2397  //data defined label margin set?
2399  mEvaluatedLabelMargin.setLength( labelMargin );
2400  mEvaluatedLabelMargin.setUnits( mLabelMargin.units() );
2401 
2402  if ( updateItem )
2403  {
2404  update();
2405  }
2406 }
2407 
2408 void QgsLayoutItemMap::updateAtlasFeature()
2409 {
2410  if ( !atlasDriven() || !mLayout->reportContext().layer() )
2411  return; // nothing to do
2412 
2413  QgsRectangle bounds = computeAtlasRectangle();
2414  if ( bounds.isNull() )
2415  return;
2416 
2417  double xa1 = bounds.xMinimum();
2418  double xa2 = bounds.xMaximum();
2419  double ya1 = bounds.yMinimum();
2420  double ya2 = bounds.yMaximum();
2421  QgsRectangle newExtent = bounds;
2422  QgsRectangle originalExtent = mExtent;
2423 
2424  //sanity check - only allow fixed scale mode for point layers
2425  bool isPointLayer = QgsWkbTypes::geometryType( mLayout->reportContext().layer()->wkbType() ) == QgsWkbTypes::PointGeometry;
2426 
2427  if ( mAtlasScalingMode == Fixed || mAtlasScalingMode == Predefined || isPointLayer )
2428  {
2429  QgsScaleCalculator calc;
2430  calc.setMapUnits( crs().mapUnits() );
2431  calc.setDpi( 25.4 );
2432  double originalScale = calc.calculate( originalExtent, rect().width() );
2433  double geomCenterX = ( xa1 + xa2 ) / 2.0;
2434  double geomCenterY = ( ya1 + ya2 ) / 2.0;
2435  QVector<qreal> scales;
2437  if ( !mLayout->reportContext().predefinedScales().empty() ) // remove when deprecated method is removed
2438  scales = mLayout->reportContext().predefinedScales();
2439  else
2440  scales = mLayout->renderContext().predefinedScales();
2442  if ( mAtlasScalingMode == Fixed || isPointLayer || scales.isEmpty() )
2443  {
2444  // only translate, keep the original scale (i.e. width x height)
2445  double xMin = geomCenterX - originalExtent.width() / 2.0;
2446  double yMin = geomCenterY - originalExtent.height() / 2.0;
2447  newExtent = QgsRectangle( xMin,
2448  yMin,
2449  xMin + originalExtent.width(),
2450  yMin + originalExtent.height() );
2451 
2452  //scale newExtent to match original scale of map
2453  //this is required for geographic coordinate systems, where the scale varies by extent
2454  double newScale = calc.calculate( newExtent, rect().width() );
2455  newExtent.scale( originalScale / newScale );
2456  }
2457  else if ( mAtlasScalingMode == Predefined )
2458  {
2459  // choose one of the predefined scales
2460  double newWidth = originalExtent.width();
2461  double newHeight = originalExtent.height();
2462  for ( int i = 0; i < scales.size(); i++ )
2463  {
2464  double ratio = scales[i] / originalScale;
2465  newWidth = originalExtent.width() * ratio;
2466  newHeight = originalExtent.height() * ratio;
2467 
2468  // compute new extent, centered on feature
2469  double xMin = geomCenterX - newWidth / 2.0;
2470  double yMin = geomCenterY - newHeight / 2.0;
2471  newExtent = QgsRectangle( xMin,
2472  yMin,
2473  xMin + newWidth,
2474  yMin + newHeight );
2475 
2476  //scale newExtent to match desired map scale
2477  //this is required for geographic coordinate systems, where the scale varies by extent
2478  double newScale = calc.calculate( newExtent, rect().width() );
2479  newExtent.scale( scales[i] / newScale );
2480 
2481  if ( ( newExtent.width() >= bounds.width() ) && ( newExtent.height() >= bounds.height() ) )
2482  {
2483  // this is the smallest extent that embeds the feature, stop here
2484  break;
2485  }
2486  }
2487  }
2488  }
2489  else if ( mAtlasScalingMode == Auto )
2490  {
2491  // auto scale
2492 
2493  double geomRatio = bounds.width() / bounds.height();
2494  double mapRatio = originalExtent.width() / originalExtent.height();
2495 
2496  // geometry height is too big
2497  if ( geomRatio < mapRatio )
2498  {
2499  // extent the bbox's width
2500  double adjWidth = ( mapRatio * bounds.height() - bounds.width() ) / 2.0;
2501  xa1 -= adjWidth;
2502  xa2 += adjWidth;
2503  }
2504  // geometry width is too big
2505  else if ( geomRatio > mapRatio )
2506  {
2507  // extent the bbox's height
2508  double adjHeight = ( bounds.width() / mapRatio - bounds.height() ) / 2.0;
2509  ya1 -= adjHeight;
2510  ya2 += adjHeight;
2511  }
2512  newExtent = QgsRectangle( xa1, ya1, xa2, ya2 );
2513 
2514  const double evaluatedAtlasMargin = atlasMargin();
2515  if ( evaluatedAtlasMargin > 0.0 )
2516  {
2517  newExtent.scale( 1 + evaluatedAtlasMargin );
2518  }
2519  }
2520 
2521  // set the new extent (and render)
2522  setExtent( newExtent );
2523  emit preparedForAtlas();
2524 }
2525 
2526 QgsRectangle QgsLayoutItemMap::computeAtlasRectangle()
2527 {
2528  // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
2529  // We have to transform the geometry to the destination CRS and ask for the bounding box
2530  // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
2531  QgsGeometry g = mLayout->reportContext().currentGeometry( crs() );
2532  // Rotating the geometry, so the bounding box is correct wrt map rotation
2533  if ( mEvaluatedMapRotation != 0.0 )
2534  {
2535  QgsPointXY prevCenter = g.boundingBox().center();
2536  g.rotate( mEvaluatedMapRotation, g.boundingBox().center() );
2537  // Rotation center will be still the bounding box center of an unrotated geometry.
2538  // Which means, if the center of bbox moves after rotation, the viewport will
2539  // also be offset, and part of the geometry will fall out of bounds.
2540  // Here we compensate for that roughly: by extending the rotated bounds
2541  // so that its center is the same as the original.
2542  QgsRectangle bounds = g.boundingBox();
2543  double dx = std::max( std::abs( prevCenter.x() - bounds.xMinimum() ),
2544  std::abs( prevCenter.x() - bounds.xMaximum() ) );
2545  double dy = std::max( std::abs( prevCenter.y() - bounds.yMinimum() ),
2546  std::abs( prevCenter.y() - bounds.yMaximum() ) );
2547  QgsPointXY center = g.boundingBox().center();
2548  return QgsRectangle( center.x() - dx, center.y() - dy,
2549  center.x() + dx, center.y() + dy );
2550  }
2551  else
2552  {
2553  return g.boundingBox();
2554  }
2555 }
2556 
2557 void QgsLayoutItemMap::createStagedRenderJob( const QgsRectangle &extent, const QSizeF size, double dpi )
2558 {
2559  QgsMapSettings settings = mapSettings( extent, size, dpi, true );
2560  settings.setLayers( mOverviewStack->modifyMapLayerList( settings.layers() ) );
2561 
2562  mStagedRendererJob = qgis::make_unique< QgsMapRendererStagedRenderJob >( settings,
2565  : QgsMapRendererStagedRenderJob::Flags( nullptr ) );
2566  mStagedRendererJob->start();
2567 }
2568 
2569 
QIcon icon() const override
Returns the item&#39;s icon.
QString displayName() const override
Gets item display name.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
The class is used as a container of context for various read/write operations on other objects...
QgsUnitTypes::LayoutUnit units() const
Returns the units for the measurement.
static double scaleFactorFromItemStyle(const QStyleOptionGraphicsItem *style)
Extracts the scale factor from an item style.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void draw(QgsLayoutItemRenderContext &context) override
Draws the item&#39;s contents using the specified item render context.
void finished()
emitted when asynchronous rendering is finished (or canceled).
Single variable definition for use within a QgsExpressionContextScope.
void addLabelBlockingItem(QgsLayoutItem *item)
Sets the specified layout item as a "label blocking item" for this map.
QTransform layoutToMapCoordsTransform() const
Creates a transform from layout coordinates to map coordinates.
void setFollowVisibilityPresetName(const QString &name)
Sets preset name for map rendering.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
sets destination coordinate reference system
A rectangle specified with double values.
Definition: qgsrectangle.h:41
Base class for all map layer types.
Definition: qgsmaplayer.h:81
void setExtent(const QgsRectangle &rect, bool magnified=true)
Set coordinates of the rectangle which should be rendered.
Job implementation that renders everything sequentially using a custom painter.
bool isVisible() const
Returns true if the annotation is visible and should be rendered.
Definition: qgsannotation.h:89
QPointF mapToItemCoords(QPointF mapCoords) const
Transforms map coordinates to item coordinates (considering rotation and move offset) ...
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
bool containsAdvancedEffects() const override
Returns true if the item contains contents with blend modes or transparency effects which can only be...
Base class for graphical items within a QgsLayout.
An individual overview which is drawn above the map content in a QgsLayoutItemMap, and shows the extent of another QgsLayoutItemMap.
virtual bool containsAdvancedEffects() const
Returns true if the item contains contents with blend modes or transparency effects which can only be...
static Q_INVOKABLE QString toString(QgsUnitTypes::DistanceUnit unit)
Returns a translated string representing a distance unit.
QgsLayoutItemMap(QgsLayout *layout)
Constructor for QgsLayoutItemMap, with the specified parent layout.
void moveContent(double dx, double dy) override
Moves the content of the item, by a specified dx and dy in layout units.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double...
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:135
int type() const override
Use antialiasing while drawing.
QString xmlData() const
Returns XML content of the style.
void startLayeredExport() override
Starts a multi-layer export operation.
void removeRenderedFeatureHandler(QgsRenderedFeatureHandlerInterface *handler)
Removes a previously added rendered feature handler.
Item contains multiple sublayers which must be individually exported.
QString toProj() const
Returns a Proj string representation of this CRS.
void readXml(const QDomElement &styleElement)
Read style configuration (for project file reading)
void addFunction(const QString &name, QgsScopedExpressionFunction *function)
Adds a function to the scope.
Layer and style map theme.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
TYPE * resolveWeakly(const QgsProject *project, MatchType matchType=MatchType::All)
Resolves the map layer by attempting to find a matching layer in a project using a weak match...
void setExtent(const QgsRectangle &extent)
Sets a new extent for the map.
double y
Definition: qgspointxy.h:48
void layerOrderChanged()
Emitted when the layer order has changed.
An item which is drawn inside a QgsLayoutItemMap, e.g., a grid or map overview.
A class to represent a 2D point.
Definition: qgspointxy.h:43
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:235
QgsMapSettings mapSettings(const QgsRectangle &extent, QSizeF size, double dpi, bool includeLayerSettings) const
Returns map settings that will be used for drawing of the map.
void extentChanged()
Emitted when the map&#39;s extent changes.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
OperationResult rotate(double rotation, const QgsPointXY &center)
Rotate this geometry around the Z axis.
static QgsLayoutItemMap * create(QgsLayout *layout)
Returns a new map item for the specified layout.
Whether to use also label candidates that are partially outside of the map view.
bool frameEnabled() const
Returns true if the item includes a frame.
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
void setOutputDpi(double dpi)
Sets DPI used for conversion between real world units (e.g. mm) and pixels.
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:731
void crsChanged()
Emitted when the CRS of the project has changed.
void setDpi(double dpi)
Sets the dpi (dots per inch) for the output resolution, to be used in scale calculations.
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
void zoomContent(double factor, QPointF point) override
Zooms content of item.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:122
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
virtual void setFrameStrokeWidth(QgsLayoutMeasurement width)
Sets the frame stroke width.
static const QStringList containsAdvancedEffects(const QgsMapSettings &mapSettings)
Checks whether any of the layers attached to a map settings object contain advanced effects...
QgsGeometry buffer(double distance, int segments) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
void projectColorsChanged()
Emitted whenever the project&#39;s color scheme has been changed.
bool drawAnnotations() const
Returns whether annotations are drawn within the map.
Flags flags() const
Returns combination of flags used for rendering.
void invalidateCache() override
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Set map of map layer style overrides (key: layer ID, value: style name) where a different style shoul...
void preparedForAtlas()
Emitted when the map has been prepared for atlas rendering, just before actual rendering.
QString mapTheme
Associated map theme, or an empty string if this export layer does not need to be associated with a m...
QList< QgsMapLayer * > layers() const
Gets list of layers for map rendering The layers are stored in the reverse order of how they are rend...
An interface for classes which can visit style entity (e.g.
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
Definition: qgsmaplayer.h:1669
Enable layer opacity and blending effects.
bool hasFixedMapPosition
Definition: qgsannotation.h:68
Individual item in a print layout.
QPolygonF transformedMapPolygon() const
Returns extent that considers rotation and shift with mOffsetX / mOffsetY.
friend class QgsLayoutItemMapOverview
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
virtual void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties)
Refreshes a data defined property for the item by reevaluating the property&#39;s value and redrawing the...
QgsLayoutItem::ExportLayerDetail exportLayerDetails() const override
Returns the details for the specified current export layer.
QPointF relativePosition() const
Returns the relative position of the annotation, if it is not attached to a fixed map position...
Map extent x minimum.
Vector graphics should not be cached and drawn as raster images.
void setFlag(Flag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
Abstract base class for annotation items which are drawn over a map.
Definition: qgsannotation.h:49
void setFrameStrokeWidth(QgsLayoutMeasurement width) override
Sets the frame stroke width.
The QgsMapSettings class contains configuration for rendering of the map.
PropertyValueType
Specifies whether the value returned by a function should be the original, user set value...
void readFromLayer(QgsMapLayer *layer)
Store layer&#39;s active style information in the instance.
void storeCurrentLayerStyles()
Stores the current project layer styles into style overrides.
void sizePositionChanged()
Emitted when the item&#39;s size or position changes.
Stores style information (renderer, opacity, labeling, diagrams etc.) applicable to a map layer...
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
QString name
User-friendly name for the export layer.
No simplification can be applied.
void mapThemeChanged(const QString &theme)
Emitted when a map theme changes definition.
static QgsRectangle fromCenterAndSize(QgsPointXY center, double width, double height)
Creates a new rectangle, given the specified center point and width and height.
QgsRectangle extent() const
Returns geographical coordinates of the rectangle that should be rendered.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
QList< QgsMapLayer * > layers() const
Returns the stored layer set.
QgsRectangle extent() const
Returns the current map extent.
Layout graphical items for displaying a map.
void setOutputSize(QSize size)
Sets the size of the resulting map image.
QString layerId
Original layer ID.
void backgroundTaskCountChanged(int count)
Emitted whenever the number of background tasks an item is executing changes.
QgsPropertyCollection mDataDefinedProperties
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
const QgsLayout * layout() const
Returns the layout the object is attached to.
Whether to render unplaced labels as an indicator/warning for users.
void setTextRenderFormat(QgsRenderContext::TextRenderFormat format)
Sets the text render format, which dictates how text is rendered (e.g.
#define FALLTHROUGH
Definition: qgis.h:763
Map extent x maximum.
QgsMapThemeCollection mapThemeCollection
Definition: qgsproject.h:101
void setLength(const double length)
Sets the length of the measurement.
void setLayers(const QList< QgsMapLayer *> &layers)
Sets the stored layers set.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
bool requiresRasterization() const override
Returns true if the item is drawn in such a way that forces the whole layout to be rasterized when ex...
bool hasBackground() const
Returns true if the item has a background.
virtual bool visitExit(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor stops visiting a node.
Whether vector selections should be shown in the rendered map.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:275
~QgsLayoutItemMap() override
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:140
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item&#39;s position and size to match the passed rect in layout coordinates...
double atlasMargin(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue)
Returns the margin size (percentage) used when the map is in atlas mode.
static QgsRenderContext createRenderContextForMap(QgsLayoutItemMap *map, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout map and painter destination.
QColor backgroundColor() const
Returns the background color for this item.
An interface for classes which provider custom handlers for features rendered as part of a map render...
void refresh() override
Enable anti-aliasing for map rendering.
StackingPosition stackingPosition() const
Returns the item&#39;s stacking position, which specifies where the in the map&#39;s stack the item should be...
static GeometryType geometryType(Type type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:812
void render(QgsRenderContext &context) const
Renders the annotation to a target render context.
double mapUnitsToLayoutUnits() const
Returns the conversion factor from map units to layout units.
QPointer< QgsLayout > mLayout
void setMapUnits(QgsUnitTypes::DistanceUnit mapUnits)
Set the map units.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
bool enabled() const
Returns whether the item will be drawn.
Contains information relating to a node (i.e.
QString description() const
Returns the descriptive name of the CRS, e.g., "WGS 84" or "GDA 94 / Vicgrid94".
bool accept(QgsStyleEntityVisitorInterface *visitor) const override
Accepts the specified style entity visitor, causing it to visit all style entities associated with th...
void setSimplifyMethod(const QgsVectorSimplifyMethod &method)
Sets the simplification setting to use when rendering vector layers.
Whether to draw labels which are partially outside of the map view.
void setMoveContentPreviewOffset(double dx, double dy) override
Sets temporary offset for the item, by a specified dx and dy in layout units.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:92
void setLabelBoundaryGeometry(const QgsGeometry &boundary)
Sets the label boundary geometry, which restricts where in the rendered map labels are permitted to b...
QgsLayoutItemMap::MapItemFlags mapFlags() const
Returns the map item&#39;s flags, which control how the map content is drawn.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the map&#39;s preset crs (coordinate reference system).
QString id() const
Returns the item&#39;s ID name.
QgsLayoutItem::Flags itemFlags() const override
Returns the item&#39;s flags, which indicate how the item behaves.
QgsRectangle requestedExtent() const
Calculates the extent to request and the yShift of the top-left point in case of rotation.
QList< QgsMapLayer * > layersToRender(const QgsExpressionContext *context=nullptr) const
Returns a list of the layers which will be rendered within this map item, considering any locked laye...
Label blocking region (in map coordinates and CRS).
Single scope for storing variables and functions for use within a QgsExpressionContext.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
virtual bool requiresRasterization() const
Returns true if the item is drawn in such a way that forces the whole layout to be rasterized when ex...
static void rotate(double angle, double &x, double &y)
Rotates a point / vector around the origin.
If set, then raster layers will not be drawn as separate tiles. This may improve the appearance in ex...
void predefinedScalesChanged()
Emitted when the list of predefined scales changes.
Enable drawing of vertex markers for layers in editing mode.
The extent is adjusted so that each feature is fully visible.
An individual grid which is drawn above the map content in a QgsLayoutItemMap.
double x
Definition: qgspointxy.h:47
QPolygonF visibleExtentPolygon() const
Returns a polygon representing the current visible map extent, considering map extents and rotation...
void mapRotationChanged(double newRotation)
Emitted when the map&#39;s rotation changes.
Use antialiasing when drawing items.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the stored overrides of styles for layers.
Full WKT2 string, conforming to ISO 19162:2018 / OGC 18-010, with all possible nodes and new keyword ...
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
void setRotation(double rotation)
Sets the rotation of the resulting map image, in degrees clockwise.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:44
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
QgsExpressionContext & expressionContext()
Gets the expression context.
Calculates scale for a given combination of canvas size, map extent, and monitor dpi.
void transformInPlace(double &x, double &y, double &z, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
void setUnits(const QgsUnitTypes::LayoutUnit units)
Sets the units for the measurement.
void setAtlasDriven(bool enabled)
Sets whether the map extent will follow the current atlas feature.
Map extent y maximum.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project&#39;s layer tree.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
static QgsLayoutMeasurement decodeMeasurement(const QString &string)
Decodes a measurement from a string.
void setFollowVisibilityPreset(bool follow)
Sets whether the map should follow a map theme.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:732
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string...
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
double mapRotation(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the layout item, in degrees clockwise.
bool nextExportPart() override
Moves to the next export part for a multi-layered export item, during a multi-layered export...
Return the current evaluated value for the property.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
void setLabelMargin(const QgsLayoutMeasurement &margin)
Sets the margin from the map edges in which no labels may be placed.
QgsCoordinateReferenceSystem mapPositionCrs() const
Returns the CRS of the map position, or an invalid CRS if the annotation does not have a fixed map po...
Contains information about the context of a rendering operation.
void removeLabelBlockingItem(QgsLayoutItem *item)
Removes the specified layout item from the map&#39;s "label blocking items".
Force output in vector format where possible, even if items require rasterization to keep their corre...
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
ExportLayerBehavior
Behavior of item when exporting to layered outputs.
double length() const
Returns the length of the measurement.
void setMapRotation(double rotation)
Sets the rotation for the map - this does not affect the layout item shape, only the way the map is d...
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties) override
QPainter * painter()
Returns the destination QPainter for the render operation.
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
virtual bool visitEnter(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor starts visiting a node.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
void setSelectionColor(const QColor &color)
Sets color that is used for drawing of selected vector features.
Map extent y minimum.
bool containsWmsLayer() const
Returns true if the map contains a WMS layer.
void writeXml(QDomElement &styleElement) const
Write style configuration (for project file writing)
QgsLayoutItemMapOverview * overview()
Returns the map item&#39;s first overview.
Enable advanced effects such as blend modes.
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
void updateBoundingRect()
Updates the bounding rect of this item. Call this function before doing any changes related to annota...
void refreshed()
Emitted when the layout has been refreshed and items should also be refreshed and updated...
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:145
The current scale of the map is used for each feature of the atlas.
QgsPointXY mapPosition
Definition: qgsannotation.h:69
double scale() const
Returns the map scale.
friend class QgsLayoutItemMapGrid
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
Stores global configuration for labeling engine.
virtual QString uuid() const
Returns the item identification string.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
This class represents a coordinate reference system (CRS).
void assignFreeId()
Sets the map id() to a number not yet used in the layout.
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:447
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
Class for doing transforms between two map coordinate systems.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
void setLayers(const QList< QgsMapLayer *> &layers)
Set list of layers for map rendering.
void stopLayeredExport() override
Stops a multi-layer export operation.
void setPathResolver(const QgsPathResolver &resolver)
Sets the path resolver for conversion between relative and absolute paths during rendering operations...
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
void themeChanged(const QString &theme)
Emitted when the map&#39;s associated theme is changed.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
void layerStyleOverridesChanged()
Emitted when layer style overrides are changed...
QString name
Definition: qgsmaplayer.h:85
Enable vector simplification and other rendering optimizations.
QString mapLayerId
Associated map layer ID, or an empty string if this export layer is not associated with a map layer...
void addRenderedFeatureHandler(QgsRenderedFeatureHandlerInterface *handler)
Adds a rendered feature handler to use while rendering the map settings.
Item overrides the default layout item painting method.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
QPolygonF visiblePolygon() const
Returns the visible area as a polygon (may be rotated)
bool atlasDriven() const
Returns whether the map extent is set to follow the current atlas feature.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:230
ExportLayerBehavior exportLayerBehavior() const override
Returns the behavior of this item during exporting to layered exports (e.g.
void zoomToExtent(const QgsRectangle &extent)
Zooms the map so that the specified extent is fully visible within the map item.
AtlasScalingMode
Scaling modes used for the serial rendering (atlas)
void addRenderedFeatureHandler(QgsRenderedFeatureHandlerInterface *handler)
Adds a rendered feature handler to use while rendering the map.
bool isLabelBlockingItem(QgsLayoutItem *item) const
Returns true if the specified item is a "label blocking item".
Q_DECL_DEPRECATED int numberExportLayers() const override
Returns the number of layers that this item requires for exporting during layered exports (e...
void setMapFlags(QgsLayoutItemMap::MapItemFlags flags)
Sets the map item&#39;s flags, which control how the map content is drawn.
QString encodeMeasurement() const
Encodes the layout measurement to a string.
TYPE * resolve(const QgsProject *project)
Resolves the map layer by attempting to find a layer with matching ID within a project.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
void changed()
Emitted when the object&#39;s properties change.
Contains details of a particular export layer relating to a layout item.
DataDefinedProperty
Data defined properties for different item types.
QgsLayoutMeasurement labelMargin() const
Returns the margin from the map edges in which no labels may be placed.
void setScale(double scale, bool forceUpdate=true)
Sets new map scale and changes only the map extent.
void refresh() override
Refreshes the item, causing a recalculation of any property overrides and recalculation of its positi...
void finalizeRestoreFromXml() override
Called after all pending items have been restored from XML.
QRectF boundingRect() const override
void renderSynchronously()
Render the map synchronously in this thread.
Whether to render unplaced labels in the map view.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
QString authid() const
Returns the authority identifier for the CRS.
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:130
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
Render above all map layers and labels.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
A scale is chosen from the predefined scales.
void setFlag(Flag f, bool enabled=true)
Sets whether a particual flag is enabled.
Labels should be rendered in individual stages by map layer. This allows separation of labels belongi...
When rendering map items to multi-layered exports, render labels belonging to different layers into s...
double calculate(const QgsRectangle &mapExtent, double canvasWidth) const
Calculate the scale denominator.
All properties for item.
void setLabelBlockingRegions(const QList< QgsLabelBlockingRegion > &regions)
Sets a list of regions to avoid placing labels within.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QgsLayoutItemMapGrid * grid()
Returns the map item&#39;s first grid.