QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
34 #include <QPainter>
35 #include <QStyleOptionGraphicsItem>
36 
38  : QgsLayoutItem( layout )
39 {
40  mBackgroundUpdateTimer = new QTimer( this );
41  mBackgroundUpdateTimer->setSingleShot( true );
42  connect( mBackgroundUpdateTimer, &QTimer::timeout, this, &QgsLayoutItemMap::recreateCachedImageInBackground );
43 
44  assignFreeId();
45 
46  setCacheMode( QGraphicsItem::NoCache );
47 
48  connect( this, &QgsLayoutItem::sizePositionChanged, this, [ = ]
49  {
50  shapeChanged();
51  } );
52 
53  mGridStack = qgis::make_unique< QgsLayoutItemMapGridStack >( this );
54  mOverviewStack = qgis::make_unique< QgsLayoutItemMapOverviewStack >( this );
55 
56  if ( layout )
57  connectUpdateSlot();
58 }
59 
61 {
62  if ( mPainterJob )
63  {
64  disconnect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
65  mPainterJob->cancel(); // blocks
66  mPainter->end();
67  }
68 }
69 
71 {
73 }
74 
76 {
77  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemMap.svg" ) );
78 }
79 
80 QgsLayoutItem::Flags QgsLayoutItemMap::itemFlags() const
81 {
83 }
84 
86 {
87  if ( !mLayout )
88  return;
89 
90  QList<QgsLayoutItemMap *> mapsList;
91  mLayout->layoutItems( mapsList );
92 
93  int maxId = -1;
94  bool used = false;
95  for ( QgsLayoutItemMap *map : qgis::as_const( mapsList ) )
96  {
97  if ( map == this )
98  continue;
99 
100  if ( map->mMapId == mMapId )
101  used = true;
102 
103  maxId = std::max( maxId, map->mMapId );
104  }
105  if ( used )
106  {
107  mMapId = maxId + 1;
108  mLayout->itemsModel()->updateItemDisplayName( this );
109  }
110  updateToolTip();
111 }
112 
114 {
115  if ( !QgsLayoutItem::id().isEmpty() )
116  {
117  return QgsLayoutItem::id();
118  }
119 
120  return tr( "Map %1" ).arg( mMapId );
121 }
122 
124 {
125  return new QgsLayoutItemMap( layout );
126 }
127 
129 {
131 
132  mCachedLayerStyleOverridesPresetName.clear();
133 
134  invalidateCache();
135 
136  updateAtlasFeature();
137 }
138 
140 {
141  if ( rect().isEmpty() )
142  return 0;
143 
144  QgsScaleCalculator calculator;
145  calculator.setMapUnits( crs().mapUnits() );
146  calculator.setDpi( 25.4 ); //Using mm
147  double widthInMm = mLayout->convertFromLayoutUnits( rect().width(), QgsUnitTypes::LayoutMillimeters ).length();
148  return calculator.calculate( extent(), widthInMm );
149 }
150 
151 void QgsLayoutItemMap::setScale( double scaleDenominator, bool forceUpdate )
152 {
153  double currentScaleDenominator = scale();
154 
155  if ( qgsDoubleNear( scaleDenominator, currentScaleDenominator ) || qgsDoubleNear( scaleDenominator, 0.0 ) )
156  {
157  return;
158  }
159 
160  double scaleRatio = scaleDenominator / currentScaleDenominator;
161  mExtent.scale( scaleRatio );
162 
163  if ( mAtlasDriven && mAtlasScalingMode == Fixed )
164  {
165  //if map is atlas controlled and set to fixed scaling mode, then scale changes should be treated as permanent
166  //and also apply to the map's original extent (see #9602)
167  //we can't use the scaleRatio calculated earlier, as the scale can vary depending on extent for geographic coordinate systems
168  QgsScaleCalculator calculator;
169  calculator.setMapUnits( crs().mapUnits() );
170  calculator.setDpi( 25.4 ); //QGraphicsView units are mm
171  scaleRatio = scaleDenominator / calculator.calculate( mExtent, rect().width() );
172  mExtent.scale( scaleRatio );
173  }
174 
175  invalidateCache();
176  if ( forceUpdate )
177  {
178  emit changed();
179  update();
180  }
181  emit extentChanged();
182 }
183 
185 {
186  if ( mExtent == extent )
187  {
188  return;
189  }
190  mExtent = extent;
191 
192  //recalculate data defined scale and extents, since that may override extent
193  refreshMapExtents();
194 
195  //adjust height
196  QRectF currentRect = rect();
197 
198  double newHeight = currentRect.width() * mExtent.height() / mExtent.width();
199 
200  attemptSetSceneRect( QRectF( pos().x(), pos().y(), currentRect.width(), newHeight ) );
201  update();
202 }
203 
205 {
206  QgsRectangle newExtent = extent;
207  QgsRectangle currentExtent = mExtent;
208  //Make sure the width/height ratio is the same as the current layout map extent.
209  //This is to keep the map item frame size fixed
210  double currentWidthHeightRatio = 1.0;
211  if ( !currentExtent.isNull() )
212  currentWidthHeightRatio = currentExtent.width() / currentExtent.height();
213  else
214  currentWidthHeightRatio = rect().width() / rect().height();
215  double newWidthHeightRatio = newExtent.width() / newExtent.height();
216 
217  if ( currentWidthHeightRatio < newWidthHeightRatio )
218  {
219  //enlarge height of new extent, ensuring the map center stays the same
220  double newHeight = newExtent.width() / currentWidthHeightRatio;
221  double deltaHeight = newHeight - newExtent.height();
222  newExtent.setYMinimum( newExtent.yMinimum() - deltaHeight / 2 );
223  newExtent.setYMaximum( newExtent.yMaximum() + deltaHeight / 2 );
224  }
225  else
226  {
227  //enlarge width of new extent, ensuring the map center stays the same
228  double newWidth = currentWidthHeightRatio * newExtent.height();
229  double deltaWidth = newWidth - newExtent.width();
230  newExtent.setXMinimum( newExtent.xMinimum() - deltaWidth / 2 );
231  newExtent.setXMaximum( newExtent.xMaximum() + deltaWidth / 2 );
232  }
233 
234  if ( mExtent == newExtent )
235  {
236  return;
237  }
238  mExtent = newExtent;
239 
240  //recalculate data defined scale and extents, since that may override extent
241  refreshMapExtents();
242 
243  invalidateCache();
244  emit changed();
245  emit extentChanged();
246 }
247 
249 {
250  return mExtent;
251 }
252 
254 {
255  QPolygonF poly;
256  mapPolygon( mExtent, poly );
257  return poly;
258 }
259 
261 {
262  if ( mCrs.isValid() )
263  return mCrs;
264  else if ( mLayout && mLayout->project() )
265  return mLayout->project()->crs();
267 }
268 
270 {
271  mCrs = crs;
272 }
273 
274 QList<QgsMapLayer *> QgsLayoutItemMap::layers() const
275 {
276  return _qgis_listRefToRaw( mLayers );
277 }
278 
279 void QgsLayoutItemMap::setLayers( const QList<QgsMapLayer *> &layers )
280 {
281  mLayers = _qgis_listRawToRef( layers );
282 }
283 
284 void QgsLayoutItemMap::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
285 {
286  if ( overrides == mLayerStyleOverrides )
287  return;
288 
289  mLayerStyleOverrides = overrides;
290  emit layerStyleOverridesChanged(); // associated legends may listen to this
291 
292 }
293 
295 {
296  mLayerStyleOverrides.clear();
297  for ( const QgsMapLayerRef &layerRef : qgis::as_const( mLayers ) )
298  {
299  if ( QgsMapLayer *layer = layerRef.get() )
300  {
301  QgsMapLayerStyle style;
302  style.readFromLayer( layer );
303  mLayerStyleOverrides.insert( layer->id(), style.xmlData() );
304  }
305  }
306 }
307 
308 void QgsLayoutItemMap::moveContent( double dx, double dy )
309 {
310  mLastRenderedImageOffsetX -= dx;
311  mLastRenderedImageOffsetY -= dy;
312  if ( !mDrawing )
313  {
314  transformShift( dx, dy );
315  mExtent.setXMinimum( mExtent.xMinimum() + dx );
316  mExtent.setXMaximum( mExtent.xMaximum() + dx );
317  mExtent.setYMinimum( mExtent.yMinimum() + dy );
318  mExtent.setYMaximum( mExtent.yMaximum() + dy );
319 
320  //in case data defined extents are set, these override the calculated values
321  refreshMapExtents();
322 
323  invalidateCache();
324  emit changed();
325  emit extentChanged();
326  }
327 }
328 
329 void QgsLayoutItemMap::zoomContent( double factor, QPointF point )
330 {
331  if ( mDrawing )
332  {
333  return;
334  }
335 
336  //find out map coordinates of position
337  double mapX = mExtent.xMinimum() + ( point.x() / rect().width() ) * ( mExtent.xMaximum() - mExtent.xMinimum() );
338  double mapY = mExtent.yMinimum() + ( 1 - ( point.y() / rect().height() ) ) * ( mExtent.yMaximum() - mExtent.yMinimum() );
339 
340  //find out new center point
341  double centerX = ( mExtent.xMaximum() + mExtent.xMinimum() ) / 2;
342  double centerY = ( mExtent.yMaximum() + mExtent.yMinimum() ) / 2;
343 
344  centerX = mapX + ( centerX - mapX ) * ( 1.0 / factor );
345  centerY = mapY + ( centerY - mapY ) * ( 1.0 / factor );
346 
347  double newIntervalX, newIntervalY;
348 
349  if ( factor > 0 )
350  {
351  newIntervalX = ( mExtent.xMaximum() - mExtent.xMinimum() ) / factor;
352  newIntervalY = ( mExtent.yMaximum() - mExtent.yMinimum() ) / factor;
353  }
354  else //no need to zoom
355  {
356  return;
357  }
358 
359  mExtent.setXMaximum( centerX + newIntervalX / 2 );
360  mExtent.setXMinimum( centerX - newIntervalX / 2 );
361  mExtent.setYMaximum( centerY + newIntervalY / 2 );
362  mExtent.setYMinimum( centerY - newIntervalY / 2 );
363 
364  if ( mAtlasDriven && mAtlasScalingMode == Fixed )
365  {
366  //if map is atlas controlled and set to fixed scaling mode, then scale changes should be treated as permanent
367  //and also apply to the map's original extent (see #9602)
368  //we can't use the scaleRatio calculated earlier, as the scale can vary depending on extent for geographic coordinate systems
369  QgsScaleCalculator calculator;
370  calculator.setMapUnits( crs().mapUnits() );
371  calculator.setDpi( 25.4 ); //QGraphicsView units are mm
372  double scaleRatio = scale() / calculator.calculate( mExtent, rect().width() );
373  mExtent.scale( scaleRatio );
374  }
375 
376  //recalculate data defined scale and extents, since that may override zoom
377  refreshMapExtents();
378 
379  invalidateCache();
380  emit changed();
381  emit extentChanged();
382 }
383 
385 {
386  const QList< QgsMapLayer * > layers = layersToRender();
387  for ( QgsMapLayer *layer : layers )
388  {
389  if ( layer->dataProvider() && layer->dataProvider()->name() == QLatin1String( "wms" ) )
390  {
391  return true;
392  }
393  }
394  return false;
395 }
396 
398 {
400  return true;
401 
402  // we MUST force the whole layout to render as a raster if any map item
403  // uses blend modes, and we are not drawing on a solid opaque background
404  // because in this case the map item needs to be rendered as a raster, but
405  // it also needs to interact with items below it
406  if ( !containsAdvancedEffects() )
407  return false;
408 
409  // TODO layer transparency is probably ok to allow without forcing rasterization
410 
411  if ( hasBackground() && qgsDoubleNear( backgroundColor().alphaF(), 1.0 ) )
412  return false;
413 
414  return true;
415 }
416 
418 {
420  return true;
421 
422  //check easy things first
423 
424  //overviews
425  if ( mOverviewStack->containsAdvancedEffects() )
426  {
427  return true;
428  }
429 
430  //grids
431  if ( mGridStack->containsAdvancedEffects() )
432  {
433  return true;
434  }
435 
436  QgsMapSettings ms;
437  ms.setLayers( layersToRender() );
438  return ( !QgsMapSettingsUtils::containsAdvancedEffects( ms ).isEmpty() );
439 }
440 
441 void QgsLayoutItemMap::setMapRotation( double rotation )
442 {
443  mMapRotation = rotation;
444  mEvaluatedMapRotation = mMapRotation;
445  invalidateCache();
446  emit mapRotationChanged( rotation );
447  emit changed();
448 }
449 
451 {
452  return valueType == QgsLayoutObject::EvaluatedValue ? mEvaluatedMapRotation : mMapRotation;
453 
454 }
455 
457 {
458  mAtlasDriven = enabled;
459 
460  if ( !enabled )
461  {
462  //if not enabling the atlas, we still need to refresh the map extents
463  //so that data defined extents and scale are recalculated
464  refreshMapExtents();
465  }
466 }
467 
469 {
470  if ( valueType == QgsLayoutObject::EvaluatedValue )
471  {
472  //evaluate data defined atlas margin
473 
474  //start with user specified margin
475  double margin = mAtlasMargin;
477 
478  bool ok = false;
479  double ddMargin = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapAtlasMargin, context, 0.0, &ok );
480  if ( ok )
481  {
482  //divide by 100 to convert to 0 -> 1.0 range
483  margin = ddMargin / 100;
484  }
485  return margin;
486  }
487  else
488  {
489  return mAtlasMargin;
490  }
491 }
492 
494 {
495  if ( mGridStack->size() < 1 )
496  {
497  QgsLayoutItemMapGrid *grid = new QgsLayoutItemMapGrid( tr( "Grid %1" ).arg( 1 ), this );
498  mGridStack->addGrid( grid );
499  }
500  return mGridStack->grid( 0 );
501 }
502 
504 {
505  if ( mOverviewStack->size() < 1 )
506  {
507  QgsLayoutItemMapOverview *overview = new QgsLayoutItemMapOverview( tr( "Overview %1" ).arg( 1 ), this );
508  mOverviewStack->addOverview( overview );
509  }
510  return mOverviewStack->overview( 0 );
511 }
512 
514 {
515 }
516 
517 bool QgsLayoutItemMap::writePropertiesToElement( QDomElement &mapElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
518 {
519  if ( mKeepLayerSet )
520  {
521  mapElem.setAttribute( QStringLiteral( "keepLayerSet" ), QStringLiteral( "true" ) );
522  }
523  else
524  {
525  mapElem.setAttribute( QStringLiteral( "keepLayerSet" ), QStringLiteral( "false" ) );
526  }
527 
528  if ( mDrawAnnotations )
529  {
530  mapElem.setAttribute( QStringLiteral( "drawCanvasItems" ), QStringLiteral( "true" ) );
531  }
532  else
533  {
534  mapElem.setAttribute( QStringLiteral( "drawCanvasItems" ), QStringLiteral( "false" ) );
535  }
536 
537  //extent
538  QDomElement extentElem = doc.createElement( QStringLiteral( "Extent" ) );
539  extentElem.setAttribute( QStringLiteral( "xmin" ), qgsDoubleToString( mExtent.xMinimum() ) );
540  extentElem.setAttribute( QStringLiteral( "xmax" ), qgsDoubleToString( mExtent.xMaximum() ) );
541  extentElem.setAttribute( QStringLiteral( "ymin" ), qgsDoubleToString( mExtent.yMinimum() ) );
542  extentElem.setAttribute( QStringLiteral( "ymax" ), qgsDoubleToString( mExtent.yMaximum() ) );
543  mapElem.appendChild( extentElem );
544 
545  if ( mCrs.isValid() )
546  {
547  QDomElement crsElem = doc.createElement( QStringLiteral( "crs" ) );
548  mCrs.writeXml( crsElem, doc );
549  mapElem.appendChild( crsElem );
550  }
551 
552  // follow map theme
553  mapElem.setAttribute( QStringLiteral( "followPreset" ), mFollowVisibilityPreset ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
554  mapElem.setAttribute( QStringLiteral( "followPresetName" ), mFollowVisibilityPresetName );
555 
556  //map rotation
557  mapElem.setAttribute( QStringLiteral( "mapRotation" ), QString::number( mMapRotation ) );
558 
559  //layer set
560  QDomElement layerSetElem = doc.createElement( QStringLiteral( "LayerSet" ) );
561  for ( const QgsMapLayerRef &layerRef : mLayers )
562  {
563  if ( !layerRef )
564  continue;
565  QDomElement layerElem = doc.createElement( QStringLiteral( "Layer" ) );
566  QDomText layerIdText = doc.createTextNode( layerRef.layerId );
567  layerElem.appendChild( layerIdText );
568 
569  layerElem.setAttribute( QStringLiteral( "name" ), layerRef.name );
570  layerElem.setAttribute( QStringLiteral( "source" ), layerRef.source );
571  layerElem.setAttribute( QStringLiteral( "provider" ), layerRef.provider );
572 
573  layerSetElem.appendChild( layerElem );
574  }
575  mapElem.appendChild( layerSetElem );
576 
577  // override styles
578  if ( mKeepLayerStyles )
579  {
580  QDomElement stylesElem = doc.createElement( QStringLiteral( "LayerStyles" ) );
581  for ( auto styleIt = mLayerStyleOverrides.constBegin(); styleIt != mLayerStyleOverrides.constEnd(); ++styleIt )
582  {
583  QDomElement styleElem = doc.createElement( QStringLiteral( "LayerStyle" ) );
584 
585  QgsMapLayerRef ref( styleIt.key() );
586  ref.resolve( mLayout->project() );
587 
588  styleElem.setAttribute( QStringLiteral( "layerid" ), ref.layerId );
589  styleElem.setAttribute( QStringLiteral( "name" ), ref.name );
590  styleElem.setAttribute( QStringLiteral( "source" ), ref.source );
591  styleElem.setAttribute( QStringLiteral( "provider" ), ref.provider );
592 
593  QgsMapLayerStyle style( styleIt.value() );
594  style.writeXml( styleElem );
595  stylesElem.appendChild( styleElem );
596  }
597  mapElem.appendChild( stylesElem );
598  }
599 
600  //grids
601  mGridStack->writeXml( mapElem, doc, context );
602 
603  //overviews
604  mOverviewStack->writeXml( mapElem, doc, context );
605 
606  //atlas
607  QDomElement atlasElem = doc.createElement( QStringLiteral( "AtlasMap" ) );
608  atlasElem.setAttribute( QStringLiteral( "atlasDriven" ), mAtlasDriven );
609  atlasElem.setAttribute( QStringLiteral( "scalingMode" ), mAtlasScalingMode );
610  atlasElem.setAttribute( QStringLiteral( "margin" ), qgsDoubleToString( mAtlasMargin ) );
611  mapElem.appendChild( atlasElem );
612 
613  return true;
614 }
615 
616 bool QgsLayoutItemMap::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
617 {
618  mUpdatesEnabled = false;
619 
620  //extent
621  QDomNodeList extentNodeList = itemElem.elementsByTagName( QStringLiteral( "Extent" ) );
622  if ( !extentNodeList.isEmpty() )
623  {
624  QDomElement extentElem = extentNodeList.at( 0 ).toElement();
625  double xmin, xmax, ymin, ymax;
626  xmin = extentElem.attribute( QStringLiteral( "xmin" ) ).toDouble();
627  xmax = extentElem.attribute( QStringLiteral( "xmax" ) ).toDouble();
628  ymin = extentElem.attribute( QStringLiteral( "ymin" ) ).toDouble();
629  ymax = extentElem.attribute( QStringLiteral( "ymax" ) ).toDouble();
630  setExtent( QgsRectangle( xmin, ymin, xmax, ymax ) );
631  }
632 
633  QDomNodeList crsNodeList = itemElem.elementsByTagName( QStringLiteral( "crs" ) );
634  if ( !crsNodeList.isEmpty() )
635  {
636  QDomElement crsElem = crsNodeList.at( 0 ).toElement();
637  mCrs.readXml( crsElem );
638  }
639  else
640  {
642  }
643 
644  //map rotation
645  mMapRotation = itemElem.attribute( QStringLiteral( "mapRotation" ), QStringLiteral( "0" ) ).toDouble();
646 
647  // follow map theme
648  mFollowVisibilityPreset = itemElem.attribute( QStringLiteral( "followPreset" ) ).compare( QLatin1String( "true" ) ) == 0;
649  mFollowVisibilityPresetName = itemElem.attribute( QStringLiteral( "followPresetName" ) );
650 
651  //mKeepLayerSet flag
652  QString keepLayerSetFlag = itemElem.attribute( QStringLiteral( "keepLayerSet" ) );
653  if ( keepLayerSetFlag.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
654  {
655  mKeepLayerSet = true;
656  }
657  else
658  {
659  mKeepLayerSet = false;
660  }
661 
662  QString drawCanvasItemsFlag = itemElem.attribute( QStringLiteral( "drawCanvasItems" ), QStringLiteral( "true" ) );
663  if ( drawCanvasItemsFlag.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
664  {
665  mDrawAnnotations = true;
666  }
667  else
668  {
669  mDrawAnnotations = false;
670  }
671 
672  mLayerStyleOverrides.clear();
673 
674  //mLayers
675  mLayers.clear();
676  QDomNodeList layerSetNodeList = itemElem.elementsByTagName( QStringLiteral( "LayerSet" ) );
677  if ( !layerSetNodeList.isEmpty() )
678  {
679  QDomElement layerSetElem = layerSetNodeList.at( 0 ).toElement();
680  QDomNodeList layerIdNodeList = layerSetElem.elementsByTagName( QStringLiteral( "Layer" ) );
681  mLayers.reserve( layerIdNodeList.size() );
682  for ( int i = 0; i < layerIdNodeList.size(); ++i )
683  {
684  QDomElement layerElem = layerIdNodeList.at( i ).toElement();
685  QString layerId = layerElem.text();
686  QString layerName = layerElem.attribute( QStringLiteral( "name" ) );
687  QString layerSource = layerElem.attribute( QStringLiteral( "source" ) );
688  QString layerProvider = layerElem.attribute( QStringLiteral( "provider" ) );
689 
690  QgsMapLayerRef ref( layerId, layerName, layerSource, layerProvider );
691  ref.resolveWeakly( mLayout->project() );
692  mLayers << ref;
693  }
694  }
695 
696  // override styles
697  QDomNodeList layerStylesNodeList = itemElem.elementsByTagName( QStringLiteral( "LayerStyles" ) );
698  mKeepLayerStyles = !layerStylesNodeList.isEmpty();
699  if ( mKeepLayerStyles )
700  {
701  QDomElement layerStylesElem = layerStylesNodeList.at( 0 ).toElement();
702  QDomNodeList layerStyleNodeList = layerStylesElem.elementsByTagName( QStringLiteral( "LayerStyle" ) );
703  for ( int i = 0; i < layerStyleNodeList.size(); ++i )
704  {
705  const QDomElement &layerStyleElement = layerStyleNodeList.at( i ).toElement();
706  QString layerId = layerStyleElement.attribute( QStringLiteral( "layerid" ) );
707  QString layerName = layerStyleElement.attribute( QStringLiteral( "name" ) );
708  QString layerSource = layerStyleElement.attribute( QStringLiteral( "source" ) );
709  QString layerProvider = layerStyleElement.attribute( QStringLiteral( "provider" ) );
710  QgsMapLayerRef ref( layerId, layerName, layerSource, layerProvider );
711  ref.resolveWeakly( mLayout->project() );
712 
713  QgsMapLayerStyle style;
714  style.readXml( layerStyleElement );
715  mLayerStyleOverrides.insert( ref.layerId, style.xmlData() );
716  }
717  }
718 
719  mDrawing = false;
720  mNumCachedLayers = 0;
721  mCacheInvalidated = true;
722 
723  //overviews
724  mOverviewStack->readXml( itemElem, doc, context );
725 
726  //grids
727  mGridStack->readXml( itemElem, doc, context );
728 
729  //atlas
730  QDomNodeList atlasNodeList = itemElem.elementsByTagName( QStringLiteral( "AtlasMap" ) );
731  if ( !atlasNodeList.isEmpty() )
732  {
733  QDomElement atlasElem = atlasNodeList.at( 0 ).toElement();
734  mAtlasDriven = ( atlasElem.attribute( QStringLiteral( "atlasDriven" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
735  if ( atlasElem.hasAttribute( QStringLiteral( "fixedScale" ) ) ) // deprecated XML
736  {
737  mAtlasScalingMode = ( atlasElem.attribute( QStringLiteral( "fixedScale" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) ) ? Fixed : Auto;
738  }
739  else if ( atlasElem.hasAttribute( QStringLiteral( "scalingMode" ) ) )
740  {
741  mAtlasScalingMode = static_cast<AtlasScalingMode>( atlasElem.attribute( QStringLiteral( "scalingMode" ) ).toInt() );
742  }
743  mAtlasMargin = atlasElem.attribute( QStringLiteral( "margin" ), QStringLiteral( "0.1" ) ).toDouble();
744  }
745 
747 
748  mUpdatesEnabled = true;
749  return true;
750 }
751 
752 void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem *style, QWidget * )
753 {
754  if ( !mLayout || !painter || !painter->device() || !mUpdatesEnabled )
755  {
756  return;
757  }
758  if ( !shouldDrawItem() )
759  {
760  return;
761  }
762 
763  QRectF thisPaintRect = rect();
764  if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
765  return;
766 
767  //TODO - try to reduce the amount of duplicate code here!
768 
769  if ( mLayout->renderContext().isPreviewRender() )
770  {
771  painter->save();
772  painter->setClipRect( thisPaintRect );
773  if ( !mCacheFinalImage || mCacheFinalImage->isNull() )
774  {
775  // No initial render available - so draw some preview text alerting user
776  painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
777  painter->drawRect( thisPaintRect );
778  painter->setBrush( Qt::NoBrush );
779  QFont messageFont;
780  messageFont.setPointSize( 12 );
781  painter->setFont( messageFont );
782  painter->setPen( QColor( 255, 255, 255, 255 ) );
783  painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering map" ) );
784  if ( mPainterJob && mCacheInvalidated && !mDrawingPreview )
785  {
786  // current job was invalidated - start a new one
787  mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( style );
788  mBackgroundUpdateTimer->start( 1 );
789  }
790  else if ( !mPainterJob && !mDrawingPreview )
791  {
792  // this is the map's very first paint - trigger a cache update
793  mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( style );
794  mBackgroundUpdateTimer->start( 1 );
795  }
796  }
797  else
798  {
799  if ( mCacheInvalidated && !mDrawingPreview )
800  {
801  // cache was invalidated - trigger a background update
802  mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( style );
803  mBackgroundUpdateTimer->start( 1 );
804  }
805 
806  //Background color is already included in cached image, so no need to draw
807 
808  double imagePixelWidth = mCacheFinalImage->width(); //how many pixels of the image are for the map extent?
809  double scale = rect().width() / imagePixelWidth;
810 
811  painter->save();
812 
813  painter->translate( mLastRenderedImageOffsetX + mXOffset, mLastRenderedImageOffsetY + mYOffset );
814  painter->scale( scale, scale );
815  painter->drawImage( 0, 0, *mCacheFinalImage );
816 
817  //restore rotation
818  painter->restore();
819  }
820 
821  painter->setClipRect( thisPaintRect, Qt::NoClip );
822 
823  if ( shouldDrawPart( OverviewMapExtent ) )
824  {
825  mOverviewStack->drawItems( painter );
826  }
827  if ( shouldDrawPart( Grid ) )
828  {
829  mGridStack->drawItems( painter );
830  }
831  drawAnnotations( painter );
832  if ( shouldDrawPart( Frame ) )
833  {
834  drawMapFrame( painter );
835  }
836  painter->restore();
837  }
838  else
839  {
840  if ( mDrawing )
841  return;
842 
843  mDrawing = true;
844  QPaintDevice *paintDevice = painter->device();
845  if ( !paintDevice )
846  return;
847 
848  QgsRectangle cExtent = extent();
849  QSizeF size( cExtent.width() * mapUnitsToLayoutUnits(), cExtent.height() * mapUnitsToLayoutUnits() );
850 
851  if ( containsAdvancedEffects() && ( !mLayout || !( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagForceVectorOutput ) ) )
852  {
853  // rasterize
854  double destinationDpi = QgsLayoutUtils::scaleFactorFromItemStyle( style ) * 25.4;
855  double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, QgsUnitTypes::LayoutInches ).length() : 1;
856  int widthInPixels = static_cast< int >( std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi ) );
857  int heightInPixels = static_cast< int >( std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi ) );
858  QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
859 
860  image.fill( Qt::transparent );
861  image.setDotsPerMeterX( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
862  image.setDotsPerMeterY( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
863  double dotsPerMM = destinationDpi / 25.4;
864  QPainter p( &image );
865 
866  QPointF tl = -boundingRect().topLeft();
867  QRect imagePaintRect( static_cast< int >( std::round( tl.x() * dotsPerMM ) ),
868  static_cast< int >( std::round( tl.y() * dotsPerMM ) ),
869  static_cast< int >( std::round( thisPaintRect.width() * dotsPerMM ) ),
870  static_cast< int >( std::round( thisPaintRect.height() * dotsPerMM ) ) );
871  p.setClipRect( imagePaintRect );
872 
873  p.translate( imagePaintRect.topLeft() );
874 
875  // Fill with background color - must be drawn onto the flattened image
876  // so that layers with opacity or blend modes can correctly interact with it
877  if ( shouldDrawPart( Background ) )
878  {
879  p.scale( dotsPerMM, dotsPerMM );
880  drawMapBackground( &p );
881  p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
882  }
883 
884  drawMap( &p, cExtent, imagePaintRect.size(), image.logicalDpiX() );
885 
886  // important - all other items, overviews, grids etc must be rendered to the
887  // flattened image, in case these have blend modes must need to interact
888  // with the map
889  p.scale( dotsPerMM, dotsPerMM );
890 
891  if ( shouldDrawPart( OverviewMapExtent ) )
892  {
893  mOverviewStack->drawItems( &p );
894  }
895  if ( shouldDrawPart( Grid ) )
896  {
897  mGridStack->drawItems( &p );
898  }
899  drawAnnotations( &p );
900 
901  painter->save();
902  painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
903  painter->drawImage( QPointF( -tl.x()* dotsPerMM, -tl.y() * dotsPerMM ), image );
904  painter->scale( dotsPerMM, dotsPerMM );
905  painter->restore();
906  }
907  else
908  {
909  // Fill with background color
910  if ( shouldDrawPart( Background ) )
911  {
912  drawMapBackground( painter );
913  }
914 
915  painter->save();
916  painter->setClipRect( thisPaintRect );
917  painter->save();
918  painter->translate( mXOffset, mYOffset );
919 
920  double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
921  size *= dotsPerMM; // output size will be in dots (pixels)
922  painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
923  drawMap( painter, cExtent, size, paintDevice->logicalDpiX() );
924 
925  painter->restore();
926 
927  painter->setClipRect( thisPaintRect, Qt::NoClip );
928 
929  if ( shouldDrawPart( OverviewMapExtent ) )
930  {
931  mOverviewStack->drawItems( painter );
932  }
933  if ( shouldDrawPart( Grid ) )
934  {
935  mGridStack->drawItems( painter );
936  }
937  drawAnnotations( painter );
938  painter->restore();
939  }
940 
941  if ( shouldDrawPart( Frame ) )
942  {
943  drawMapFrame( painter );
944  }
945  mDrawing = false;
946  }
947 }
948 
950 {
951  return ( hasBackground() ? 1 : 0 )
952  + layersToRender().length()
953  + 1 // for grids, if they exist
954  + 1 // for overviews, if they exist
955  + ( frameEnabled() ? 1 : 0 );
956 }
957 
959 {
962 }
963 
964 void QgsLayoutItemMap::drawMap( QPainter *painter, const QgsRectangle &extent, QSizeF size, double dpi )
965 {
966  if ( !painter )
967  {
968  return;
969  }
970  if ( qgsDoubleNear( size.width(), 0.0 ) || qgsDoubleNear( size.height(), 0.0 ) )
971  {
972  //don't attempt to draw if size is invalid
973  return;
974  }
975 
976  // render
977  QgsMapRendererCustomPainterJob job( mapSettings( extent, size, dpi, true ), painter );
978  // Render the map in this thread. This is done because of problems
979  // with printing to printer on Windows (printing to PDF is fine though).
980  // Raster images were not displayed - see #10599
981  job.renderSynchronously();
982 }
983 
984 void QgsLayoutItemMap::recreateCachedImageInBackground()
985 {
986  if ( mPainterJob )
987  {
988  disconnect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
989  QgsMapRendererCustomPainterJob *oldJob = mPainterJob.release();
990  QPainter *oldPainter = mPainter.release();
991  QImage *oldImage = mCacheRenderingImage.release();
992  connect( oldJob, &QgsMapRendererCustomPainterJob::finished, this, [oldPainter, oldJob, oldImage]
993  {
994  oldJob->deleteLater();
995  delete oldPainter;
996  delete oldImage;
997  } );
998  oldJob->cancelWithoutBlocking();
999  }
1000  else
1001  {
1002  mCacheRenderingImage.reset( nullptr );
1003  }
1004 
1005  Q_ASSERT( !mPainterJob );
1006  Q_ASSERT( !mPainter );
1007  Q_ASSERT( !mCacheRenderingImage );
1008 
1009  QgsRectangle ext = extent();
1010  double widthLayoutUnits = ext.width() * mapUnitsToLayoutUnits();
1011  double heightLayoutUnits = ext.height() * mapUnitsToLayoutUnits();
1012 
1013  int w = static_cast< int >( std::round( widthLayoutUnits * mPreviewScaleFactor ) );
1014  int h = static_cast< int >( std::round( heightLayoutUnits * mPreviewScaleFactor ) );
1015 
1016  // limit size of image for better performance
1017  if ( w > 5000 || h > 5000 )
1018  {
1019  if ( w > h )
1020  {
1021  w = 5000;
1022  h = static_cast< int>( std::round( w * heightLayoutUnits / widthLayoutUnits ) );
1023  }
1024  else
1025  {
1026  h = 5000;
1027  w = static_cast< int >( std::round( h * widthLayoutUnits / heightLayoutUnits ) );
1028  }
1029  }
1030 
1031  if ( w <= 0 || h <= 0 )
1032  return;
1033 
1034  mCacheRenderingImage.reset( new QImage( w, h, QImage::Format_ARGB32 ) );
1035 
1036  // set DPI of the image
1037  mCacheRenderingImage->setDotsPerMeterX( static_cast< int >( std::round( 1000 * w / widthLayoutUnits ) ) );
1038  mCacheRenderingImage->setDotsPerMeterY( static_cast< int >( std::round( 1000 * h / heightLayoutUnits ) ) );
1039 
1040  if ( hasBackground() )
1041  {
1042  //Initially fill image with specified background color. This ensures that layers with blend modes will
1043  //preview correctly
1044  mCacheRenderingImage->fill( backgroundColor().rgba() );
1045  }
1046  else
1047  {
1048  //no background, but start with empty fill to avoid artifacts
1049  mCacheRenderingImage->fill( QColor( 255, 255, 255, 0 ).rgba() );
1050  }
1051 
1052  mCacheInvalidated = false;
1053  mPainter.reset( new QPainter( mCacheRenderingImage.get() ) );
1054  QgsMapSettings settings( mapSettings( ext, QSizeF( w, h ), mCacheRenderingImage->logicalDpiX(), true ) );
1055  mPainterJob.reset( new QgsMapRendererCustomPainterJob( settings, mPainter.get() ) );
1056  connect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
1057  mPainterJob->start();
1058 
1059  // from now on we can accept refresh requests again
1060  // this must be reset only after the job has been started, because
1061  // some providers (yes, it's you WCS and AMS!) during preparation
1062  // do network requests and start an internal event loop, which may
1063  // end up calling refresh() and would schedule another refresh,
1064  // deleting the one we have just started.
1065 
1066  // ^^ that comment was directly copied from a similar fix in QgsMapCanvas. And
1067  // with little surprise, both those providers are still badly behaved and causing
1068  // annoying bugs for us to deal with...
1069  mDrawingPreview = false;
1070 }
1071 
1072 QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, double dpi, bool includeLayerSettings ) const
1073 {
1074  QgsExpressionContext expressionContext = createExpressionContext();
1075  QgsCoordinateReferenceSystem renderCrs = crs();
1076 
1077  QgsMapSettings jobMapSettings;
1078  jobMapSettings.setDestinationCrs( renderCrs );
1079  jobMapSettings.setExtent( extent );
1080  jobMapSettings.setOutputSize( size.toSize() );
1081  jobMapSettings.setOutputDpi( dpi );
1082  jobMapSettings.setBackgroundColor( Qt::transparent );
1083  jobMapSettings.setRotation( mEvaluatedMapRotation );
1084  if ( mLayout )
1085  jobMapSettings.setEllipsoid( mLayout->project()->ellipsoid() );
1086 
1087  if ( includeLayerSettings )
1088  {
1089  //set layers to render
1090  QList<QgsMapLayer *> layers = layersToRender( &expressionContext );
1091  if ( mLayout && -1 != mLayout->renderContext().currentExportLayer() )
1092  {
1093  const int layerIdx = mLayout->renderContext().currentExportLayer() - ( hasBackground() ? 1 : 0 );
1094  if ( layerIdx >= 0 && layerIdx < layers.length() )
1095  {
1096  // exporting with separate layers (e.g., to svg layers), so we only want to render a single map layer
1097  QgsMapLayer *ml = layers[ layers.length() - layerIdx - 1 ];
1098  layers.clear();
1099  layers << ml;
1100  }
1101  else
1102  {
1103  // exporting decorations such as map frame/grid/overview, so no map layers required
1104  layers.clear();
1105  }
1106  }
1107  jobMapSettings.setLayers( layers );
1108  jobMapSettings.setLayerStyleOverrides( layerStyleOverridesToRender( expressionContext ) );
1109  }
1110 
1111  if ( !mLayout->renderContext().isPreviewRender() )
1112  {
1113  //if outputting layout, disable optimisations like layer simplification
1114  jobMapSettings.setFlag( QgsMapSettings::UseRenderingOptimization, false );
1115  }
1116 
1117  jobMapSettings.setExpressionContext( expressionContext );
1118 
1119  // layout-specific overrides of flags
1120  jobMapSettings.setFlag( QgsMapSettings::ForceVectorOutput, true ); // force vector output (no caching of marker images etc.)
1121  jobMapSettings.setFlag( QgsMapSettings::Antialiasing, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
1122  jobMapSettings.setFlag( QgsMapSettings::DrawEditingInfo, false );
1123  jobMapSettings.setSelectionColor( mLayout->renderContext().selectionColor() );
1124  jobMapSettings.setFlag( QgsMapSettings::DrawSelection, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagDrawSelection );
1126  jobMapSettings.setTransformContext( mLayout->project()->transformContext() );
1127  jobMapSettings.setPathResolver( mLayout->project()->pathResolver() );
1128 
1129  jobMapSettings.setLabelingEngineSettings( mLayout->project()->labelingEngineSettings() );
1130  // override the default text render format inherited from the labeling engine settings using the layout's render context setting
1131  jobMapSettings.setTextRenderFormat( mLayout->renderContext().textRenderFormat() );
1132 
1133  return jobMapSettings;
1134 }
1135 
1137 {
1138  assignFreeId();
1139 
1140  mOverviewStack->finalizeRestoreFromXml();
1141  mGridStack->finalizeRestoreFromXml();
1142 }
1143 
1144 void QgsLayoutItemMap::setMoveContentPreviewOffset( double xOffset, double yOffset )
1145 {
1146  mXOffset = xOffset;
1147  mYOffset = yOffset;
1148 }
1149 
1151 {
1152  return mCurrentRectangle;
1153 }
1154 
1156 {
1158 
1159  //Can't utilize QgsExpressionContextUtils::mapSettingsScope as we don't always
1160  //have a QgsMapSettings object available when the context is required, so we manually
1161  //add the same variables here
1162  QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Map Settings" ) );
1163 
1164  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_id" ), id(), true ) );
1165  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_rotation" ), mMapRotation, true ) );
1166  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_scale" ), scale(), true ) );
1167 
1168  QgsRectangle currentExtent( extent() );
1169  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent" ), QVariant::fromValue( QgsGeometry::fromRect( currentExtent ) ), true ) );
1170  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_width" ), currentExtent.width(), true ) );
1171  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_height" ), currentExtent.height(), true ) );
1172  QgsGeometry centerPoint = QgsGeometry::fromPointXY( currentExtent.center() );
1173  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_center" ), QVariant::fromValue( centerPoint ), true ) );
1174 
1175  QgsCoordinateReferenceSystem mapCrs = crs();
1176  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs" ), mapCrs.authid(), true ) );
1177  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_definition" ), mapCrs.toProj4(), true ) );
1178  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_units" ), QgsUnitTypes::toString( mapCrs.mapUnits() ), true ) );
1179 
1180  QVariantList layersIds;
1181  QVariantList layers;
1182  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layer_ids" ), layersIds, true ) );
1183  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layers" ), layers, true ) );
1184 
1185  context.appendScope( scope );
1186 
1187  // The scope map_layer_ids and map_layers variables have been added to the context, only now we can
1188  // call layersToRender (just in case layersToRender relies on evaluating an expression which uses
1189  // other variables contained within the map settings scope
1190  const QList<QgsMapLayer *> layersInMap = layersToRender( &context );
1191 
1192  layersIds.reserve( layersInMap.count() );
1193  layers.reserve( layersInMap.count() );
1194  for ( QgsMapLayer *layer : layersInMap )
1195  {
1196  layersIds << layer->id();
1197  layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( layer ) );
1198  }
1199  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layer_ids" ), layersIds, true ) );
1200  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layers" ), layers, true ) );
1201 
1202  scope->addFunction( QStringLiteral( "is_layer_visible" ), new QgsExpressionContextUtils::GetLayerVisibility( layersInMap ) );
1203 
1204  return context;
1205 }
1206 
1208 {
1209  double extentWidth = extent().width();
1210  if ( extentWidth <= 0 )
1211  {
1212  return 1;
1213  }
1214  return rect().width() / extentWidth;
1215 }
1216 
1218 {
1219  double dx = mXOffset;
1220  double dy = mYOffset;
1221  transformShift( dx, dy );
1222  QPolygonF poly = visibleExtentPolygon();
1223  poly.translate( -dx, -dy );
1224  return poly;
1225 }
1226 
1227 QPointF QgsLayoutItemMap::mapToItemCoords( QPointF mapCoords ) const
1228 {
1229  QPolygonF mapPoly = transformedMapPolygon();
1230  if ( mapPoly.empty() )
1231  {
1232  return QPointF( 0, 0 );
1233  }
1234 
1235  QgsRectangle tExtent = transformedExtent();
1236  QgsPointXY rotationPoint( ( tExtent.xMaximum() + tExtent.xMinimum() ) / 2.0, ( tExtent.yMaximum() + tExtent.yMinimum() ) / 2.0 );
1237  double dx = mapCoords.x() - rotationPoint.x();
1238  double dy = mapCoords.y() - rotationPoint.y();
1239  QgsLayoutUtils::rotate( -mEvaluatedMapRotation, dx, dy );
1240  QgsPointXY backRotatedCoords( rotationPoint.x() + dx, rotationPoint.y() + dy );
1241 
1242  QgsRectangle unrotatedExtent = transformedExtent();
1243  double xItem = rect().width() * ( backRotatedCoords.x() - unrotatedExtent.xMinimum() ) / unrotatedExtent.width();
1244  double yItem = rect().height() * ( 1 - ( backRotatedCoords.y() - unrotatedExtent.yMinimum() ) / unrotatedExtent.height() );
1245  return QPointF( xItem, yItem );
1246 }
1247 
1249 {
1251  QgsRectangle newExtent = mExtent;
1252  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
1253  {
1254  extent = newExtent;
1255  }
1256  else
1257  {
1258  QPolygonF poly;
1259  mapPolygon( newExtent, poly );
1260  QRectF bRect = poly.boundingRect();
1261  extent.setXMinimum( bRect.left() );
1262  extent.setXMaximum( bRect.right() );
1263  extent.setYMinimum( bRect.top() );
1264  extent.setYMaximum( bRect.bottom() );
1265  }
1266  return extent;
1267 }
1268 
1270 {
1271  if ( mDrawing )
1272  return;
1273 
1274  mCacheInvalidated = true;
1275  update();
1276 }
1277 
1279 {
1280  QRectF rectangle = rect();
1281  double frameExtension = frameEnabled() ? pen().widthF() / 2.0 : 0.0;
1282 
1283  double topExtension = 0.0;
1284  double rightExtension = 0.0;
1285  double bottomExtension = 0.0;
1286  double leftExtension = 0.0;
1287 
1288  if ( mGridStack )
1289  mGridStack->calculateMaxGridExtension( topExtension, rightExtension, bottomExtension, leftExtension );
1290 
1291  topExtension = std::max( topExtension, frameExtension );
1292  rightExtension = std::max( rightExtension, frameExtension );
1293  bottomExtension = std::max( bottomExtension, frameExtension );
1294  leftExtension = std::max( leftExtension, frameExtension );
1295 
1296  rectangle.setLeft( rectangle.left() - leftExtension );
1297  rectangle.setRight( rectangle.right() + rightExtension );
1298  rectangle.setTop( rectangle.top() - topExtension );
1299  rectangle.setBottom( rectangle.bottom() + bottomExtension );
1300  if ( rectangle != mCurrentRectangle )
1301  {
1302  prepareGeometryChange();
1303  mCurrentRectangle = rectangle;
1304  }
1305 }
1306 
1308 {
1310 
1311  //updates data defined properties and redraws item to match
1312  if ( property == QgsLayoutObject::MapRotation || property == QgsLayoutObject::MapScale ||
1313  property == QgsLayoutObject::MapXMin || property == QgsLayoutObject::MapYMin ||
1314  property == QgsLayoutObject::MapXMax || property == QgsLayoutObject::MapYMax ||
1315  property == QgsLayoutObject::MapAtlasMargin ||
1316  property == QgsLayoutObject::AllProperties )
1317  {
1318  QgsRectangle beforeExtent = mExtent;
1319  refreshMapExtents( &context );
1320  emit changed();
1321  if ( mExtent != beforeExtent )
1322  {
1323  emit extentChanged();
1324  }
1325  }
1326 
1327  //force redraw
1328  mCacheInvalidated = true;
1329 
1331 }
1332 
1333 void QgsLayoutItemMap::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
1334 {
1335  if ( !mLayers.isEmpty() || mLayerStyleOverrides.isEmpty() )
1336  {
1337  for ( QgsMapLayer *layer : layers )
1338  {
1339  mLayerStyleOverrides.remove( layer->id() );
1340  }
1341  _qgis_removeLayers( mLayers, layers );
1342  }
1343 }
1344 
1345 void QgsLayoutItemMap::painterJobFinished()
1346 {
1347  mPainter->end();
1348  mPainterJob.reset( nullptr );
1349  mPainter.reset( nullptr );
1350  mCacheFinalImage = std::move( mCacheRenderingImage );
1351  mLastRenderedImageOffsetX = 0;
1352  mLastRenderedImageOffsetY = 0;
1353  update();
1354 }
1355 
1356 void QgsLayoutItemMap::shapeChanged()
1357 {
1358  // keep center as center
1359  QgsPointXY oldCenter = mExtent.center();
1360 
1361  double w = rect().width();
1362  double h = rect().height();
1363 
1364  // keep same width as before
1365  double newWidth = mExtent.width();
1366  // but scale height to match item's aspect ratio
1367  double newHeight = newWidth * h / w;
1368 
1369  mExtent = QgsRectangle::fromCenterAndSize( oldCenter, newWidth, newHeight );
1370 
1371  //recalculate data defined scale and extents
1372  refreshMapExtents();
1374  invalidateCache();
1375  emit changed();
1376  emit extentChanged();
1377 }
1378 
1379 void QgsLayoutItemMap::mapThemeChanged( const QString &theme )
1380 {
1381  if ( theme == mCachedLayerStyleOverridesPresetName )
1382  mCachedLayerStyleOverridesPresetName.clear(); // force cache regeneration at next redraw
1383 }
1384 
1385 void QgsLayoutItemMap::connectUpdateSlot()
1386 {
1387  //connect signal from layer registry to update in case of new or deleted layers
1388  QgsProject *project = mLayout->project();
1389  if ( project )
1390  {
1391  // handles updating the stored layer state BEFORE the layers are removed
1392  connect( project, static_cast < void ( QgsProject::* )( const QList<QgsMapLayer *>& layers ) > ( &QgsProject::layersWillBeRemoved ),
1393  this, &QgsLayoutItemMap::layersAboutToBeRemoved );
1394  // redraws the map AFTER layers are removed
1395  connect( project->layerTreeRoot(), &QgsLayerTree::layerOrderChanged, this, [ = ]
1396  {
1397  if ( layers().isEmpty() )
1398  {
1399  //using project layers, and layer order has changed
1400  invalidateCache();
1401  }
1402  } );
1403 
1404  connect( project, &QgsProject::crsChanged, this, [ = ]
1405  {
1406  if ( !mCrs.isValid() )
1407  {
1408  //using project CRS, which just changed....
1409  invalidateCache();
1410  }
1411  } );
1412 
1413  }
1415 
1416  connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsLayoutItemMap::mapThemeChanged );
1417 }
1418 
1419 void QgsLayoutItemMap::updateToolTip()
1420 {
1421  setToolTip( displayName() );
1422 }
1423 
1424 QList<QgsMapLayer *> QgsLayoutItemMap::layersToRender( const QgsExpressionContext *context ) const
1425 {
1426  QgsExpressionContext scopedContext;
1427  if ( !context )
1428  scopedContext = createExpressionContext();
1429  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
1430 
1431  QList<QgsMapLayer *> renderLayers;
1432 
1433  if ( mFollowVisibilityPreset )
1434  {
1435  QString presetName = mFollowVisibilityPresetName;
1436 
1437  // preset name can be overridden by data-defined one
1438  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, *evalContext, presetName );
1439 
1440  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
1441  renderLayers = mLayout->project()->mapThemeCollection()->mapThemeVisibleLayers( presetName );
1442  else // fallback to using map canvas layers
1443  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
1444  }
1445  else if ( !layers().isEmpty() )
1446  {
1447  renderLayers = layers();
1448  }
1449  else
1450  {
1451  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
1452  }
1453 
1454  bool ok = false;
1455  QString ddLayers = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapLayers, *evalContext, QString(), &ok );
1456  if ( ok )
1457  {
1458  renderLayers.clear();
1459 
1460  const QStringList layerNames = ddLayers.split( '|' );
1461  //need to convert layer names to layer ids
1462  for ( const QString &name : layerNames )
1463  {
1464  const QList< QgsMapLayer * > matchingLayers = mLayout->project()->mapLayersByName( name );
1465  for ( QgsMapLayer *layer : matchingLayers )
1466  {
1467  renderLayers << layer;
1468  }
1469  }
1470  }
1471 
1472  //remove atlas coverage layer if required
1473  if ( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagHideCoverageLayer )
1474  {
1475  //hiding coverage layer
1476  int removeAt = renderLayers.indexOf( mLayout->reportContext().layer() );
1477  if ( removeAt != -1 )
1478  {
1479  renderLayers.removeAt( removeAt );
1480  }
1481  }
1482 
1483  return renderLayers;
1484 }
1485 
1486 QMap<QString, QString> QgsLayoutItemMap::layerStyleOverridesToRender( const QgsExpressionContext &context ) const
1487 {
1488  if ( mFollowVisibilityPreset )
1489  {
1490  QString presetName = mFollowVisibilityPresetName;
1491 
1492  // data defined preset name?
1493  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
1494 
1495  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
1496  {
1497  if ( presetName.isEmpty() || presetName != mCachedLayerStyleOverridesPresetName )
1498  {
1499  // have to regenerate cache of style overrides
1500  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
1501  mCachedLayerStyleOverridesPresetName = presetName;
1502  }
1503 
1504  return mCachedPresetLayerStyleOverrides;
1505  }
1506  else
1507  return QMap<QString, QString>();
1508  }
1509  else if ( mKeepLayerStyles )
1510  {
1511  return mLayerStyleOverrides;
1512  }
1513  else
1514  {
1515  return QMap<QString, QString>();
1516  }
1517 }
1518 
1519 QgsRectangle QgsLayoutItemMap::transformedExtent() const
1520 {
1521  double dx = mXOffset;
1522  double dy = mYOffset;
1523  transformShift( dx, dy );
1524  return QgsRectangle( mExtent.xMinimum() - dx, mExtent.yMinimum() - dy, mExtent.xMaximum() - dx, mExtent.yMaximum() - dy );
1525 }
1526 
1527 void QgsLayoutItemMap::mapPolygon( const QgsRectangle &extent, QPolygonF &poly ) const
1528 {
1529  poly.clear();
1530  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
1531  {
1532  poly << QPointF( extent.xMinimum(), extent.yMaximum() );
1533  poly << QPointF( extent.xMaximum(), extent.yMaximum() );
1534  poly << QPointF( extent.xMaximum(), extent.yMinimum() );
1535  poly << QPointF( extent.xMinimum(), extent.yMinimum() );
1536  //ensure polygon is closed by readding first point
1537  poly << QPointF( poly.at( 0 ) );
1538  return;
1539  }
1540 
1541  //there is rotation
1542  QgsPointXY rotationPoint( ( extent.xMaximum() + extent.xMinimum() ) / 2.0, ( extent.yMaximum() + extent.yMinimum() ) / 2.0 );
1543  double dx, dy; //x-, y- shift from rotation point to corner point
1544 
1545  //top left point
1546  dx = rotationPoint.x() - extent.xMinimum();
1547  dy = rotationPoint.y() - extent.yMaximum();
1548  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
1549  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
1550 
1551  //top right point
1552  dx = rotationPoint.x() - extent.xMaximum();
1553  dy = rotationPoint.y() - extent.yMaximum();
1554  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
1555  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
1556 
1557  //bottom right point
1558  dx = rotationPoint.x() - extent.xMaximum();
1559  dy = rotationPoint.y() - extent.yMinimum();
1560  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
1561  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
1562 
1563  //bottom left point
1564  dx = rotationPoint.x() - extent.xMinimum();
1565  dy = rotationPoint.y() - extent.yMinimum();
1566  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
1567  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
1568 
1569  //ensure polygon is closed by readding first point
1570  poly << QPointF( poly.at( 0 ) );
1571 }
1572 
1573 void QgsLayoutItemMap::transformShift( double &xShift, double &yShift ) const
1574 {
1575  double mmToMapUnits = 1.0 / mapUnitsToLayoutUnits();
1576  double dxScaled = xShift * mmToMapUnits;
1577  double dyScaled = - yShift * mmToMapUnits;
1578 
1579  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dxScaled, dyScaled );
1580 
1581  xShift = dxScaled;
1582  yShift = dyScaled;
1583 }
1584 
1585 void QgsLayoutItemMap::drawAnnotations( QPainter *painter )
1586 {
1587  if ( !mLayout || !mLayout->project() || !mDrawAnnotations )
1588  {
1589  return;
1590  }
1591 
1592  const QList< QgsAnnotation * > annotations = mLayout->project()->annotationManager()->annotations();
1593  if ( annotations.isEmpty() )
1594  return;
1595 
1597  rc.setForceVectorOutput( true );
1599  QList< QgsMapLayer * > layers = layersToRender( &rc.expressionContext() );
1600 
1601  for ( QgsAnnotation *annotation : annotations )
1602  {
1603  if ( !annotation || !annotation->isVisible() )
1604  {
1605  continue;
1606  }
1607  if ( annotation->mapLayer() && !layers.contains( annotation->mapLayer() ) )
1608  continue;
1609 
1610  drawAnnotation( annotation, rc );
1611  }
1612 }
1613 
1614 void QgsLayoutItemMap::drawAnnotation( const QgsAnnotation *annotation, QgsRenderContext &context )
1615 {
1616  if ( !annotation || !annotation->isVisible() || !context.painter() || !context.painter()->device() )
1617  {
1618  return;
1619  }
1620 
1621  context.painter()->save();
1622  context.painter()->setRenderHint( QPainter::Antialiasing, context.flags() & QgsRenderContext::Antialiasing );
1623 
1624  double itemX, itemY;
1625  if ( annotation->hasFixedMapPosition() )
1626  {
1627  QPointF mapPos = layoutMapPosForItem( annotation );
1628  itemX = mapPos.x();
1629  itemY = mapPos.y();
1630  }
1631  else
1632  {
1633  itemX = annotation->relativePosition().x() * rect().width();
1634  itemY = annotation->relativePosition().y() * rect().height();
1635  }
1636  context.painter()->translate( itemX, itemY );
1637 
1638  //setup painter scaling to dots so that symbology is drawn to scale
1639  double dotsPerMM = context.painter()->device()->logicalDpiX() / 25.4;
1640  context.painter()->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
1641 
1642  annotation->render( context );
1643  context.painter()->restore();
1644 }
1645 
1646 QPointF QgsLayoutItemMap::layoutMapPosForItem( const QgsAnnotation *annotation ) const
1647 {
1648  if ( !annotation )
1649  return QPointF( 0, 0 );
1650 
1651  double mapX = 0.0;
1652  double mapY = 0.0;
1653 
1654  mapX = annotation->mapPosition().x();
1655  mapY = annotation->mapPosition().y();
1656  QgsCoordinateReferenceSystem annotationCrs = annotation->mapPositionCrs();
1657 
1658  if ( annotationCrs != crs() )
1659  {
1660  //need to reproject
1661  QgsCoordinateTransform t( annotationCrs, crs(), mLayout->project() );
1662  double z = 0.0;
1663  try
1664  {
1665  t.transformInPlace( mapX, mapY, z );
1666  }
1667  catch ( const QgsCsException & )
1668  {
1669  }
1670  }
1671 
1672  return mapToItemCoords( QPointF( mapX, mapY ) );
1673 }
1674 
1675 void QgsLayoutItemMap::drawMapFrame( QPainter *p )
1676 {
1677  if ( frameEnabled() && p )
1678  {
1679  p->save();
1680  p->setPen( pen() );
1681  p->setBrush( Qt::NoBrush );
1682  p->setRenderHint( QPainter::Antialiasing, true );
1683  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
1684  p->restore();
1685  }
1686 }
1687 
1688 void QgsLayoutItemMap::drawMapBackground( QPainter *p )
1689 {
1690  if ( hasBackground() && p )
1691  {
1692  p->save();
1693  p->setBrush( brush() );//this causes a problem in atlas generation
1694  p->setPen( Qt::NoPen );
1695  p->setRenderHint( QPainter::Antialiasing, true );
1696  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
1697  p->restore();
1698  }
1699 }
1700 
1701 bool QgsLayoutItemMap::shouldDrawPart( QgsLayoutItemMap::PartType part ) const
1702 {
1703  int currentExportLayer = mLayout->renderContext().currentExportLayer();
1704 
1705  if ( -1 == currentExportLayer )
1706  {
1707  //all parts of the map are visible
1708  return true;
1709  }
1710 
1711  int idx = numberExportLayers();
1712  if ( isSelected() )
1713  {
1714  --idx;
1715  if ( SelectionBoxes == part )
1716  {
1717  return currentExportLayer == idx;
1718  }
1719  }
1720 
1721  if ( frameEnabled() )
1722  {
1723  --idx;
1724  if ( Frame == part )
1725  {
1726  return currentExportLayer == idx;
1727  }
1728  }
1729  --idx;
1730  if ( OverviewMapExtent == part )
1731  {
1732  return currentExportLayer == idx;
1733  }
1734  --idx;
1735  if ( Grid == part )
1736  {
1737  return currentExportLayer == idx;
1738  }
1739  if ( hasBackground() )
1740  {
1741  if ( Background == part )
1742  {
1743  return currentExportLayer == 0;
1744  }
1745  }
1746 
1747  return true; // for Layer
1748 }
1749 
1750 void QgsLayoutItemMap::refreshMapExtents( const QgsExpressionContext *context )
1751 {
1752  QgsExpressionContext scopedContext;
1753  if ( !context )
1754  scopedContext = createExpressionContext();
1755  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
1756 
1757  //data defined map extents set?
1758  QgsRectangle newExtent = extent();
1759  bool useDdXMin = false;
1760  bool useDdXMax = false;
1761  bool useDdYMin = false;
1762  bool useDdYMax = false;
1763  double minXD = 0;
1764  double minYD = 0;
1765  double maxXD = 0;
1766  double maxYD = 0;
1767 
1768  bool ok = false;
1769  minXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMin, *evalContext, 0.0, &ok );
1770  if ( ok )
1771  {
1772  useDdXMin = true;
1773  newExtent.setXMinimum( minXD );
1774  }
1775  minYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMin, *evalContext, 0.0, &ok );
1776  if ( ok )
1777  {
1778  useDdYMin = true;
1779  newExtent.setYMinimum( minYD );
1780  }
1781  maxXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMax, *evalContext, 0.0, &ok );
1782  if ( ok )
1783  {
1784  useDdXMax = true;
1785  newExtent.setXMaximum( maxXD );
1786  }
1787  maxYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMax, *evalContext, 0.0, &ok );
1788  if ( ok )
1789  {
1790  useDdYMax = true;
1791  newExtent.setYMaximum( maxYD );
1792  }
1793 
1794  if ( newExtent != mExtent )
1795  {
1796  //calculate new extents to fit data defined extents
1797 
1798  //Make sure the width/height ratio is the same as in current map extent.
1799  //This is to keep the map item frame and the page layout fixed
1800  double currentWidthHeightRatio = mExtent.width() / mExtent.height();
1801  double newWidthHeightRatio = newExtent.width() / newExtent.height();
1802 
1803  if ( currentWidthHeightRatio < newWidthHeightRatio )
1804  {
1805  //enlarge height of new extent, ensuring the map center stays the same
1806  double newHeight = newExtent.width() / currentWidthHeightRatio;
1807  double deltaHeight = newHeight - newExtent.height();
1808  newExtent.setYMinimum( newExtent.yMinimum() - deltaHeight / 2 );
1809  newExtent.setYMaximum( newExtent.yMaximum() + deltaHeight / 2 );
1810  }
1811  else
1812  {
1813  //enlarge width of new extent, ensuring the map center stays the same
1814  double newWidth = currentWidthHeightRatio * newExtent.height();
1815  double deltaWidth = newWidth - newExtent.width();
1816  newExtent.setXMinimum( newExtent.xMinimum() - deltaWidth / 2 );
1817  newExtent.setXMaximum( newExtent.xMaximum() + deltaWidth / 2 );
1818  }
1819 
1820  mExtent = newExtent;
1821  }
1822 
1823  //now refresh scale, as this potentially overrides extents
1824 
1825  //data defined map scale set?
1826  double scaleD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapScale, *evalContext, 0.0, &ok );
1827  if ( ok )
1828  {
1829  setScale( scaleD, false );
1830  newExtent = mExtent;
1831  }
1832 
1833  if ( useDdXMax || useDdXMin || useDdYMax || useDdYMin )
1834  {
1835  //if only one of min/max was set for either x or y, then make sure our extent is locked on that value
1836  //as we can do this without altering the scale
1837  if ( useDdXMin && !useDdXMax )
1838  {
1839  double xMax = mExtent.xMaximum() - ( mExtent.xMinimum() - minXD );
1840  newExtent.setXMinimum( minXD );
1841  newExtent.setXMaximum( xMax );
1842  }
1843  else if ( !useDdXMin && useDdXMax )
1844  {
1845  double xMin = mExtent.xMinimum() - ( mExtent.xMaximum() - maxXD );
1846  newExtent.setXMinimum( xMin );
1847  newExtent.setXMaximum( maxXD );
1848  }
1849  if ( useDdYMin && !useDdYMax )
1850  {
1851  double yMax = mExtent.yMaximum() - ( mExtent.yMinimum() - minYD );
1852  newExtent.setYMinimum( minYD );
1853  newExtent.setYMaximum( yMax );
1854  }
1855  else if ( !useDdYMin && useDdYMax )
1856  {
1857  double yMin = mExtent.yMinimum() - ( mExtent.yMaximum() - maxYD );
1858  newExtent.setYMinimum( yMin );
1859  newExtent.setYMaximum( maxYD );
1860  }
1861 
1862  if ( newExtent != mExtent )
1863  {
1864  mExtent = newExtent;
1865  }
1866  }
1867 
1868  //lastly, map rotation overrides all
1869  double mapRotation = mMapRotation;
1870 
1871  //data defined map rotation set?
1872  mapRotation = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapRotation, *evalContext, mapRotation );
1873 
1874  if ( !qgsDoubleNear( mEvaluatedMapRotation, mapRotation ) )
1875  {
1876  mEvaluatedMapRotation = mapRotation;
1877  emit mapRotationChanged( mapRotation );
1878  }
1879 }
1880 
1881 void QgsLayoutItemMap::updateAtlasFeature()
1882 {
1883  if ( !atlasDriven() || !mLayout->reportContext().layer() )
1884  return; // nothing to do
1885 
1886  QgsRectangle bounds = computeAtlasRectangle();
1887  if ( bounds.isNull() )
1888  return;
1889 
1890  double xa1 = bounds.xMinimum();
1891  double xa2 = bounds.xMaximum();
1892  double ya1 = bounds.yMinimum();
1893  double ya2 = bounds.yMaximum();
1894  QgsRectangle newExtent = bounds;
1895  QgsRectangle originalExtent = mExtent;
1896 
1897  //sanity check - only allow fixed scale mode for point layers
1898  bool isPointLayer = QgsWkbTypes::geometryType( mLayout->reportContext().layer()->wkbType() ) == QgsWkbTypes::PointGeometry;
1899 
1900  if ( mAtlasScalingMode == Fixed || mAtlasScalingMode == Predefined || isPointLayer )
1901  {
1902  QgsScaleCalculator calc;
1903  calc.setMapUnits( crs().mapUnits() );
1904  calc.setDpi( 25.4 );
1905  double originalScale = calc.calculate( originalExtent, rect().width() );
1906  double geomCenterX = ( xa1 + xa2 ) / 2.0;
1907  double geomCenterY = ( ya1 + ya2 ) / 2.0;
1908 
1909  if ( mAtlasScalingMode == Fixed || isPointLayer )
1910  {
1911  // only translate, keep the original scale (i.e. width x height)
1912  double xMin = geomCenterX - originalExtent.width() / 2.0;
1913  double yMin = geomCenterY - originalExtent.height() / 2.0;
1914  newExtent = QgsRectangle( xMin,
1915  yMin,
1916  xMin + originalExtent.width(),
1917  yMin + originalExtent.height() );
1918 
1919  //scale newExtent to match original scale of map
1920  //this is required for geographic coordinate systems, where the scale varies by extent
1921  double newScale = calc.calculate( newExtent, rect().width() );
1922  newExtent.scale( originalScale / newScale );
1923  }
1924  else if ( mAtlasScalingMode == Predefined )
1925  {
1926  // choose one of the predefined scales
1927  double newWidth = originalExtent.width();
1928  double newHeight = originalExtent.height();
1929  QVector<qreal> scales = mLayout->reportContext().predefinedScales();
1930  for ( int i = 0; i < scales.size(); i++ )
1931  {
1932  double ratio = scales[i] / originalScale;
1933  newWidth = originalExtent.width() * ratio;
1934  newHeight = originalExtent.height() * ratio;
1935 
1936  // compute new extent, centered on feature
1937  double xMin = geomCenterX - newWidth / 2.0;
1938  double yMin = geomCenterY - newHeight / 2.0;
1939  newExtent = QgsRectangle( xMin,
1940  yMin,
1941  xMin + newWidth,
1942  yMin + newHeight );
1943 
1944  //scale newExtent to match desired map scale
1945  //this is required for geographic coordinate systems, where the scale varies by extent
1946  double newScale = calc.calculate( newExtent, rect().width() );
1947  newExtent.scale( scales[i] / newScale );
1948 
1949  if ( ( newExtent.width() >= bounds.width() ) && ( newExtent.height() >= bounds.height() ) )
1950  {
1951  // this is the smallest extent that embeds the feature, stop here
1952  break;
1953  }
1954  }
1955  }
1956  }
1957  else if ( mAtlasScalingMode == Auto )
1958  {
1959  // auto scale
1960 
1961  double geomRatio = bounds.width() / bounds.height();
1962  double mapRatio = originalExtent.width() / originalExtent.height();
1963 
1964  // geometry height is too big
1965  if ( geomRatio < mapRatio )
1966  {
1967  // extent the bbox's width
1968  double adjWidth = ( mapRatio * bounds.height() - bounds.width() ) / 2.0;
1969  xa1 -= adjWidth;
1970  xa2 += adjWidth;
1971  }
1972  // geometry width is too big
1973  else if ( geomRatio > mapRatio )
1974  {
1975  // extent the bbox's height
1976  double adjHeight = ( bounds.width() / mapRatio - bounds.height() ) / 2.0;
1977  ya1 -= adjHeight;
1978  ya2 += adjHeight;
1979  }
1980  newExtent = QgsRectangle( xa1, ya1, xa2, ya2 );
1981 
1982  const double evaluatedAtlasMargin = atlasMargin();
1983  if ( evaluatedAtlasMargin > 0.0 )
1984  {
1985  newExtent.scale( 1 + evaluatedAtlasMargin );
1986  }
1987  }
1988 
1989  // set the new extent (and render)
1990  setExtent( newExtent );
1991  emit preparedForAtlas();
1992 }
1993 
1994 QgsRectangle QgsLayoutItemMap::computeAtlasRectangle()
1995 {
1996  // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
1997  // We have to transform the geometry to the destination CRS and ask for the bounding box
1998  // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
1999  QgsGeometry g = mLayout->reportContext().currentGeometry( crs() );
2000  // Rotating the geometry, so the bounding box is correct wrt map rotation
2001  if ( mEvaluatedMapRotation != 0.0 )
2002  {
2003  QgsPointXY prevCenter = g.boundingBox().center();
2004  g.rotate( mEvaluatedMapRotation, g.boundingBox().center() );
2005  // Rotation center will be still the bounding box center of an unrotated geometry.
2006  // Which means, if the center of bbox moves after rotation, the viewport will
2007  // also be offset, and part of the geometry will fall out of bounds.
2008  // Here we compensate for that roughly: by extending the rotated bounds
2009  // so that its center is the same as the original.
2010  QgsRectangle bounds = g.boundingBox();
2011  double dx = std::max( std::abs( prevCenter.x() - bounds.xMinimum() ),
2012  std::abs( prevCenter.x() - bounds.xMaximum() ) );
2013  double dy = std::max( std::abs( prevCenter.y() - bounds.yMinimum() ),
2014  std::abs( prevCenter.y() - bounds.yMaximum() ) );
2015  QgsPointXY center = g.boundingBox().center();
2016  return QgsRectangle( center.x() - dx, center.y() - dy,
2017  center.x() + dx, center.y() + dy );
2018  }
2019  else
2020  {
2021  return g.boundingBox();
2022  }
2023 }
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...
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 setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
sets destination coordinate reference system
A rectangle specified with double values.
Definition: qgsrectangle.h:40
Base class for all map layer types.
Definition: qgsmaplayer.h:63
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.
QgsCoordinateReferenceSystem mapPositionCrs() const
Returns the CRS of the map position, or an invalid CRS if the annotation does not have a fixed map po...
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.
QgsMapSettings mapSettings(const QgsRectangle &extent, QSizeF size, double dpi, bool includeLayerSettings) const
Returns map settings that will be used for drawing of the map.
double calculate(const QgsRectangle &mapExtent, double canvasWidth)
Calculate the scale denominator.
An individual overview which is drawn above the map content in a QgsLayoutItemMap, and shows the extent of another QgsLayoutItemMap.
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.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:134
int type() const override
Use antialiasing while drawing.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
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.
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...
Layer and style map theme.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:171
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
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.
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:234
void extentChanged()
Is emitted when the map&#39;s extent changes.
QgsRectangle requestedExtent() const
Calculates the extent to request and the yShift of the top-left point in case of rotation.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
bool containsWmsLayer() const
Returns true if the map contains a WMS layer.
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.
TYPE * resolveWeakly(const QgsProject *project)
Resolves the map layer by attempting to find a matching layer in a project using a weak match...
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:435
void setOutputDpi(double dpi)
Sets DPI used for conversion between real world units (e.g. mm) and pixels.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
bool drawAnnotations() const
Returns whether annotations are drawn within the map.
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.
bool isVisible() const
Returns true if the annotation is visible and should be rendered.
Definition: qgsannotation.h:88
void zoomContent(double factor, QPointF point) override
Zooms content of item.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
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...
QPointF mapToItemCoords(QPointF mapCoords) const
Transforms map coordinates to item coordinates (considering rotation and move offset) ...
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()
Is emitted when the map has been prepared for atlas rendering, just before actual rendering...
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
Definition: qgsmaplayer.h:1482
Enable layer opacity and blending effects.
bool hasFixedMapPosition
Definition: qgsannotation.h:67
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...
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:48
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.
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.
Layout graphical items for displaying a map.
void setOutputSize(QSize size)
Sets the size of the resulting map image.
QString layerId
Original layer ID.
QgsPropertyCollection mDataDefinedProperties
void setTextRenderFormat(QgsRenderContext::TextRenderFormat format)
Sets the text render format, which dictates how text is rendered (e.g.
Map extent x maximum.
QgsMapThemeCollection mapThemeCollection
Definition: qgsproject.h:98
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:176
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...
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:161
Whether vector selections should be shown in the rendered map.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:238
~QgsLayoutItemMap() override
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:139
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.
double mapRotation(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the layout item, in degrees clockwise.
void refresh() override
Enable anti-aliasing for map rendering.
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:801
QPointer< QgsLayout > mLayout
void setMapUnits(QgsUnitTypes::DistanceUnit mapUnits)
Set the map units.
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:229
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
bool hasBackground() const
Returns true if the item has a background.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
void setMoveContentPreviewOffset(double dx, double dy) override
Sets temporary offset for the item, by a specified dx and dy in layout units.
Reads and writes project states.
Definition: qgsproject.h:89
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the map&#39;s preset crs (coordinate reference system).
QgsLayoutItem::Flags itemFlags() const override
Returns the item&#39;s flags, which indicate how the item behaves.
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
static void rotate(double angle, double &x, double &y)
Rotates a point / vector around the origin.
Enable drawing of vertex markers for layers in editing mode.
The extent is adjusted so that each feature is fully visible.
void render(QgsRenderContext &context) const
Renders the annotation to a target render context.
An individual grid which is drawn above the map content in a QgsLayoutItemMap.
double x
Definition: qgspointxy.h:47
void mapRotationChanged(double newRotation)
Is emitted when the map&#39;s rotation changes.
QPointF relativePosition() const
Returns the relative position of the annotation, if it is not attached to a fixed map position...
Use antialiasing when drawing items.
QString xmlData() const
Returns XML content of the style.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the stored overrides of styles for layers.
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:43
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 setAtlasDriven(bool enabled)
Sets whether the map extent will follow the current atlas feature.
Map extent y maximum.
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
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 setBackgroundColor(const QColor &color)
Sets the background color of the map.
Return the current evaluated value for the property.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
void writeXml(QDomElement &styleElement) const
Write style configuration (for project file writing)
QgsRectangle extent() const
Returns the current map extent.
Contains information about the context of a rendering operation.
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.
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
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.
QList< QgsMapLayer * > layers() const
Returns the stored layer set.
void setSelectionColor(const QColor &color)
Sets color that is used for drawing of selected vector features.
Map extent y minimum.
QgsLayoutItemMapOverview * overview()
Returns the map item&#39;s first overview.
Enable advanced effects such as blend modes.
virtual bool containsAdvancedEffects() const
Returns true if the item contains contents with blend modes or transparency effects which can only be...
double mapUnitsToLayoutUnits() const
Returns the conversion factor from map units to layout units.
void updateBoundingRect()
Updates the bounding rect of this item. Call this function before doing any changes related to annota...
void refreshed()
Is 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:144
The current scale of the map is used for each feature of the atlas.
QgsPointXY mapPosition
Definition: qgsannotation.h:68
friend class QgsLayoutItemMapGrid
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...
QColor backgroundColor() const
Returns the background color for this item.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
This class represents a coordinate reference system (CRS).
QPolygonF transformedMapPolygon() const
Returns extent that considers rotation and shift with mOffsetX / mOffsetY.
void assignFreeId()
Sets the map id() to a number not yet used in the layout.
QString authid() const
Returns the authority identifier for the CRS.
Flags flags() const
Returns combination of flags used for rendering.
Class for doing transforms between two map coordinate systems.
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.
QString id() const
Returns the item&#39;s ID name.
double scale() const
Returns the map scale.
void layerStyleOverridesChanged()
Emitted when layer style overrides are changed...
Enable vector simplification and other rendering optimizations.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the stored layers set.
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...
Item overrides the default layout item painting method.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
const QgsLayout * layout() const
Returns the layout the object is attached to.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project&#39;s layer tree.
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)
int numberExportLayers() const override
Returns the number of layers that this item requires for exporting during layered exports (e...
bool atlasDriven() const
Returns whether the map extent is set to follow the current atlas feature.
TYPE * resolve(const QgsProject *project)
Resolves the map layer by attempting to find a layer with matching ID within a project.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:201
void changed()
Emitted when the object&#39;s properties change.
DataDefinedProperty
Data defined properties for different item types.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:166
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.
bool frameEnabled() const
Returns true if the item includes a frame.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
QPolygonF visibleExtentPolygon() const
Returns a polygon representing the current visible map extent, considering map extents and rotation...
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:129
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:208
A scale is chosen from the predefined scales.
QString toProj4() const
Returns a Proj4 string representation of this CRS.
All properties for item.
void setLayers(const QList< QgsMapLayer * > &layers)
Set list of layers for map rendering.
QgsLayoutItemMapGrid * grid()
Returns the map item&#39;s first grid.