QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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  if ( mOverviewStack->hasEnabledItems() )
1063  {
1064  mCurrentExportPart = OverviewMapExtent;
1065  return true;
1066  }
1067  FALLTHROUGH
1068 
1069  case OverviewMapExtent:
1070  if ( frameEnabled() )
1071  {
1072  mCurrentExportPart = Frame;
1073  return true;
1074  }
1075 
1076  FALLTHROUGH
1077 
1078  case Frame:
1079  if ( isSelected() && !mLayout->renderContext().isPreviewRender() )
1080  {
1081  mCurrentExportPart = SelectionBoxes;
1082  return true;
1083  }
1084  FALLTHROUGH
1085 
1086  case SelectionBoxes:
1087  mCurrentExportPart = End;
1088  return false;
1089 
1090  case End:
1091  return false;
1092 
1093  case NotLayered:
1094  return false;
1095  }
1096  return false;
1097 }
1098 
1100 {
1101  return ItemContainsSubLayers;
1102 }
1103 
1105 {
1106  ExportLayerDetail detail;
1107 
1108  switch ( mCurrentExportPart )
1109  {
1110  case Start:
1111  break;
1112 
1113  case Background:
1114  detail.name = tr( "%1: Background" ).arg( displayName() );
1115  return detail;
1116 
1117  case Layer:
1118  if ( !mExportThemes.empty() && mExportThemeIt != mExportThemes.end() )
1119  detail.mapTheme = *mExportThemeIt;
1120 
1121  if ( mStagedRendererJob )
1122  {
1123  switch ( mStagedRendererJob->currentStage() )
1124  {
1126  {
1127  detail.mapLayerId = mStagedRendererJob->currentLayerId();
1128  if ( const QgsMapLayer *layer = mLayout->project()->mapLayer( detail.mapLayerId ) )
1129  {
1130  if ( !detail.mapTheme.isEmpty() )
1131  detail.name = QStringLiteral( "%1 (%2): %3" ).arg( displayName(), detail.mapTheme, layer->name() );
1132  else
1133  detail.name = QStringLiteral( "%1: %2" ).arg( displayName(), layer->name() );
1134  }
1135  return detail;
1136  }
1137 
1139  detail.mapLayerId = mStagedRendererJob->currentLayerId();
1140  if ( const QgsMapLayer *layer = mLayout->project()->mapLayer( detail.mapLayerId ) )
1141  {
1142  if ( !detail.mapTheme.isEmpty() )
1143  detail.name = QStringLiteral( "%1 (%2): %3 (Labels)" ).arg( displayName(), detail.mapTheme, layer->name() );
1144  else
1145  detail.name = tr( "%1: %2 (Labels)" ).arg( displayName(), layer->name() );
1146  }
1147  else
1148  {
1149  if ( !detail.mapTheme.isEmpty() )
1150  detail.name = tr( "%1 (%2): Labels" ).arg( displayName(), detail.mapTheme );
1151  else
1152  detail.name = tr( "%1: Labels" ).arg( displayName() );
1153  }
1154  return detail;
1155 
1157  break;
1158  }
1159  }
1160  else
1161  {
1162  // we must be on the first layer, not having had a chance to create the render job yet
1163  const QList< QgsMapLayer * > layers = layersToRender();
1164  if ( !layers.isEmpty() )
1165  {
1166  const QgsMapLayer *layer = layers.constLast();
1167  if ( !detail.mapTheme.isEmpty() )
1168  detail.name = QStringLiteral( "%1 (%2): %3" ).arg( displayName(), detail.mapTheme, layer->name() );
1169  else
1170  detail.name = QStringLiteral( "%1: %2" ).arg( displayName(), layer->name() );
1171  detail.mapLayerId = layer->id();
1172  }
1173  }
1174  break;
1175 
1176  case Grid:
1177  detail.name = tr( "%1: Grids" ).arg( displayName() );
1178  return detail;
1179 
1180  case OverviewMapExtent:
1181  detail.name = tr( "%1: Overviews" ).arg( displayName() );
1182  return detail;
1183 
1184  case Frame:
1185  detail.name = tr( "%1: Frame" ).arg( displayName() );
1186  return detail;
1187 
1188  case SelectionBoxes:
1189  case End:
1190  case NotLayered:
1191  break;
1192  }
1193 
1194  return detail;
1195 }
1196 
1198 {
1201 }
1202 
1203 void QgsLayoutItemMap::drawMap( QPainter *painter, const QgsRectangle &extent, QSizeF size, double dpi )
1204 {
1205  if ( !painter )
1206  {
1207  return;
1208  }
1209  if ( qgsDoubleNear( size.width(), 0.0 ) || qgsDoubleNear( size.height(), 0.0 ) )
1210  {
1211  //don't attempt to draw if size is invalid
1212  return;
1213  }
1214 
1215  // render
1216  QgsMapSettings ms( mapSettings( extent, size, dpi, true ) );
1217  if ( shouldDrawPart( OverviewMapExtent ) )
1218  {
1219  ms.setLayers( mOverviewStack->modifyMapLayerList( ms.layers() ) );
1220  }
1221 
1222  QgsMapRendererCustomPainterJob job( ms, painter );
1223  // Render the map in this thread. This is done because of problems
1224  // with printing to printer on Windows (printing to PDF is fine though).
1225  // Raster images were not displayed - see #10599
1226  job.renderSynchronously();
1227 
1228  mRenderingErrors = job.errors();
1229 }
1230 
1231 void QgsLayoutItemMap::recreateCachedImageInBackground()
1232 {
1233  if ( mPainterJob )
1234  {
1235  disconnect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
1236  QgsMapRendererCustomPainterJob *oldJob = mPainterJob.release();
1237  QPainter *oldPainter = mPainter.release();
1238  QImage *oldImage = mCacheRenderingImage.release();
1239  connect( oldJob, &QgsMapRendererCustomPainterJob::finished, this, [oldPainter, oldJob, oldImage]
1240  {
1241  oldJob->deleteLater();
1242  delete oldPainter;
1243  delete oldImage;
1244  } );
1245  oldJob->cancelWithoutBlocking();
1246  }
1247  else
1248  {
1249  mCacheRenderingImage.reset( nullptr );
1250  emit backgroundTaskCountChanged( 1 );
1251  }
1252 
1253  Q_ASSERT( !mPainterJob );
1254  Q_ASSERT( !mPainter );
1255  Q_ASSERT( !mCacheRenderingImage );
1256 
1257  QgsRectangle ext = extent();
1258  double widthLayoutUnits = ext.width() * mapUnitsToLayoutUnits();
1259  double heightLayoutUnits = ext.height() * mapUnitsToLayoutUnits();
1260 
1261  int w = static_cast< int >( std::round( widthLayoutUnits * mPreviewScaleFactor ) );
1262  int h = static_cast< int >( std::round( heightLayoutUnits * mPreviewScaleFactor ) );
1263 
1264  // limit size of image for better performance
1265  if ( w > 5000 || h > 5000 )
1266  {
1267  if ( w > h )
1268  {
1269  w = 5000;
1270  h = static_cast< int>( std::round( w * heightLayoutUnits / widthLayoutUnits ) );
1271  }
1272  else
1273  {
1274  h = 5000;
1275  w = static_cast< int >( std::round( h * widthLayoutUnits / heightLayoutUnits ) );
1276  }
1277  }
1278 
1279  if ( w <= 0 || h <= 0 )
1280  return;
1281 
1282  mCacheRenderingImage.reset( new QImage( w, h, QImage::Format_ARGB32 ) );
1283 
1284  // set DPI of the image
1285  mCacheRenderingImage->setDotsPerMeterX( static_cast< int >( std::round( 1000 * w / widthLayoutUnits ) ) );
1286  mCacheRenderingImage->setDotsPerMeterY( static_cast< int >( std::round( 1000 * h / heightLayoutUnits ) ) );
1287 
1288  if ( hasBackground() )
1289  {
1290  //Initially fill image with specified background color. This ensures that layers with blend modes will
1291  //preview correctly
1292  mCacheRenderingImage->fill( backgroundColor().rgba() );
1293  }
1294  else
1295  {
1296  //no background, but start with empty fill to avoid artifacts
1297  mCacheRenderingImage->fill( QColor( 255, 255, 255, 0 ).rgba() );
1298  }
1299 
1300  mCacheInvalidated = false;
1301  mPainter.reset( new QPainter( mCacheRenderingImage.get() ) );
1302  QgsMapSettings settings( mapSettings( ext, QSizeF( w, h ), mCacheRenderingImage->logicalDpiX(), true ) );
1303 
1304  if ( shouldDrawPart( OverviewMapExtent ) )
1305  {
1306  settings.setLayers( mOverviewStack->modifyMapLayerList( settings.layers() ) );
1307  }
1308 
1309  mPainterJob.reset( new QgsMapRendererCustomPainterJob( settings, mPainter.get() ) );
1310  connect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
1311  mPainterJob->start();
1312 
1313  // from now on we can accept refresh requests again
1314  // this must be reset only after the job has been started, because
1315  // some providers (yes, it's you WCS and AMS!) during preparation
1316  // do network requests and start an internal event loop, which may
1317  // end up calling refresh() and would schedule another refresh,
1318  // deleting the one we have just started.
1319 
1320  // ^^ that comment was directly copied from a similar fix in QgsMapCanvas. And
1321  // with little surprise, both those providers are still badly behaved and causing
1322  // annoying bugs for us to deal with...
1323  mDrawingPreview = false;
1324 }
1325 
1326 QgsLayoutItemMap::MapItemFlags QgsLayoutItemMap::mapFlags() const
1327 {
1328  return mMapFlags;
1329 }
1330 
1331 void QgsLayoutItemMap::setMapFlags( QgsLayoutItemMap::MapItemFlags mapFlags )
1332 {
1333  mMapFlags = mapFlags;
1334 }
1335 
1336 QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, double dpi, bool includeLayerSettings ) const
1337 {
1338  QgsExpressionContext expressionContext = createExpressionContext();
1339  QgsCoordinateReferenceSystem renderCrs = crs();
1340 
1341  QgsMapSettings jobMapSettings;
1342  jobMapSettings.setDestinationCrs( renderCrs );
1343  jobMapSettings.setExtent( extent );
1344  jobMapSettings.setOutputSize( size.toSize() );
1345  jobMapSettings.setOutputDpi( dpi );
1346  jobMapSettings.setBackgroundColor( Qt::transparent );
1347  jobMapSettings.setRotation( mEvaluatedMapRotation );
1348  if ( mLayout )
1349  jobMapSettings.setEllipsoid( mLayout->project()->ellipsoid() );
1350 
1351  if ( includeLayerSettings )
1352  {
1353  //set layers to render
1354  QList<QgsMapLayer *> layers = layersToRender( &expressionContext );
1355  jobMapSettings.setLayers( layers );
1356  jobMapSettings.setLayerStyleOverrides( layerStyleOverridesToRender( expressionContext ) );
1357  }
1358 
1359  if ( !mLayout->renderContext().isPreviewRender() )
1360  {
1361  //if outputting layout, we disable optimisations like layer simplification by default, UNLESS the context specifically tells us to use them
1362  jobMapSettings.setFlag( QgsMapSettings::UseRenderingOptimization, mLayout->renderContext().simplifyMethod().simplifyHints() != QgsVectorSimplifyMethod::NoSimplification );
1363  jobMapSettings.setSimplifyMethod( mLayout->renderContext().simplifyMethod() );
1364  }
1365  else
1366  {
1367  // preview render - always use optimization
1368  jobMapSettings.setFlag( QgsMapSettings::UseRenderingOptimization, true );
1369  }
1370 
1371  jobMapSettings.setExpressionContext( expressionContext );
1372 
1373  // layout-specific overrides of flags
1374  jobMapSettings.setFlag( QgsMapSettings::ForceVectorOutput, true ); // force vector output (no caching of marker images etc.)
1375  jobMapSettings.setFlag( QgsMapSettings::Antialiasing, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
1376  jobMapSettings.setFlag( QgsMapSettings::DrawEditingInfo, false );
1377  jobMapSettings.setSelectionColor( mLayout->renderContext().selectionColor() );
1378  jobMapSettings.setFlag( QgsMapSettings::DrawSelection, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagDrawSelection );
1381  jobMapSettings.setTransformContext( mLayout->project()->transformContext() );
1382  jobMapSettings.setPathResolver( mLayout->project()->pathResolver() );
1383 
1384  QgsLabelingEngineSettings labelSettings = mLayout->project()->labelingEngineSettings();
1385 
1386  // override project "show partial labels" setting with this map's setting
1389  jobMapSettings.setLabelingEngineSettings( labelSettings );
1390 
1391  // override the default text render format inherited from the labeling engine settings using the layout's render context setting
1392  jobMapSettings.setTextRenderFormat( mLayout->renderContext().textRenderFormat() );
1393 
1394  if ( mEvaluatedLabelMargin.length() > 0 )
1395  {
1396  QPolygonF visiblePoly = jobMapSettings.visiblePolygon();
1397  visiblePoly.append( visiblePoly.at( 0 ) ); //close polygon
1398  const double layoutLabelMargin = mLayout->convertToLayoutUnits( mEvaluatedLabelMargin );
1399  const double layoutLabelMarginInMapUnits = layoutLabelMargin / rect().width() * jobMapSettings.extent().width();
1400  QgsGeometry mapBoundaryGeom = QgsGeometry::fromQPolygonF( visiblePoly );
1401  mapBoundaryGeom = mapBoundaryGeom.buffer( -layoutLabelMarginInMapUnits, 0 );
1402  jobMapSettings.setLabelBoundaryGeometry( mapBoundaryGeom );
1403  }
1404 
1405  if ( !mBlockingLabelItems.isEmpty() )
1406  {
1407  jobMapSettings.setLabelBlockingRegions( createLabelBlockingRegions( jobMapSettings ) );
1408  }
1409 
1410  for ( QgsRenderedFeatureHandlerInterface *handler : qgis::as_const( mRenderedFeatureHandlers ) )
1411  {
1412  jobMapSettings.addRenderedFeatureHandler( handler );
1413  }
1414 
1415  return jobMapSettings;
1416 }
1417 
1419 {
1420  assignFreeId();
1421 
1422  mBlockingLabelItems.clear();
1423  for ( const QString &uuid : qgis::as_const( mBlockingLabelItemUuids ) )
1424  {
1425  QgsLayoutItem *item = mLayout->itemByUuid( uuid, true );
1426  if ( item )
1427  {
1428  addLabelBlockingItem( item );
1429  }
1430  }
1431 
1432  mOverviewStack->finalizeRestoreFromXml();
1433  mGridStack->finalizeRestoreFromXml();
1434 }
1435 
1436 void QgsLayoutItemMap::setMoveContentPreviewOffset( double xOffset, double yOffset )
1437 {
1438  mXOffset = xOffset;
1439  mYOffset = yOffset;
1440 }
1441 
1443 {
1444  return mCurrentRectangle;
1445 }
1446 
1448 {
1450 
1451  //Can't utilize QgsExpressionContextUtils::mapSettingsScope as we don't always
1452  //have a QgsMapSettings object available when the context is required, so we manually
1453  //add the same variables here
1454  QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Map Settings" ) );
1455 
1456  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_id" ), id(), true ) );
1457  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_rotation" ), mMapRotation, true ) );
1458  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_scale" ), scale(), true ) );
1459 
1460  QgsRectangle currentExtent( extent() );
1461  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent" ), QVariant::fromValue( QgsGeometry::fromRect( currentExtent ) ), true ) );
1462  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_width" ), currentExtent.width(), true ) );
1463  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_height" ), currentExtent.height(), true ) );
1464  QgsGeometry centerPoint = QgsGeometry::fromPointXY( currentExtent.center() );
1465  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_center" ), QVariant::fromValue( centerPoint ), true ) );
1466 
1467  QgsCoordinateReferenceSystem mapCrs = crs();
1468  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs" ), mapCrs.authid(), true ) );
1469  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_definition" ), mapCrs.toProj4(), true ) );
1470  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_description" ), mapCrs.description(), true ) );
1471  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_units" ), QgsUnitTypes::toString( mapCrs.mapUnits() ), true ) );
1472  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_acronym" ), mapCrs.projectionAcronym(), true ) );
1473  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_ellipsoid" ), mapCrs.ellipsoidAcronym(), true ) );
1474  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_proj4" ), mapCrs.toProj4(), true ) );
1475  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_wkt" ), mapCrs.toWkt(), true ) );
1476 
1477  QVariantList layersIds;
1478  QVariantList layers;
1479  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layer_ids" ), layersIds, true ) );
1480  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layers" ), layers, true ) );
1481 
1482  context.appendScope( scope );
1483 
1484  // The scope map_layer_ids and map_layers variables have been added to the context, only now we can
1485  // call layersToRender (just in case layersToRender relies on evaluating an expression which uses
1486  // other variables contained within the map settings scope
1487  const QList<QgsMapLayer *> layersInMap = layersToRender( &context );
1488 
1489  layersIds.reserve( layersInMap.count() );
1490  layers.reserve( layersInMap.count() );
1491  for ( QgsMapLayer *layer : layersInMap )
1492  {
1493  layersIds << layer->id();
1494  layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( layer ) );
1495  }
1496  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layer_ids" ), layersIds, true ) );
1497  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layers" ), layers, true ) );
1498 
1499  scope->addFunction( QStringLiteral( "is_layer_visible" ), new QgsExpressionContextUtils::GetLayerVisibility( layersInMap, scale() ) );
1500 
1501  return context;
1502 }
1503 
1505 {
1506  double extentWidth = extent().width();
1507  if ( extentWidth <= 0 )
1508  {
1509  return 1;
1510  }
1511  return rect().width() / extentWidth;
1512 }
1513 
1515 {
1516  double dx = mXOffset;
1517  double dy = mYOffset;
1518  transformShift( dx, dy );
1519  QPolygonF poly = visibleExtentPolygon();
1520  poly.translate( -dx, -dy );
1521  return poly;
1522 }
1523 
1525 {
1526  if ( !mBlockingLabelItems.contains( item ) )
1527  mBlockingLabelItems.append( item );
1528 
1529  connect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemMap::invalidateCache, Qt::UniqueConnection );
1530 }
1531 
1533 {
1534  mBlockingLabelItems.removeAll( item );
1535  if ( item )
1537 }
1538 
1540 {
1541  return mBlockingLabelItems.contains( item );
1542 }
1543 
1545 {
1546  // NOTE: if visitEnter returns false it means "don't visit the item", not "abort all further visitations"
1548  return true;
1549 
1550  if ( mOverviewStack )
1551  {
1552  for ( int i = 0; i < mOverviewStack->size(); ++i )
1553  {
1554  if ( mOverviewStack->item( i )->accept( visitor ) )
1555  return false;
1556  }
1557  }
1558 
1559  if ( mGridStack )
1560  {
1561  for ( int i = 0; i < mGridStack->size(); ++i )
1562  {
1563  if ( mGridStack->item( i )->accept( visitor ) )
1564  return false;
1565  }
1566  }
1567 
1569  return false;
1570 
1571  return true;
1572 }
1573 
1575 {
1576  mRenderedFeatureHandlers.append( handler );
1577 }
1578 
1580 {
1581  mRenderedFeatureHandlers.removeAll( handler );
1582 }
1583 
1584 QPointF QgsLayoutItemMap::mapToItemCoords( QPointF mapCoords ) const
1585 {
1586  QPolygonF mapPoly = transformedMapPolygon();
1587  if ( mapPoly.empty() )
1588  {
1589  return QPointF( 0, 0 );
1590  }
1591 
1592  QgsRectangle tExtent = transformedExtent();
1593  QgsPointXY rotationPoint( ( tExtent.xMaximum() + tExtent.xMinimum() ) / 2.0, ( tExtent.yMaximum() + tExtent.yMinimum() ) / 2.0 );
1594  double dx = mapCoords.x() - rotationPoint.x();
1595  double dy = mapCoords.y() - rotationPoint.y();
1596  QgsLayoutUtils::rotate( -mEvaluatedMapRotation, dx, dy );
1597  QgsPointXY backRotatedCoords( rotationPoint.x() + dx, rotationPoint.y() + dy );
1598 
1599  QgsRectangle unrotatedExtent = transformedExtent();
1600  double xItem = rect().width() * ( backRotatedCoords.x() - unrotatedExtent.xMinimum() ) / unrotatedExtent.width();
1601  double yItem = rect().height() * ( 1 - ( backRotatedCoords.y() - unrotatedExtent.yMinimum() ) / unrotatedExtent.height() );
1602  return QPointF( xItem, yItem );
1603 }
1604 
1606 {
1608  QgsRectangle newExtent = mExtent;
1609  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
1610  {
1611  extent = newExtent;
1612  }
1613  else
1614  {
1615  QPolygonF poly;
1616  mapPolygon( newExtent, poly );
1617  QRectF bRect = poly.boundingRect();
1618  extent.setXMinimum( bRect.left() );
1619  extent.setXMaximum( bRect.right() );
1620  extent.setYMinimum( bRect.top() );
1621  extent.setYMaximum( bRect.bottom() );
1622  }
1623  return extent;
1624 }
1625 
1627 {
1628  if ( mDrawing )
1629  return;
1630 
1631  mCacheInvalidated = true;
1632  update();
1633 }
1634 
1636 {
1637  QRectF rectangle = rect();
1638  double frameExtension = frameEnabled() ? pen().widthF() / 2.0 : 0.0;
1639 
1640  double topExtension = 0.0;
1641  double rightExtension = 0.0;
1642  double bottomExtension = 0.0;
1643  double leftExtension = 0.0;
1644 
1645  if ( mGridStack )
1646  mGridStack->calculateMaxGridExtension( topExtension, rightExtension, bottomExtension, leftExtension );
1647 
1648  topExtension = std::max( topExtension, frameExtension );
1649  rightExtension = std::max( rightExtension, frameExtension );
1650  bottomExtension = std::max( bottomExtension, frameExtension );
1651  leftExtension = std::max( leftExtension, frameExtension );
1652 
1653  rectangle.setLeft( rectangle.left() - leftExtension );
1654  rectangle.setRight( rectangle.right() + rightExtension );
1655  rectangle.setTop( rectangle.top() - topExtension );
1656  rectangle.setBottom( rectangle.bottom() + bottomExtension );
1657  if ( rectangle != mCurrentRectangle )
1658  {
1659  prepareGeometryChange();
1660  mCurrentRectangle = rectangle;
1661  }
1662 }
1663 
1665 {
1667 
1668  //updates data defined properties and redraws item to match
1669  if ( property == QgsLayoutObject::MapRotation || property == QgsLayoutObject::MapScale ||
1670  property == QgsLayoutObject::MapXMin || property == QgsLayoutObject::MapYMin ||
1671  property == QgsLayoutObject::MapXMax || property == QgsLayoutObject::MapYMax ||
1672  property == QgsLayoutObject::MapAtlasMargin ||
1673  property == QgsLayoutObject::AllProperties )
1674  {
1675  QgsRectangle beforeExtent = mExtent;
1676  refreshMapExtents( &context );
1677  emit changed();
1678  if ( mExtent != beforeExtent )
1679  {
1680  emit extentChanged();
1681  }
1682  }
1683  if ( property == QgsLayoutObject::MapLabelMargin || property == QgsLayoutObject::AllProperties )
1684  {
1685  refreshLabelMargin( false );
1686  }
1687 
1688  //force redraw
1689  mCacheInvalidated = true;
1690 
1692 }
1693 
1694 void QgsLayoutItemMap::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
1695 {
1696  if ( !mLayers.isEmpty() || mLayerStyleOverrides.isEmpty() )
1697  {
1698  for ( QgsMapLayer *layer : layers )
1699  {
1700  mLayerStyleOverrides.remove( layer->id() );
1701  }
1702  _qgis_removeLayers( mLayers, layers );
1703  }
1704 }
1705 
1706 void QgsLayoutItemMap::painterJobFinished()
1707 {
1708  mPainter->end();
1709  mPainterJob.reset( nullptr );
1710  mPainter.reset( nullptr );
1711  mCacheFinalImage = std::move( mCacheRenderingImage );
1712  mLastRenderedImageOffsetX = 0;
1713  mLastRenderedImageOffsetY = 0;
1714  emit backgroundTaskCountChanged( 0 );
1715  update();
1716 }
1717 
1718 void QgsLayoutItemMap::shapeChanged()
1719 {
1720  // keep center as center
1721  QgsPointXY oldCenter = mExtent.center();
1722 
1723  double w = rect().width();
1724  double h = rect().height();
1725 
1726  // keep same width as before
1727  double newWidth = mExtent.width();
1728  // but scale height to match item's aspect ratio
1729  double newHeight = newWidth * h / w;
1730 
1731  mExtent = QgsRectangle::fromCenterAndSize( oldCenter, newWidth, newHeight );
1732 
1733  //recalculate data defined scale and extents
1734  refreshMapExtents();
1736  invalidateCache();
1737  emit changed();
1738  emit extentChanged();
1739 }
1740 
1741 void QgsLayoutItemMap::mapThemeChanged( const QString &theme )
1742 {
1743  if ( theme == mCachedLayerStyleOverridesPresetName )
1744  mCachedLayerStyleOverridesPresetName.clear(); // force cache regeneration at next redraw
1745 }
1746 
1747 void QgsLayoutItemMap::connectUpdateSlot()
1748 {
1749  //connect signal from layer registry to update in case of new or deleted layers
1750  QgsProject *project = mLayout->project();
1751  if ( project )
1752  {
1753  // handles updating the stored layer state BEFORE the layers are removed
1754  connect( project, static_cast < void ( QgsProject::* )( const QList<QgsMapLayer *>& layers ) > ( &QgsProject::layersWillBeRemoved ),
1755  this, &QgsLayoutItemMap::layersAboutToBeRemoved );
1756  // redraws the map AFTER layers are removed
1757  connect( project->layerTreeRoot(), &QgsLayerTree::layerOrderChanged, this, [ = ]
1758  {
1759  if ( layers().isEmpty() )
1760  {
1761  //using project layers, and layer order has changed
1762  invalidateCache();
1763  }
1764  } );
1765 
1766  connect( project, &QgsProject::crsChanged, this, [ = ]
1767  {
1768  if ( !mCrs.isValid() )
1769  {
1770  //using project CRS, which just changed....
1771  invalidateCache();
1772  }
1773  } );
1774 
1775  // If project colors change, we need to redraw the map, as layer symbols may rely on project colors
1776  connect( project, &QgsProject::projectColorsChanged, this, [ = ]
1777  {
1778  invalidateCache();
1779  } );
1780  }
1782  connect( &mLayout->renderContext(), &QgsLayoutRenderContext::predefinedScalesChanged, this, [ = ]
1783  {
1784  if ( mAtlasScalingMode == Predefined )
1785  updateAtlasFeature();
1786  } );
1787 
1788  connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsLayoutItemMap::mapThemeChanged );
1789 }
1790 
1792 {
1793  QPolygonF thisExtent = visibleExtentPolygon();
1794  QTransform mapTransform;
1795  QPolygonF thisRectPoly = QPolygonF( QRectF( 0, 0, rect().width(), rect().height() ) );
1796  //workaround QT Bug #21329
1797  thisRectPoly.pop_back();
1798  thisExtent.pop_back();
1799 
1800  QPolygonF thisItemPolyInLayout = mapToScene( thisRectPoly );
1801 
1802  //create transform from layout coordinates to map coordinates
1803  QTransform::quadToQuad( thisItemPolyInLayout, thisExtent, mapTransform );
1804  return mapTransform;
1805 }
1806 
1807 QList<QgsLabelBlockingRegion> QgsLayoutItemMap::createLabelBlockingRegions( const QgsMapSettings & ) const
1808 {
1809  const QTransform mapTransform = layoutToMapCoordsTransform();
1810  QList< QgsLabelBlockingRegion > blockers;
1811  blockers.reserve( mBlockingLabelItems.count() );
1812  for ( const auto &item : qgis::as_const( mBlockingLabelItems ) )
1813  {
1814  // invisible items don't block labels!
1815  if ( !item )
1816  continue;
1817 
1818  // layout items may be temporarily hidden during layered exports
1819  if ( item->property( "wasVisible" ).isValid() )
1820  {
1821  if ( !item->property( "wasVisible" ).toBool() )
1822  continue;
1823  }
1824  else if ( !item->isVisible() )
1825  continue;
1826 
1827  QPolygonF itemRectInMapCoordinates = mapTransform.map( item->mapToScene( item->rect() ) );
1828  itemRectInMapCoordinates.append( itemRectInMapCoordinates.at( 0 ) ); //close polygon
1829  QgsGeometry blockingRegion = QgsGeometry::fromQPolygonF( itemRectInMapCoordinates );
1830  blockers << QgsLabelBlockingRegion( blockingRegion );
1831  }
1832  return blockers;
1833 }
1834 
1836 {
1837  return mLabelMargin;
1838 }
1839 
1841 {
1842  mLabelMargin = margin;
1843  refreshLabelMargin( false );
1844 }
1845 
1846 void QgsLayoutItemMap::updateToolTip()
1847 {
1848  setToolTip( displayName() );
1849 }
1850 
1851 QString QgsLayoutItemMap::themeToRender( const QgsExpressionContext &context ) const
1852 {
1853  QString presetName;
1854 
1855  if ( mFollowVisibilityPreset )
1856  {
1857  presetName = mFollowVisibilityPresetName;
1858  // preset name can be overridden by data-defined one
1859  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
1860  }
1861  else if ( !mExportThemes.empty() && mExportThemeIt != mExportThemes.end() )
1862  presetName = *mExportThemeIt;
1863  return presetName;
1864 }
1865 
1866 QList<QgsMapLayer *> QgsLayoutItemMap::layersToRender( const QgsExpressionContext *context ) const
1867 {
1868  QgsExpressionContext scopedContext;
1869  if ( !context )
1870  scopedContext = createExpressionContext();
1871  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
1872 
1873  QList<QgsMapLayer *> renderLayers;
1874 
1875  QString presetName = themeToRender( *evalContext );
1876  if ( !presetName.isEmpty() )
1877  {
1878  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
1879  renderLayers = mLayout->project()->mapThemeCollection()->mapThemeVisibleLayers( presetName );
1880  else // fallback to using map canvas layers
1881  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
1882  }
1883  else if ( !layers().isEmpty() )
1884  {
1885  renderLayers = layers();
1886  }
1887  else
1888  {
1889  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
1890  }
1891 
1892  bool ok = false;
1893  QString ddLayers = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapLayers, *evalContext, QString(), &ok );
1894  if ( ok )
1895  {
1896  renderLayers.clear();
1897 
1898  const QStringList layerNames = ddLayers.split( '|' );
1899  //need to convert layer names to layer ids
1900  for ( const QString &name : layerNames )
1901  {
1902  const QList< QgsMapLayer * > matchingLayers = mLayout->project()->mapLayersByName( name );
1903  for ( QgsMapLayer *layer : matchingLayers )
1904  {
1905  renderLayers << layer;
1906  }
1907  }
1908  }
1909 
1910  //remove atlas coverage layer if required
1911  if ( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagHideCoverageLayer )
1912  {
1913  //hiding coverage layer
1914  int removeAt = renderLayers.indexOf( mLayout->reportContext().layer() );
1915  if ( removeAt != -1 )
1916  {
1917  renderLayers.removeAt( removeAt );
1918  }
1919  }
1920 
1921  // remove any invalid layers
1922  renderLayers.erase( std::remove_if( renderLayers.begin(), renderLayers.end(), []( QgsMapLayer * layer )
1923  {
1924  return !layer || !layer->isValid();
1925  } ), renderLayers.end() );
1926 
1927  return renderLayers;
1928 }
1929 
1930 QMap<QString, QString> QgsLayoutItemMap::layerStyleOverridesToRender( const QgsExpressionContext &context ) const
1931 {
1932  QString presetName = themeToRender( context );
1933  if ( !presetName.isEmpty() )
1934  {
1935  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
1936  {
1937  if ( presetName != mCachedLayerStyleOverridesPresetName )
1938  {
1939  // have to regenerate cache of style overrides
1940  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
1941  mCachedLayerStyleOverridesPresetName = presetName;
1942  }
1943 
1944  return mCachedPresetLayerStyleOverrides;
1945  }
1946  else
1947  return QMap<QString, QString>();
1948  }
1949  else if ( mFollowVisibilityPreset )
1950  {
1951  QString presetName = mFollowVisibilityPresetName;
1952  // data defined preset name?
1953  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
1954  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
1955  {
1956  if ( presetName.isEmpty() || presetName != mCachedLayerStyleOverridesPresetName )
1957  {
1958  // have to regenerate cache of style overrides
1959  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
1960  mCachedLayerStyleOverridesPresetName = presetName;
1961  }
1962 
1963  return mCachedPresetLayerStyleOverrides;
1964  }
1965  else
1966  return QMap<QString, QString>();
1967  }
1968  else if ( mKeepLayerStyles )
1969  {
1970  return mLayerStyleOverrides;
1971  }
1972  else
1973  {
1974  return QMap<QString, QString>();
1975  }
1976 }
1977 
1978 QgsRectangle QgsLayoutItemMap::transformedExtent() const
1979 {
1980  double dx = mXOffset;
1981  double dy = mYOffset;
1982  transformShift( dx, dy );
1983  return QgsRectangle( mExtent.xMinimum() - dx, mExtent.yMinimum() - dy, mExtent.xMaximum() - dx, mExtent.yMaximum() - dy );
1984 }
1985 
1986 void QgsLayoutItemMap::mapPolygon( const QgsRectangle &extent, QPolygonF &poly ) const
1987 {
1988  poly.clear();
1989  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
1990  {
1991  poly << QPointF( extent.xMinimum(), extent.yMaximum() );
1992  poly << QPointF( extent.xMaximum(), extent.yMaximum() );
1993  poly << QPointF( extent.xMaximum(), extent.yMinimum() );
1994  poly << QPointF( extent.xMinimum(), extent.yMinimum() );
1995  //ensure polygon is closed by readding first point
1996  poly << QPointF( poly.at( 0 ) );
1997  return;
1998  }
1999 
2000  //there is rotation
2001  QgsPointXY rotationPoint( ( extent.xMaximum() + extent.xMinimum() ) / 2.0, ( extent.yMaximum() + extent.yMinimum() ) / 2.0 );
2002  double dx, dy; //x-, y- shift from rotation point to corner point
2003 
2004  //top left point
2005  dx = rotationPoint.x() - extent.xMinimum();
2006  dy = rotationPoint.y() - extent.yMaximum();
2007  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2008  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2009 
2010  //top right point
2011  dx = rotationPoint.x() - extent.xMaximum();
2012  dy = rotationPoint.y() - extent.yMaximum();
2013  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2014  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2015 
2016  //bottom right point
2017  dx = rotationPoint.x() - extent.xMaximum();
2018  dy = rotationPoint.y() - extent.yMinimum();
2019  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2020  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2021 
2022  //bottom left point
2023  dx = rotationPoint.x() - extent.xMinimum();
2024  dy = rotationPoint.y() - extent.yMinimum();
2025  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2026  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2027 
2028  //ensure polygon is closed by readding first point
2029  poly << QPointF( poly.at( 0 ) );
2030 }
2031 
2032 void QgsLayoutItemMap::transformShift( double &xShift, double &yShift ) const
2033 {
2034  double mmToMapUnits = 1.0 / mapUnitsToLayoutUnits();
2035  double dxScaled = xShift * mmToMapUnits;
2036  double dyScaled = - yShift * mmToMapUnits;
2037 
2038  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dxScaled, dyScaled );
2039 
2040  xShift = dxScaled;
2041  yShift = dyScaled;
2042 }
2043 
2044 void QgsLayoutItemMap::drawAnnotations( QPainter *painter )
2045 {
2046  if ( !mLayout || !mLayout->project() || !mDrawAnnotations )
2047  {
2048  return;
2049  }
2050 
2051  const QList< QgsAnnotation * > annotations = mLayout->project()->annotationManager()->annotations();
2052  if ( annotations.isEmpty() )
2053  return;
2054 
2056  rc.setForceVectorOutput( true );
2058  QList< QgsMapLayer * > layers = layersToRender( &rc.expressionContext() );
2059 
2060  for ( QgsAnnotation *annotation : annotations )
2061  {
2062  if ( !annotation || !annotation->isVisible() )
2063  {
2064  continue;
2065  }
2066  if ( annotation->mapLayer() && !layers.contains( annotation->mapLayer() ) )
2067  continue;
2068 
2069  drawAnnotation( annotation, rc );
2070  }
2071 }
2072 
2073 void QgsLayoutItemMap::drawAnnotation( const QgsAnnotation *annotation, QgsRenderContext &context )
2074 {
2075  if ( !annotation || !annotation->isVisible() || !context.painter() || !context.painter()->device() )
2076  {
2077  return;
2078  }
2079 
2080  context.painter()->save();
2081  context.painter()->setRenderHint( QPainter::Antialiasing, context.flags() & QgsRenderContext::Antialiasing );
2082 
2083  double itemX, itemY;
2084  if ( annotation->hasFixedMapPosition() )
2085  {
2086  QPointF mapPos = layoutMapPosForItem( annotation );
2087  itemX = mapPos.x();
2088  itemY = mapPos.y();
2089  }
2090  else
2091  {
2092  itemX = annotation->relativePosition().x() * rect().width();
2093  itemY = annotation->relativePosition().y() * rect().height();
2094  }
2095  context.painter()->translate( itemX, itemY );
2096 
2097  //setup painter scaling to dots so that symbology is drawn to scale
2098  double dotsPerMM = context.painter()->device()->logicalDpiX() / 25.4;
2099  context.painter()->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
2100 
2101  annotation->render( context );
2102  context.painter()->restore();
2103 }
2104 
2105 QPointF QgsLayoutItemMap::layoutMapPosForItem( const QgsAnnotation *annotation ) const
2106 {
2107  if ( !annotation )
2108  return QPointF( 0, 0 );
2109 
2110  double mapX = 0.0;
2111  double mapY = 0.0;
2112 
2113  mapX = annotation->mapPosition().x();
2114  mapY = annotation->mapPosition().y();
2115  QgsCoordinateReferenceSystem annotationCrs = annotation->mapPositionCrs();
2116 
2117  if ( annotationCrs != crs() )
2118  {
2119  //need to reproject
2120  QgsCoordinateTransform t( annotationCrs, crs(), mLayout->project() );
2121  double z = 0.0;
2122  try
2123  {
2124  t.transformInPlace( mapX, mapY, z );
2125  }
2126  catch ( const QgsCsException & )
2127  {
2128  }
2129  }
2130 
2131  return mapToItemCoords( QPointF( mapX, mapY ) );
2132 }
2133 
2134 void QgsLayoutItemMap::drawMapFrame( QPainter *p )
2135 {
2136  if ( frameEnabled() && p )
2137  {
2138  p->save();
2139  p->setPen( pen() );
2140  p->setBrush( Qt::NoBrush );
2141  p->setRenderHint( QPainter::Antialiasing, true );
2142  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
2143  p->restore();
2144  }
2145 }
2146 
2147 void QgsLayoutItemMap::drawMapBackground( QPainter *p )
2148 {
2149  if ( hasBackground() && p )
2150  {
2151  p->save();
2152  p->setBrush( brush() );//this causes a problem in atlas generation
2153  p->setPen( Qt::NoPen );
2154  p->setRenderHint( QPainter::Antialiasing, true );
2155  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
2156  p->restore();
2157  }
2158 }
2159 
2160 bool QgsLayoutItemMap::shouldDrawPart( QgsLayoutItemMap::PartType part ) const
2161 {
2162  if ( mCurrentExportPart == NotLayered )
2163  {
2164  //all parts of the map are visible
2165  return true;
2166  }
2167 
2168  switch ( part )
2169  {
2170  case NotLayered:
2171  return true;
2172 
2173  case Start:
2174  return false;
2175 
2176  case Background:
2177  return mCurrentExportPart == Background && hasBackground();
2178 
2179  case Layer:
2180  return mCurrentExportPart == Layer;
2181 
2182  case Grid:
2183  return mCurrentExportPart == Grid && mGridStack->hasEnabledItems();
2184 
2185  case OverviewMapExtent:
2186  return mCurrentExportPart == OverviewMapExtent && mOverviewStack->hasEnabledItems();
2187 
2188  case Frame:
2189  return mCurrentExportPart == Frame && frameEnabled();
2190 
2191  case SelectionBoxes:
2192  return mCurrentExportPart == SelectionBoxes && isSelected();
2193 
2194  case End:
2195  return false;
2196  }
2197 
2198  return false;
2199 }
2200 
2201 void QgsLayoutItemMap::refreshMapExtents( const QgsExpressionContext *context )
2202 {
2203  QgsExpressionContext scopedContext;
2204  if ( !context )
2205  scopedContext = createExpressionContext();
2206  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
2207 
2208  //data defined map extents set?
2209  QgsRectangle newExtent = extent();
2210  bool useDdXMin = false;
2211  bool useDdXMax = false;
2212  bool useDdYMin = false;
2213  bool useDdYMax = false;
2214  double minXD = 0;
2215  double minYD = 0;
2216  double maxXD = 0;
2217  double maxYD = 0;
2218 
2219  bool ok = false;
2220  minXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMin, *evalContext, 0.0, &ok );
2221  if ( ok )
2222  {
2223  useDdXMin = true;
2224  newExtent.setXMinimum( minXD );
2225  }
2226  minYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMin, *evalContext, 0.0, &ok );
2227  if ( ok )
2228  {
2229  useDdYMin = true;
2230  newExtent.setYMinimum( minYD );
2231  }
2232  maxXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMax, *evalContext, 0.0, &ok );
2233  if ( ok )
2234  {
2235  useDdXMax = true;
2236  newExtent.setXMaximum( maxXD );
2237  }
2238  maxYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMax, *evalContext, 0.0, &ok );
2239  if ( ok )
2240  {
2241  useDdYMax = true;
2242  newExtent.setYMaximum( maxYD );
2243  }
2244 
2245  if ( newExtent != mExtent )
2246  {
2247  //calculate new extents to fit data defined extents
2248 
2249  //Make sure the width/height ratio is the same as in current map extent.
2250  //This is to keep the map item frame and the page layout fixed
2251  double currentWidthHeightRatio = mExtent.width() / mExtent.height();
2252  double newWidthHeightRatio = newExtent.width() / newExtent.height();
2253 
2254  if ( currentWidthHeightRatio < newWidthHeightRatio )
2255  {
2256  //enlarge height of new extent, ensuring the map center stays the same
2257  double newHeight = newExtent.width() / currentWidthHeightRatio;
2258  double deltaHeight = newHeight - newExtent.height();
2259  newExtent.setYMinimum( newExtent.yMinimum() - deltaHeight / 2 );
2260  newExtent.setYMaximum( newExtent.yMaximum() + deltaHeight / 2 );
2261  }
2262  else
2263  {
2264  //enlarge width of new extent, ensuring the map center stays the same
2265  double newWidth = currentWidthHeightRatio * newExtent.height();
2266  double deltaWidth = newWidth - newExtent.width();
2267  newExtent.setXMinimum( newExtent.xMinimum() - deltaWidth / 2 );
2268  newExtent.setXMaximum( newExtent.xMaximum() + deltaWidth / 2 );
2269  }
2270 
2271  mExtent = newExtent;
2272  }
2273 
2274  //now refresh scale, as this potentially overrides extents
2275 
2276  //data defined map scale set?
2277  double scaleD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapScale, *evalContext, 0.0, &ok );
2278  if ( ok )
2279  {
2280  setScale( scaleD, false );
2281  newExtent = mExtent;
2282  }
2283 
2284  if ( useDdXMax || useDdXMin || useDdYMax || useDdYMin )
2285  {
2286  //if only one of min/max was set for either x or y, then make sure our extent is locked on that value
2287  //as we can do this without altering the scale
2288  if ( useDdXMin && !useDdXMax )
2289  {
2290  double xMax = mExtent.xMaximum() - ( mExtent.xMinimum() - minXD );
2291  newExtent.setXMinimum( minXD );
2292  newExtent.setXMaximum( xMax );
2293  }
2294  else if ( !useDdXMin && useDdXMax )
2295  {
2296  double xMin = mExtent.xMinimum() - ( mExtent.xMaximum() - maxXD );
2297  newExtent.setXMinimum( xMin );
2298  newExtent.setXMaximum( maxXD );
2299  }
2300  if ( useDdYMin && !useDdYMax )
2301  {
2302  double yMax = mExtent.yMaximum() - ( mExtent.yMinimum() - minYD );
2303  newExtent.setYMinimum( minYD );
2304  newExtent.setYMaximum( yMax );
2305  }
2306  else if ( !useDdYMin && useDdYMax )
2307  {
2308  double yMin = mExtent.yMinimum() - ( mExtent.yMaximum() - maxYD );
2309  newExtent.setYMinimum( yMin );
2310  newExtent.setYMaximum( maxYD );
2311  }
2312 
2313  if ( newExtent != mExtent )
2314  {
2315  mExtent = newExtent;
2316  }
2317  }
2318 
2319  //lastly, map rotation overrides all
2320  double mapRotation = mMapRotation;
2321 
2322  //data defined map rotation set?
2323  mapRotation = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapRotation, *evalContext, mapRotation );
2324 
2325  if ( !qgsDoubleNear( mEvaluatedMapRotation, mapRotation ) )
2326  {
2327  mEvaluatedMapRotation = mapRotation;
2328  emit mapRotationChanged( mapRotation );
2329  }
2330 }
2331 
2332 void QgsLayoutItemMap::refreshLabelMargin( bool updateItem )
2333 {
2334  //data defined label margin set?
2336  mEvaluatedLabelMargin.setLength( labelMargin );
2337  mEvaluatedLabelMargin.setUnits( mLabelMargin.units() );
2338 
2339  if ( updateItem )
2340  {
2341  update();
2342  }
2343 }
2344 
2345 void QgsLayoutItemMap::updateAtlasFeature()
2346 {
2347  if ( !atlasDriven() || !mLayout->reportContext().layer() )
2348  return; // nothing to do
2349 
2350  QgsRectangle bounds = computeAtlasRectangle();
2351  if ( bounds.isNull() )
2352  return;
2353 
2354  double xa1 = bounds.xMinimum();
2355  double xa2 = bounds.xMaximum();
2356  double ya1 = bounds.yMinimum();
2357  double ya2 = bounds.yMaximum();
2358  QgsRectangle newExtent = bounds;
2359  QgsRectangle originalExtent = mExtent;
2360 
2361  //sanity check - only allow fixed scale mode for point layers
2362  bool isPointLayer = QgsWkbTypes::geometryType( mLayout->reportContext().layer()->wkbType() ) == QgsWkbTypes::PointGeometry;
2363 
2364  if ( mAtlasScalingMode == Fixed || mAtlasScalingMode == Predefined || isPointLayer )
2365  {
2366  QgsScaleCalculator calc;
2367  calc.setMapUnits( crs().mapUnits() );
2368  calc.setDpi( 25.4 );
2369  double originalScale = calc.calculate( originalExtent, rect().width() );
2370  double geomCenterX = ( xa1 + xa2 ) / 2.0;
2371  double geomCenterY = ( ya1 + ya2 ) / 2.0;
2372  QVector<qreal> scales;
2374  if ( !mLayout->reportContext().predefinedScales().empty() ) // remove when deprecated method is removed
2375  scales = mLayout->reportContext().predefinedScales();
2376  else
2377  scales = mLayout->renderContext().predefinedScales();
2379  if ( mAtlasScalingMode == Fixed || isPointLayer || scales.isEmpty() )
2380  {
2381  // only translate, keep the original scale (i.e. width x height)
2382  double xMin = geomCenterX - originalExtent.width() / 2.0;
2383  double yMin = geomCenterY - originalExtent.height() / 2.0;
2384  newExtent = QgsRectangle( xMin,
2385  yMin,
2386  xMin + originalExtent.width(),
2387  yMin + originalExtent.height() );
2388 
2389  //scale newExtent to match original scale of map
2390  //this is required for geographic coordinate systems, where the scale varies by extent
2391  double newScale = calc.calculate( newExtent, rect().width() );
2392  newExtent.scale( originalScale / newScale );
2393  }
2394  else if ( mAtlasScalingMode == Predefined )
2395  {
2396  // choose one of the predefined scales
2397  double newWidth = originalExtent.width();
2398  double newHeight = originalExtent.height();
2399  for ( int i = 0; i < scales.size(); i++ )
2400  {
2401  double ratio = scales[i] / originalScale;
2402  newWidth = originalExtent.width() * ratio;
2403  newHeight = originalExtent.height() * ratio;
2404 
2405  // compute new extent, centered on feature
2406  double xMin = geomCenterX - newWidth / 2.0;
2407  double yMin = geomCenterY - newHeight / 2.0;
2408  newExtent = QgsRectangle( xMin,
2409  yMin,
2410  xMin + newWidth,
2411  yMin + newHeight );
2412 
2413  //scale newExtent to match desired map scale
2414  //this is required for geographic coordinate systems, where the scale varies by extent
2415  double newScale = calc.calculate( newExtent, rect().width() );
2416  newExtent.scale( scales[i] / newScale );
2417 
2418  if ( ( newExtent.width() >= bounds.width() ) && ( newExtent.height() >= bounds.height() ) )
2419  {
2420  // this is the smallest extent that embeds the feature, stop here
2421  break;
2422  }
2423  }
2424  }
2425  }
2426  else if ( mAtlasScalingMode == Auto )
2427  {
2428  // auto scale
2429 
2430  double geomRatio = bounds.width() / bounds.height();
2431  double mapRatio = originalExtent.width() / originalExtent.height();
2432 
2433  // geometry height is too big
2434  if ( geomRatio < mapRatio )
2435  {
2436  // extent the bbox's width
2437  double adjWidth = ( mapRatio * bounds.height() - bounds.width() ) / 2.0;
2438  xa1 -= adjWidth;
2439  xa2 += adjWidth;
2440  }
2441  // geometry width is too big
2442  else if ( geomRatio > mapRatio )
2443  {
2444  // extent the bbox's height
2445  double adjHeight = ( bounds.width() / mapRatio - bounds.height() ) / 2.0;
2446  ya1 -= adjHeight;
2447  ya2 += adjHeight;
2448  }
2449  newExtent = QgsRectangle( xa1, ya1, xa2, ya2 );
2450 
2451  const double evaluatedAtlasMargin = atlasMargin();
2452  if ( evaluatedAtlasMargin > 0.0 )
2453  {
2454  newExtent.scale( 1 + evaluatedAtlasMargin );
2455  }
2456  }
2457 
2458  // set the new extent (and render)
2459  setExtent( newExtent );
2460  emit preparedForAtlas();
2461 }
2462 
2463 QgsRectangle QgsLayoutItemMap::computeAtlasRectangle()
2464 {
2465  // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
2466  // We have to transform the geometry to the destination CRS and ask for the bounding box
2467  // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
2468  QgsGeometry g = mLayout->reportContext().currentGeometry( crs() );
2469  // Rotating the geometry, so the bounding box is correct wrt map rotation
2470  if ( mEvaluatedMapRotation != 0.0 )
2471  {
2472  QgsPointXY prevCenter = g.boundingBox().center();
2473  g.rotate( mEvaluatedMapRotation, g.boundingBox().center() );
2474  // Rotation center will be still the bounding box center of an unrotated geometry.
2475  // Which means, if the center of bbox moves after rotation, the viewport will
2476  // also be offset, and part of the geometry will fall out of bounds.
2477  // Here we compensate for that roughly: by extending the rotated bounds
2478  // so that its center is the same as the original.
2479  QgsRectangle bounds = g.boundingBox();
2480  double dx = std::max( std::abs( prevCenter.x() - bounds.xMinimum() ),
2481  std::abs( prevCenter.x() - bounds.xMaximum() ) );
2482  double dy = std::max( std::abs( prevCenter.y() - bounds.yMinimum() ),
2483  std::abs( prevCenter.y() - bounds.yMaximum() ) );
2484  QgsPointXY center = g.boundingBox().center();
2485  return QgsRectangle( center.x() - dx, center.y() - dy,
2486  center.x() + dx, center.y() + dy );
2487  }
2488  else
2489  {
2490  return g.boundingBox();
2491  }
2492 }
2493 
2494 void QgsLayoutItemMap::createStagedRenderJob( const QgsRectangle &extent, const QSizeF size, double dpi )
2495 {
2496  // TODO - overview?
2497  mStagedRendererJob = qgis::make_unique< QgsMapRendererStagedRenderJob >( mapSettings( extent, size, dpi, true ),
2500  : QgsMapRendererStagedRenderJob::Flags( nullptr ) );
2501  mStagedRendererJob->start();
2502 }
2503 
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.
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.
void setExtent(const QgsRectangle &extent)
Sets a new extent for the map.
double y
Definition: qgspointxy.h:48
void layerOrderChanged()
Emitted when the layer order has changed.
A class to represent a 2D point.
Definition: qgspointxy.h:43
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h: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:280
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...
TYPE * resolveWeakly(const QgsProject *project)
Resolves the map layer by attempting to find a matching layer in a project using a weak match...
void setOutputDpi(double dpi)
Sets DPI used for conversion between real world units (e.g. mm) and pixels.
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:649
QString toProj4() const
Returns a Proj4 string representation of this CRS.
void crsChanged()
Emitted when the CRS of the project has changed.
void setDpi(double dpi)
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:1632
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:681
Map extent x maximum.
QgsMapThemeCollection mapThemeCollection
Definition: qgsproject.h:98
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:240
~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.
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...
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:89
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.
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:650
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.
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.