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