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