QGIS API Documentation  3.25.0-Master (349eb7cb1e)
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_p.h"
31 #include "qgsvectorlayer.h"
32 #include "qgsexpressioncontext.h"
33 #include "qgsapplication.h"
35 #include "qgsstyleentityvisitor.h"
36 #include "qgsannotationlayer.h"
38 #include "qgsprojoperation.h"
39 #include "qgslabelingresults.h"
40 #include "qgsvectortileutils.h"
41 
42 #include <QPainter>
43 #include <QStyleOptionGraphicsItem>
44 #include <QTimer>
45 
47  : QgsLayoutItem( layout )
48  , mAtlasClippingSettings( new QgsLayoutItemMapAtlasClippingSettings( this ) )
49  , mItemClippingSettings( new QgsLayoutItemMapItemClipPathSettings( this ) )
50 {
51  mBackgroundUpdateTimer = new QTimer( this );
52  mBackgroundUpdateTimer->setSingleShot( true );
53  connect( mBackgroundUpdateTimer, &QTimer::timeout, this, &QgsLayoutItemMap::recreateCachedImageInBackground );
54 
55  assignFreeId();
56 
57  setCacheMode( QGraphicsItem::NoCache );
58 
59  connect( this, &QgsLayoutItem::sizePositionChanged, this, [ = ]
60  {
61  shapeChanged();
62  } );
63 
64  mGridStack = std::make_unique< QgsLayoutItemMapGridStack >( this );
65  mOverviewStack = std::make_unique< QgsLayoutItemMapOverviewStack >( this );
66 
67  connect( mAtlasClippingSettings, &QgsLayoutItemMapAtlasClippingSettings::changed, this, [ = ]
68  {
69  refresh();
70  } );
71 
72  connect( mItemClippingSettings, &QgsLayoutItemMapItemClipPathSettings::changed, this, [ = ]
73  {
74  refresh();
75  } );
76 
78  {
81  if ( mCrs != crs )
82  {
83  setCrs( crs );
85  }
86  } );
87 
88  if ( layout )
89  connectUpdateSlot();
90 }
91 
93 {
94  if ( mPainterJob )
95  {
96  disconnect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
98  mPainterJob->cancel(); // blocks
99  mPainter->end();
100  }
101 }
102 
104 {
106 }
107 
109 {
110  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemMap.svg" ) );
111 }
112 
113 QgsLayoutItem::Flags QgsLayoutItemMap::itemFlags() const
114 {
116 }
117 
119 {
120  if ( !mLayout )
121  return;
122 
123  QList<QgsLayoutItemMap *> mapsList;
124  mLayout->layoutItems( mapsList );
125 
126  int maxId = -1;
127  bool used = false;
128  for ( QgsLayoutItemMap *map : std::as_const( mapsList ) )
129  {
130  if ( map == this )
131  continue;
132 
133  if ( map->mMapId == mMapId )
134  used = true;
135 
136  maxId = std::max( maxId, map->mMapId );
137  }
138  if ( used )
139  {
140  mMapId = maxId + 1;
141  mLayout->itemsModel()->updateItemDisplayName( this );
142  }
143  updateToolTip();
144 }
145 
147 {
148  if ( !QgsLayoutItem::id().isEmpty() )
149  {
150  return QgsLayoutItem::id();
151  }
152 
153  return tr( "Map %1" ).arg( mMapId );
154 }
155 
157 {
158  return new QgsLayoutItemMap( layout );
159 }
160 
162 {
164 
165  mCachedLayerStyleOverridesPresetName.clear();
166 
167  invalidateCache();
168 
169  updateAtlasFeature();
170 }
171 
173 {
174  if ( rect().isEmpty() )
175  return 0;
176 
177  QgsScaleCalculator calculator;
178  calculator.setMapUnits( crs().mapUnits() );
179  calculator.setDpi( 25.4 ); //Using mm
180  double widthInMm = mLayout->convertFromLayoutUnits( rect().width(), QgsUnitTypes::LayoutMillimeters ).length();
181  return calculator.calculate( extent(), widthInMm );
182 }
183 
184 void QgsLayoutItemMap::setScale( double scaleDenominator, bool forceUpdate )
185 {
186  double currentScaleDenominator = scale();
187 
188  if ( qgsDoubleNear( scaleDenominator, currentScaleDenominator ) || qgsDoubleNear( scaleDenominator, 0.0 ) )
189  {
190  return;
191  }
192 
193  double scaleRatio = scaleDenominator / currentScaleDenominator;
194  mExtent.scale( scaleRatio );
195 
196  if ( mAtlasDriven && mAtlasScalingMode == Fixed )
197  {
198  //if map is atlas controlled and set to fixed scaling mode, then scale changes should be treated as permanent
199  //and also apply to the map's original extent (see #9602)
200  //we can't use the scaleRatio calculated earlier, as the scale can vary depending on extent for geographic coordinate systems
201  QgsScaleCalculator calculator;
202  calculator.setMapUnits( crs().mapUnits() );
203  calculator.setDpi( 25.4 ); //QGraphicsView units are mm
204  scaleRatio = scaleDenominator / calculator.calculate( mExtent, rect().width() );
205  mExtent.scale( scaleRatio );
206  }
207 
208  invalidateCache();
209  if ( forceUpdate )
210  {
211  emit changed();
212  update();
213  }
214  emit extentChanged();
215 }
216 
218 {
219  if ( mExtent == extent )
220  {
221  return;
222  }
223  mExtent = extent;
224 
225  //recalculate data defined scale and extents, since that may override extent
226  refreshMapExtents();
227 
228  //adjust height
229  QRectF currentRect = rect();
230 
231  double newHeight = currentRect.width() * mExtent.height() / mExtent.width();
232 
233  attemptSetSceneRect( QRectF( pos().x(), pos().y(), currentRect.width(), newHeight ) );
234  update();
235 }
236 
238 {
239  QgsRectangle newExtent = extent;
240  QgsRectangle currentExtent = mExtent;
241  //Make sure the width/height ratio is the same as the current layout map extent.
242  //This is to keep the map item frame size fixed
243  double currentWidthHeightRatio = 1.0;
244  if ( !currentExtent.isNull() )
245  currentWidthHeightRatio = currentExtent.width() / currentExtent.height();
246  else
247  currentWidthHeightRatio = rect().width() / rect().height();
248  double newWidthHeightRatio = newExtent.width() / newExtent.height();
249 
250  if ( currentWidthHeightRatio < newWidthHeightRatio )
251  {
252  //enlarge height of new extent, ensuring the map center stays the same
253  double newHeight = newExtent.width() / currentWidthHeightRatio;
254  double deltaHeight = newHeight - newExtent.height();
255  newExtent.setYMinimum( newExtent.yMinimum() - deltaHeight / 2 );
256  newExtent.setYMaximum( newExtent.yMaximum() + deltaHeight / 2 );
257  }
258  else
259  {
260  //enlarge width of new extent, ensuring the map center stays the same
261  double newWidth = currentWidthHeightRatio * newExtent.height();
262  double deltaWidth = newWidth - newExtent.width();
263  newExtent.setXMinimum( newExtent.xMinimum() - deltaWidth / 2 );
264  newExtent.setXMaximum( newExtent.xMaximum() + deltaWidth / 2 );
265  }
266 
267  if ( mExtent == newExtent )
268  {
269  return;
270  }
271  mExtent = newExtent;
272 
273  //recalculate data defined scale and extents, since that may override extent
274  refreshMapExtents();
275 
276  invalidateCache();
277  emit changed();
278  emit extentChanged();
279 }
280 
282 {
283  return mExtent;
284 }
285 
286 QPolygonF QgsLayoutItemMap::calculateVisibleExtentPolygon( bool includeClipping ) const
287 {
288  QPolygonF poly;
289  mapPolygon( mExtent, poly );
290 
291  if ( includeClipping && mItemClippingSettings->isActive() )
292  {
293  const QgsGeometry geom = mItemClippingSettings->clippedMapExtent();
294  if ( !geom.isEmpty() )
295  {
296  poly = poly.intersected( geom.asQPolygonF() );
297  }
298  }
299 
300  return poly;
301 }
302 
304 {
305  return calculateVisibleExtentPolygon( true );
306 }
307 
309 {
310  if ( mCrs.isValid() )
311  return mCrs;
312  else if ( mLayout && mLayout->project() )
313  return mLayout->project()->crs();
315 }
316 
318 {
319  if ( mCrs == crs )
320  return;
321 
322  mCrs = crs;
323  emit crsChanged();
324 }
325 
326 QList<QgsMapLayer *> QgsLayoutItemMap::layers() const
327 {
328  return _qgis_listRefToRaw( mLayers );
329 }
330 
331 void QgsLayoutItemMap::setLayers( const QList<QgsMapLayer *> &layers )
332 {
333  mLayers = _qgis_listRawToRef( layers );
334 }
335 
336 void QgsLayoutItemMap::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
337 {
338  if ( overrides == mLayerStyleOverrides )
339  return;
340 
341  mLayerStyleOverrides = overrides;
342  emit layerStyleOverridesChanged(); // associated legends may listen to this
343 
344 }
345 
347 {
348  mLayerStyleOverrides.clear();
349  for ( const QgsMapLayerRef &layerRef : std::as_const( mLayers ) )
350  {
351  if ( QgsMapLayer *layer = layerRef.get() )
352  {
353  QgsMapLayerStyle style;
354  style.readFromLayer( layer );
355  mLayerStyleOverrides.insert( layer->id(), style.xmlData() );
356  }
357  }
358 }
359 
361 {
362  if ( mFollowVisibilityPreset == follow )
363  return;
364 
365  mFollowVisibilityPreset = follow;
366 
367  if ( !mFollowVisibilityPresetName.isEmpty() )
368  emit themeChanged( mFollowVisibilityPreset ? mFollowVisibilityPresetName : QString() );
369 }
370 
372 {
373  if ( name == mFollowVisibilityPresetName )
374  return;
375 
376  mFollowVisibilityPresetName = name;
377  if ( mFollowVisibilityPreset )
378  emit themeChanged( mFollowVisibilityPresetName );
379 }
380 
381 void QgsLayoutItemMap::moveContent( double dx, double dy )
382 {
383  mLastRenderedImageOffsetX -= dx;
384  mLastRenderedImageOffsetY -= dy;
385  if ( !mDrawing )
386  {
387  transformShift( dx, dy );
388  mExtent.setXMinimum( mExtent.xMinimum() + dx );
389  mExtent.setXMaximum( mExtent.xMaximum() + dx );
390  mExtent.setYMinimum( mExtent.yMinimum() + dy );
391  mExtent.setYMaximum( mExtent.yMaximum() + dy );
392 
393  //in case data defined extents are set, these override the calculated values
394  refreshMapExtents();
395 
396  invalidateCache();
397  emit changed();
398  emit extentChanged();
399  }
400 }
401 
402 void QgsLayoutItemMap::zoomContent( double factor, QPointF point )
403 {
404  if ( mDrawing )
405  {
406  return;
407  }
408 
409  //find out map coordinates of position
410  double mapX = mExtent.xMinimum() + ( point.x() / rect().width() ) * ( mExtent.xMaximum() - mExtent.xMinimum() );
411  double mapY = mExtent.yMinimum() + ( 1 - ( point.y() / rect().height() ) ) * ( mExtent.yMaximum() - mExtent.yMinimum() );
412 
413  //find out new center point
414  double centerX = ( mExtent.xMaximum() + mExtent.xMinimum() ) / 2;
415  double centerY = ( mExtent.yMaximum() + mExtent.yMinimum() ) / 2;
416 
417  centerX = mapX + ( centerX - mapX ) * ( 1.0 / factor );
418  centerY = mapY + ( centerY - mapY ) * ( 1.0 / factor );
419 
420  double newIntervalX, newIntervalY;
421 
422  if ( factor > 0 )
423  {
424  newIntervalX = ( mExtent.xMaximum() - mExtent.xMinimum() ) / factor;
425  newIntervalY = ( mExtent.yMaximum() - mExtent.yMinimum() ) / factor;
426  }
427  else //no need to zoom
428  {
429  return;
430  }
431 
432  mExtent.setXMaximum( centerX + newIntervalX / 2 );
433  mExtent.setXMinimum( centerX - newIntervalX / 2 );
434  mExtent.setYMaximum( centerY + newIntervalY / 2 );
435  mExtent.setYMinimum( centerY - newIntervalY / 2 );
436 
437  if ( mAtlasDriven && mAtlasScalingMode == Fixed )
438  {
439  //if map is atlas controlled and set to fixed scaling mode, then scale changes should be treated as permanent
440  //and also apply to the map's original extent (see #9602)
441  //we can't use the scaleRatio calculated earlier, as the scale can vary depending on extent for geographic coordinate systems
442  QgsScaleCalculator calculator;
443  calculator.setMapUnits( crs().mapUnits() );
444  calculator.setDpi( 25.4 ); //QGraphicsView units are mm
445  double scaleRatio = scale() / calculator.calculate( mExtent, rect().width() );
446  mExtent.scale( scaleRatio );
447  }
448 
449  //recalculate data defined scale and extents, since that may override zoom
450  refreshMapExtents();
451 
452  invalidateCache();
453  emit changed();
454  emit extentChanged();
455 }
456 
458 {
459  const QList< QgsMapLayer * > layers = layersToRender();
460  for ( QgsMapLayer *layer : layers )
461  {
462  if ( layer->dataProvider() && layer->providerType() == QLatin1String( "wms" ) )
463  {
464  return true;
465  }
466  }
467  return false;
468 }
469 
471 {
473  return true;
474 
475  // we MUST force the whole layout to render as a raster if any map item
476  // uses blend modes, and we are not drawing on a solid opaque background
477  // because in this case the map item needs to be rendered as a raster, but
478  // it also needs to interact with items below it
479  if ( !containsAdvancedEffects() )
480  return false;
481 
482  if ( hasBackground() && qgsDoubleNear( backgroundColor().alphaF(), 1.0 ) )
483  return false;
484 
485  return true;
486 }
487 
489 {
491  return true;
492 
493  //check easy things first
494 
495  //overviews
496  if ( mOverviewStack->containsAdvancedEffects() )
497  {
498  return true;
499  }
500 
501  //grids
502  if ( mGridStack->containsAdvancedEffects() )
503  {
504  return true;
505  }
506 
507  QgsMapSettings ms;
508  ms.setLayers( layersToRender() );
509  return ( !QgsMapSettingsUtils::containsAdvancedEffects( ms ).isEmpty() );
510 }
511 
512 void QgsLayoutItemMap::setMapRotation( double rotation )
513 {
514  mMapRotation = rotation;
515  mEvaluatedMapRotation = mMapRotation;
516  invalidateCache();
517  emit mapRotationChanged( rotation );
518  emit changed();
519 }
520 
522 {
523  return valueType == QgsLayoutObject::EvaluatedValue ? mEvaluatedMapRotation : mMapRotation;
524 
525 }
526 
528 {
529  mAtlasDriven = enabled;
530 
531  if ( !enabled )
532  {
533  //if not enabling the atlas, we still need to refresh the map extents
534  //so that data defined extents and scale are recalculated
535  refreshMapExtents();
536  }
537 }
538 
540 {
541  if ( valueType == QgsLayoutObject::EvaluatedValue )
542  {
543  //evaluate data defined atlas margin
544 
545  //start with user specified margin
546  double margin = mAtlasMargin;
548 
549  bool ok = false;
550  double ddMargin = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapAtlasMargin, context, 0.0, &ok );
551  if ( ok )
552  {
553  //divide by 100 to convert to 0 -> 1.0 range
554  margin = ddMargin / 100;
555  }
556  return margin;
557  }
558  else
559  {
560  return mAtlasMargin;
561  }
562 }
563 
565 {
566  if ( mGridStack->size() < 1 )
567  {
568  QgsLayoutItemMapGrid *grid = new QgsLayoutItemMapGrid( tr( "Grid %1" ).arg( 1 ), this );
569  mGridStack->addGrid( grid );
570  }
571  return mGridStack->grid( 0 );
572 }
573 
575 {
576  if ( mOverviewStack->size() < 1 )
577  {
578  QgsLayoutItemMapOverview *overview = new QgsLayoutItemMapOverview( tr( "Overview %1" ).arg( 1 ), this );
579  mOverviewStack->addOverview( overview );
580  }
581  return mOverviewStack->overview( 0 );
582 }
583 
585 {
586 }
587 
588 bool QgsLayoutItemMap::writePropertiesToElement( QDomElement &mapElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
589 {
590  if ( mKeepLayerSet )
591  {
592  mapElem.setAttribute( QStringLiteral( "keepLayerSet" ), QStringLiteral( "true" ) );
593  }
594  else
595  {
596  mapElem.setAttribute( QStringLiteral( "keepLayerSet" ), QStringLiteral( "false" ) );
597  }
598 
599  if ( mDrawAnnotations )
600  {
601  mapElem.setAttribute( QStringLiteral( "drawCanvasItems" ), QStringLiteral( "true" ) );
602  }
603  else
604  {
605  mapElem.setAttribute( QStringLiteral( "drawCanvasItems" ), QStringLiteral( "false" ) );
606  }
607 
608  //extent
609  QDomElement extentElem = doc.createElement( QStringLiteral( "Extent" ) );
610  extentElem.setAttribute( QStringLiteral( "xmin" ), qgsDoubleToString( mExtent.xMinimum() ) );
611  extentElem.setAttribute( QStringLiteral( "xmax" ), qgsDoubleToString( mExtent.xMaximum() ) );
612  extentElem.setAttribute( QStringLiteral( "ymin" ), qgsDoubleToString( mExtent.yMinimum() ) );
613  extentElem.setAttribute( QStringLiteral( "ymax" ), qgsDoubleToString( mExtent.yMaximum() ) );
614  mapElem.appendChild( extentElem );
615 
616  if ( mCrs.isValid() )
617  {
618  QDomElement crsElem = doc.createElement( QStringLiteral( "crs" ) );
619  mCrs.writeXml( crsElem, doc );
620  mapElem.appendChild( crsElem );
621  }
622 
623  // follow map theme
624  mapElem.setAttribute( QStringLiteral( "followPreset" ), mFollowVisibilityPreset ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
625  mapElem.setAttribute( QStringLiteral( "followPresetName" ), mFollowVisibilityPresetName );
626 
627  //map rotation
628  mapElem.setAttribute( QStringLiteral( "mapRotation" ), QString::number( mMapRotation ) );
629 
630  //layer set
631  QDomElement layerSetElem = doc.createElement( QStringLiteral( "LayerSet" ) );
632  for ( const QgsMapLayerRef &layerRef : mLayers )
633  {
634  if ( !layerRef )
635  continue;
636  QDomElement layerElem = doc.createElement( QStringLiteral( "Layer" ) );
637  QDomText layerIdText = doc.createTextNode( layerRef.layerId );
638  layerElem.appendChild( layerIdText );
639 
640  layerElem.setAttribute( QStringLiteral( "name" ), layerRef.name );
641  layerElem.setAttribute( QStringLiteral( "source" ), layerRef.source );
642  layerElem.setAttribute( QStringLiteral( "provider" ), layerRef.provider );
643 
644  layerSetElem.appendChild( layerElem );
645  }
646  mapElem.appendChild( layerSetElem );
647 
648  // override styles
649  if ( mKeepLayerStyles )
650  {
651  QDomElement stylesElem = doc.createElement( QStringLiteral( "LayerStyles" ) );
652  for ( auto styleIt = mLayerStyleOverrides.constBegin(); styleIt != mLayerStyleOverrides.constEnd(); ++styleIt )
653  {
654  QDomElement styleElem = doc.createElement( QStringLiteral( "LayerStyle" ) );
655 
656  QgsMapLayerRef ref( styleIt.key() );
657  ref.resolve( mLayout->project() );
658 
659  styleElem.setAttribute( QStringLiteral( "layerid" ), ref.layerId );
660  styleElem.setAttribute( QStringLiteral( "name" ), ref.name );
661  styleElem.setAttribute( QStringLiteral( "source" ), ref.source );
662  styleElem.setAttribute( QStringLiteral( "provider" ), ref.provider );
663 
664  QgsMapLayerStyle style( styleIt.value() );
665  style.writeXml( styleElem );
666  stylesElem.appendChild( styleElem );
667  }
668  mapElem.appendChild( stylesElem );
669  }
670 
671  //grids
672  mGridStack->writeXml( mapElem, doc, context );
673 
674  //overviews
675  mOverviewStack->writeXml( mapElem, doc, context );
676 
677  //atlas
678  QDomElement atlasElem = doc.createElement( QStringLiteral( "AtlasMap" ) );
679  atlasElem.setAttribute( QStringLiteral( "atlasDriven" ), mAtlasDriven );
680  atlasElem.setAttribute( QStringLiteral( "scalingMode" ), mAtlasScalingMode );
681  atlasElem.setAttribute( QStringLiteral( "margin" ), qgsDoubleToString( mAtlasMargin ) );
682  mapElem.appendChild( atlasElem );
683 
684  mapElem.setAttribute( QStringLiteral( "labelMargin" ), mLabelMargin.encodeMeasurement() );
685  mapElem.setAttribute( QStringLiteral( "mapFlags" ), static_cast< int>( mMapFlags ) );
686 
687  QDomElement labelBlockingItemsElem = doc.createElement( QStringLiteral( "labelBlockingItems" ) );
688  for ( const auto &item : std::as_const( mBlockingLabelItems ) )
689  {
690  if ( !item )
691  continue;
692 
693  QDomElement blockingItemElem = doc.createElement( QStringLiteral( "item" ) );
694  blockingItemElem.setAttribute( QStringLiteral( "uuid" ), item->uuid() );
695  labelBlockingItemsElem.appendChild( blockingItemElem );
696  }
697  mapElem.appendChild( labelBlockingItemsElem );
698 
699  //temporal settings
700  mapElem.setAttribute( QStringLiteral( "isTemporal" ), isTemporal() ? 1 : 0 );
701  if ( isTemporal() )
702  {
703  mapElem.setAttribute( QStringLiteral( "temporalRangeBegin" ), temporalRange().begin().toString( Qt::ISODate ) );
704  mapElem.setAttribute( QStringLiteral( "temporalRangeEnd" ), temporalRange().end().toString( Qt::ISODate ) );
705  }
706 
707  mAtlasClippingSettings->writeXml( mapElem, doc, context );
708  mItemClippingSettings->writeXml( mapElem, doc, context );
709 
710  return true;
711 }
712 
713 bool QgsLayoutItemMap::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
714 {
715  mUpdatesEnabled = false;
716 
717  //extent
718  QDomNodeList extentNodeList = itemElem.elementsByTagName( QStringLiteral( "Extent" ) );
719  if ( !extentNodeList.isEmpty() )
720  {
721  QDomElement extentElem = extentNodeList.at( 0 ).toElement();
722  double xmin, xmax, ymin, ymax;
723  xmin = extentElem.attribute( QStringLiteral( "xmin" ) ).toDouble();
724  xmax = extentElem.attribute( QStringLiteral( "xmax" ) ).toDouble();
725  ymin = extentElem.attribute( QStringLiteral( "ymin" ) ).toDouble();
726  ymax = extentElem.attribute( QStringLiteral( "ymax" ) ).toDouble();
727  setExtent( QgsRectangle( xmin, ymin, xmax, ymax ) );
728  }
729 
730  QDomNodeList crsNodeList = itemElem.elementsByTagName( QStringLiteral( "crs" ) );
732  if ( !crsNodeList.isEmpty() )
733  {
734  QDomElement crsElem = crsNodeList.at( 0 ).toElement();
735  crs.readXml( crsElem );
736  }
737  setCrs( crs );
738 
739  //map rotation
740  mMapRotation = itemElem.attribute( QStringLiteral( "mapRotation" ), QStringLiteral( "0" ) ).toDouble();
741  mEvaluatedMapRotation = mMapRotation;
742 
743  // follow map theme
744  mFollowVisibilityPreset = itemElem.attribute( QStringLiteral( "followPreset" ) ).compare( QLatin1String( "true" ) ) == 0;
745  mFollowVisibilityPresetName = itemElem.attribute( QStringLiteral( "followPresetName" ) );
746 
747  //mKeepLayerSet flag
748  QString keepLayerSetFlag = itemElem.attribute( QStringLiteral( "keepLayerSet" ) );
749  if ( keepLayerSetFlag.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
750  {
751  mKeepLayerSet = true;
752  }
753  else
754  {
755  mKeepLayerSet = false;
756  }
757 
758  QString drawCanvasItemsFlag = itemElem.attribute( QStringLiteral( "drawCanvasItems" ), QStringLiteral( "true" ) );
759  if ( drawCanvasItemsFlag.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
760  {
761  mDrawAnnotations = true;
762  }
763  else
764  {
765  mDrawAnnotations = false;
766  }
767 
768  mLayerStyleOverrides.clear();
769 
770  //mLayers
771  mLayers.clear();
772  QDomNodeList layerSetNodeList = itemElem.elementsByTagName( QStringLiteral( "LayerSet" ) );
773  if ( !layerSetNodeList.isEmpty() )
774  {
775  QDomElement layerSetElem = layerSetNodeList.at( 0 ).toElement();
776  QDomNodeList layerIdNodeList = layerSetElem.elementsByTagName( QStringLiteral( "Layer" ) );
777  mLayers.reserve( layerIdNodeList.size() );
778  for ( int i = 0; i < layerIdNodeList.size(); ++i )
779  {
780  QDomElement layerElem = layerIdNodeList.at( i ).toElement();
781  QString layerId = layerElem.text();
782  QString layerName = layerElem.attribute( QStringLiteral( "name" ) );
783  QString layerSource = layerElem.attribute( QStringLiteral( "source" ) );
784  QString layerProvider = layerElem.attribute( QStringLiteral( "provider" ) );
785 
786  QgsMapLayerRef ref( layerId, layerName, layerSource, layerProvider );
787  ref.resolveWeakly( mLayout->project() );
788  mLayers << ref;
789  }
790  }
791 
792  // override styles
793  QDomNodeList layerStylesNodeList = itemElem.elementsByTagName( QStringLiteral( "LayerStyles" ) );
794  mKeepLayerStyles = !layerStylesNodeList.isEmpty();
795  if ( mKeepLayerStyles )
796  {
797  QDomElement layerStylesElem = layerStylesNodeList.at( 0 ).toElement();
798  QDomNodeList layerStyleNodeList = layerStylesElem.elementsByTagName( QStringLiteral( "LayerStyle" ) );
799  for ( int i = 0; i < layerStyleNodeList.size(); ++i )
800  {
801  const QDomElement &layerStyleElement = layerStyleNodeList.at( i ).toElement();
802  QString layerId = layerStyleElement.attribute( QStringLiteral( "layerid" ) );
803  QString layerName = layerStyleElement.attribute( QStringLiteral( "name" ) );
804  QString layerSource = layerStyleElement.attribute( QStringLiteral( "source" ) );
805  QString layerProvider = layerStyleElement.attribute( QStringLiteral( "provider" ) );
806  QgsMapLayerRef ref( layerId, layerName, layerSource, layerProvider );
807  ref.resolveWeakly( mLayout->project() );
808 
809  QgsMapLayerStyle style;
810  style.readXml( layerStyleElement );
811  mLayerStyleOverrides.insert( ref.layerId, style.xmlData() );
812  }
813  }
814 
815  mDrawing = false;
816  mNumCachedLayers = 0;
817  mCacheInvalidated = true;
818 
819  //overviews
820  mOverviewStack->readXml( itemElem, doc, context );
821 
822  //grids
823  mGridStack->readXml( itemElem, doc, context );
824 
825  //atlas
826  QDomNodeList atlasNodeList = itemElem.elementsByTagName( QStringLiteral( "AtlasMap" ) );
827  if ( !atlasNodeList.isEmpty() )
828  {
829  QDomElement atlasElem = atlasNodeList.at( 0 ).toElement();
830  mAtlasDriven = ( atlasElem.attribute( QStringLiteral( "atlasDriven" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
831  if ( atlasElem.hasAttribute( QStringLiteral( "fixedScale" ) ) ) // deprecated XML
832  {
833  mAtlasScalingMode = ( atlasElem.attribute( QStringLiteral( "fixedScale" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) ) ? Fixed : Auto;
834  }
835  else if ( atlasElem.hasAttribute( QStringLiteral( "scalingMode" ) ) )
836  {
837  mAtlasScalingMode = static_cast<AtlasScalingMode>( atlasElem.attribute( QStringLiteral( "scalingMode" ) ).toInt() );
838  }
839  mAtlasMargin = atlasElem.attribute( QStringLiteral( "margin" ), QStringLiteral( "0.1" ) ).toDouble();
840  }
841 
842  setLabelMargin( QgsLayoutMeasurement::decodeMeasurement( itemElem.attribute( QStringLiteral( "labelMargin" ), QStringLiteral( "0" ) ) ) );
843 
844  mMapFlags = static_cast< MapItemFlags>( itemElem.attribute( QStringLiteral( "mapFlags" ), nullptr ).toInt() );
845 
846  // label blocking items
847  mBlockingLabelItems.clear();
848  mBlockingLabelItemUuids.clear();
849  QDomNodeList labelBlockingNodeList = itemElem.elementsByTagName( QStringLiteral( "labelBlockingItems" ) );
850  if ( !labelBlockingNodeList.isEmpty() )
851  {
852  QDomElement blockingItems = labelBlockingNodeList.at( 0 ).toElement();
853  QDomNodeList labelBlockingNodeList = blockingItems.childNodes();
854  for ( int i = 0; i < labelBlockingNodeList.size(); ++i )
855  {
856  const QDomElement &itemBlockingElement = labelBlockingNodeList.at( i ).toElement();
857  const QString itemUuid = itemBlockingElement.attribute( QStringLiteral( "uuid" ) );
858  mBlockingLabelItemUuids << itemUuid;
859  }
860  }
861 
862  mAtlasClippingSettings->readXml( itemElem, doc, context );
863  mItemClippingSettings->readXml( itemElem, doc, context );
864 
866 
867  //temporal settings
868  setIsTemporal( itemElem.attribute( QStringLiteral( "isTemporal" ) ).toInt() );
869  if ( isTemporal() )
870  {
871  const QDateTime begin = QDateTime::fromString( itemElem.attribute( QStringLiteral( "temporalRangeBegin" ) ), Qt::ISODate );
872  const QDateTime end = QDateTime::fromString( itemElem.attribute( QStringLiteral( "temporalRangeEnd" ) ), Qt::ISODate );
873  setTemporalRange( QgsDateTimeRange( begin, end, true, begin == end ) );
874  }
875 
876  mUpdatesEnabled = true;
877  return true;
878 }
879 
880 QPainterPath QgsLayoutItemMap::framePath() const
881 {
882  if ( mItemClippingSettings->isActive() )
883  {
884  const QgsGeometry g = mItemClippingSettings->clipPathInMapItemCoordinates();
885  if ( !g.isNull() )
886  return g.constGet()->asQPainterPath();
887  }
888  return QgsLayoutItem::framePath();
889 }
890 
891 void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem *style, QWidget * )
892 {
893  if ( !mLayout || !painter || !painter->device() || !mUpdatesEnabled )
894  {
895  return;
896  }
897  if ( !shouldDrawItem() )
898  {
899  return;
900  }
901 
902  QRectF thisPaintRect = rect();
903  if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
904  return;
905 
906  //TODO - try to reduce the amount of duplicate code here!
907 
908  if ( mLayout->renderContext().isPreviewRender() )
909  {
910  QgsScopedQPainterState painterState( painter );
911  painter->setClipRect( thisPaintRect );
912  if ( !mCacheFinalImage || mCacheFinalImage->isNull() )
913  {
914  // No initial render available - so draw some preview text alerting user
915  painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
916  painter->drawRect( thisPaintRect );
917  painter->setBrush( Qt::NoBrush );
918  QFont messageFont;
919  messageFont.setPointSize( 12 );
920  painter->setFont( messageFont );
921  painter->setPen( QColor( 255, 255, 255, 255 ) );
922  painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering map" ) );
923  if ( mPainterJob && mCacheInvalidated && !mDrawingPreview )
924  {
925  // current job was invalidated - start a new one
926  mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( style, painter );
927  mBackgroundUpdateTimer->start( 1 );
928  }
929  else if ( !mPainterJob && !mDrawingPreview )
930  {
931  // this is the map's very first paint - trigger a cache update
932  mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( style, painter );
933  mBackgroundUpdateTimer->start( 1 );
934  }
935  }
936  else
937  {
938  if ( mCacheInvalidated && !mDrawingPreview )
939  {
940  // cache was invalidated - trigger a background update
941  mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( style, painter );
942  mBackgroundUpdateTimer->start( 1 );
943  }
944 
945  //Background color is already included in cached image, so no need to draw
946 
947  double imagePixelWidth = mCacheFinalImage->width(); //how many pixels of the image are for the map extent?
948  double scale = rect().width() / imagePixelWidth;
949 
950  QgsScopedQPainterState rotatedPainterState( painter );
951 
952  painter->translate( mLastRenderedImageOffsetX + mXOffset, mLastRenderedImageOffsetY + mYOffset );
953  painter->scale( scale, scale );
954  painter->drawImage( 0, 0, *mCacheFinalImage );
955 
956  //restore rotation
957  }
958 
959  painter->setClipRect( thisPaintRect, Qt::NoClip );
960 
961  mOverviewStack->drawItems( painter, false );
962  mGridStack->drawItems( painter );
963  drawAnnotations( painter );
964  drawMapFrame( painter );
965  }
966  else
967  {
968  if ( mDrawing )
969  return;
970 
971  mDrawing = true;
972  QPaintDevice *paintDevice = painter->device();
973  if ( !paintDevice )
974  return;
975 
976  QgsRectangle cExtent = extent();
977  QSizeF size( cExtent.width() * mapUnitsToLayoutUnits(), cExtent.height() * mapUnitsToLayoutUnits() );
978 
979 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
980  if ( mLayout && mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering )
981  painter->setRenderHint( QPainter::LosslessImageRendering, true );
982 #endif
983 
984  if ( containsAdvancedEffects() && ( !mLayout || !( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagForceVectorOutput ) ) )
985  {
986  // rasterize
987  double destinationDpi = QgsLayoutUtils::scaleFactorFromItemStyle( style, painter ) * 25.4;
988  double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, QgsUnitTypes::LayoutInches ).length() : 1;
989  int widthInPixels = static_cast< int >( std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi ) );
990  int heightInPixels = static_cast< int >( std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi ) );
991  QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
992 
993  image.fill( Qt::transparent );
994  image.setDotsPerMeterX( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
995  image.setDotsPerMeterY( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
996  double dotsPerMM = destinationDpi / 25.4;
997  QPainter p( &image );
998 
999  QPointF tl = -boundingRect().topLeft();
1000  QRect imagePaintRect( static_cast< int >( std::round( tl.x() * dotsPerMM ) ),
1001  static_cast< int >( std::round( tl.y() * dotsPerMM ) ),
1002  static_cast< int >( std::round( thisPaintRect.width() * dotsPerMM ) ),
1003  static_cast< int >( std::round( thisPaintRect.height() * dotsPerMM ) ) );
1004  p.setClipRect( imagePaintRect );
1005 
1006  p.translate( imagePaintRect.topLeft() );
1007 
1008  // Fill with background color - must be drawn onto the flattened image
1009  // so that layers with opacity or blend modes can correctly interact with it
1010  if ( shouldDrawPart( Background ) )
1011  {
1012  p.scale( dotsPerMM, dotsPerMM );
1013  drawMapBackground( &p );
1014  p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
1015  }
1016 
1017  drawMap( &p, cExtent, imagePaintRect.size(), image.logicalDpiX() );
1018 
1019  // important - all other items, overviews, grids etc must be rendered to the
1020  // flattened image, in case these have blend modes must need to interact
1021  // with the map
1022  p.scale( dotsPerMM, dotsPerMM );
1023 
1024  if ( shouldDrawPart( OverviewMapExtent ) )
1025  {
1026  mOverviewStack->drawItems( &p, false );
1027  }
1028  if ( shouldDrawPart( Grid ) )
1029  {
1030  mGridStack->drawItems( &p );
1031  }
1032  drawAnnotations( &p );
1033 
1034  QgsScopedQPainterState painterState( painter );
1035  painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
1036  painter->drawImage( QPointF( -tl.x()* dotsPerMM, -tl.y() * dotsPerMM ), image );
1037  painter->scale( dotsPerMM, dotsPerMM );
1038  }
1039  else
1040  {
1041  // Fill with background color
1042  if ( shouldDrawPart( Background ) )
1043  {
1044  drawMapBackground( painter );
1045  }
1046 
1047  QgsScopedQPainterState painterState( painter );
1048  painter->setClipRect( thisPaintRect );
1049 
1050  if ( shouldDrawPart( Layer ) && !qgsDoubleNear( size.width(), 0.0 ) && !qgsDoubleNear( size.height(), 0.0 ) )
1051  {
1052  QgsScopedQPainterState stagedPainterState( painter );
1053  painter->translate( mXOffset, mYOffset );
1054 
1055  double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
1056  size *= dotsPerMM; // output size will be in dots (pixels)
1057  painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
1058 
1059  if ( mCurrentExportPart != NotLayered )
1060  {
1061  if ( !mStagedRendererJob )
1062  {
1063  createStagedRenderJob( cExtent, size, paintDevice->logicalDpiX() );
1064  }
1065 
1066  mStagedRendererJob->renderCurrentPart( painter );
1067  }
1068  else
1069  {
1070  drawMap( painter, cExtent, size, paintDevice->logicalDpiX() );
1071  }
1072  }
1073 
1074  painter->setClipRect( thisPaintRect, Qt::NoClip );
1075 
1076  if ( shouldDrawPart( OverviewMapExtent ) )
1077  {
1078  mOverviewStack->drawItems( painter, false );
1079  }
1080  if ( shouldDrawPart( Grid ) )
1081  {
1082  mGridStack->drawItems( painter );
1083  }
1084  drawAnnotations( painter );
1085  }
1086 
1087  if ( shouldDrawPart( Frame ) )
1088  {
1089  drawMapFrame( painter );
1090  }
1091 
1092  mDrawing = false;
1093  }
1094 }
1095 
1097 {
1098  const int layerCount = layersToRender().length();
1099  return ( hasBackground() ? 1 : 0 )
1100  + ( layerCount + ( layerCount ? 1 : 0 ) ) // +1 for label layer, if labels present
1101  + ( mGridStack->hasEnabledItems() ? 1 : 0 )
1102  + ( mOverviewStack->hasEnabledItems() ? 1 : 0 )
1103  + ( frameEnabled() ? 1 : 0 );
1104 }
1105 
1107 {
1108  mCurrentExportPart = Start;
1109  // only follow export themes if the map isn't set to follow a fixed theme
1110  mExportThemes = !mFollowVisibilityPreset ? mLayout->renderContext().exportThemes() : QStringList();
1111  mExportThemeIt = mExportThemes.begin();
1112 }
1113 
1115 {
1116  mCurrentExportPart = NotLayered;
1117  mExportThemes.clear();
1118  mExportThemeIt = mExportThemes.begin();
1119 }
1120 
1122 {
1123  switch ( mCurrentExportPart )
1124  {
1125  case Start:
1126  if ( hasBackground() )
1127  {
1128  mCurrentExportPart = Background;
1129  return true;
1130  }
1131  FALLTHROUGH
1132 
1133  case Background:
1134  mCurrentExportPart = Layer;
1135  return true;
1136 
1137  case Layer:
1138  if ( mStagedRendererJob )
1139  {
1140  if ( mStagedRendererJob->nextPart() )
1141  return true;
1142  else
1143  {
1144  mExportLabelingResults.reset( mStagedRendererJob->takeLabelingResults() );
1145  mStagedRendererJob.reset(); // no more map layer parts
1146  }
1147  }
1148 
1149  if ( mExportThemeIt != mExportThemes.end() && ++mExportThemeIt != mExportThemes.end() )
1150  {
1151  // move to next theme and continue exporting map layers
1152  return true;
1153  }
1154 
1155  if ( mGridStack->hasEnabledItems() )
1156  {
1157  mCurrentExportPart = Grid;
1158  return true;
1159  }
1160  FALLTHROUGH
1161 
1162  case Grid:
1163  for ( int i = 0; i < mOverviewStack->size(); ++i )
1164  {
1165  QgsLayoutItemMapItem *item = mOverviewStack->item( i );
1167  {
1168  mCurrentExportPart = OverviewMapExtent;
1169  return true;
1170  }
1171  }
1172  FALLTHROUGH
1173 
1174  case OverviewMapExtent:
1175  if ( frameEnabled() )
1176  {
1177  mCurrentExportPart = Frame;
1178  return true;
1179  }
1180 
1181  FALLTHROUGH
1182 
1183  case Frame:
1184  if ( isSelected() && !mLayout->renderContext().isPreviewRender() )
1185  {
1186  mCurrentExportPart = SelectionBoxes;
1187  return true;
1188  }
1189  FALLTHROUGH
1190 
1191  case SelectionBoxes:
1192  mCurrentExportPart = End;
1193  return false;
1194 
1195  case End:
1196  return false;
1197 
1198  case NotLayered:
1199  return false;
1200  }
1201  return false;
1202 }
1203 
1205 {
1206  return ItemContainsSubLayers;
1207 }
1208 
1210 {
1211  ExportLayerDetail detail;
1212 
1213  switch ( mCurrentExportPart )
1214  {
1215  case Start:
1216  break;
1217 
1218  case Background:
1219  detail.name = tr( "%1: Background" ).arg( displayName() );
1220  return detail;
1221 
1222  case Layer:
1223  if ( !mExportThemes.empty() && mExportThemeIt != mExportThemes.end() )
1224  detail.mapTheme = *mExportThemeIt;
1225 
1226  if ( mStagedRendererJob )
1227  {
1228  switch ( mStagedRendererJob->currentStage() )
1229  {
1231  {
1232  detail.mapLayerId = mStagedRendererJob->currentLayerId();
1233  detail.compositionMode = mStagedRendererJob->currentLayerCompositionMode();
1234  detail.opacity = mStagedRendererJob->currentLayerOpacity();
1235  if ( const QgsMapLayer *layer = mLayout->project()->mapLayer( detail.mapLayerId ) )
1236  {
1237  if ( !detail.mapTheme.isEmpty() )
1238  detail.name = QStringLiteral( "%1 (%2): %3" ).arg( displayName(), detail.mapTheme, layer->name() );
1239  else
1240  detail.name = QStringLiteral( "%1: %2" ).arg( displayName(), layer->name() );
1241  }
1242  else if ( mLayout->project()->mainAnnotationLayer()->id() == detail.mapLayerId )
1243  {
1244  // master annotation layer
1245  if ( !detail.mapTheme.isEmpty() )
1246  detail.name = QStringLiteral( "%1 (%2): %3" ).arg( displayName(), detail.mapTheme, tr( "Annotations" ) );
1247  else
1248  detail.name = QStringLiteral( "%1: %2" ).arg( displayName(), tr( "Annotations" ) );
1249  }
1250  else
1251  {
1252  // might be an item based layer
1253  const QList<QgsLayoutItemMapOverview *> res = mOverviewStack->asList();
1254  for ( QgsLayoutItemMapOverview *item : res )
1255  {
1256  if ( !item || !item->enabled() || item->stackingPosition() == QgsLayoutItemMapItem::StackAboveMapLabels )
1257  continue;
1258 
1259  if ( item->mapLayer() && detail.mapLayerId == item->mapLayer()->id() )
1260  {
1261  if ( !detail.mapTheme.isEmpty() )
1262  detail.name = QStringLiteral( "%1 (%2): %3" ).arg( displayName(), detail.mapTheme, item->mapLayer()->name() );
1263  else
1264  detail.name = QStringLiteral( "%1: %2" ).arg( displayName(), item->mapLayer()->name() );
1265  break;
1266  }
1267  }
1268  }
1269  return detail;
1270  }
1271 
1273  detail.mapLayerId = mStagedRendererJob->currentLayerId();
1274  if ( const QgsMapLayer *layer = mLayout->project()->mapLayer( detail.mapLayerId ) )
1275  {
1276  if ( !detail.mapTheme.isEmpty() )
1277  detail.name = QStringLiteral( "%1 (%2): %3 (Labels)" ).arg( displayName(), detail.mapTheme, layer->name() );
1278  else
1279  detail.name = tr( "%1: %2 (Labels)" ).arg( displayName(), layer->name() );
1280  }
1281  else
1282  {
1283  if ( !detail.mapTheme.isEmpty() )
1284  detail.name = tr( "%1 (%2): Labels" ).arg( displayName(), detail.mapTheme );
1285  else
1286  detail.name = tr( "%1: Labels" ).arg( displayName() );
1287  }
1288  return detail;
1289 
1291  break;
1292  }
1293  }
1294  else
1295  {
1296  // we must be on the first layer, not having had a chance to create the render job yet
1297  const QList< QgsMapLayer * > layers = layersToRender();
1298  if ( !layers.isEmpty() )
1299  {
1300  const QgsMapLayer *layer = layers.constLast();
1301  if ( !detail.mapTheme.isEmpty() )
1302  detail.name = QStringLiteral( "%1 (%2): %3" ).arg( displayName(), detail.mapTheme, layer->name() );
1303  else
1304  detail.name = QStringLiteral( "%1: %2" ).arg( displayName(), layer->name() );
1305  detail.mapLayerId = layer->id();
1306  }
1307  }
1308  break;
1309 
1310  case Grid:
1311  detail.name = tr( "%1: Grids" ).arg( displayName() );
1312  return detail;
1313 
1314  case OverviewMapExtent:
1315  detail.name = tr( "%1: Overviews" ).arg( displayName() );
1316  return detail;
1317 
1318  case Frame:
1319  detail.name = tr( "%1: Frame" ).arg( displayName() );
1320  return detail;
1321 
1322  case SelectionBoxes:
1323  case End:
1324  case NotLayered:
1325  break;
1326  }
1327 
1328  return detail;
1329 }
1330 
1332 {
1335 }
1336 
1337 void QgsLayoutItemMap::drawMap( QPainter *painter, const QgsRectangle &extent, QSizeF size, double dpi )
1338 {
1339  if ( !painter )
1340  {
1341  return;
1342  }
1343  if ( qgsDoubleNear( size.width(), 0.0 ) || qgsDoubleNear( size.height(), 0.0 ) )
1344  {
1345  //don't attempt to draw if size is invalid
1346  return;
1347  }
1348 
1349  // render
1350  QgsMapSettings ms( mapSettings( extent, size, dpi, true ) );
1351  if ( shouldDrawPart( OverviewMapExtent ) )
1352  {
1353  ms.setLayers( mOverviewStack->modifyMapLayerList( ms.layers() ) );
1354  }
1355 
1356  QgsMapRendererCustomPainterJob job( ms, painter );
1357 #ifdef HAVE_SERVER_PYTHON_PLUGINS
1358  job.setFeatureFilterProvider( mLayout->renderContext().featureFilterProvider() );
1359 #endif
1360 
1361  // Render the map in this thread. This is done because of problems
1362  // with printing to printer on Windows (printing to PDF is fine though).
1363  // Raster images were not displayed - see #10599
1364  job.renderSynchronously();
1365 
1366  mExportLabelingResults.reset( job.takeLabelingResults() );
1367 
1368  mRenderingErrors = job.errors();
1369 }
1370 
1371 void QgsLayoutItemMap::recreateCachedImageInBackground()
1372 {
1373  if ( mPainterJob )
1374  {
1375  disconnect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
1376  QgsMapRendererCustomPainterJob *oldJob = mPainterJob.release();
1377  QPainter *oldPainter = mPainter.release();
1378  QImage *oldImage = mCacheRenderingImage.release();
1379  connect( oldJob, &QgsMapRendererCustomPainterJob::finished, this, [oldPainter, oldJob, oldImage]
1380  {
1381  oldJob->deleteLater();
1382  delete oldPainter;
1383  delete oldImage;
1384  } );
1385  oldJob->cancelWithoutBlocking();
1386  }
1387  else
1388  {
1389  mCacheRenderingImage.reset( nullptr );
1390  emit backgroundTaskCountChanged( 1 );
1391  }
1392 
1393  Q_ASSERT( !mPainterJob );
1394  Q_ASSERT( !mPainter );
1395  Q_ASSERT( !mCacheRenderingImage );
1396 
1397  QgsRectangle ext = extent();
1398  double widthLayoutUnits = ext.width() * mapUnitsToLayoutUnits();
1399  double heightLayoutUnits = ext.height() * mapUnitsToLayoutUnits();
1400 
1401  int w = static_cast< int >( std::round( widthLayoutUnits * mPreviewScaleFactor ) );
1402  int h = static_cast< int >( std::round( heightLayoutUnits * mPreviewScaleFactor ) );
1403 
1404  // limit size of image for better performance
1405  if ( w > 5000 || h > 5000 )
1406  {
1407  if ( w > h )
1408  {
1409  w = 5000;
1410  h = static_cast< int>( std::round( w * heightLayoutUnits / widthLayoutUnits ) );
1411  }
1412  else
1413  {
1414  h = 5000;
1415  w = static_cast< int >( std::round( h * widthLayoutUnits / heightLayoutUnits ) );
1416  }
1417  }
1418 
1419  if ( w <= 0 || h <= 0 )
1420  return;
1421 
1422  mCacheRenderingImage.reset( new QImage( w, h, QImage::Format_ARGB32 ) );
1423 
1424  // set DPI of the image
1425  mCacheRenderingImage->setDotsPerMeterX( static_cast< int >( std::round( 1000 * w / widthLayoutUnits ) ) );
1426  mCacheRenderingImage->setDotsPerMeterY( static_cast< int >( std::round( 1000 * h / heightLayoutUnits ) ) );
1427 
1428  //start with empty fill to avoid artifacts
1429  mCacheRenderingImage->fill( QColor( 255, 255, 255, 0 ).rgba() );
1430  if ( hasBackground() )
1431  {
1432  //Initially fill image with specified background color. This ensures that layers with blend modes will
1433  //preview correctly
1434  if ( mItemClippingSettings->isActive() )
1435  {
1436  QPainter p( mCacheRenderingImage.get() );
1437  const QPainterPath path = framePath();
1438  p.setPen( Qt::NoPen );
1439  p.setBrush( backgroundColor() );
1440  p.scale( mCacheRenderingImage->width() / widthLayoutUnits, mCacheRenderingImage->height() / heightLayoutUnits );
1441  p.drawPath( path );
1442  p.end();
1443  }
1444  else
1445  {
1446  mCacheRenderingImage->fill( backgroundColor().rgba() );
1447  }
1448  }
1449 
1450  mCacheInvalidated = false;
1451  mPainter.reset( new QPainter( mCacheRenderingImage.get() ) );
1452  QgsMapSettings settings( mapSettings( ext, QSizeF( w, h ), mCacheRenderingImage->logicalDpiX(), true ) );
1453 
1454  if ( shouldDrawPart( OverviewMapExtent ) )
1455  {
1456  settings.setLayers( mOverviewStack->modifyMapLayerList( settings.layers() ) );
1457  }
1458 
1459  mPainterJob.reset( new QgsMapRendererCustomPainterJob( settings, mPainter.get() ) );
1460  connect( mPainterJob.get(), &QgsMapRendererCustomPainterJob::finished, this, &QgsLayoutItemMap::painterJobFinished );
1461  mPainterJob->start();
1462 
1463  // from now on we can accept refresh requests again
1464  // this must be reset only after the job has been started, because
1465  // some providers (yes, it's you WCS and AMS!) during preparation
1466  // do network requests and start an internal event loop, which may
1467  // end up calling refresh() and would schedule another refresh,
1468  // deleting the one we have just started.
1469 
1470  // ^^ that comment was directly copied from a similar fix in QgsMapCanvas. And
1471  // with little surprise, both those providers are still badly behaved and causing
1472  // annoying bugs for us to deal with...
1473  mDrawingPreview = false;
1474 }
1475 
1476 QgsLayoutItemMap::MapItemFlags QgsLayoutItemMap::mapFlags() const
1477 {
1478  return mMapFlags;
1479 }
1480 
1481 void QgsLayoutItemMap::setMapFlags( QgsLayoutItemMap::MapItemFlags mapFlags )
1482 {
1483  mMapFlags = mapFlags;
1484 }
1485 
1486 QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, double dpi, bool includeLayerSettings ) const
1487 {
1488  QgsExpressionContext expressionContext = createExpressionContext();
1489  QgsCoordinateReferenceSystem renderCrs = crs();
1490 
1491  QgsMapSettings jobMapSettings;
1492  jobMapSettings.setDestinationCrs( renderCrs );
1493  jobMapSettings.setExtent( extent );
1494  jobMapSettings.setOutputSize( size.toSize() );
1495  jobMapSettings.setOutputDpi( dpi );
1496  if ( layout()->renderContext().isPreviewRender() )
1497  jobMapSettings.setDpiTarget( layout()->renderContext().dpi() );
1498  jobMapSettings.setBackgroundColor( Qt::transparent );
1499  jobMapSettings.setRotation( mEvaluatedMapRotation );
1500  if ( mLayout )
1501  jobMapSettings.setEllipsoid( mLayout->project()->ellipsoid() );
1502 
1503  if ( includeLayerSettings )
1504  {
1505  //set layers to render
1506  QList<QgsMapLayer *> layers = layersToRender( &expressionContext );
1507 
1508  if ( !mLayout->project()->mainAnnotationLayer()->isEmpty() )
1509  {
1510  // render main annotation layer above all other layers
1511  layers.insert( 0, mLayout->project()->mainAnnotationLayer() );
1512  }
1513 
1514  jobMapSettings.setLayers( layers );
1515  jobMapSettings.setLayerStyleOverrides( layerStyleOverridesToRender( expressionContext ) );
1516  }
1517 
1518  if ( !mLayout->renderContext().isPreviewRender() )
1519  {
1520  //if outputting layout, we disable optimisations like layer simplification by default, UNLESS the context specifically tells us to use them
1521  jobMapSettings.setFlag( Qgis::MapSettingsFlag::UseRenderingOptimization, mLayout->renderContext().simplifyMethod().simplifyHints() != QgsVectorSimplifyMethod::NoSimplification );
1522  jobMapSettings.setSimplifyMethod( mLayout->renderContext().simplifyMethod() );
1524  }
1525  else
1526  {
1527  // preview render - always use optimization
1529  jobMapSettings.setRendererUsage( Qgis::RendererUsage::View );
1530  }
1531 
1532  jobMapSettings.setExpressionContext( expressionContext );
1533 
1534  // layout-specific overrides of flags
1535  jobMapSettings.setFlag( Qgis::MapSettingsFlag::ForceVectorOutput, true ); // force vector output (no caching of marker images etc.)
1539  jobMapSettings.setFlag( Qgis::MapSettingsFlag::DrawEditingInfo, false );
1540  jobMapSettings.setSelectionColor( mLayout->renderContext().selectionColor() );
1544  jobMapSettings.setTransformContext( mLayout->project()->transformContext() );
1545  jobMapSettings.setPathResolver( mLayout->project()->pathResolver() );
1546 
1547  QgsLabelingEngineSettings labelSettings = mLayout->project()->labelingEngineSettings();
1548 
1549  // override project "show partial labels" setting with this map's setting
1553  jobMapSettings.setLabelingEngineSettings( labelSettings );
1554 
1555  // override the default text render format inherited from the labeling engine settings using the layout's render context setting
1556  jobMapSettings.setTextRenderFormat( mLayout->renderContext().textRenderFormat() );
1557 
1558  QgsGeometry labelBoundary;
1559  if ( mEvaluatedLabelMargin.length() > 0 )
1560  {
1561  QPolygonF visiblePoly = jobMapSettings.visiblePolygon();
1562  visiblePoly.append( visiblePoly.at( 0 ) ); //close polygon
1563  const double layoutLabelMargin = mLayout->convertToLayoutUnits( mEvaluatedLabelMargin );
1564  const double layoutLabelMarginInMapUnits = layoutLabelMargin / rect().width() * jobMapSettings.extent().width();
1565  QgsGeometry mapBoundaryGeom = QgsGeometry::fromQPolygonF( visiblePoly );
1566  mapBoundaryGeom = mapBoundaryGeom.buffer( -layoutLabelMarginInMapUnits, 0 );
1567  labelBoundary = mapBoundaryGeom;
1568  }
1569 
1570  if ( !mBlockingLabelItems.isEmpty() )
1571  {
1572  jobMapSettings.setLabelBlockingRegions( createLabelBlockingRegions( jobMapSettings ) );
1573  }
1574 
1575  for ( QgsRenderedFeatureHandlerInterface *handler : std::as_const( mRenderedFeatureHandlers ) )
1576  {
1577  jobMapSettings.addRenderedFeatureHandler( handler );
1578  }
1579 
1580  if ( isTemporal() )
1581  jobMapSettings.setTemporalRange( temporalRange() );
1582 
1583  if ( mAtlasClippingSettings->enabled() && mLayout->reportContext().feature().isValid() )
1584  {
1585  QgsGeometry clipGeom( mLayout->reportContext().currentGeometry( jobMapSettings.destinationCrs() ) );
1586  QgsMapClippingRegion region( clipGeom );
1587  region.setFeatureClip( mAtlasClippingSettings->featureClippingType() );
1588  region.setRestrictedLayers( mAtlasClippingSettings->layersToClip() );
1589  region.setRestrictToLayers( mAtlasClippingSettings->restrictToLayers() );
1590  jobMapSettings.addClippingRegion( region );
1591 
1592  if ( mAtlasClippingSettings->forceLabelsInsideFeature() )
1593  {
1594  if ( !labelBoundary.isEmpty() )
1595  {
1596  labelBoundary = clipGeom.intersection( labelBoundary );
1597  }
1598  else
1599  {
1600  labelBoundary = clipGeom;
1601  }
1602  }
1603  }
1604 
1605  if ( mItemClippingSettings->isActive() )
1606  {
1607  const QgsGeometry clipGeom = mItemClippingSettings->clippedMapExtent();
1608  if ( !clipGeom.isEmpty() )
1609  {
1610  jobMapSettings.addClippingRegion( mItemClippingSettings->toMapClippingRegion() );
1611 
1612  if ( mItemClippingSettings->forceLabelsInsideClipPath() )
1613  {
1614  const double layoutLabelMargin = mLayout->convertToLayoutUnits( mEvaluatedLabelMargin );
1615  const double layoutLabelMarginInMapUnits = layoutLabelMargin / rect().width() * jobMapSettings.extent().width();
1616  QgsGeometry mapBoundaryGeom = clipGeom;
1617  mapBoundaryGeom = mapBoundaryGeom.buffer( -layoutLabelMarginInMapUnits, 0 );
1618  if ( !labelBoundary.isEmpty() )
1619  {
1620  labelBoundary = mapBoundaryGeom.intersection( labelBoundary );
1621  }
1622  else
1623  {
1624  labelBoundary = mapBoundaryGeom;
1625  }
1626  }
1627  }
1628  }
1629 
1630  if ( !labelBoundary.isNull() )
1631  jobMapSettings.setLabelBoundaryGeometry( labelBoundary );
1632 
1633  return jobMapSettings;
1634 }
1635 
1637 {
1638  assignFreeId();
1639 
1640  mBlockingLabelItems.clear();
1641  for ( const QString &uuid : std::as_const( mBlockingLabelItemUuids ) )
1642  {
1643  QgsLayoutItem *item = mLayout->itemByUuid( uuid, true );
1644  if ( item )
1645  {
1646  addLabelBlockingItem( item );
1647  }
1648  }
1649 
1650  mOverviewStack->finalizeRestoreFromXml();
1651  mGridStack->finalizeRestoreFromXml();
1652  mItemClippingSettings->finalizeRestoreFromXml();
1653 }
1654 
1655 void QgsLayoutItemMap::setMoveContentPreviewOffset( double xOffset, double yOffset )
1656 {
1657  mXOffset = xOffset;
1658  mYOffset = yOffset;
1659 }
1660 
1662 {
1663  return mCurrentRectangle;
1664 }
1665 
1667 {
1669 
1670  //Can't utilize QgsExpressionContextUtils::mapSettingsScope as we don't always
1671  //have a QgsMapSettings object available when the context is required, so we manually
1672  //add the same variables here
1673  QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Map Settings" ) );
1674 
1675  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_id" ), id(), true ) );
1676  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_rotation" ), mMapRotation, true ) );
1677  const double mapScale = scale();
1678  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_scale" ), mapScale, true ) );
1679 
1680  scope->setVariable( QStringLiteral( "zoom_level" ), QgsVectorTileUtils::scaleToZoomLevel( mapScale, 0, 99999 ), true );
1681  scope->setVariable( QStringLiteral( "vector_tile_zoom" ), QgsVectorTileUtils::scaleToZoom( mapScale ), true );
1682 
1683  QgsRectangle currentExtent( extent() );
1684  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent" ), QVariant::fromValue( QgsGeometry::fromRect( currentExtent ) ), true ) );
1685  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_width" ), currentExtent.width(), true ) );
1686  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_height" ), currentExtent.height(), true ) );
1687  QgsGeometry centerPoint = QgsGeometry::fromPointXY( currentExtent.center() );
1688  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_center" ), QVariant::fromValue( centerPoint ), true ) );
1689 
1690  QgsCoordinateReferenceSystem mapCrs = crs();
1691  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs" ), mapCrs.authid(), true ) );
1692  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_definition" ), mapCrs.toProj(), true ) );
1693  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_description" ), mapCrs.description(), true ) );
1694  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_units" ), QgsUnitTypes::toString( mapCrs.mapUnits() ), true ) );
1695  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_acronym" ), mapCrs.projectionAcronym(), true ) );
1696  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_projection" ), mapCrs.operation().description(), true ) );
1697  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_ellipsoid" ), mapCrs.ellipsoidAcronym(), true ) );
1698  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_proj4" ), mapCrs.toProj(), true ) );
1699  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_wkt" ), mapCrs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ), true ) );
1700 
1701  QVariantList layersIds;
1702  QVariantList layers;
1703  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layer_ids" ), layersIds, true ) );
1704  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layers" ), layers, true ) );
1705 
1706  context.appendScope( scope );
1707 
1708  // The scope map_layer_ids and map_layers variables have been added to the context, only now we can
1709  // call layersToRender (just in case layersToRender relies on evaluating an expression which uses
1710  // other variables contained within the map settings scope
1711  const QList<QgsMapLayer *> layersInMap = layersToRender( &context );
1712 
1713  layersIds.reserve( layersInMap.count() );
1714  layers.reserve( layersInMap.count() );
1715  for ( QgsMapLayer *layer : layersInMap )
1716  {
1717  layersIds << layer->id();
1718  layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( layer ) );
1719  }
1720  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layer_ids" ), layersIds, true ) );
1721  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layers" ), layers, true ) );
1722 
1723  scope->addFunction( QStringLiteral( "is_layer_visible" ), new QgsExpressionContextUtils::GetLayerVisibility( layersInMap, scale() ) );
1724 
1725  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_start_time" ), isTemporal() ? temporalRange().begin() : QVariant(), true ) );
1726  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_end_time" ), isTemporal() ? temporalRange().end() : QVariant(), true ) );
1727  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_interval" ), isTemporal() ? ( temporalRange().end() - temporalRange().begin() ) : QVariant(), true ) );
1728 
1729 #if 0 // not relevant here! (but left so as to respect all the dangerous warnings in QgsExpressionContextUtils::mapSettingsScope)
1730  if ( mapSettings.frameRate() >= 0 )
1731  scope->setVariable( QStringLiteral( "frame_rate" ), mapSettings.frameRate(), true );
1732  if ( mapSettings.currentFrame() >= 0 )
1733  scope->setVariable( QStringLiteral( "frame_number" ), mapSettings.currentFrame(), true );
1734 #endif
1735 
1736  return context;
1737 }
1738 
1740 {
1741  double extentWidth = extent().width();
1742  if ( extentWidth <= 0 )
1743  {
1744  return 1;
1745  }
1746  return rect().width() / extentWidth;
1747 }
1748 
1750 {
1751  double dx = mXOffset;
1752  double dy = mYOffset;
1753  transformShift( dx, dy );
1754  QPolygonF poly = calculateVisibleExtentPolygon( false );
1755  poly.translate( -dx, -dy );
1756  return poly;
1757 }
1758 
1760 {
1761  if ( !mBlockingLabelItems.contains( item ) )
1762  mBlockingLabelItems.append( item );
1763 
1764  connect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemMap::invalidateCache, Qt::UniqueConnection );
1765 }
1766 
1768 {
1769  mBlockingLabelItems.removeAll( item );
1770  if ( item )
1772 }
1773 
1775 {
1776  return mBlockingLabelItems.contains( item );
1777 }
1778 
1780 {
1781  return mPreviewLabelingResults.get();
1782 }
1783 
1785 {
1786  // NOTE: if visitEnter returns false it means "don't visit the item", not "abort all further visitations"
1788  return true;
1789 
1790  if ( mOverviewStack )
1791  {
1792  for ( int i = 0; i < mOverviewStack->size(); ++i )
1793  {
1794  if ( mOverviewStack->item( i )->accept( visitor ) )
1795  return false;
1796  }
1797  }
1798 
1799  if ( mGridStack )
1800  {
1801  for ( int i = 0; i < mGridStack->size(); ++i )
1802  {
1803  if ( mGridStack->item( i )->accept( visitor ) )
1804  return false;
1805  }
1806  }
1807 
1809  return false;
1810 
1811  return true;
1812 }
1813 
1815 {
1816  mRenderedFeatureHandlers.append( handler );
1817 }
1818 
1820 {
1821  mRenderedFeatureHandlers.removeAll( handler );
1822 }
1823 
1824 QPointF QgsLayoutItemMap::mapToItemCoords( QPointF mapCoords ) const
1825 {
1826  QPolygonF mapPoly = transformedMapPolygon();
1827  if ( mapPoly.empty() )
1828  {
1829  return QPointF( 0, 0 );
1830  }
1831 
1832  QgsRectangle tExtent = transformedExtent();
1833  QgsPointXY rotationPoint( ( tExtent.xMaximum() + tExtent.xMinimum() ) / 2.0, ( tExtent.yMaximum() + tExtent.yMinimum() ) / 2.0 );
1834  double dx = mapCoords.x() - rotationPoint.x();
1835  double dy = mapCoords.y() - rotationPoint.y();
1836  QgsLayoutUtils::rotate( -mEvaluatedMapRotation, dx, dy );
1837  QgsPointXY backRotatedCoords( rotationPoint.x() + dx, rotationPoint.y() + dy );
1838 
1839  QgsRectangle unrotatedExtent = transformedExtent();
1840  double xItem = rect().width() * ( backRotatedCoords.x() - unrotatedExtent.xMinimum() ) / unrotatedExtent.width();
1841  double yItem = rect().height() * ( 1 - ( backRotatedCoords.y() - unrotatedExtent.yMinimum() ) / unrotatedExtent.height() );
1842  return QPointF( xItem, yItem );
1843 }
1844 
1846 {
1848  QgsRectangle newExtent = mExtent;
1849  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
1850  {
1851  extent = newExtent;
1852  }
1853  else
1854  {
1855  QPolygonF poly;
1856  mapPolygon( newExtent, poly );
1857  QRectF bRect = poly.boundingRect();
1858  extent.setXMinimum( bRect.left() );
1859  extent.setXMaximum( bRect.right() );
1860  extent.setYMinimum( bRect.top() );
1861  extent.setYMaximum( bRect.bottom() );
1862  }
1863  return extent;
1864 }
1865 
1867 {
1868  if ( mDrawing )
1869  return;
1870 
1871  mCacheInvalidated = true;
1872  update();
1873 }
1874 
1876 {
1877  QRectF rectangle = rect();
1878  double frameExtension = frameEnabled() ? pen().widthF() / 2.0 : 0.0;
1879 
1880  double topExtension = 0.0;
1881  double rightExtension = 0.0;
1882  double bottomExtension = 0.0;
1883  double leftExtension = 0.0;
1884 
1885  if ( mGridStack )
1886  mGridStack->calculateMaxGridExtension( topExtension, rightExtension, bottomExtension, leftExtension );
1887 
1888  topExtension = std::max( topExtension, frameExtension );
1889  rightExtension = std::max( rightExtension, frameExtension );
1890  bottomExtension = std::max( bottomExtension, frameExtension );
1891  leftExtension = std::max( leftExtension, frameExtension );
1892 
1893  rectangle.setLeft( rectangle.left() - leftExtension );
1894  rectangle.setRight( rectangle.right() + rightExtension );
1895  rectangle.setTop( rectangle.top() - topExtension );
1896  rectangle.setBottom( rectangle.bottom() + bottomExtension );
1897  if ( rectangle != mCurrentRectangle )
1898  {
1899  prepareGeometryChange();
1900  mCurrentRectangle = rectangle;
1901  }
1902 }
1903 
1905 {
1907  if ( property == QgsLayoutObject::MapCrs || property == QgsLayoutObject::AllProperties )
1908  {
1909  bool ok;
1910  const QString crsVar = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapCrs, context, QString(), &ok );
1911  if ( ok && QgsCoordinateReferenceSystem( crsVar ).isValid() )
1912  {
1913  const QgsCoordinateReferenceSystem newCrs( crsVar );
1914  if ( newCrs.isValid() )
1915  {
1916  setCrs( newCrs );
1917  }
1918  }
1919  }
1920  //updates data defined properties and redraws item to match
1921  if ( property == QgsLayoutObject::MapRotation || property == QgsLayoutObject::MapScale ||
1922  property == QgsLayoutObject::MapXMin || property == QgsLayoutObject::MapYMin ||
1923  property == QgsLayoutObject::MapXMax || property == QgsLayoutObject::MapYMax ||
1924  property == QgsLayoutObject::MapAtlasMargin ||
1925  property == QgsLayoutObject::AllProperties )
1926  {
1927  QgsRectangle beforeExtent = mExtent;
1928  refreshMapExtents( &context );
1929  emit changed();
1930  if ( mExtent != beforeExtent )
1931  {
1932  emit extentChanged();
1933  }
1934  }
1935  if ( property == QgsLayoutObject::MapLabelMargin || property == QgsLayoutObject::AllProperties )
1936  {
1937  refreshLabelMargin( false );
1938  }
1939  if ( property == QgsLayoutObject::MapStylePreset || property == QgsLayoutObject::AllProperties )
1940  {
1941  const QString previousTheme = mLastEvaluatedThemeName.isEmpty() ? mFollowVisibilityPresetName : mLastEvaluatedThemeName;
1942  mLastEvaluatedThemeName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, mFollowVisibilityPresetName );
1943  if ( mLastEvaluatedThemeName != previousTheme )
1944  emit themeChanged( mLastEvaluatedThemeName );
1945  }
1946 
1947  if ( isTemporal() && ( property == QgsLayoutObject::StartDateTime || property == QgsLayoutObject::EndDateTime || property == QgsLayoutObject::AllProperties ) )
1948  {
1949  QDateTime begin = temporalRange().begin();
1950  QDateTime end = temporalRange().end();
1951 
1952  if ( property == QgsLayoutObject::StartDateTime || property == QgsLayoutObject::AllProperties )
1954  if ( property == QgsLayoutObject::EndDateTime || property == QgsLayoutObject::AllProperties )
1956 
1957  setTemporalRange( QgsDateTimeRange( begin, end, true, begin == end ) );
1958  }
1959 
1960  //force redraw
1961  mCacheInvalidated = true;
1962 
1964 }
1965 
1966 void QgsLayoutItemMap::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
1967 {
1968  if ( !mLayers.isEmpty() || mLayerStyleOverrides.isEmpty() )
1969  {
1970  for ( QgsMapLayer *layer : layers )
1971  {
1972  mLayerStyleOverrides.remove( layer->id() );
1973  }
1974  _qgis_removeLayers( mLayers, layers );
1975  }
1976 }
1977 
1978 void QgsLayoutItemMap::painterJobFinished()
1979 {
1980  mPainter->end();
1981  mPreviewLabelingResults.reset( mPainterJob->takeLabelingResults() );
1982  mPainterJob.reset( nullptr );
1983  mPainter.reset( nullptr );
1984  mCacheFinalImage = std::move( mCacheRenderingImage );
1985  mLastRenderedImageOffsetX = 0;
1986  mLastRenderedImageOffsetY = 0;
1987  emit backgroundTaskCountChanged( 0 );
1988  update();
1989  emit previewRefreshed();
1990 }
1991 
1992 void QgsLayoutItemMap::shapeChanged()
1993 {
1994  // keep center as center
1995  QgsPointXY oldCenter = mExtent.center();
1996 
1997  double w = rect().width();
1998  double h = rect().height();
1999 
2000  // keep same width as before
2001  double newWidth = mExtent.width();
2002  // but scale height to match item's aspect ratio
2003  double newHeight = newWidth * h / w;
2004 
2005  mExtent = QgsRectangle::fromCenterAndSize( oldCenter, newWidth, newHeight );
2006 
2007  //recalculate data defined scale and extents
2008  refreshMapExtents();
2010  invalidateCache();
2011  emit changed();
2012  emit extentChanged();
2013 }
2014 
2015 void QgsLayoutItemMap::mapThemeChanged( const QString &theme )
2016 {
2017  if ( theme == mCachedLayerStyleOverridesPresetName )
2018  mCachedLayerStyleOverridesPresetName.clear(); // force cache regeneration at next redraw
2019 }
2020 
2021 void QgsLayoutItemMap::currentMapThemeRenamed( const QString &theme, const QString &newTheme )
2022 {
2023  if ( theme == mFollowVisibilityPresetName )
2024  {
2025  mFollowVisibilityPresetName = newTheme;
2026  }
2027 }
2028 
2029 void QgsLayoutItemMap::connectUpdateSlot()
2030 {
2031  //connect signal from layer registry to update in case of new or deleted layers
2032  QgsProject *project = mLayout->project();
2033  if ( project )
2034  {
2035  // handles updating the stored layer state BEFORE the layers are removed
2036  connect( project, static_cast < void ( QgsProject::* )( const QList<QgsMapLayer *>& layers ) > ( &QgsProject::layersWillBeRemoved ),
2037  this, &QgsLayoutItemMap::layersAboutToBeRemoved );
2038  // redraws the map AFTER layers are removed
2039  connect( project->layerTreeRoot(), &QgsLayerTree::layerOrderChanged, this, [ = ]
2040  {
2041  if ( layers().isEmpty() )
2042  {
2043  //using project layers, and layer order has changed
2044  invalidateCache();
2045  }
2046  } );
2047 
2048  connect( project, &QgsProject::crsChanged, this, [ = ]
2049  {
2050  if ( !mCrs.isValid() )
2051  {
2052  //using project CRS, which just changed....
2053  invalidateCache();
2054  emit crsChanged();
2055  }
2056  } );
2057 
2058  // If project colors change, we need to redraw the map, as layer symbols may rely on project colors
2059  connect( project, &QgsProject::projectColorsChanged, this, [ = ]
2060  {
2061  invalidateCache();
2062  } );
2063 
2064  connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsLayoutItemMap::mapThemeChanged );
2065  connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsLayoutItemMap::currentMapThemeRenamed );
2066  }
2067  connect( mLayout, &QgsLayout::refreshed, this, &QgsLayoutItemMap::invalidateCache );
2068  connect( &mLayout->renderContext(), &QgsLayoutRenderContext::predefinedScalesChanged, this, [ = ]
2069  {
2070  if ( mAtlasScalingMode == Predefined )
2071  updateAtlasFeature();
2072  } );
2073 }
2074 
2076 {
2077  QPolygonF thisExtent = calculateVisibleExtentPolygon( false );
2078  QTransform mapTransform;
2079  QPolygonF thisRectPoly = QPolygonF( QRectF( 0, 0, rect().width(), rect().height() ) );
2080  //workaround QT Bug #21329
2081  thisRectPoly.pop_back();
2082  thisExtent.pop_back();
2083 
2084  QPolygonF thisItemPolyInLayout = mapToScene( thisRectPoly );
2085 
2086  //create transform from layout coordinates to map coordinates
2087  QTransform::quadToQuad( thisItemPolyInLayout, thisExtent, mapTransform );
2088  return mapTransform;
2089 }
2090 
2091 QList<QgsLabelBlockingRegion> QgsLayoutItemMap::createLabelBlockingRegions( const QgsMapSettings & ) const
2092 {
2093  const QTransform mapTransform = layoutToMapCoordsTransform();
2094  QList< QgsLabelBlockingRegion > blockers;
2095  blockers.reserve( mBlockingLabelItems.count() );
2096  for ( const auto &item : std::as_const( mBlockingLabelItems ) )
2097  {
2098  // invisible items don't block labels!
2099  if ( !item )
2100  continue;
2101 
2102  // layout items may be temporarily hidden during layered exports
2103  if ( item->property( "wasVisible" ).isValid() )
2104  {
2105  if ( !item->property( "wasVisible" ).toBool() )
2106  continue;
2107  }
2108  else if ( !item->isVisible() )
2109  continue;
2110 
2111  QPolygonF itemRectInMapCoordinates = mapTransform.map( item->mapToScene( item->rect() ) );
2112  itemRectInMapCoordinates.append( itemRectInMapCoordinates.at( 0 ) ); //close polygon
2113  QgsGeometry blockingRegion = QgsGeometry::fromQPolygonF( itemRectInMapCoordinates );
2114  blockers << QgsLabelBlockingRegion( blockingRegion );
2115  }
2116  return blockers;
2117 }
2118 
2120 {
2121  return mLabelMargin;
2122 }
2123 
2125 {
2126  mLabelMargin = margin;
2127  refreshLabelMargin( false );
2128 }
2129 
2130 void QgsLayoutItemMap::updateToolTip()
2131 {
2132  setToolTip( displayName() );
2133 }
2134 
2135 QString QgsLayoutItemMap::themeToRender( const QgsExpressionContext &context ) const
2136 {
2137  QString presetName;
2138 
2139  if ( mFollowVisibilityPreset )
2140  {
2141  presetName = mFollowVisibilityPresetName;
2142  // preset name can be overridden by data-defined one
2143  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
2144  }
2145  else if ( !mExportThemes.empty() && mExportThemeIt != mExportThemes.end() )
2146  presetName = *mExportThemeIt;
2147  return presetName;
2148 }
2149 
2150 QList<QgsMapLayer *> QgsLayoutItemMap::layersToRender( const QgsExpressionContext *context ) const
2151 {
2152  QgsExpressionContext scopedContext;
2153  if ( !context )
2154  scopedContext = createExpressionContext();
2155  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
2156 
2157  QList<QgsMapLayer *> renderLayers;
2158 
2159  QString presetName = themeToRender( *evalContext );
2160  if ( !presetName.isEmpty() )
2161  {
2162  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
2163  renderLayers = mLayout->project()->mapThemeCollection()->mapThemeVisibleLayers( presetName );
2164  else // fallback to using map canvas layers
2165  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
2166  }
2167  else if ( !layers().isEmpty() )
2168  {
2169  renderLayers = layers();
2170  }
2171  else
2172  {
2173  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
2174  }
2175 
2176  bool ok = false;
2177  QString ddLayers = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapLayers, *evalContext, QString(), &ok );
2178  if ( ok )
2179  {
2180  renderLayers.clear();
2181 
2182  const QStringList layerNames = ddLayers.split( '|' );
2183  //need to convert layer names to layer ids
2184  for ( const QString &name : layerNames )
2185  {
2186  const QList< QgsMapLayer * > matchingLayers = mLayout->project()->mapLayersByName( name );
2187  for ( QgsMapLayer *layer : matchingLayers )
2188  {
2189  renderLayers << layer;
2190  }
2191  }
2192  }
2193 
2194  //remove atlas coverage layer if required
2195  if ( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagHideCoverageLayer )
2196  {
2197  //hiding coverage layer
2198  int removeAt = renderLayers.indexOf( mLayout->reportContext().layer() );
2199  if ( removeAt != -1 )
2200  {
2201  renderLayers.removeAt( removeAt );
2202  }
2203  }
2204 
2205  // remove any invalid layers
2206  renderLayers.erase( std::remove_if( renderLayers.begin(), renderLayers.end(), []( QgsMapLayer * layer )
2207  {
2208  return !layer || !layer->isValid();
2209  } ), renderLayers.end() );
2210 
2211  return renderLayers;
2212 }
2213 
2214 QMap<QString, QString> QgsLayoutItemMap::layerStyleOverridesToRender( const QgsExpressionContext &context ) const
2215 {
2216  QString presetName = themeToRender( context );
2217  if ( !presetName.isEmpty() )
2218  {
2219  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
2220  {
2221  if ( presetName != mCachedLayerStyleOverridesPresetName )
2222  {
2223  // have to regenerate cache of style overrides
2224  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
2225  mCachedLayerStyleOverridesPresetName = presetName;
2226  }
2227 
2228  return mCachedPresetLayerStyleOverrides;
2229  }
2230  else
2231  return QMap<QString, QString>();
2232  }
2233  else if ( mFollowVisibilityPreset )
2234  {
2235  QString presetName = mFollowVisibilityPresetName;
2236  // data defined preset name?
2237  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
2238  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
2239  {
2240  if ( presetName.isEmpty() || presetName != mCachedLayerStyleOverridesPresetName )
2241  {
2242  // have to regenerate cache of style overrides
2243  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
2244  mCachedLayerStyleOverridesPresetName = presetName;
2245  }
2246 
2247  return mCachedPresetLayerStyleOverrides;
2248  }
2249  else
2250  return QMap<QString, QString>();
2251  }
2252  else if ( mKeepLayerStyles )
2253  {
2254  return mLayerStyleOverrides;
2255  }
2256  else
2257  {
2258  return QMap<QString, QString>();
2259  }
2260 }
2261 
2262 QgsRectangle QgsLayoutItemMap::transformedExtent() const
2263 {
2264  double dx = mXOffset;
2265  double dy = mYOffset;
2266  transformShift( dx, dy );
2267  return QgsRectangle( mExtent.xMinimum() - dx, mExtent.yMinimum() - dy, mExtent.xMaximum() - dx, mExtent.yMaximum() - dy );
2268 }
2269 
2270 void QgsLayoutItemMap::mapPolygon( const QgsRectangle &extent, QPolygonF &poly ) const
2271 {
2272  poly.clear();
2273  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
2274  {
2275  poly << QPointF( extent.xMinimum(), extent.yMaximum() );
2276  poly << QPointF( extent.xMaximum(), extent.yMaximum() );
2277  poly << QPointF( extent.xMaximum(), extent.yMinimum() );
2278  poly << QPointF( extent.xMinimum(), extent.yMinimum() );
2279  //ensure polygon is closed by readding first point
2280  poly << QPointF( poly.at( 0 ) );
2281  return;
2282  }
2283 
2284  //there is rotation
2285  QgsPointXY rotationPoint( ( extent.xMaximum() + extent.xMinimum() ) / 2.0, ( extent.yMaximum() + extent.yMinimum() ) / 2.0 );
2286  double dx, dy; //x-, y- shift from rotation point to corner point
2287 
2288  //top left point
2289  dx = rotationPoint.x() - extent.xMinimum();
2290  dy = rotationPoint.y() - extent.yMaximum();
2291  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2292  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2293 
2294  //top right point
2295  dx = rotationPoint.x() - extent.xMaximum();
2296  dy = rotationPoint.y() - extent.yMaximum();
2297  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2298  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2299 
2300  //bottom right point
2301  dx = rotationPoint.x() - extent.xMaximum();
2302  dy = rotationPoint.y() - extent.yMinimum();
2303  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2304  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2305 
2306  //bottom left point
2307  dx = rotationPoint.x() - extent.xMinimum();
2308  dy = rotationPoint.y() - extent.yMinimum();
2309  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2310  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2311 
2312  //ensure polygon is closed by readding first point
2313  poly << QPointF( poly.at( 0 ) );
2314 }
2315 
2316 void QgsLayoutItemMap::transformShift( double &xShift, double &yShift ) const
2317 {
2318  double mmToMapUnits = 1.0 / mapUnitsToLayoutUnits();
2319  double dxScaled = xShift * mmToMapUnits;
2320  double dyScaled = - yShift * mmToMapUnits;
2321 
2322  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dxScaled, dyScaled );
2323 
2324  xShift = dxScaled;
2325  yShift = dyScaled;
2326 }
2327 
2328 void QgsLayoutItemMap::drawAnnotations( QPainter *painter )
2329 {
2330  if ( !mLayout || !mLayout->project() || !mDrawAnnotations )
2331  {
2332  return;
2333  }
2334 
2335  const QList< QgsAnnotation * > annotations = mLayout->project()->annotationManager()->annotations();
2336  if ( annotations.isEmpty() )
2337  return;
2338 
2340  rc.setForceVectorOutput( true );
2342  QList< QgsMapLayer * > layers = layersToRender( &rc.expressionContext() );
2343 
2344  for ( QgsAnnotation *annotation : annotations )
2345  {
2346  if ( !annotation || !annotation->isVisible() )
2347  {
2348  continue;
2349  }
2350  if ( annotation->mapLayer() && !layers.contains( annotation->mapLayer() ) )
2351  continue;
2352 
2353  drawAnnotation( annotation, rc );
2354  }
2355 }
2356 
2357 void QgsLayoutItemMap::drawAnnotation( const QgsAnnotation *annotation, QgsRenderContext &context )
2358 {
2359  if ( !annotation || !annotation->isVisible() || !context.painter() || !context.painter()->device() )
2360  {
2361  return;
2362  }
2363 
2364  QgsScopedQPainterState painterState( context.painter() );
2365  context.setPainterFlagsUsingContext();
2366 
2367  double itemX, itemY;
2368  if ( annotation->hasFixedMapPosition() )
2369  {
2370  QPointF mapPos = layoutMapPosForItem( annotation );
2371  itemX = mapPos.x();
2372  itemY = mapPos.y();
2373  }
2374  else
2375  {
2376  itemX = annotation->relativePosition().x() * rect().width();
2377  itemY = annotation->relativePosition().y() * rect().height();
2378  }
2379  context.painter()->translate( itemX, itemY );
2380 
2381  //setup painter scaling to dots so that symbology is drawn to scale
2382  double dotsPerMM = context.painter()->device()->logicalDpiX() / 25.4;
2383  context.painter()->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
2384 
2385  annotation->render( context );
2386 }
2387 
2388 QPointF QgsLayoutItemMap::layoutMapPosForItem( const QgsAnnotation *annotation ) const
2389 {
2390  if ( !annotation )
2391  return QPointF( 0, 0 );
2392 
2393  double mapX = 0.0;
2394  double mapY = 0.0;
2395 
2396  mapX = annotation->mapPosition().x();
2397  mapY = annotation->mapPosition().y();
2398  QgsCoordinateReferenceSystem annotationCrs = annotation->mapPositionCrs();
2399 
2400  if ( annotationCrs != crs() )
2401  {
2402  //need to reproject
2403  QgsCoordinateTransform t( annotationCrs, crs(), mLayout->project() );
2404  double z = 0.0;
2405  try
2406  {
2407  t.transformInPlace( mapX, mapY, z );
2408  }
2409  catch ( const QgsCsException & )
2410  {
2411  }
2412  }
2413 
2414  return mapToItemCoords( QPointF( mapX, mapY ) );
2415 }
2416 
2417 void QgsLayoutItemMap::drawMapFrame( QPainter *p )
2418 {
2419  if ( frameEnabled() && p )
2420  {
2423 
2425  }
2426 }
2427 
2428 void QgsLayoutItemMap::drawMapBackground( QPainter *p )
2429 {
2430  if ( hasBackground() && p )
2431  {
2434 
2436  }
2437 }
2438 
2439 bool QgsLayoutItemMap::shouldDrawPart( QgsLayoutItemMap::PartType part ) const
2440 {
2441  if ( mCurrentExportPart == NotLayered )
2442  {
2443  //all parts of the map are visible
2444  return true;
2445  }
2446 
2447  switch ( part )
2448  {
2449  case NotLayered:
2450  return true;
2451 
2452  case Start:
2453  return false;
2454 
2455  case Background:
2456  return mCurrentExportPart == Background && hasBackground();
2457 
2458  case Layer:
2459  return mCurrentExportPart == Layer;
2460 
2461  case Grid:
2462  return mCurrentExportPart == Grid && mGridStack->hasEnabledItems();
2463 
2464  case OverviewMapExtent:
2465  return mCurrentExportPart == OverviewMapExtent && mOverviewStack->hasEnabledItems();
2466 
2467  case Frame:
2468  return mCurrentExportPart == Frame && frameEnabled();
2469 
2470  case SelectionBoxes:
2471  return mCurrentExportPart == SelectionBoxes && isSelected();
2472 
2473  case End:
2474  return false;
2475  }
2476 
2477  return false;
2478 }
2479 
2480 void QgsLayoutItemMap::refreshMapExtents( const QgsExpressionContext *context )
2481 {
2482  QgsExpressionContext scopedContext;
2483  if ( !context )
2484  scopedContext = createExpressionContext();
2485 
2486  bool ok = false;
2487  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
2488 
2489 
2490  //data defined map extents set?
2491  QgsRectangle newExtent = extent();
2492  bool useDdXMin = false;
2493  bool useDdXMax = false;
2494  bool useDdYMin = false;
2495  bool useDdYMax = false;
2496  double minXD = 0;
2497  double minYD = 0;
2498  double maxXD = 0;
2499  double maxYD = 0;
2500 
2501  minXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMin, *evalContext, 0.0, &ok );
2502  if ( ok )
2503  {
2504  useDdXMin = true;
2505  newExtent.setXMinimum( minXD );
2506  }
2507  minYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMin, *evalContext, 0.0, &ok );
2508  if ( ok )
2509  {
2510  useDdYMin = true;
2511  newExtent.setYMinimum( minYD );
2512  }
2513  maxXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMax, *evalContext, 0.0, &ok );
2514  if ( ok )
2515  {
2516  useDdXMax = true;
2517  newExtent.setXMaximum( maxXD );
2518  }
2519  maxYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMax, *evalContext, 0.0, &ok );
2520  if ( ok )
2521  {
2522  useDdYMax = true;
2523  newExtent.setYMaximum( maxYD );
2524  }
2525 
2526  if ( newExtent != mExtent )
2527  {
2528  //calculate new extents to fit data defined extents
2529 
2530  //Make sure the width/height ratio is the same as in current map extent.
2531  //This is to keep the map item frame and the page layout fixed
2532  double currentWidthHeightRatio = mExtent.width() / mExtent.height();
2533  double newWidthHeightRatio = newExtent.width() / newExtent.height();
2534 
2535  if ( currentWidthHeightRatio < newWidthHeightRatio )
2536  {
2537  //enlarge height of new extent, ensuring the map center stays the same
2538  double newHeight = newExtent.width() / currentWidthHeightRatio;
2539  double deltaHeight = newHeight - newExtent.height();
2540  newExtent.setYMinimum( newExtent.yMinimum() - deltaHeight / 2 );
2541  newExtent.setYMaximum( newExtent.yMaximum() + deltaHeight / 2 );
2542  }
2543  else
2544  {
2545  //enlarge width of new extent, ensuring the map center stays the same
2546  double newWidth = currentWidthHeightRatio * newExtent.height();
2547  double deltaWidth = newWidth - newExtent.width();
2548  newExtent.setXMinimum( newExtent.xMinimum() - deltaWidth / 2 );
2549  newExtent.setXMaximum( newExtent.xMaximum() + deltaWidth / 2 );
2550  }
2551 
2552  mExtent = newExtent;
2553  }
2554 
2555  //now refresh scale, as this potentially overrides extents
2556 
2557  //data defined map scale set?
2558  double scaleD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapScale, *evalContext, 0.0, &ok );
2559  if ( ok )
2560  {
2561  setScale( scaleD, false );
2562  newExtent = mExtent;
2563  }
2564 
2565  if ( useDdXMax || useDdXMin || useDdYMax || useDdYMin )
2566  {
2567  //if only one of min/max was set for either x or y, then make sure our extent is locked on that value
2568  //as we can do this without altering the scale
2569  if ( useDdXMin && !useDdXMax )
2570  {
2571  double xMax = mExtent.xMaximum() - ( mExtent.xMinimum() - minXD );
2572  newExtent.setXMinimum( minXD );
2573  newExtent.setXMaximum( xMax );
2574  }
2575  else if ( !useDdXMin && useDdXMax )
2576  {
2577  double xMin = mExtent.xMinimum() - ( mExtent.xMaximum() - maxXD );
2578  newExtent.setXMinimum( xMin );
2579  newExtent.setXMaximum( maxXD );
2580  }
2581  if ( useDdYMin && !useDdYMax )
2582  {
2583  double yMax = mExtent.yMaximum() - ( mExtent.yMinimum() - minYD );
2584  newExtent.setYMinimum( minYD );
2585  newExtent.setYMaximum( yMax );
2586  }
2587  else if ( !useDdYMin && useDdYMax )
2588  {
2589  double yMin = mExtent.yMinimum() - ( mExtent.yMaximum() - maxYD );
2590  newExtent.setYMinimum( yMin );
2591  newExtent.setYMaximum( maxYD );
2592  }
2593 
2594  if ( newExtent != mExtent )
2595  {
2596  mExtent = newExtent;
2597  }
2598  }
2599 
2600  //lastly, map rotation overrides all
2601  double mapRotation = mMapRotation;
2602 
2603  //data defined map rotation set?
2605 
2606  if ( !qgsDoubleNear( mEvaluatedMapRotation, mapRotation ) )
2607  {
2608  mEvaluatedMapRotation = mapRotation;
2610  }
2611 }
2612 
2613 void QgsLayoutItemMap::refreshLabelMargin( bool updateItem )
2614 {
2615  //data defined label margin set?
2617  mEvaluatedLabelMargin.setLength( labelMargin );
2618  mEvaluatedLabelMargin.setUnits( mLabelMargin.units() );
2619 
2620  if ( updateItem )
2621  {
2622  update();
2623  }
2624 }
2625 
2626 void QgsLayoutItemMap::updateAtlasFeature()
2627 {
2628  if ( !atlasDriven() || !mLayout->reportContext().layer() )
2629  return; // nothing to do
2630 
2631  QgsRectangle bounds = computeAtlasRectangle();
2632  if ( bounds.isNull() )
2633  return;
2634 
2635  double xa1 = bounds.xMinimum();
2636  double xa2 = bounds.xMaximum();
2637  double ya1 = bounds.yMinimum();
2638  double ya2 = bounds.yMaximum();
2639  QgsRectangle newExtent = bounds;
2640  QgsRectangle originalExtent = mExtent;
2641 
2642  //sanity check - only allow fixed scale mode for point layers
2643  bool isPointLayer = QgsWkbTypes::geometryType( mLayout->reportContext().layer()->wkbType() ) == QgsWkbTypes::PointGeometry;
2644 
2645  if ( mAtlasScalingMode == Fixed || mAtlasScalingMode == Predefined || isPointLayer )
2646  {
2647  QgsScaleCalculator calc;
2648  calc.setMapUnits( crs().mapUnits() );
2649  calc.setDpi( 25.4 );
2650  double originalScale = calc.calculate( originalExtent, rect().width() );
2651  double geomCenterX = ( xa1 + xa2 ) / 2.0;
2652  double geomCenterY = ( ya1 + ya2 ) / 2.0;
2653  QVector<qreal> scales;
2655  if ( !mLayout->reportContext().predefinedScales().empty() ) // remove when deprecated method is removed
2656  scales = mLayout->reportContext().predefinedScales();
2657  else
2658  scales = mLayout->renderContext().predefinedScales();
2660  if ( mAtlasScalingMode == Fixed || scales.isEmpty() || ( isPointLayer && mAtlasScalingMode != Predefined ) )
2661  {
2662  // only translate, keep the original scale (i.e. width x height)
2663  double xMin = geomCenterX - originalExtent.width() / 2.0;
2664  double yMin = geomCenterY - originalExtent.height() / 2.0;
2665  newExtent = QgsRectangle( xMin,
2666  yMin,
2667  xMin + originalExtent.width(),
2668  yMin + originalExtent.height() );
2669 
2670  //scale newExtent to match original scale of map
2671  //this is required for geographic coordinate systems, where the scale varies by extent
2672  double newScale = calc.calculate( newExtent, rect().width() );
2673  newExtent.scale( originalScale / newScale );
2674  }
2675  else if ( mAtlasScalingMode == Predefined )
2676  {
2677  // choose one of the predefined scales
2678  double newWidth = originalExtent.width();
2679  double newHeight = originalExtent.height();
2680  for ( int i = 0; i < scales.size(); i++ )
2681  {
2682  double ratio = scales[i] / originalScale;
2683  newWidth = originalExtent.width() * ratio;
2684  newHeight = originalExtent.height() * ratio;
2685 
2686  // compute new extent, centered on feature
2687  double xMin = geomCenterX - newWidth / 2.0;
2688  double yMin = geomCenterY - newHeight / 2.0;
2689  newExtent = QgsRectangle( xMin,
2690  yMin,
2691  xMin + newWidth,
2692  yMin + newHeight );
2693 
2694  //scale newExtent to match desired map scale
2695  //this is required for geographic coordinate systems, where the scale varies by extent
2696  double newScale = calc.calculate( newExtent, rect().width() );
2697  newExtent.scale( scales[i] / newScale );
2698 
2699  if ( ( newExtent.width() >= bounds.width() ) && ( newExtent.height() >= bounds.height() ) )
2700  {
2701  // this is the smallest extent that embeds the feature, stop here
2702  break;
2703  }
2704  }
2705  }
2706  }
2707  else if ( mAtlasScalingMode == Auto )
2708  {
2709  // auto scale
2710 
2711  double geomRatio = bounds.width() / bounds.height();
2712  double mapRatio = originalExtent.width() / originalExtent.height();
2713 
2714  // geometry height is too big
2715  if ( geomRatio < mapRatio )
2716  {
2717  // extent the bbox's width
2718  double adjWidth = ( mapRatio * bounds.height() - bounds.width() ) / 2.0;
2719  xa1 -= adjWidth;
2720  xa2 += adjWidth;
2721  }
2722  // geometry width is too big
2723  else if ( geomRatio > mapRatio )
2724  {
2725  // extent the bbox's height
2726  double adjHeight = ( bounds.width() / mapRatio - bounds.height() ) / 2.0;
2727  ya1 -= adjHeight;
2728  ya2 += adjHeight;
2729  }
2730  newExtent = QgsRectangle( xa1, ya1, xa2, ya2 );
2731 
2732  const double evaluatedAtlasMargin = atlasMargin();
2733  if ( evaluatedAtlasMargin > 0.0 )
2734  {
2735  newExtent.scale( 1 + evaluatedAtlasMargin );
2736  }
2737  }
2738 
2739  // set the new extent (and render)
2740  setExtent( newExtent );
2741  emit preparedForAtlas();
2742 }
2743 
2744 QgsRectangle QgsLayoutItemMap::computeAtlasRectangle()
2745 {
2746  // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
2747  // We have to transform the geometry to the destination CRS and ask for the bounding box
2748  // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
2749  QgsGeometry g = mLayout->reportContext().currentGeometry( crs() );
2750  // Rotating the geometry, so the bounding box is correct wrt map rotation
2751  if ( mEvaluatedMapRotation != 0.0 )
2752  {
2753  QgsPointXY prevCenter = g.boundingBox().center();
2754  g.rotate( mEvaluatedMapRotation, g.boundingBox().center() );
2755  // Rotation center will be still the bounding box center of an unrotated geometry.
2756  // Which means, if the center of bbox moves after rotation, the viewport will
2757  // also be offset, and part of the geometry will fall out of bounds.
2758  // Here we compensate for that roughly: by extending the rotated bounds
2759  // so that its center is the same as the original.
2760  QgsRectangle bounds = g.boundingBox();
2761  double dx = std::max( std::abs( prevCenter.x() - bounds.xMinimum() ),
2762  std::abs( prevCenter.x() - bounds.xMaximum() ) );
2763  double dy = std::max( std::abs( prevCenter.y() - bounds.yMinimum() ),
2764  std::abs( prevCenter.y() - bounds.yMaximum() ) );
2765  QgsPointXY center = g.boundingBox().center();
2766  return QgsRectangle( center.x() - dx, center.y() - dy,
2767  center.x() + dx, center.y() + dy );
2768  }
2769  else
2770  {
2771  return g.boundingBox();
2772  }
2773 }
2774 
2775 void QgsLayoutItemMap::createStagedRenderJob( const QgsRectangle &extent, const QSizeF size, double dpi )
2776 {
2777  QgsMapSettings settings = mapSettings( extent, size, dpi, true );
2778  settings.setLayers( mOverviewStack->modifyMapLayerList( settings.layers() ) );
2779 
2780  mStagedRendererJob = std::make_unique< QgsMapRendererStagedRenderJob >( settings,
2783  : QgsMapRendererStagedRenderJob::Flags() );
2784  mStagedRendererJob->start();
2785 }
2786 
2787 
2788 
2789 //
2790 // QgsLayoutItemMapAtlasClippingSettings
2791 //
2792 
2794  : QObject( map )
2795  , mMap( map )
2796 {
2797  if ( mMap->layout() && mMap->layout()->project() )
2798  {
2799  connect( mMap->layout()->project(), static_cast < void ( QgsProject::* )( const QList<QgsMapLayer *>& layers ) > ( &QgsProject::layersWillBeRemoved ),
2800  this, &QgsLayoutItemMapAtlasClippingSettings::layersAboutToBeRemoved );
2801  }
2802 }
2803 
2805 {
2806  return mClipToAtlasFeature;
2807 }
2808 
2810 {
2811  if ( enabled == mClipToAtlasFeature )
2812  return;
2813 
2814  mClipToAtlasFeature = enabled;
2815  emit changed();
2816 }
2817 
2819 {
2820  return mFeatureClippingType;
2821 }
2822 
2824 {
2825  if ( mFeatureClippingType == type )
2826  return;
2827 
2828  mFeatureClippingType = type;
2829  emit changed();
2830 }
2831 
2833 {
2834  return mForceLabelsInsideFeature;
2835 }
2836 
2838 {
2839  if ( forceInside == mForceLabelsInsideFeature )
2840  return;
2841 
2842  mForceLabelsInsideFeature = forceInside;
2843  emit changed();
2844 }
2845 
2847 {
2848  return mRestrictToLayers;
2849 }
2850 
2852 {
2853  if ( mRestrictToLayers == enabled )
2854  return;
2855 
2856  mRestrictToLayers = enabled;
2857  emit changed();
2858 }
2859 
2861 {
2862  return _qgis_listRefToRaw( mLayersToClip );
2863 }
2864 
2865 void QgsLayoutItemMapAtlasClippingSettings::setLayersToClip( const QList< QgsMapLayer * > &layersToClip )
2866 {
2867  mLayersToClip = _qgis_listRawToRef( layersToClip );
2868  emit changed();
2869 }
2870 
2871 bool QgsLayoutItemMapAtlasClippingSettings::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
2872 {
2873  QDomElement settingsElem = document.createElement( QStringLiteral( "atlasClippingSettings" ) );
2874  settingsElem.setAttribute( QStringLiteral( "enabled" ), mClipToAtlasFeature ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2875  settingsElem.setAttribute( QStringLiteral( "forceLabelsInside" ), mForceLabelsInsideFeature ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2876  settingsElem.setAttribute( QStringLiteral( "clippingType" ), QString::number( static_cast<int>( mFeatureClippingType ) ) );
2877  settingsElem.setAttribute( QStringLiteral( "restrictLayers" ), mRestrictToLayers ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2878 
2879  //layer set
2880  QDomElement layerSetElem = document.createElement( QStringLiteral( "layersToClip" ) );
2881  for ( const QgsMapLayerRef &layerRef : mLayersToClip )
2882  {
2883  if ( !layerRef )
2884  continue;
2885  QDomElement layerElem = document.createElement( QStringLiteral( "Layer" ) );
2886  QDomText layerIdText = document.createTextNode( layerRef.layerId );
2887  layerElem.appendChild( layerIdText );
2888 
2889  layerElem.setAttribute( QStringLiteral( "name" ), layerRef.name );
2890  layerElem.setAttribute( QStringLiteral( "source" ), layerRef.source );
2891  layerElem.setAttribute( QStringLiteral( "provider" ), layerRef.provider );
2892 
2893  layerSetElem.appendChild( layerElem );
2894  }
2895  settingsElem.appendChild( layerSetElem );
2896 
2897  element.appendChild( settingsElem );
2898  return true;
2899 }
2900 
2901 bool QgsLayoutItemMapAtlasClippingSettings::readXml( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext & )
2902 {
2903  const QDomElement settingsElem = element.firstChildElement( QStringLiteral( "atlasClippingSettings" ) );
2904 
2905  mClipToAtlasFeature = settingsElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
2906  mForceLabelsInsideFeature = settingsElem.attribute( QStringLiteral( "forceLabelsInside" ), QStringLiteral( "0" ) ).toInt();
2907  mFeatureClippingType = static_cast< QgsMapClippingRegion::FeatureClippingType >( settingsElem.attribute( QStringLiteral( "clippingType" ), QStringLiteral( "0" ) ).toInt() );
2908  mRestrictToLayers = settingsElem.attribute( QStringLiteral( "restrictLayers" ), QStringLiteral( "0" ) ).toInt();
2909 
2910  mLayersToClip.clear();
2911  QDomNodeList layerSetNodeList = settingsElem.elementsByTagName( QStringLiteral( "layersToClip" ) );
2912  if ( !layerSetNodeList.isEmpty() )
2913  {
2914  QDomElement layerSetElem = layerSetNodeList.at( 0 ).toElement();
2915  QDomNodeList layerIdNodeList = layerSetElem.elementsByTagName( QStringLiteral( "Layer" ) );
2916  mLayersToClip.reserve( layerIdNodeList.size() );
2917  for ( int i = 0; i < layerIdNodeList.size(); ++i )
2918  {
2919  QDomElement layerElem = layerIdNodeList.at( i ).toElement();
2920  QString layerId = layerElem.text();
2921  QString layerName = layerElem.attribute( QStringLiteral( "name" ) );
2922  QString layerSource = layerElem.attribute( QStringLiteral( "source" ) );
2923  QString layerProvider = layerElem.attribute( QStringLiteral( "provider" ) );
2924 
2925  QgsMapLayerRef ref( layerId, layerName, layerSource, layerProvider );
2926  if ( mMap->layout() && mMap->layout()->project() )
2927  ref.resolveWeakly( mMap->layout()->project() );
2928  mLayersToClip << ref;
2929  }
2930  }
2931 
2932  return true;
2933 }
2934 
2935 void QgsLayoutItemMapAtlasClippingSettings::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
2936 {
2937  if ( !mLayersToClip.isEmpty() )
2938  {
2939  _qgis_removeLayers( mLayersToClip, layers );
2940  }
2941 }
2942 
2943 //
2944 // QgsLayoutItemMapItemClipPathSettings
2945 //
2947  : QObject( map )
2948  , mMap( map )
2949 {
2950 }
2951 
2953 {
2954  return mEnabled && mClipPathSource;
2955 }
2956 
2958 {
2959  return mEnabled;
2960 }
2961 
2963 {
2964  if ( enabled == mEnabled )
2965  return;
2966 
2967  mEnabled = enabled;
2968 
2969  if ( mClipPathSource )
2970  {
2971  // may need to refresh the clip source in order to get it to render/not render depending on enabled state
2972  mClipPathSource->refresh();
2973  }
2974  emit changed();
2975 }
2976 
2978 {
2979  if ( isActive() )
2980  {
2981  QgsGeometry clipGeom( mClipPathSource->clipPath() );
2982  clipGeom.transform( mMap->layoutToMapCoordsTransform() );
2983  return clipGeom;
2984  }
2985  return QgsGeometry();
2986 }
2987 
2989 {
2990  if ( isActive() )
2991  {
2992  QgsGeometry clipGeom( mClipPathSource->clipPath() );
2993  clipGeom.transform( mMap->sceneTransform().inverted() );
2994  return clipGeom;
2995  }
2996  return QgsGeometry();
2997 }
2998 
3000 {
3002  region.setFeatureClip( mFeatureClippingType );
3003  return region;
3004 }
3005 
3007 {
3008  if ( mClipPathSource == item )
3009  return;
3010 
3011  if ( mClipPathSource )
3012  {
3013  disconnect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::refresh );
3014  disconnect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::refresh );
3015  disconnect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::extentChanged );
3016  disconnect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::extentChanged );
3017  }
3018 
3019  QgsLayoutItem *oldItem = mClipPathSource;
3020  mClipPathSource = item;
3021 
3022  if ( mClipPathSource )
3023  {
3024  // if item size or rotation changes, we need to redraw this map
3025  connect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::refresh );
3026  connect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::refresh );
3027  // and if clip item size or rotation changes, then effectively we've changed the visible extent of the map
3028  connect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::extentChanged );
3029  connect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::extentChanged );
3030  // trigger a redraw of the clip source, so that it becomes invisible
3031  mClipPathSource->refresh();
3032  }
3033 
3034  if ( oldItem )
3035  {
3036  // may need to refresh the previous item in order to get it to render
3037  oldItem->refresh();
3038  }
3039 
3040  emit changed();
3041 }
3042 
3044 {
3045  return mClipPathSource;
3046 }
3047 
3049 {
3050  return mFeatureClippingType;
3051 }
3052 
3054 {
3055  if ( mFeatureClippingType == type )
3056  return;
3057 
3058  mFeatureClippingType = type;
3059  emit changed();
3060 }
3061 
3063 {
3064  return mForceLabelsInsideClipPath;
3065 }
3066 
3068 {
3069  if ( forceInside == mForceLabelsInsideClipPath )
3070  return;
3071 
3072  mForceLabelsInsideClipPath = forceInside;
3073  emit changed();
3074 }
3075 
3076 bool QgsLayoutItemMapItemClipPathSettings::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
3077 {
3078  QDomElement settingsElem = document.createElement( QStringLiteral( "itemClippingSettings" ) );
3079  settingsElem.setAttribute( QStringLiteral( "enabled" ), mEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
3080  settingsElem.setAttribute( QStringLiteral( "forceLabelsInside" ), mForceLabelsInsideClipPath ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
3081  settingsElem.setAttribute( QStringLiteral( "clippingType" ), QString::number( static_cast<int>( mFeatureClippingType ) ) );
3082  if ( mClipPathSource )
3083  settingsElem.setAttribute( QStringLiteral( "clipSource" ), mClipPathSource->uuid() );
3084  else
3085  settingsElem.setAttribute( QStringLiteral( "clipSource" ), QString() );
3086 
3087  element.appendChild( settingsElem );
3088  return true;
3089 }
3090 
3091 bool QgsLayoutItemMapItemClipPathSettings::readXml( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext & )
3092 {
3093  const QDomElement settingsElem = element.firstChildElement( QStringLiteral( "itemClippingSettings" ) );
3094 
3095  mEnabled = settingsElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
3096  mForceLabelsInsideClipPath = settingsElem.attribute( QStringLiteral( "forceLabelsInside" ), QStringLiteral( "0" ) ).toInt();
3097  mFeatureClippingType = static_cast< QgsMapClippingRegion::FeatureClippingType >( settingsElem.attribute( QStringLiteral( "clippingType" ), QStringLiteral( "0" ) ).toInt() );
3098  mClipPathUuid = settingsElem.attribute( QStringLiteral( "clipSource" ) );
3099 
3100  return true;
3101 }
3102 
3104 {
3105  if ( !mClipPathUuid.isEmpty() )
3106  {
3107  if ( QgsLayoutItem *item = mMap->layout()->itemByUuid( mClipPathUuid, true ) )
3108  {
3109  setSourceItem( item );
3110  }
3111  }
3112 }
@ Export
Renderer used for printing or exporting to a file.
@ View
Renderer used for displaying on screen.
@ DrawEditingInfo
Enable drawing of vertex markers for layers in editing mode.
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
@ ForceVectorOutput
Vector graphics should not be cached and drawn as raster images.
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ DrawSelection
Whether vector selections should be shown in the rendered map.
@ Antialiasing
Enable anti-aliasing for map rendering.
@ UseAdvancedEffects
Enable layer opacity and blending effects.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
virtual QPainterPath asQPainterPath() const =0
Returns the geometry represented as a QPainterPath.
QDateTime valueAsDateTime(int key, const QgsExpressionContext &context, const QDateTime &defaultDateTime=QDateTime(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a datetime.
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.
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.
Abstract base class for annotation items which are drawn over a map.
Definition: qgsannotation.h:54
bool hasFixedMapPosition
Definition: qgsannotation.h:72
QgsCoordinateReferenceSystem mapPositionCrs() const
Returns the CRS of the map position, or an invalid CRS if the annotation does not have a fixed map po...
QgsPointXY mapPosition
Definition: qgsannotation.h:73
void render(QgsRenderContext &context) const
Renders the annotation to a target render context.
bool isVisible() const
Returns true if the annotation is visible and should be rendered.
Definition: qgsannotation.h:95
QPointF relativePosition() const
Returns the relative position of the annotation, if it is not attached to a fixed map position.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsCoordinateReferenceSystemRegistry * coordinateReferenceSystemRegistry()
Returns the application's coordinate reference system (CRS) registry, which handles known CRS definit...
void userCrsChanged(const QString &id)
Emitted whenever an existing user CRS definition is changed.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QString toProj() const
Returns a Proj string representation of this CRS.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
void updateDefinition()
Updates the definition and parameters of the coordinate reference system to their latest values.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
QgsProjOperation operation() const
Returns information about the PROJ operation associated with the coordinate reference system,...
Q_GADGET QgsUnitTypes::DistanceUnit mapUnits
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addFunction(const QString &name, QgsScopedExpressionFunction *function)
Adds a function to the scope.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
void setVariable(const QString &name, const QVariant &value, bool isStatic=false)
Convenience method for setting a variable in the context scope by name name and value.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
static QgsGeometry fromRect(const QgsRectangle &rect) SIP_HOLDGIL
Creates a new geometry from a QgsRectangle.
QgsGeometry intersection(const QgsGeometry &geometry) const
Returns a geometry representing the points shared by this geometry and other.
static QgsGeometry fromPointXY(const QgsPointXY &point) SIP_HOLDGIL
Creates a new geometry from a QgsPointXY object.
QPolygonF asQPolygonF() const SIP_HOLDGIL
Returns contents of the geometry as a QPolygonF.
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...
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::GeometryOperationResult rotate(double rotation, const QgsPointXY &center)
Rotate this geometry around the Z axis.
Label blocking region (in map coordinates and CRS).
Stores global configuration for labeling engine.
@ UsePartialCandidates
Whether to use also label candidates that are partially outside of the map view.
@ CollectUnplacedLabels
Whether unplaced labels should be collected in the labeling results (regardless of whether they are b...
@ DrawUnplacedLabels
Whether to render unplaced labels as an indicator/warning for users.
void setFlag(Flag f, bool enabled=true)
Sets whether a particual flag is enabled.
Class that stores computed placement from labeling engine.
void layerOrderChanged()
Emitted when the layer order has changed.
Contains settings relating to clipping a layout map by the current atlas feature.
void setFeatureClippingType(QgsMapClippingRegion::FeatureClippingType type)
Sets the feature clipping type to apply when clipping to the current atlas feature.
bool restrictToLayers() const
Returns true if clipping should be restricted to a subset of layers.
QgsLayoutItemMapAtlasClippingSettings(QgsLayoutItemMap *map=nullptr)
Constructor for QgsLayoutItemMapAtlasClippingSettings, with the specified map parent.
bool readXml(const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context)
Sets the setting's state from a DOM document, where element is the DOM node corresponding to a 'Layou...
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const
Stores settings in a DOM element, where element is the DOM element corresponding to a 'LayoutMap' tag...
void setRestrictToLayers(bool enabled)
Sets whether clipping should be restricted to a subset of layers.
void setLayersToClip(const QList< QgsMapLayer * > &layers)
Sets the list of map layers to clip to the atlas feature.
QList< QgsMapLayer * > layersToClip() const
Returns the list of map layers to clip to the atlas feature.
void setEnabled(bool enabled)
Sets whether the map content should be clipped to the current atlas feature.
void changed()
Emitted when the atlas clipping settings are changed.
bool forceLabelsInsideFeature() const
Returns true if labels should only be placed inside the atlas feature geometry.
bool enabled() const
Returns true if the map content should be clipped to the current atlas feature.
void setForceLabelsInsideFeature(bool forceInside)
Sets whether labels should only be placed inside the atlas feature geometry.
QgsMapClippingRegion::FeatureClippingType featureClippingType() const
Returns the feature clipping type to apply when clipping to the current atlas feature.
An individual grid which is drawn above the map content in a QgsLayoutItemMap.
Contains settings relating to clipping a layout map by another layout item.
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const
Stores settings in a DOM element, where element is the DOM element corresponding to a 'LayoutMap' tag...
void setForceLabelsInsideClipPath(bool forceInside)
Sets whether labels should only be placed inside the clip path geometry.
void setSourceItem(QgsLayoutItem *item)
Sets the source item which will provide the clipping path for the map.
QgsLayoutItemMapItemClipPathSettings(QgsLayoutItemMap *map=nullptr)
Constructor for QgsLayoutItemMapItemClipPathSettings, with the specified map parent.
bool readXml(const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context)
Sets the setting's state from a DOM document, where element is the DOM node corresponding to a 'Layou...
QgsGeometry clipPathInMapItemCoordinates() const
Returns the clipping path geometry, in the map item's coordinate space.
QgsGeometry clippedMapExtent() const
Returns the geometry to use for clipping the parent map, in the map item's CRS.
QgsLayoutItem * sourceItem()
Returns the source item which will provide the clipping path for the map, or nullptr if no item is se...
void setEnabled(bool enabled)
Sets whether the map content should be clipped to the associated item.
bool forceLabelsInsideClipPath() const
Returns true if labels should only be placed inside the clip path geometry.
void finalizeRestoreFromXml()
To be called after all pending items have been restored from XML.
QgsMapClippingRegion::FeatureClippingType featureClippingType() const
Returns the feature clipping type to apply when clipping to the associated item.
bool enabled() const
Returns true if the map content should be clipped to the associated item.
QgsMapClippingRegion toMapClippingRegion() const
Returns the clip path as a map clipping region.
void changed()
Emitted when the item clipping settings are changed.
void setFeatureClippingType(QgsMapClippingRegion::FeatureClippingType type)
Sets the feature clipping type to apply when clipping to the associated item.
bool isActive() const
Returns true if the item clipping is enabled and set to a valid source item.
An item which is drawn inside a QgsLayoutItemMap, e.g., a grid or map overview.
@ StackAboveMapLabels
Render above all map layers and labels.
StackingPosition stackingPosition() const
Returns the item's stacking position, which specifies where the in the map's stack the item should be...
bool enabled() const
Returns whether the item will be drawn.
An individual overview which is drawn above the map content in a QgsLayoutItemMap,...
Layout graphical items for displaying a map.
void setFollowVisibilityPreset(bool follow)
Sets whether the map should follow a map theme.
bool nextExportPart() override
Moves to the next export part for a multi-layered export item, during a multi-layered export.
void removeRenderedFeatureHandler(QgsRenderedFeatureHandlerInterface *handler)
Removes a previously added rendered feature handler.
void extentChanged()
Emitted when the map's extent changes.
QPointF mapToItemCoords(QPointF mapCoords) const
Transforms map coordinates to item coordinates (considering rotation and move offset)
bool accept(QgsStyleEntityVisitorInterface *visitor) const override
Accepts the specified style entity visitor, causing it to visit all style entities associated with th...
~QgsLayoutItemMap() override
QIcon icon() const override
Returns the item's icon.
void preparedForAtlas()
Emitted when the map has been prepared for atlas rendering, just before actual rendering.
void setFollowVisibilityPresetName(const QString &name)
Sets preset name for map rendering.
QTransform layoutToMapCoordsTransform() const
Creates a transform from layout coordinates to map coordinates.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
QgsMapSettings mapSettings(const QgsRectangle &extent, QSizeF size, double dpi, bool includeLayerSettings) const
Returns map settings that will be used for drawing of the map.
bool isLabelBlockingItem(QgsLayoutItem *item) const
Returns true if the specified item is a "label blocking item".
void storeCurrentLayerStyles()
Stores the current project layer styles into style overrides.
void setAtlasDriven(bool enabled)
Sets whether the map extent will follow the current atlas feature.
QgsLayoutMeasurement labelMargin() const
Returns the margin from the map edges in which no labels may be placed.
AtlasScalingMode
Scaling modes used for the serial rendering (atlas)
@ Predefined
A scale is chosen from the predefined scales.
@ Auto
The extent is adjusted so that each feature is fully visible.
@ Fixed
The current scale of the map is used for each feature of the atlas.
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...
Q_DECL_DEPRECATED int numberExportLayers() const override
Returns the number of layers that this item requires for exporting during layered exports (e....
void layerStyleOverridesChanged()
Emitted when layer style overrides are changed...
void updateBoundingRect()
Updates the bounding rect of this item. Call this function before doing any changes related to annota...
void moveContent(double dx, double dy) override
Moves the content of the item, by a specified dx and dy in layout units.
QgsLayoutItemMapGrid * grid()
Returns the map item's first grid.
void mapRotationChanged(double newRotation)
Emitted when the map's rotation changes.
int type() const override
void previewRefreshed()
Emitted whenever the item's map preview has been refreshed.
friend class QgsLayoutItemMapOverview
void setExtent(const QgsRectangle &extent)
Sets a new extent for the map.
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
QPolygonF visibleExtentPolygon() const
Returns a polygon representing the current visible map extent, considering map extents and rotation.
QgsRectangle requestedExtent() const
Calculates the extent to request and the yShift of the top-left point in case of rotation.
QgsLayoutItemMap(QgsLayout *layout)
Constructor for QgsLayoutItemMap, with the specified parent layout.
void setMapFlags(QgsLayoutItemMap::MapItemFlags flags)
Sets the map item's flags, which control how the map content is drawn.
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties) override
void zoomContent(double factor, QPointF point) override
Zooms content of item.
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...
void crsChanged()
Emitted when the map's coordinate reference system is changed.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the stored layers set.
QPolygonF transformedMapPolygon() const
Returns extent that considers rotation and shift with mOffsetX / mOffsetY.
static QgsLayoutItemMap * create(QgsLayout *layout)
Returns a new map item for the specified layout.
QRectF boundingRect() const override
QString displayName() const override
Gets item display name.
bool atlasDriven() const
Returns whether the map extent is set to follow the current atlas feature.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the stored overrides of styles for layers.
void stopLayeredExport() override
Stops a multi-layer export operation.
void addRenderedFeatureHandler(QgsRenderedFeatureHandlerInterface *handler)
Adds a rendered feature handler to use while rendering the map.
QPainterPath framePath() const override
Returns the path to use when drawing the item's frame or background.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void startLayeredExport() override
Starts a multi-layer export operation.
bool containsWmsLayer() const
Returns true if the map contains a WMS layer.
void setScale(double scale, bool forceUpdate=true)
Sets new map scale and changes only the map extent.
void refresh() override
double mapRotation(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the layout item, in degrees clockwise.
double mapUnitsToLayoutUnits() const
Returns the conversion factor from map units to layout units.
QgsLayoutItemMap::MapItemFlags mapFlags() const
Returns the map item's flags, which control how the map content is drawn.
void setLabelMargin(const QgsLayoutMeasurement &margin)
Sets the margin from the map edges in which no labels may be placed.
void themeChanged(const QString &theme)
Emitted when the map's associated theme is changed.
QgsLayoutItem::ExportLayerDetail exportLayerDetails() const override
Returns the details for the specified current export layer.
void zoomToExtent(const QgsRectangle &extent)
Zooms the map so that the specified extent is fully visible within the map item.
double scale() const
Returns the map scale.
@ ShowPartialLabels
Whether to draw labels which are partially outside of the map view.
@ ShowUnplacedLabels
Whether to render unplaced labels in the map view.
bool drawAnnotations() const
Returns whether annotations are drawn within the map.
void removeLabelBlockingItem(QgsLayoutItem *item)
Removes the specified layout item from the map's "label blocking items".
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the map's preset crs (coordinate reference system).
void invalidateCache() override
QgsRectangle extent() const
Returns the current map extent.
QgsLayoutItemMapOverview * overview()
Returns the map item's first overview.
void finalizeRestoreFromXml() override
Called after all pending items have been restored from XML.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
void setFrameStrokeWidth(QgsLayoutMeasurement width) override
Sets the frame stroke width.
bool containsAdvancedEffects() const override
Returns true if the item contains contents with blend modes or transparency effects which can only be...
friend class QgsLayoutItemMapGrid
QgsLayoutItem::Flags itemFlags() const override
Returns the item's flags, which indicate how the item behaves.
QList< QgsMapLayer * > layers() const
Returns the stored layer set.
void setMoveContentPreviewOffset(double dx, double dy) override
Sets temporary offset for the item, by a specified dx and dy in layout units.
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...
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
double atlasMargin(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue)
Returns the margin size (percentage) used when the map is in atlas mode.
void addLabelBlockingItem(QgsLayoutItem *item)
Sets the specified layout item as a "label blocking item" for this map.
void assignFreeId()
Sets the map id() to a number not yet used in the layout.
ExportLayerBehavior exportLayerBehavior() const override
Returns the behavior of this item during exporting to layered exports (e.g.
QgsLabelingResults * previewLabelingResults() const
Returns the labeling results of the most recent preview map render.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:45
Base class for graphical items within a QgsLayout.
virtual void drawFrame(QgsRenderContext &context)
Draws the frame around the item.
virtual QPainterPath framePath() const
Returns the path to use when drawing the item's frame or background.
virtual void setFrameStrokeWidth(QgsLayoutMeasurement width)
Sets the frame stroke width.
void rotationChanged(double newRotation)
Emitted on item rotation change.
QColor backgroundColor() const
Returns the background color for this item.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
virtual void drawBackground(QgsRenderContext &context)
Draws the background for the item.
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...
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
virtual void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties)
Refreshes a data defined property for the item by reevaluating the property's value and redrawing the...
@ FlagOverridesPaint
Item overrides the default layout item painting method.
virtual bool containsAdvancedEffects() const
Returns true if the item contains contents with blend modes or transparency effects which can only be...
void sizePositionChanged()
Emitted when the item's size or position changes.
virtual QString uuid() const
Returns the item identification string.
QString id() const
Returns the item's ID name.
bool frameEnabled() const
Returns true if the item includes a frame.
ExportLayerBehavior
Behavior of item when exporting to layered outputs.
@ ItemContainsSubLayers
Item contains multiple sublayers which must be individually exported.
void clipPathChanged()
Emitted when the item's clipping path has changed.
bool hasBackground() const
Returns true if the item has a background.
void refresh() override
Refreshes the item, causing a recalculation of any property overrides and recalculation of its positi...
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item's position and size to match the passed rect in layout coordinates.
void backgroundTaskCountChanged(int count)
Emitted whenever the number of background tasks an item is executing changes.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
void setLength(const double length)
Sets the length of the measurement.
static QgsLayoutMeasurement decodeMeasurement(const QString &string)
Decodes a measurement from a string.
QString encodeMeasurement() const
Encodes the layout measurement to a string.
double length() const
Returns the length of the measurement.
QgsUnitTypes::LayoutUnit units() const
Returns the units for the measurement.
void setUnits(const QgsUnitTypes::LayoutUnit units)
Sets the units for the measurement.
QgsPropertyCollection mDataDefinedProperties
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
QPointer< QgsLayout > mLayout
DataDefinedProperty
Data defined properties for different item types.
@ MapYMin
Map extent y minimum.
@ MapStylePreset
Layer and style map theme.
@ MapXMax
Map extent x maximum.
@ StartDateTime
Temporal range's start DateTime.
@ AllProperties
All properties for item.
@ EndDateTime
Temporal range's end DateTime.
@ MapAtlasMargin
Map atlas margin.
@ MapYMax
Map extent y maximum.
@ MapXMin
Map extent x minimum.
@ MapScale
Map scale.
@ MapLabelMargin
Map label margin.
@ MapLayers
Map layer set.
@ MapRotation
Map rotation.
PropertyValueType
Specifies whether the value returned by a function should be the original, user set value,...
@ EvaluatedValue
Return the current evaluated value for the property.
void predefinedScalesChanged()
Emitted when the list of predefined scales changes.
@ FlagRenderLabelsByMapLayer
When rendering map items to multi-layered exports, render labels belonging to different layers into s...
@ FlagUseAdvancedEffects
Enable advanced effects such as blend modes.
@ FlagDrawSelection
Draw selection.
@ FlagLosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ FlagAntialiasing
Use antialiasing when drawing items.
@ FlagForceVectorOutput
Force output in vector format where possible, even if items require rasterization to keep their corre...
@ FlagHideCoverageLayer
Hide coverage layer in outputs.
@ FlagDisableTiledRasterLayerRenders
If set, then raster layers will not be drawn as separate tiles. This may improve the appearance in ex...
static QgsRenderContext createRenderContextForMap(QgsLayoutItemMap *map, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout map and painter destination.
static void rotate(double angle, double &x, double &y)
Rotates a point / vector around the origin.
static Q_DECL_DEPRECATED double scaleFactorFromItemStyle(const QStyleOptionGraphicsItem *style)
Extracts the scale factor from an item style.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
QgsLayoutItem * itemByUuid(const QString &uuid, bool includeTemplateUuids=false) const
Returns the layout item with matching uuid unique identifier, or nullptr if a matching item could not...
Definition: qgslayout.cpp:238
void refreshed()
Emitted when the layout has been refreshed and items should also be refreshed and updated.
QgsProject * project() const
The project associated with the layout.
Definition: qgslayout.cpp:132
A map clipping region (in map coordinates and CRS).
void setRestrictToLayers(bool enabled)
Sets whether clipping should be restricted to a subset of layers.
FeatureClippingType
Feature clipping behavior, which controls how features from vector layers will be clipped.
void setFeatureClip(FeatureClippingType type)
Sets the feature clipping type.
void setRestrictedLayers(const QList< QgsMapLayer * > &layers)
Sets a list of layers to restrict the clipping region effects to.
Stores style information (renderer, opacity, labeling, diagrams etc.) applicable to a map layer.
void readXml(const QDomElement &styleElement)
Read style configuration (for project file reading)
void readFromLayer(QgsMapLayer *layer)
Store layer's active style information in the instance.
void writeXml(QDomElement &styleElement) const
Write style configuration (for project file writing)
QString xmlData() const
Returns XML content of the style.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QString name
Definition: qgsmaplayer.h:76
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Job implementation that renders everything sequentially using a custom painter.
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
void finished()
emitted when asynchronous rendering is finished (or canceled).
@ RenderLabelsByMapLayer
Labels should be rendered in individual stages by map layer. This allows separation of labels belongi...
static QStringList containsAdvancedEffects(const QgsMapSettings &mapSettings, EffectsCheckFlags flags=QgsMapSettingsUtils::EffectsCheckFlags())
Checks whether any of the layers attached to a map settings object contain advanced effects.
The QgsMapSettings class contains configuration for rendering of the map.
void addClippingRegion(const QgsMapClippingRegion &region)
Adds a new clipping region to the map settings.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered in the map.
void setSelectionColor(const QColor &color)
Sets the color that is used for drawing of selected vector features.
void setSimplifyMethod(const QgsVectorSimplifyMethod &method)
Sets the simplification setting to use when rendering vector layers.
QPolygonF visiblePolygon() const
Returns the visible area as a polygon (may be rotated)
void addRenderedFeatureHandler(QgsRenderedFeatureHandlerInterface *handler)
Adds a rendered feature handler to use while rendering the map settings.
void setTextRenderFormat(Qgis::TextRenderFormat format)
Sets the text render format, which dictates how text is rendered (e.g.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
void setDpiTarget(double dpi)
Sets the target dpi (dots per inch) to be taken into consideration when rendering.
long long currentFrame() const
Returns the current frame number of the map, for maps which are part of an animation.
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
void setRendererUsage(Qgis::RendererUsage rendererUsage)
Sets the rendering usage.
QgsRectangle extent() const
Returns geographical coordinates of the rectangle that should be rendered.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the map of map layer style overrides (key: layer ID, value: style name) where a different style ...
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
double frameRate() const
Returns the frame rate of the map (in frames per second), for maps which are part of an animation.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
void setRotation(double rotation)
Sets the rotation of the resulting map image, in degrees clockwise.
void setPathResolver(const QgsPathResolver &resolver)
Sets the path resolver for conversion between relative and absolute paths during rendering operations...
void setLabelBoundaryGeometry(const QgsGeometry &boundary)
Sets the label boundary geometry, which restricts where in the rendered map labels are permitted to b...
void setLabelBlockingRegions(const QList< QgsLabelBlockingRegion > &regions)
Sets a list of regions to avoid placing labels within.
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
void mapThemeRenamed(const QString &name, const QString &newName)
Emitted when a map theme within the collection is renamed.
void mapThemeChanged(const QString &theme)
Emitted when a map theme changes definition.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
QString description() const
Description.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:104
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
void crsChanged()
Emitted when the CRS of the project has changed.
QgsMapThemeCollection * mapThemeCollection
Definition: qgsproject.h:112
void projectColorsChanged()
Emitted whenever the project's color scheme has been changed.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:256
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
static QgsRectangle fromCenterAndSize(const QgsPointXY &center, double width, double height)
Creates a new rectangle, given the specified center point and width and height.
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
An interface for classes which provider custom handlers for features rendered as part of a map render...
Calculates scale for a given combination of canvas size, map extent, and monitor dpi.
double calculate(const QgsRectangle &mapExtent, double canvasWidth) const
Calculate the scale denominator.
void setDpi(double dpi)
Sets the dpi (dots per inch) for the output resolution, to be used in scale calculations.
void setMapUnits(QgsUnitTypes::DistanceUnit mapUnits)
Set the map units.
Scoped object for saving and restoring a QPainter object's state.
An interface for classes which can visit style entity (e.g.
@ LayoutItem
Individual item in a print layout.
virtual bool visitExit(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor stops visiting a node.
virtual bool visitEnter(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor starts visiting a node.
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
bool isTemporal() const
Returns true if the object's temporal range is enabled, and the object will be filtered when renderin...
void setIsTemporal(bool enabled)
Sets whether the temporal range is enabled (i.e.
void setTemporalRange(const QgsDateTimeRange &range)
Sets the temporal range for the object.
@ LayoutInches
Inches.
Definition: qgsunittypes.h:186
@ LayoutMillimeters
Millimeters.
Definition: qgsunittypes.h:183
static Q_INVOKABLE QString toString(QgsUnitTypes::DistanceUnit unit)
Returns a translated string representing a distance unit.
@ NoSimplification
No simplification can be applied.
static double scaleToZoom(double mapScale, double z0Scale=559082264.0287178)
Finds zoom level given map scale denominator.
static int scaleToZoomLevel(double mapScale, int sourceMinZoom, int sourceMaxZoom, double z0Scale=559082264.0287178)
Finds the best fitting zoom level given a map scale denominator and allowed zoom level range.
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:968
#define FALLTHROUGH
Definition: qgis.h:2657
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:2630
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:2014
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2629
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2075
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
Definition: qgsmaplayer.h:2146
const QgsCoordinateReferenceSystem & crs
Single variable definition for use within a QgsExpressionContextScope.
Contains details of a particular export layer relating to a layout item.
QPainter::CompositionMode compositionMode
Associated composition mode if this layer is associated with a map layer.
QString mapLayerId
Associated map layer ID, or an empty string if this export layer is not associated with a map layer.
double opacity
Associated opacity, if this layer is associated with a map layer.
QString name
User-friendly name for the export layer.
QString mapTheme
Associated map theme, or an empty string if this export layer does not need to be associated with a m...
Contains information relating to a node (i.e.
QString source
Weak reference to layer public source.
QString name
Weak reference to layer name.
TYPE * resolve(const QgsProject *project)
Resolves the map layer by attempting to find a layer with matching ID within a project.
QString provider
Weak reference to layer provider.
TYPE * resolveWeakly(const QgsProject *project, MatchType matchType=MatchType::All)
Resolves the map layer by attempting to find a matching layer in a project using a weak match.
QString layerId
Original layer ID.