QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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  return context;
1730 }
1731 
1733 {
1734  double extentWidth = extent().width();
1735  if ( extentWidth <= 0 )
1736  {
1737  return 1;
1738  }
1739  return rect().width() / extentWidth;
1740 }
1741 
1743 {
1744  double dx = mXOffset;
1745  double dy = mYOffset;
1746  transformShift( dx, dy );
1747  QPolygonF poly = calculateVisibleExtentPolygon( false );
1748  poly.translate( -dx, -dy );
1749  return poly;
1750 }
1751 
1753 {
1754  if ( !mBlockingLabelItems.contains( item ) )
1755  mBlockingLabelItems.append( item );
1756 
1757  connect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemMap::invalidateCache, Qt::UniqueConnection );
1758 }
1759 
1761 {
1762  mBlockingLabelItems.removeAll( item );
1763  if ( item )
1765 }
1766 
1768 {
1769  return mBlockingLabelItems.contains( item );
1770 }
1771 
1773 {
1774  return mPreviewLabelingResults.get();
1775 }
1776 
1778 {
1779  // NOTE: if visitEnter returns false it means "don't visit the item", not "abort all further visitations"
1781  return true;
1782 
1783  if ( mOverviewStack )
1784  {
1785  for ( int i = 0; i < mOverviewStack->size(); ++i )
1786  {
1787  if ( mOverviewStack->item( i )->accept( visitor ) )
1788  return false;
1789  }
1790  }
1791 
1792  if ( mGridStack )
1793  {
1794  for ( int i = 0; i < mGridStack->size(); ++i )
1795  {
1796  if ( mGridStack->item( i )->accept( visitor ) )
1797  return false;
1798  }
1799  }
1800 
1802  return false;
1803 
1804  return true;
1805 }
1806 
1808 {
1809  mRenderedFeatureHandlers.append( handler );
1810 }
1811 
1813 {
1814  mRenderedFeatureHandlers.removeAll( handler );
1815 }
1816 
1817 QPointF QgsLayoutItemMap::mapToItemCoords( QPointF mapCoords ) const
1818 {
1819  QPolygonF mapPoly = transformedMapPolygon();
1820  if ( mapPoly.empty() )
1821  {
1822  return QPointF( 0, 0 );
1823  }
1824 
1825  QgsRectangle tExtent = transformedExtent();
1826  QgsPointXY rotationPoint( ( tExtent.xMaximum() + tExtent.xMinimum() ) / 2.0, ( tExtent.yMaximum() + tExtent.yMinimum() ) / 2.0 );
1827  double dx = mapCoords.x() - rotationPoint.x();
1828  double dy = mapCoords.y() - rotationPoint.y();
1829  QgsLayoutUtils::rotate( -mEvaluatedMapRotation, dx, dy );
1830  QgsPointXY backRotatedCoords( rotationPoint.x() + dx, rotationPoint.y() + dy );
1831 
1832  QgsRectangle unrotatedExtent = transformedExtent();
1833  double xItem = rect().width() * ( backRotatedCoords.x() - unrotatedExtent.xMinimum() ) / unrotatedExtent.width();
1834  double yItem = rect().height() * ( 1 - ( backRotatedCoords.y() - unrotatedExtent.yMinimum() ) / unrotatedExtent.height() );
1835  return QPointF( xItem, yItem );
1836 }
1837 
1839 {
1841  QgsRectangle newExtent = mExtent;
1842  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
1843  {
1844  extent = newExtent;
1845  }
1846  else
1847  {
1848  QPolygonF poly;
1849  mapPolygon( newExtent, poly );
1850  QRectF bRect = poly.boundingRect();
1851  extent.setXMinimum( bRect.left() );
1852  extent.setXMaximum( bRect.right() );
1853  extent.setYMinimum( bRect.top() );
1854  extent.setYMaximum( bRect.bottom() );
1855  }
1856  return extent;
1857 }
1858 
1860 {
1861  if ( mDrawing )
1862  return;
1863 
1864  mCacheInvalidated = true;
1865  update();
1866 }
1867 
1869 {
1870  QRectF rectangle = rect();
1871  double frameExtension = frameEnabled() ? pen().widthF() / 2.0 : 0.0;
1872 
1873  double topExtension = 0.0;
1874  double rightExtension = 0.0;
1875  double bottomExtension = 0.0;
1876  double leftExtension = 0.0;
1877 
1878  if ( mGridStack )
1879  mGridStack->calculateMaxGridExtension( topExtension, rightExtension, bottomExtension, leftExtension );
1880 
1881  topExtension = std::max( topExtension, frameExtension );
1882  rightExtension = std::max( rightExtension, frameExtension );
1883  bottomExtension = std::max( bottomExtension, frameExtension );
1884  leftExtension = std::max( leftExtension, frameExtension );
1885 
1886  rectangle.setLeft( rectangle.left() - leftExtension );
1887  rectangle.setRight( rectangle.right() + rightExtension );
1888  rectangle.setTop( rectangle.top() - topExtension );
1889  rectangle.setBottom( rectangle.bottom() + bottomExtension );
1890  if ( rectangle != mCurrentRectangle )
1891  {
1892  prepareGeometryChange();
1893  mCurrentRectangle = rectangle;
1894  }
1895 }
1896 
1898 {
1900  if ( property == QgsLayoutObject::MapCrs || property == QgsLayoutObject::AllProperties )
1901  {
1902  bool ok;
1903  const QString crsVar = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapCrs, context, QString(), &ok );
1904  if ( ok && QgsCoordinateReferenceSystem( crsVar ).isValid() )
1905  {
1906  const QgsCoordinateReferenceSystem newCrs( crsVar );
1907  if ( newCrs.isValid() )
1908  {
1909  setCrs( newCrs );
1910  }
1911  }
1912  }
1913  //updates data defined properties and redraws item to match
1914  if ( property == QgsLayoutObject::MapRotation || property == QgsLayoutObject::MapScale ||
1915  property == QgsLayoutObject::MapXMin || property == QgsLayoutObject::MapYMin ||
1916  property == QgsLayoutObject::MapXMax || property == QgsLayoutObject::MapYMax ||
1917  property == QgsLayoutObject::MapAtlasMargin ||
1918  property == QgsLayoutObject::AllProperties )
1919  {
1920  QgsRectangle beforeExtent = mExtent;
1921  refreshMapExtents( &context );
1922  emit changed();
1923  if ( mExtent != beforeExtent )
1924  {
1925  emit extentChanged();
1926  }
1927  }
1928  if ( property == QgsLayoutObject::MapLabelMargin || property == QgsLayoutObject::AllProperties )
1929  {
1930  refreshLabelMargin( false );
1931  }
1932  if ( property == QgsLayoutObject::MapStylePreset || property == QgsLayoutObject::AllProperties )
1933  {
1934  const QString previousTheme = mLastEvaluatedThemeName.isEmpty() ? mFollowVisibilityPresetName : mLastEvaluatedThemeName;
1935  mLastEvaluatedThemeName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, mFollowVisibilityPresetName );
1936  if ( mLastEvaluatedThemeName != previousTheme )
1937  emit themeChanged( mLastEvaluatedThemeName );
1938  }
1939 
1940  if ( isTemporal() && ( property == QgsLayoutObject::StartDateTime || property == QgsLayoutObject::EndDateTime || property == QgsLayoutObject::AllProperties ) )
1941  {
1942  QDateTime begin = temporalRange().begin();
1943  QDateTime end = temporalRange().end();
1944 
1945  if ( property == QgsLayoutObject::StartDateTime || property == QgsLayoutObject::AllProperties )
1947  if ( property == QgsLayoutObject::EndDateTime || property == QgsLayoutObject::AllProperties )
1949 
1950  setTemporalRange( QgsDateTimeRange( begin, end, true, begin == end ) );
1951  }
1952 
1953  //force redraw
1954  mCacheInvalidated = true;
1955 
1957 }
1958 
1959 void QgsLayoutItemMap::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
1960 {
1961  if ( !mLayers.isEmpty() || mLayerStyleOverrides.isEmpty() )
1962  {
1963  for ( QgsMapLayer *layer : layers )
1964  {
1965  mLayerStyleOverrides.remove( layer->id() );
1966  }
1967  _qgis_removeLayers( mLayers, layers );
1968  }
1969 }
1970 
1971 void QgsLayoutItemMap::painterJobFinished()
1972 {
1973  mPainter->end();
1974  mPreviewLabelingResults.reset( mPainterJob->takeLabelingResults() );
1975  mPainterJob.reset( nullptr );
1976  mPainter.reset( nullptr );
1977  mCacheFinalImage = std::move( mCacheRenderingImage );
1978  mLastRenderedImageOffsetX = 0;
1979  mLastRenderedImageOffsetY = 0;
1980  emit backgroundTaskCountChanged( 0 );
1981  update();
1982  emit previewRefreshed();
1983 }
1984 
1985 void QgsLayoutItemMap::shapeChanged()
1986 {
1987  // keep center as center
1988  QgsPointXY oldCenter = mExtent.center();
1989 
1990  double w = rect().width();
1991  double h = rect().height();
1992 
1993  // keep same width as before
1994  double newWidth = mExtent.width();
1995  // but scale height to match item's aspect ratio
1996  double newHeight = newWidth * h / w;
1997 
1998  mExtent = QgsRectangle::fromCenterAndSize( oldCenter, newWidth, newHeight );
1999 
2000  //recalculate data defined scale and extents
2001  refreshMapExtents();
2003  invalidateCache();
2004  emit changed();
2005  emit extentChanged();
2006 }
2007 
2008 void QgsLayoutItemMap::mapThemeChanged( const QString &theme )
2009 {
2010  if ( theme == mCachedLayerStyleOverridesPresetName )
2011  mCachedLayerStyleOverridesPresetName.clear(); // force cache regeneration at next redraw
2012 }
2013 
2014 void QgsLayoutItemMap::currentMapThemeRenamed( const QString &theme, const QString &newTheme )
2015 {
2016  if ( theme == mFollowVisibilityPresetName )
2017  {
2018  mFollowVisibilityPresetName = newTheme;
2019  }
2020 }
2021 
2022 void QgsLayoutItemMap::connectUpdateSlot()
2023 {
2024  //connect signal from layer registry to update in case of new or deleted layers
2025  QgsProject *project = mLayout->project();
2026  if ( project )
2027  {
2028  // handles updating the stored layer state BEFORE the layers are removed
2029  connect( project, static_cast < void ( QgsProject::* )( const QList<QgsMapLayer *>& layers ) > ( &QgsProject::layersWillBeRemoved ),
2030  this, &QgsLayoutItemMap::layersAboutToBeRemoved );
2031  // redraws the map AFTER layers are removed
2032  connect( project->layerTreeRoot(), &QgsLayerTree::layerOrderChanged, this, [ = ]
2033  {
2034  if ( layers().isEmpty() )
2035  {
2036  //using project layers, and layer order has changed
2037  invalidateCache();
2038  }
2039  } );
2040 
2041  connect( project, &QgsProject::crsChanged, this, [ = ]
2042  {
2043  if ( !mCrs.isValid() )
2044  {
2045  //using project CRS, which just changed....
2046  invalidateCache();
2047  emit crsChanged();
2048  }
2049  } );
2050 
2051  // If project colors change, we need to redraw the map, as layer symbols may rely on project colors
2052  connect( project, &QgsProject::projectColorsChanged, this, [ = ]
2053  {
2054  invalidateCache();
2055  } );
2056 
2057  connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsLayoutItemMap::mapThemeChanged );
2058  connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsLayoutItemMap::currentMapThemeRenamed );
2059  }
2060  connect( mLayout, &QgsLayout::refreshed, this, &QgsLayoutItemMap::invalidateCache );
2061  connect( &mLayout->renderContext(), &QgsLayoutRenderContext::predefinedScalesChanged, this, [ = ]
2062  {
2063  if ( mAtlasScalingMode == Predefined )
2064  updateAtlasFeature();
2065  } );
2066 }
2067 
2069 {
2070  QPolygonF thisExtent = calculateVisibleExtentPolygon( false );
2071  QTransform mapTransform;
2072  QPolygonF thisRectPoly = QPolygonF( QRectF( 0, 0, rect().width(), rect().height() ) );
2073  //workaround QT Bug #21329
2074  thisRectPoly.pop_back();
2075  thisExtent.pop_back();
2076 
2077  QPolygonF thisItemPolyInLayout = mapToScene( thisRectPoly );
2078 
2079  //create transform from layout coordinates to map coordinates
2080  QTransform::quadToQuad( thisItemPolyInLayout, thisExtent, mapTransform );
2081  return mapTransform;
2082 }
2083 
2084 QList<QgsLabelBlockingRegion> QgsLayoutItemMap::createLabelBlockingRegions( const QgsMapSettings & ) const
2085 {
2086  const QTransform mapTransform = layoutToMapCoordsTransform();
2087  QList< QgsLabelBlockingRegion > blockers;
2088  blockers.reserve( mBlockingLabelItems.count() );
2089  for ( const auto &item : std::as_const( mBlockingLabelItems ) )
2090  {
2091  // invisible items don't block labels!
2092  if ( !item )
2093  continue;
2094 
2095  // layout items may be temporarily hidden during layered exports
2096  if ( item->property( "wasVisible" ).isValid() )
2097  {
2098  if ( !item->property( "wasVisible" ).toBool() )
2099  continue;
2100  }
2101  else if ( !item->isVisible() )
2102  continue;
2103 
2104  QPolygonF itemRectInMapCoordinates = mapTransform.map( item->mapToScene( item->rect() ) );
2105  itemRectInMapCoordinates.append( itemRectInMapCoordinates.at( 0 ) ); //close polygon
2106  QgsGeometry blockingRegion = QgsGeometry::fromQPolygonF( itemRectInMapCoordinates );
2107  blockers << QgsLabelBlockingRegion( blockingRegion );
2108  }
2109  return blockers;
2110 }
2111 
2113 {
2114  return mLabelMargin;
2115 }
2116 
2118 {
2119  mLabelMargin = margin;
2120  refreshLabelMargin( false );
2121 }
2122 
2123 void QgsLayoutItemMap::updateToolTip()
2124 {
2125  setToolTip( displayName() );
2126 }
2127 
2128 QString QgsLayoutItemMap::themeToRender( const QgsExpressionContext &context ) const
2129 {
2130  QString presetName;
2131 
2132  if ( mFollowVisibilityPreset )
2133  {
2134  presetName = mFollowVisibilityPresetName;
2135  // preset name can be overridden by data-defined one
2136  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
2137  }
2138  else if ( !mExportThemes.empty() && mExportThemeIt != mExportThemes.end() )
2139  presetName = *mExportThemeIt;
2140  return presetName;
2141 }
2142 
2143 QList<QgsMapLayer *> QgsLayoutItemMap::layersToRender( const QgsExpressionContext *context ) const
2144 {
2145  QgsExpressionContext scopedContext;
2146  if ( !context )
2147  scopedContext = createExpressionContext();
2148  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
2149 
2150  QList<QgsMapLayer *> renderLayers;
2151 
2152  QString presetName = themeToRender( *evalContext );
2153  if ( !presetName.isEmpty() )
2154  {
2155  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
2156  renderLayers = mLayout->project()->mapThemeCollection()->mapThemeVisibleLayers( presetName );
2157  else // fallback to using map canvas layers
2158  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
2159  }
2160  else if ( !layers().isEmpty() )
2161  {
2162  renderLayers = layers();
2163  }
2164  else
2165  {
2166  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
2167  }
2168 
2169  bool ok = false;
2170  QString ddLayers = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapLayers, *evalContext, QString(), &ok );
2171  if ( ok )
2172  {
2173  renderLayers.clear();
2174 
2175  const QStringList layerNames = ddLayers.split( '|' );
2176  //need to convert layer names to layer ids
2177  for ( const QString &name : layerNames )
2178  {
2179  const QList< QgsMapLayer * > matchingLayers = mLayout->project()->mapLayersByName( name );
2180  for ( QgsMapLayer *layer : matchingLayers )
2181  {
2182  renderLayers << layer;
2183  }
2184  }
2185  }
2186 
2187  //remove atlas coverage layer if required
2188  if ( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagHideCoverageLayer )
2189  {
2190  //hiding coverage layer
2191  int removeAt = renderLayers.indexOf( mLayout->reportContext().layer() );
2192  if ( removeAt != -1 )
2193  {
2194  renderLayers.removeAt( removeAt );
2195  }
2196  }
2197 
2198  // remove any invalid layers
2199  renderLayers.erase( std::remove_if( renderLayers.begin(), renderLayers.end(), []( QgsMapLayer * layer )
2200  {
2201  return !layer || !layer->isValid();
2202  } ), renderLayers.end() );
2203 
2204  return renderLayers;
2205 }
2206 
2207 QMap<QString, QString> QgsLayoutItemMap::layerStyleOverridesToRender( const QgsExpressionContext &context ) const
2208 {
2209  QString presetName = themeToRender( context );
2210  if ( !presetName.isEmpty() )
2211  {
2212  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
2213  {
2214  if ( presetName != mCachedLayerStyleOverridesPresetName )
2215  {
2216  // have to regenerate cache of style overrides
2217  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
2218  mCachedLayerStyleOverridesPresetName = presetName;
2219  }
2220 
2221  return mCachedPresetLayerStyleOverrides;
2222  }
2223  else
2224  return QMap<QString, QString>();
2225  }
2226  else if ( mFollowVisibilityPreset )
2227  {
2228  QString presetName = mFollowVisibilityPresetName;
2229  // data defined preset name?
2230  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
2231  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
2232  {
2233  if ( presetName.isEmpty() || presetName != mCachedLayerStyleOverridesPresetName )
2234  {
2235  // have to regenerate cache of style overrides
2236  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
2237  mCachedLayerStyleOverridesPresetName = presetName;
2238  }
2239 
2240  return mCachedPresetLayerStyleOverrides;
2241  }
2242  else
2243  return QMap<QString, QString>();
2244  }
2245  else if ( mKeepLayerStyles )
2246  {
2247  return mLayerStyleOverrides;
2248  }
2249  else
2250  {
2251  return QMap<QString, QString>();
2252  }
2253 }
2254 
2255 QgsRectangle QgsLayoutItemMap::transformedExtent() const
2256 {
2257  double dx = mXOffset;
2258  double dy = mYOffset;
2259  transformShift( dx, dy );
2260  return QgsRectangle( mExtent.xMinimum() - dx, mExtent.yMinimum() - dy, mExtent.xMaximum() - dx, mExtent.yMaximum() - dy );
2261 }
2262 
2263 void QgsLayoutItemMap::mapPolygon( const QgsRectangle &extent, QPolygonF &poly ) const
2264 {
2265  poly.clear();
2266  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
2267  {
2268  poly << QPointF( extent.xMinimum(), extent.yMaximum() );
2269  poly << QPointF( extent.xMaximum(), extent.yMaximum() );
2270  poly << QPointF( extent.xMaximum(), extent.yMinimum() );
2271  poly << QPointF( extent.xMinimum(), extent.yMinimum() );
2272  //ensure polygon is closed by readding first point
2273  poly << QPointF( poly.at( 0 ) );
2274  return;
2275  }
2276 
2277  //there is rotation
2278  QgsPointXY rotationPoint( ( extent.xMaximum() + extent.xMinimum() ) / 2.0, ( extent.yMaximum() + extent.yMinimum() ) / 2.0 );
2279  double dx, dy; //x-, y- shift from rotation point to corner point
2280 
2281  //top left point
2282  dx = rotationPoint.x() - extent.xMinimum();
2283  dy = rotationPoint.y() - extent.yMaximum();
2284  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2285  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2286 
2287  //top right point
2288  dx = rotationPoint.x() - extent.xMaximum();
2289  dy = rotationPoint.y() - extent.yMaximum();
2290  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2291  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2292 
2293  //bottom right point
2294  dx = rotationPoint.x() - extent.xMaximum();
2295  dy = rotationPoint.y() - extent.yMinimum();
2296  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2297  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2298 
2299  //bottom left point
2300  dx = rotationPoint.x() - extent.xMinimum();
2301  dy = rotationPoint.y() - extent.yMinimum();
2302  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2303  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2304 
2305  //ensure polygon is closed by readding first point
2306  poly << QPointF( poly.at( 0 ) );
2307 }
2308 
2309 void QgsLayoutItemMap::transformShift( double &xShift, double &yShift ) const
2310 {
2311  double mmToMapUnits = 1.0 / mapUnitsToLayoutUnits();
2312  double dxScaled = xShift * mmToMapUnits;
2313  double dyScaled = - yShift * mmToMapUnits;
2314 
2315  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dxScaled, dyScaled );
2316 
2317  xShift = dxScaled;
2318  yShift = dyScaled;
2319 }
2320 
2321 void QgsLayoutItemMap::drawAnnotations( QPainter *painter )
2322 {
2323  if ( !mLayout || !mLayout->project() || !mDrawAnnotations )
2324  {
2325  return;
2326  }
2327 
2328  const QList< QgsAnnotation * > annotations = mLayout->project()->annotationManager()->annotations();
2329  if ( annotations.isEmpty() )
2330  return;
2331 
2333  rc.setForceVectorOutput( true );
2335  QList< QgsMapLayer * > layers = layersToRender( &rc.expressionContext() );
2336 
2337  for ( QgsAnnotation *annotation : annotations )
2338  {
2339  if ( !annotation || !annotation->isVisible() )
2340  {
2341  continue;
2342  }
2343  if ( annotation->mapLayer() && !layers.contains( annotation->mapLayer() ) )
2344  continue;
2345 
2346  drawAnnotation( annotation, rc );
2347  }
2348 }
2349 
2350 void QgsLayoutItemMap::drawAnnotation( const QgsAnnotation *annotation, QgsRenderContext &context )
2351 {
2352  if ( !annotation || !annotation->isVisible() || !context.painter() || !context.painter()->device() )
2353  {
2354  return;
2355  }
2356 
2357  QgsScopedQPainterState painterState( context.painter() );
2358  context.setPainterFlagsUsingContext();
2359 
2360  double itemX, itemY;
2361  if ( annotation->hasFixedMapPosition() )
2362  {
2363  QPointF mapPos = layoutMapPosForItem( annotation );
2364  itemX = mapPos.x();
2365  itemY = mapPos.y();
2366  }
2367  else
2368  {
2369  itemX = annotation->relativePosition().x() * rect().width();
2370  itemY = annotation->relativePosition().y() * rect().height();
2371  }
2372  context.painter()->translate( itemX, itemY );
2373 
2374  //setup painter scaling to dots so that symbology is drawn to scale
2375  double dotsPerMM = context.painter()->device()->logicalDpiX() / 25.4;
2376  context.painter()->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
2377 
2378  annotation->render( context );
2379 }
2380 
2381 QPointF QgsLayoutItemMap::layoutMapPosForItem( const QgsAnnotation *annotation ) const
2382 {
2383  if ( !annotation )
2384  return QPointF( 0, 0 );
2385 
2386  double mapX = 0.0;
2387  double mapY = 0.0;
2388 
2389  mapX = annotation->mapPosition().x();
2390  mapY = annotation->mapPosition().y();
2391  QgsCoordinateReferenceSystem annotationCrs = annotation->mapPositionCrs();
2392 
2393  if ( annotationCrs != crs() )
2394  {
2395  //need to reproject
2396  QgsCoordinateTransform t( annotationCrs, crs(), mLayout->project() );
2397  double z = 0.0;
2398  try
2399  {
2400  t.transformInPlace( mapX, mapY, z );
2401  }
2402  catch ( const QgsCsException & )
2403  {
2404  }
2405  }
2406 
2407  return mapToItemCoords( QPointF( mapX, mapY ) );
2408 }
2409 
2410 void QgsLayoutItemMap::drawMapFrame( QPainter *p )
2411 {
2412  if ( frameEnabled() && p )
2413  {
2416 
2418  }
2419 }
2420 
2421 void QgsLayoutItemMap::drawMapBackground( QPainter *p )
2422 {
2423  if ( hasBackground() && p )
2424  {
2427 
2429  }
2430 }
2431 
2432 bool QgsLayoutItemMap::shouldDrawPart( QgsLayoutItemMap::PartType part ) const
2433 {
2434  if ( mCurrentExportPart == NotLayered )
2435  {
2436  //all parts of the map are visible
2437  return true;
2438  }
2439 
2440  switch ( part )
2441  {
2442  case NotLayered:
2443  return true;
2444 
2445  case Start:
2446  return false;
2447 
2448  case Background:
2449  return mCurrentExportPart == Background && hasBackground();
2450 
2451  case Layer:
2452  return mCurrentExportPart == Layer;
2453 
2454  case Grid:
2455  return mCurrentExportPart == Grid && mGridStack->hasEnabledItems();
2456 
2457  case OverviewMapExtent:
2458  return mCurrentExportPart == OverviewMapExtent && mOverviewStack->hasEnabledItems();
2459 
2460  case Frame:
2461  return mCurrentExportPart == Frame && frameEnabled();
2462 
2463  case SelectionBoxes:
2464  return mCurrentExportPart == SelectionBoxes && isSelected();
2465 
2466  case End:
2467  return false;
2468  }
2469 
2470  return false;
2471 }
2472 
2473 void QgsLayoutItemMap::refreshMapExtents( const QgsExpressionContext *context )
2474 {
2475  QgsExpressionContext scopedContext;
2476  if ( !context )
2477  scopedContext = createExpressionContext();
2478 
2479  bool ok = false;
2480  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
2481 
2482 
2483  //data defined map extents set?
2484  QgsRectangle newExtent = extent();
2485  bool useDdXMin = false;
2486  bool useDdXMax = false;
2487  bool useDdYMin = false;
2488  bool useDdYMax = false;
2489  double minXD = 0;
2490  double minYD = 0;
2491  double maxXD = 0;
2492  double maxYD = 0;
2493 
2494  minXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMin, *evalContext, 0.0, &ok );
2495  if ( ok )
2496  {
2497  useDdXMin = true;
2498  newExtent.setXMinimum( minXD );
2499  }
2500  minYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMin, *evalContext, 0.0, &ok );
2501  if ( ok )
2502  {
2503  useDdYMin = true;
2504  newExtent.setYMinimum( minYD );
2505  }
2506  maxXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMax, *evalContext, 0.0, &ok );
2507  if ( ok )
2508  {
2509  useDdXMax = true;
2510  newExtent.setXMaximum( maxXD );
2511  }
2512  maxYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMax, *evalContext, 0.0, &ok );
2513  if ( ok )
2514  {
2515  useDdYMax = true;
2516  newExtent.setYMaximum( maxYD );
2517  }
2518 
2519  if ( newExtent != mExtent )
2520  {
2521  //calculate new extents to fit data defined extents
2522 
2523  //Make sure the width/height ratio is the same as in current map extent.
2524  //This is to keep the map item frame and the page layout fixed
2525  double currentWidthHeightRatio = mExtent.width() / mExtent.height();
2526  double newWidthHeightRatio = newExtent.width() / newExtent.height();
2527 
2528  if ( currentWidthHeightRatio < newWidthHeightRatio )
2529  {
2530  //enlarge height of new extent, ensuring the map center stays the same
2531  double newHeight = newExtent.width() / currentWidthHeightRatio;
2532  double deltaHeight = newHeight - newExtent.height();
2533  newExtent.setYMinimum( newExtent.yMinimum() - deltaHeight / 2 );
2534  newExtent.setYMaximum( newExtent.yMaximum() + deltaHeight / 2 );
2535  }
2536  else
2537  {
2538  //enlarge width of new extent, ensuring the map center stays the same
2539  double newWidth = currentWidthHeightRatio * newExtent.height();
2540  double deltaWidth = newWidth - newExtent.width();
2541  newExtent.setXMinimum( newExtent.xMinimum() - deltaWidth / 2 );
2542  newExtent.setXMaximum( newExtent.xMaximum() + deltaWidth / 2 );
2543  }
2544 
2545  mExtent = newExtent;
2546  }
2547 
2548  //now refresh scale, as this potentially overrides extents
2549 
2550  //data defined map scale set?
2551  double scaleD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapScale, *evalContext, 0.0, &ok );
2552  if ( ok )
2553  {
2554  setScale( scaleD, false );
2555  newExtent = mExtent;
2556  }
2557 
2558  if ( useDdXMax || useDdXMin || useDdYMax || useDdYMin )
2559  {
2560  //if only one of min/max was set for either x or y, then make sure our extent is locked on that value
2561  //as we can do this without altering the scale
2562  if ( useDdXMin && !useDdXMax )
2563  {
2564  double xMax = mExtent.xMaximum() - ( mExtent.xMinimum() - minXD );
2565  newExtent.setXMinimum( minXD );
2566  newExtent.setXMaximum( xMax );
2567  }
2568  else if ( !useDdXMin && useDdXMax )
2569  {
2570  double xMin = mExtent.xMinimum() - ( mExtent.xMaximum() - maxXD );
2571  newExtent.setXMinimum( xMin );
2572  newExtent.setXMaximum( maxXD );
2573  }
2574  if ( useDdYMin && !useDdYMax )
2575  {
2576  double yMax = mExtent.yMaximum() - ( mExtent.yMinimum() - minYD );
2577  newExtent.setYMinimum( minYD );
2578  newExtent.setYMaximum( yMax );
2579  }
2580  else if ( !useDdYMin && useDdYMax )
2581  {
2582  double yMin = mExtent.yMinimum() - ( mExtent.yMaximum() - maxYD );
2583  newExtent.setYMinimum( yMin );
2584  newExtent.setYMaximum( maxYD );
2585  }
2586 
2587  if ( newExtent != mExtent )
2588  {
2589  mExtent = newExtent;
2590  }
2591  }
2592 
2593  //lastly, map rotation overrides all
2594  double mapRotation = mMapRotation;
2595 
2596  //data defined map rotation set?
2598 
2599  if ( !qgsDoubleNear( mEvaluatedMapRotation, mapRotation ) )
2600  {
2601  mEvaluatedMapRotation = mapRotation;
2603  }
2604 }
2605 
2606 void QgsLayoutItemMap::refreshLabelMargin( bool updateItem )
2607 {
2608  //data defined label margin set?
2610  mEvaluatedLabelMargin.setLength( labelMargin );
2611  mEvaluatedLabelMargin.setUnits( mLabelMargin.units() );
2612 
2613  if ( updateItem )
2614  {
2615  update();
2616  }
2617 }
2618 
2619 void QgsLayoutItemMap::updateAtlasFeature()
2620 {
2621  if ( !atlasDriven() || !mLayout->reportContext().layer() )
2622  return; // nothing to do
2623 
2624  QgsRectangle bounds = computeAtlasRectangle();
2625  if ( bounds.isNull() )
2626  return;
2627 
2628  double xa1 = bounds.xMinimum();
2629  double xa2 = bounds.xMaximum();
2630  double ya1 = bounds.yMinimum();
2631  double ya2 = bounds.yMaximum();
2632  QgsRectangle newExtent = bounds;
2633  QgsRectangle originalExtent = mExtent;
2634 
2635  //sanity check - only allow fixed scale mode for point layers
2636  bool isPointLayer = QgsWkbTypes::geometryType( mLayout->reportContext().layer()->wkbType() ) == QgsWkbTypes::PointGeometry;
2637 
2638  if ( mAtlasScalingMode == Fixed || mAtlasScalingMode == Predefined || isPointLayer )
2639  {
2640  QgsScaleCalculator calc;
2641  calc.setMapUnits( crs().mapUnits() );
2642  calc.setDpi( 25.4 );
2643  double originalScale = calc.calculate( originalExtent, rect().width() );
2644  double geomCenterX = ( xa1 + xa2 ) / 2.0;
2645  double geomCenterY = ( ya1 + ya2 ) / 2.0;
2646  QVector<qreal> scales;
2648  if ( !mLayout->reportContext().predefinedScales().empty() ) // remove when deprecated method is removed
2649  scales = mLayout->reportContext().predefinedScales();
2650  else
2651  scales = mLayout->renderContext().predefinedScales();
2653  if ( mAtlasScalingMode == Fixed || scales.isEmpty() || ( isPointLayer && mAtlasScalingMode != Predefined ) )
2654  {
2655  // only translate, keep the original scale (i.e. width x height)
2656  double xMin = geomCenterX - originalExtent.width() / 2.0;
2657  double yMin = geomCenterY - originalExtent.height() / 2.0;
2658  newExtent = QgsRectangle( xMin,
2659  yMin,
2660  xMin + originalExtent.width(),
2661  yMin + originalExtent.height() );
2662 
2663  //scale newExtent to match original scale of map
2664  //this is required for geographic coordinate systems, where the scale varies by extent
2665  double newScale = calc.calculate( newExtent, rect().width() );
2666  newExtent.scale( originalScale / newScale );
2667  }
2668  else if ( mAtlasScalingMode == Predefined )
2669  {
2670  // choose one of the predefined scales
2671  double newWidth = originalExtent.width();
2672  double newHeight = originalExtent.height();
2673  for ( int i = 0; i < scales.size(); i++ )
2674  {
2675  double ratio = scales[i] / originalScale;
2676  newWidth = originalExtent.width() * ratio;
2677  newHeight = originalExtent.height() * ratio;
2678 
2679  // compute new extent, centered on feature
2680  double xMin = geomCenterX - newWidth / 2.0;
2681  double yMin = geomCenterY - newHeight / 2.0;
2682  newExtent = QgsRectangle( xMin,
2683  yMin,
2684  xMin + newWidth,
2685  yMin + newHeight );
2686 
2687  //scale newExtent to match desired map scale
2688  //this is required for geographic coordinate systems, where the scale varies by extent
2689  double newScale = calc.calculate( newExtent, rect().width() );
2690  newExtent.scale( scales[i] / newScale );
2691 
2692  if ( ( newExtent.width() >= bounds.width() ) && ( newExtent.height() >= bounds.height() ) )
2693  {
2694  // this is the smallest extent that embeds the feature, stop here
2695  break;
2696  }
2697  }
2698  }
2699  }
2700  else if ( mAtlasScalingMode == Auto )
2701  {
2702  // auto scale
2703 
2704  double geomRatio = bounds.width() / bounds.height();
2705  double mapRatio = originalExtent.width() / originalExtent.height();
2706 
2707  // geometry height is too big
2708  if ( geomRatio < mapRatio )
2709  {
2710  // extent the bbox's width
2711  double adjWidth = ( mapRatio * bounds.height() - bounds.width() ) / 2.0;
2712  xa1 -= adjWidth;
2713  xa2 += adjWidth;
2714  }
2715  // geometry width is too big
2716  else if ( geomRatio > mapRatio )
2717  {
2718  // extent the bbox's height
2719  double adjHeight = ( bounds.width() / mapRatio - bounds.height() ) / 2.0;
2720  ya1 -= adjHeight;
2721  ya2 += adjHeight;
2722  }
2723  newExtent = QgsRectangle( xa1, ya1, xa2, ya2 );
2724 
2725  const double evaluatedAtlasMargin = atlasMargin();
2726  if ( evaluatedAtlasMargin > 0.0 )
2727  {
2728  newExtent.scale( 1 + evaluatedAtlasMargin );
2729  }
2730  }
2731 
2732  // set the new extent (and render)
2733  setExtent( newExtent );
2734  emit preparedForAtlas();
2735 }
2736 
2737 QgsRectangle QgsLayoutItemMap::computeAtlasRectangle()
2738 {
2739  // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
2740  // We have to transform the geometry to the destination CRS and ask for the bounding box
2741  // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
2742  QgsGeometry g = mLayout->reportContext().currentGeometry( crs() );
2743  // Rotating the geometry, so the bounding box is correct wrt map rotation
2744  if ( mEvaluatedMapRotation != 0.0 )
2745  {
2746  QgsPointXY prevCenter = g.boundingBox().center();
2747  g.rotate( mEvaluatedMapRotation, g.boundingBox().center() );
2748  // Rotation center will be still the bounding box center of an unrotated geometry.
2749  // Which means, if the center of bbox moves after rotation, the viewport will
2750  // also be offset, and part of the geometry will fall out of bounds.
2751  // Here we compensate for that roughly: by extending the rotated bounds
2752  // so that its center is the same as the original.
2753  QgsRectangle bounds = g.boundingBox();
2754  double dx = std::max( std::abs( prevCenter.x() - bounds.xMinimum() ),
2755  std::abs( prevCenter.x() - bounds.xMaximum() ) );
2756  double dy = std::max( std::abs( prevCenter.y() - bounds.yMinimum() ),
2757  std::abs( prevCenter.y() - bounds.yMaximum() ) );
2758  QgsPointXY center = g.boundingBox().center();
2759  return QgsRectangle( center.x() - dx, center.y() - dy,
2760  center.x() + dx, center.y() + dy );
2761  }
2762  else
2763  {
2764  return g.boundingBox();
2765  }
2766 }
2767 
2768 void QgsLayoutItemMap::createStagedRenderJob( const QgsRectangle &extent, const QSizeF size, double dpi )
2769 {
2770  QgsMapSettings settings = mapSettings( extent, size, dpi, true );
2771  settings.setLayers( mOverviewStack->modifyMapLayerList( settings.layers() ) );
2772 
2773  mStagedRendererJob = std::make_unique< QgsMapRendererStagedRenderJob >( settings,
2776  : QgsMapRendererStagedRenderJob::Flags() );
2777  mStagedRendererJob->start();
2778 }
2779 
2780 
2781 
2782 //
2783 // QgsLayoutItemMapAtlasClippingSettings
2784 //
2785 
2787  : QObject( map )
2788  , mMap( map )
2789 {
2790  if ( mMap->layout() && mMap->layout()->project() )
2791  {
2792  connect( mMap->layout()->project(), static_cast < void ( QgsProject::* )( const QList<QgsMapLayer *>& layers ) > ( &QgsProject::layersWillBeRemoved ),
2793  this, &QgsLayoutItemMapAtlasClippingSettings::layersAboutToBeRemoved );
2794  }
2795 }
2796 
2798 {
2799  return mClipToAtlasFeature;
2800 }
2801 
2803 {
2804  if ( enabled == mClipToAtlasFeature )
2805  return;
2806 
2807  mClipToAtlasFeature = enabled;
2808  emit changed();
2809 }
2810 
2812 {
2813  return mFeatureClippingType;
2814 }
2815 
2817 {
2818  if ( mFeatureClippingType == type )
2819  return;
2820 
2821  mFeatureClippingType = type;
2822  emit changed();
2823 }
2824 
2826 {
2827  return mForceLabelsInsideFeature;
2828 }
2829 
2831 {
2832  if ( forceInside == mForceLabelsInsideFeature )
2833  return;
2834 
2835  mForceLabelsInsideFeature = forceInside;
2836  emit changed();
2837 }
2838 
2840 {
2841  return mRestrictToLayers;
2842 }
2843 
2845 {
2846  if ( mRestrictToLayers == enabled )
2847  return;
2848 
2849  mRestrictToLayers = enabled;
2850  emit changed();
2851 }
2852 
2854 {
2855  return _qgis_listRefToRaw( mLayersToClip );
2856 }
2857 
2858 void QgsLayoutItemMapAtlasClippingSettings::setLayersToClip( const QList< QgsMapLayer * > &layersToClip )
2859 {
2860  mLayersToClip = _qgis_listRawToRef( layersToClip );
2861  emit changed();
2862 }
2863 
2864 bool QgsLayoutItemMapAtlasClippingSettings::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
2865 {
2866  QDomElement settingsElem = document.createElement( QStringLiteral( "atlasClippingSettings" ) );
2867  settingsElem.setAttribute( QStringLiteral( "enabled" ), mClipToAtlasFeature ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2868  settingsElem.setAttribute( QStringLiteral( "forceLabelsInside" ), mForceLabelsInsideFeature ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2869  settingsElem.setAttribute( QStringLiteral( "clippingType" ), QString::number( static_cast<int>( mFeatureClippingType ) ) );
2870  settingsElem.setAttribute( QStringLiteral( "restrictLayers" ), mRestrictToLayers ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2871 
2872  //layer set
2873  QDomElement layerSetElem = document.createElement( QStringLiteral( "layersToClip" ) );
2874  for ( const QgsMapLayerRef &layerRef : mLayersToClip )
2875  {
2876  if ( !layerRef )
2877  continue;
2878  QDomElement layerElem = document.createElement( QStringLiteral( "Layer" ) );
2879  QDomText layerIdText = document.createTextNode( layerRef.layerId );
2880  layerElem.appendChild( layerIdText );
2881 
2882  layerElem.setAttribute( QStringLiteral( "name" ), layerRef.name );
2883  layerElem.setAttribute( QStringLiteral( "source" ), layerRef.source );
2884  layerElem.setAttribute( QStringLiteral( "provider" ), layerRef.provider );
2885 
2886  layerSetElem.appendChild( layerElem );
2887  }
2888  settingsElem.appendChild( layerSetElem );
2889 
2890  element.appendChild( settingsElem );
2891  return true;
2892 }
2893 
2894 bool QgsLayoutItemMapAtlasClippingSettings::readXml( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext & )
2895 {
2896  const QDomElement settingsElem = element.firstChildElement( QStringLiteral( "atlasClippingSettings" ) );
2897 
2898  mClipToAtlasFeature = settingsElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
2899  mForceLabelsInsideFeature = settingsElem.attribute( QStringLiteral( "forceLabelsInside" ), QStringLiteral( "0" ) ).toInt();
2900  mFeatureClippingType = static_cast< QgsMapClippingRegion::FeatureClippingType >( settingsElem.attribute( QStringLiteral( "clippingType" ), QStringLiteral( "0" ) ).toInt() );
2901  mRestrictToLayers = settingsElem.attribute( QStringLiteral( "restrictLayers" ), QStringLiteral( "0" ) ).toInt();
2902 
2903  mLayersToClip.clear();
2904  QDomNodeList layerSetNodeList = settingsElem.elementsByTagName( QStringLiteral( "layersToClip" ) );
2905  if ( !layerSetNodeList.isEmpty() )
2906  {
2907  QDomElement layerSetElem = layerSetNodeList.at( 0 ).toElement();
2908  QDomNodeList layerIdNodeList = layerSetElem.elementsByTagName( QStringLiteral( "Layer" ) );
2909  mLayersToClip.reserve( layerIdNodeList.size() );
2910  for ( int i = 0; i < layerIdNodeList.size(); ++i )
2911  {
2912  QDomElement layerElem = layerIdNodeList.at( i ).toElement();
2913  QString layerId = layerElem.text();
2914  QString layerName = layerElem.attribute( QStringLiteral( "name" ) );
2915  QString layerSource = layerElem.attribute( QStringLiteral( "source" ) );
2916  QString layerProvider = layerElem.attribute( QStringLiteral( "provider" ) );
2917 
2918  QgsMapLayerRef ref( layerId, layerName, layerSource, layerProvider );
2919  if ( mMap->layout() && mMap->layout()->project() )
2920  ref.resolveWeakly( mMap->layout()->project() );
2921  mLayersToClip << ref;
2922  }
2923  }
2924 
2925  return true;
2926 }
2927 
2928 void QgsLayoutItemMapAtlasClippingSettings::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
2929 {
2930  if ( !mLayersToClip.isEmpty() )
2931  {
2932  _qgis_removeLayers( mLayersToClip, layers );
2933  }
2934 }
2935 
2936 //
2937 // QgsLayoutItemMapItemClipPathSettings
2938 //
2940  : QObject( map )
2941  , mMap( map )
2942 {
2943 }
2944 
2946 {
2947  return mEnabled && mClipPathSource;
2948 }
2949 
2951 {
2952  return mEnabled;
2953 }
2954 
2956 {
2957  if ( enabled == mEnabled )
2958  return;
2959 
2960  mEnabled = enabled;
2961 
2962  if ( mClipPathSource )
2963  {
2964  // may need to refresh the clip source in order to get it to render/not render depending on enabled state
2965  mClipPathSource->refresh();
2966  }
2967  emit changed();
2968 }
2969 
2971 {
2972  if ( isActive() )
2973  {
2974  QgsGeometry clipGeom( mClipPathSource->clipPath() );
2975  clipGeom.transform( mMap->layoutToMapCoordsTransform() );
2976  return clipGeom;
2977  }
2978  return QgsGeometry();
2979 }
2980 
2982 {
2983  if ( isActive() )
2984  {
2985  QgsGeometry clipGeom( mClipPathSource->clipPath() );
2986  clipGeom.transform( mMap->sceneTransform().inverted() );
2987  return clipGeom;
2988  }
2989  return QgsGeometry();
2990 }
2991 
2993 {
2995  region.setFeatureClip( mFeatureClippingType );
2996  return region;
2997 }
2998 
3000 {
3001  if ( mClipPathSource == item )
3002  return;
3003 
3004  if ( mClipPathSource )
3005  {
3006  disconnect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::refresh );
3007  disconnect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::refresh );
3008  disconnect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::extentChanged );
3009  disconnect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::extentChanged );
3010  }
3011 
3012  QgsLayoutItem *oldItem = mClipPathSource;
3013  mClipPathSource = item;
3014 
3015  if ( mClipPathSource )
3016  {
3017  // if item size or rotation changes, we need to redraw this map
3018  connect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::refresh );
3019  connect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::refresh );
3020  // and if clip item size or rotation changes, then effectively we've changed the visible extent of the map
3021  connect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::extentChanged );
3022  connect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::extentChanged );
3023  // trigger a redraw of the clip source, so that it becomes invisible
3024  mClipPathSource->refresh();
3025  }
3026 
3027  if ( oldItem )
3028  {
3029  // may need to refresh the previous item in order to get it to render
3030  oldItem->refresh();
3031  }
3032 
3033  emit changed();
3034 }
3035 
3037 {
3038  return mClipPathSource;
3039 }
3040 
3042 {
3043  return mFeatureClippingType;
3044 }
3045 
3047 {
3048  if ( mFeatureClippingType == type )
3049  return;
3050 
3051  mFeatureClippingType = type;
3052  emit changed();
3053 }
3054 
3056 {
3057  return mForceLabelsInsideClipPath;
3058 }
3059 
3061 {
3062  if ( forceInside == mForceLabelsInsideClipPath )
3063  return;
3064 
3065  mForceLabelsInsideClipPath = forceInside;
3066  emit changed();
3067 }
3068 
3069 bool QgsLayoutItemMapItemClipPathSettings::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
3070 {
3071  QDomElement settingsElem = document.createElement( QStringLiteral( "itemClippingSettings" ) );
3072  settingsElem.setAttribute( QStringLiteral( "enabled" ), mEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
3073  settingsElem.setAttribute( QStringLiteral( "forceLabelsInside" ), mForceLabelsInsideClipPath ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
3074  settingsElem.setAttribute( QStringLiteral( "clippingType" ), QString::number( static_cast<int>( mFeatureClippingType ) ) );
3075  if ( mClipPathSource )
3076  settingsElem.setAttribute( QStringLiteral( "clipSource" ), mClipPathSource->uuid() );
3077  else
3078  settingsElem.setAttribute( QStringLiteral( "clipSource" ), QString() );
3079 
3080  element.appendChild( settingsElem );
3081  return true;
3082 }
3083 
3084 bool QgsLayoutItemMapItemClipPathSettings::readXml( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext & )
3085 {
3086  const QDomElement settingsElem = element.firstChildElement( QStringLiteral( "itemClippingSettings" ) );
3087 
3088  mEnabled = settingsElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
3089  mForceLabelsInsideClipPath = settingsElem.attribute( QStringLiteral( "forceLabelsInside" ), QStringLiteral( "0" ) ).toInt();
3090  mFeatureClippingType = static_cast< QgsMapClippingRegion::FeatureClippingType >( settingsElem.attribute( QStringLiteral( "clippingType" ), QStringLiteral( "0" ) ).toInt() );
3091  mClipPathUuid = settingsElem.attribute( QStringLiteral( "clipSource" ) );
3092 
3093  return true;
3094 }
3095 
3097 {
3098  if ( !mClipPathUuid.isEmpty() )
3099  {
3100  if ( QgsLayoutItem *item = mMap->layout()->itemByUuid( mClipPathUuid, true ) )
3101  {
3102  setSourceItem( item );
3103  }
3104  }
3105 }
@ 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.
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.
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:101
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:109
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:2092
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:2065
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:1530
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2064
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1578
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
Definition: qgsmaplayer.h:2133
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.