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