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