QGIS API Documentation  3.23.0-Master (eb871beae0)
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.)
1536  jobMapSettings.setFlag( Qgis::MapSettingsFlag::DrawEditingInfo, false );
1537  jobMapSettings.setSelectionColor( mLayout->renderContext().selectionColor() );
1541  jobMapSettings.setTransformContext( mLayout->project()->transformContext() );
1542  jobMapSettings.setPathResolver( mLayout->project()->pathResolver() );
1543 
1544  QgsLabelingEngineSettings labelSettings = mLayout->project()->labelingEngineSettings();
1545 
1546  // override project "show partial labels" setting with this map's setting
1550  jobMapSettings.setLabelingEngineSettings( labelSettings );
1551 
1552  // override the default text render format inherited from the labeling engine settings using the layout's render context setting
1553  jobMapSettings.setTextRenderFormat( mLayout->renderContext().textRenderFormat() );
1554 
1555  QgsGeometry labelBoundary;
1556  if ( mEvaluatedLabelMargin.length() > 0 )
1557  {
1558  QPolygonF visiblePoly = jobMapSettings.visiblePolygon();
1559  visiblePoly.append( visiblePoly.at( 0 ) ); //close polygon
1560  const double layoutLabelMargin = mLayout->convertToLayoutUnits( mEvaluatedLabelMargin );
1561  const double layoutLabelMarginInMapUnits = layoutLabelMargin / rect().width() * jobMapSettings.extent().width();
1562  QgsGeometry mapBoundaryGeom = QgsGeometry::fromQPolygonF( visiblePoly );
1563  mapBoundaryGeom = mapBoundaryGeom.buffer( -layoutLabelMarginInMapUnits, 0 );
1564  labelBoundary = mapBoundaryGeom;
1565  }
1566 
1567  if ( !mBlockingLabelItems.isEmpty() )
1568  {
1569  jobMapSettings.setLabelBlockingRegions( createLabelBlockingRegions( jobMapSettings ) );
1570  }
1571 
1572  for ( QgsRenderedFeatureHandlerInterface *handler : std::as_const( mRenderedFeatureHandlers ) )
1573  {
1574  jobMapSettings.addRenderedFeatureHandler( handler );
1575  }
1576 
1577  if ( isTemporal() )
1578  jobMapSettings.setTemporalRange( temporalRange() );
1579 
1580  if ( mAtlasClippingSettings->enabled() && mLayout->reportContext().feature().isValid() )
1581  {
1582  QgsGeometry clipGeom( mLayout->reportContext().currentGeometry( jobMapSettings.destinationCrs() ) );
1583  QgsMapClippingRegion region( clipGeom );
1584  region.setFeatureClip( mAtlasClippingSettings->featureClippingType() );
1585  region.setRestrictedLayers( mAtlasClippingSettings->layersToClip() );
1586  region.setRestrictToLayers( mAtlasClippingSettings->restrictToLayers() );
1587  jobMapSettings.addClippingRegion( region );
1588 
1589  if ( mAtlasClippingSettings->forceLabelsInsideFeature() )
1590  {
1591  if ( !labelBoundary.isEmpty() )
1592  {
1593  labelBoundary = clipGeom.intersection( labelBoundary );
1594  }
1595  else
1596  {
1597  labelBoundary = clipGeom;
1598  }
1599  }
1600  }
1601 
1602  if ( mItemClippingSettings->isActive() )
1603  {
1604  const QgsGeometry clipGeom = mItemClippingSettings->clippedMapExtent();
1605  if ( !clipGeom.isEmpty() )
1606  {
1607  jobMapSettings.addClippingRegion( mItemClippingSettings->toMapClippingRegion() );
1608 
1609  if ( mItemClippingSettings->forceLabelsInsideClipPath() )
1610  {
1611  const double layoutLabelMargin = mLayout->convertToLayoutUnits( mEvaluatedLabelMargin );
1612  const double layoutLabelMarginInMapUnits = layoutLabelMargin / rect().width() * jobMapSettings.extent().width();
1613  QgsGeometry mapBoundaryGeom = clipGeom;
1614  mapBoundaryGeom = mapBoundaryGeom.buffer( -layoutLabelMarginInMapUnits, 0 );
1615  if ( !labelBoundary.isEmpty() )
1616  {
1617  labelBoundary = mapBoundaryGeom.intersection( labelBoundary );
1618  }
1619  else
1620  {
1621  labelBoundary = mapBoundaryGeom;
1622  }
1623  }
1624  }
1625  }
1626 
1627  if ( !labelBoundary.isNull() )
1628  jobMapSettings.setLabelBoundaryGeometry( labelBoundary );
1629 
1630  return jobMapSettings;
1631 }
1632 
1634 {
1635  assignFreeId();
1636 
1637  mBlockingLabelItems.clear();
1638  for ( const QString &uuid : std::as_const( mBlockingLabelItemUuids ) )
1639  {
1640  QgsLayoutItem *item = mLayout->itemByUuid( uuid, true );
1641  if ( item )
1642  {
1643  addLabelBlockingItem( item );
1644  }
1645  }
1646 
1647  mOverviewStack->finalizeRestoreFromXml();
1648  mGridStack->finalizeRestoreFromXml();
1649  mItemClippingSettings->finalizeRestoreFromXml();
1650 }
1651 
1652 void QgsLayoutItemMap::setMoveContentPreviewOffset( double xOffset, double yOffset )
1653 {
1654  mXOffset = xOffset;
1655  mYOffset = yOffset;
1656 }
1657 
1659 {
1660  return mCurrentRectangle;
1661 }
1662 
1664 {
1666 
1667  //Can't utilize QgsExpressionContextUtils::mapSettingsScope as we don't always
1668  //have a QgsMapSettings object available when the context is required, so we manually
1669  //add the same variables here
1670  QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Map Settings" ) );
1671 
1672  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_id" ), id(), true ) );
1673  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_rotation" ), mMapRotation, true ) );
1674  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_scale" ), scale(), true ) );
1675 
1676  QgsRectangle currentExtent( extent() );
1677  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent" ), QVariant::fromValue( QgsGeometry::fromRect( currentExtent ) ), true ) );
1678  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_width" ), currentExtent.width(), true ) );
1679  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_height" ), currentExtent.height(), true ) );
1680  QgsGeometry centerPoint = QgsGeometry::fromPointXY( currentExtent.center() );
1681  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_center" ), QVariant::fromValue( centerPoint ), true ) );
1682 
1683  QgsCoordinateReferenceSystem mapCrs = crs();
1684  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs" ), mapCrs.authid(), true ) );
1685  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_definition" ), mapCrs.toProj(), true ) );
1686  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_description" ), mapCrs.description(), true ) );
1687  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_units" ), QgsUnitTypes::toString( mapCrs.mapUnits() ), true ) );
1688  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_acronym" ), mapCrs.projectionAcronym(), true ) );
1689  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_projection" ), mapCrs.operation().description(), true ) );
1690  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_ellipsoid" ), mapCrs.ellipsoidAcronym(), true ) );
1691  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_proj4" ), mapCrs.toProj(), true ) );
1692  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_wkt" ), mapCrs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ), true ) );
1693 
1694  QVariantList layersIds;
1695  QVariantList layers;
1696  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layer_ids" ), layersIds, true ) );
1697  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layers" ), layers, true ) );
1698 
1699  context.appendScope( scope );
1700 
1701  // The scope map_layer_ids and map_layers variables have been added to the context, only now we can
1702  // call layersToRender (just in case layersToRender relies on evaluating an expression which uses
1703  // other variables contained within the map settings scope
1704  const QList<QgsMapLayer *> layersInMap = layersToRender( &context );
1705 
1706  layersIds.reserve( layersInMap.count() );
1707  layers.reserve( layersInMap.count() );
1708  for ( QgsMapLayer *layer : layersInMap )
1709  {
1710  layersIds << layer->id();
1711  layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( layer ) );
1712  }
1713  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layer_ids" ), layersIds, true ) );
1714  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_layers" ), layers, true ) );
1715 
1716  scope->addFunction( QStringLiteral( "is_layer_visible" ), new QgsExpressionContextUtils::GetLayerVisibility( layersInMap, scale() ) );
1717 
1718  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_start_time" ), isTemporal() ? temporalRange().begin() : QVariant(), true ) );
1719  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_end_time" ), isTemporal() ? temporalRange().end() : QVariant(), true ) );
1720  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_interval" ), isTemporal() ? ( temporalRange().end() - temporalRange().begin() ) : QVariant(), true ) );
1721 
1722  return context;
1723 }
1724 
1726 {
1727  double extentWidth = extent().width();
1728  if ( extentWidth <= 0 )
1729  {
1730  return 1;
1731  }
1732  return rect().width() / extentWidth;
1733 }
1734 
1736 {
1737  double dx = mXOffset;
1738  double dy = mYOffset;
1739  transformShift( dx, dy );
1740  QPolygonF poly = calculateVisibleExtentPolygon( false );
1741  poly.translate( -dx, -dy );
1742  return poly;
1743 }
1744 
1746 {
1747  if ( !mBlockingLabelItems.contains( item ) )
1748  mBlockingLabelItems.append( item );
1749 
1750  connect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemMap::invalidateCache, Qt::UniqueConnection );
1751 }
1752 
1754 {
1755  mBlockingLabelItems.removeAll( item );
1756  if ( item )
1758 }
1759 
1761 {
1762  return mBlockingLabelItems.contains( item );
1763 }
1764 
1766 {
1767  return mPreviewLabelingResults.get();
1768 }
1769 
1771 {
1772  // NOTE: if visitEnter returns false it means "don't visit the item", not "abort all further visitations"
1774  return true;
1775 
1776  if ( mOverviewStack )
1777  {
1778  for ( int i = 0; i < mOverviewStack->size(); ++i )
1779  {
1780  if ( mOverviewStack->item( i )->accept( visitor ) )
1781  return false;
1782  }
1783  }
1784 
1785  if ( mGridStack )
1786  {
1787  for ( int i = 0; i < mGridStack->size(); ++i )
1788  {
1789  if ( mGridStack->item( i )->accept( visitor ) )
1790  return false;
1791  }
1792  }
1793 
1795  return false;
1796 
1797  return true;
1798 }
1799 
1801 {
1802  mRenderedFeatureHandlers.append( handler );
1803 }
1804 
1806 {
1807  mRenderedFeatureHandlers.removeAll( handler );
1808 }
1809 
1810 QPointF QgsLayoutItemMap::mapToItemCoords( QPointF mapCoords ) const
1811 {
1812  QPolygonF mapPoly = transformedMapPolygon();
1813  if ( mapPoly.empty() )
1814  {
1815  return QPointF( 0, 0 );
1816  }
1817 
1818  QgsRectangle tExtent = transformedExtent();
1819  QgsPointXY rotationPoint( ( tExtent.xMaximum() + tExtent.xMinimum() ) / 2.0, ( tExtent.yMaximum() + tExtent.yMinimum() ) / 2.0 );
1820  double dx = mapCoords.x() - rotationPoint.x();
1821  double dy = mapCoords.y() - rotationPoint.y();
1822  QgsLayoutUtils::rotate( -mEvaluatedMapRotation, dx, dy );
1823  QgsPointXY backRotatedCoords( rotationPoint.x() + dx, rotationPoint.y() + dy );
1824 
1825  QgsRectangle unrotatedExtent = transformedExtent();
1826  double xItem = rect().width() * ( backRotatedCoords.x() - unrotatedExtent.xMinimum() ) / unrotatedExtent.width();
1827  double yItem = rect().height() * ( 1 - ( backRotatedCoords.y() - unrotatedExtent.yMinimum() ) / unrotatedExtent.height() );
1828  return QPointF( xItem, yItem );
1829 }
1830 
1832 {
1834  QgsRectangle newExtent = mExtent;
1835  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
1836  {
1837  extent = newExtent;
1838  }
1839  else
1840  {
1841  QPolygonF poly;
1842  mapPolygon( newExtent, poly );
1843  QRectF bRect = poly.boundingRect();
1844  extent.setXMinimum( bRect.left() );
1845  extent.setXMaximum( bRect.right() );
1846  extent.setYMinimum( bRect.top() );
1847  extent.setYMaximum( bRect.bottom() );
1848  }
1849  return extent;
1850 }
1851 
1853 {
1854  if ( mDrawing )
1855  return;
1856 
1857  mCacheInvalidated = true;
1858  update();
1859 }
1860 
1862 {
1863  QRectF rectangle = rect();
1864  double frameExtension = frameEnabled() ? pen().widthF() / 2.0 : 0.0;
1865 
1866  double topExtension = 0.0;
1867  double rightExtension = 0.0;
1868  double bottomExtension = 0.0;
1869  double leftExtension = 0.0;
1870 
1871  if ( mGridStack )
1872  mGridStack->calculateMaxGridExtension( topExtension, rightExtension, bottomExtension, leftExtension );
1873 
1874  topExtension = std::max( topExtension, frameExtension );
1875  rightExtension = std::max( rightExtension, frameExtension );
1876  bottomExtension = std::max( bottomExtension, frameExtension );
1877  leftExtension = std::max( leftExtension, frameExtension );
1878 
1879  rectangle.setLeft( rectangle.left() - leftExtension );
1880  rectangle.setRight( rectangle.right() + rightExtension );
1881  rectangle.setTop( rectangle.top() - topExtension );
1882  rectangle.setBottom( rectangle.bottom() + bottomExtension );
1883  if ( rectangle != mCurrentRectangle )
1884  {
1885  prepareGeometryChange();
1886  mCurrentRectangle = rectangle;
1887  }
1888 }
1889 
1891 {
1893  if ( property == QgsLayoutObject::MapCrs || property == QgsLayoutObject::AllProperties )
1894  {
1895  bool ok;
1896  const QString crsVar = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapCrs, context, QString(), &ok );
1897  if ( ok && QgsCoordinateReferenceSystem( crsVar ).isValid() )
1898  {
1899  const QgsCoordinateReferenceSystem newCrs( crsVar );
1900  if ( newCrs.isValid() )
1901  {
1902  setCrs( newCrs );
1903  }
1904  }
1905  }
1906  //updates data defined properties and redraws item to match
1907  if ( property == QgsLayoutObject::MapRotation || property == QgsLayoutObject::MapScale ||
1908  property == QgsLayoutObject::MapXMin || property == QgsLayoutObject::MapYMin ||
1909  property == QgsLayoutObject::MapXMax || property == QgsLayoutObject::MapYMax ||
1910  property == QgsLayoutObject::MapAtlasMargin ||
1911  property == QgsLayoutObject::AllProperties )
1912  {
1913  QgsRectangle beforeExtent = mExtent;
1914  refreshMapExtents( &context );
1915  emit changed();
1916  if ( mExtent != beforeExtent )
1917  {
1918  emit extentChanged();
1919  }
1920  }
1921  if ( property == QgsLayoutObject::MapLabelMargin || property == QgsLayoutObject::AllProperties )
1922  {
1923  refreshLabelMargin( false );
1924  }
1925  if ( property == QgsLayoutObject::MapStylePreset || property == QgsLayoutObject::AllProperties )
1926  {
1927  const QString previousTheme = mLastEvaluatedThemeName.isEmpty() ? mFollowVisibilityPresetName : mLastEvaluatedThemeName;
1928  mLastEvaluatedThemeName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, mFollowVisibilityPresetName );
1929  if ( mLastEvaluatedThemeName != previousTheme )
1930  emit themeChanged( mLastEvaluatedThemeName );
1931  }
1932 
1933  if ( isTemporal() && ( property == QgsLayoutObject::StartDateTime || property == QgsLayoutObject::EndDateTime || property == QgsLayoutObject::AllProperties ) )
1934  {
1935  QDateTime begin = temporalRange().begin();
1936  QDateTime end = temporalRange().end();
1937 
1938  if ( property == QgsLayoutObject::StartDateTime || property == QgsLayoutObject::AllProperties )
1940  if ( property == QgsLayoutObject::EndDateTime || property == QgsLayoutObject::AllProperties )
1942 
1943  setTemporalRange( QgsDateTimeRange( begin, end, true, begin == end ) );
1944  }
1945 
1946  //force redraw
1947  mCacheInvalidated = true;
1948 
1950 }
1951 
1952 void QgsLayoutItemMap::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
1953 {
1954  if ( !mLayers.isEmpty() || mLayerStyleOverrides.isEmpty() )
1955  {
1956  for ( QgsMapLayer *layer : layers )
1957  {
1958  mLayerStyleOverrides.remove( layer->id() );
1959  }
1960  _qgis_removeLayers( mLayers, layers );
1961  }
1962 }
1963 
1964 void QgsLayoutItemMap::painterJobFinished()
1965 {
1966  mPainter->end();
1967  mPreviewLabelingResults.reset( mPainterJob->takeLabelingResults() );
1968  mPainterJob.reset( nullptr );
1969  mPainter.reset( nullptr );
1970  mCacheFinalImage = std::move( mCacheRenderingImage );
1971  mLastRenderedImageOffsetX = 0;
1972  mLastRenderedImageOffsetY = 0;
1973  emit backgroundTaskCountChanged( 0 );
1974  update();
1975  emit previewRefreshed();
1976 }
1977 
1978 void QgsLayoutItemMap::shapeChanged()
1979 {
1980  // keep center as center
1981  QgsPointXY oldCenter = mExtent.center();
1982 
1983  double w = rect().width();
1984  double h = rect().height();
1985 
1986  // keep same width as before
1987  double newWidth = mExtent.width();
1988  // but scale height to match item's aspect ratio
1989  double newHeight = newWidth * h / w;
1990 
1991  mExtent = QgsRectangle::fromCenterAndSize( oldCenter, newWidth, newHeight );
1992 
1993  //recalculate data defined scale and extents
1994  refreshMapExtents();
1996  invalidateCache();
1997  emit changed();
1998  emit extentChanged();
1999 }
2000 
2001 void QgsLayoutItemMap::mapThemeChanged( const QString &theme )
2002 {
2003  if ( theme == mCachedLayerStyleOverridesPresetName )
2004  mCachedLayerStyleOverridesPresetName.clear(); // force cache regeneration at next redraw
2005 }
2006 
2007 void QgsLayoutItemMap::currentMapThemeRenamed( const QString &theme, const QString &newTheme )
2008 {
2009  if ( theme == mFollowVisibilityPresetName )
2010  {
2011  mFollowVisibilityPresetName = newTheme;
2012  }
2013 }
2014 
2015 void QgsLayoutItemMap::connectUpdateSlot()
2016 {
2017  //connect signal from layer registry to update in case of new or deleted layers
2018  QgsProject *project = mLayout->project();
2019  if ( project )
2020  {
2021  // handles updating the stored layer state BEFORE the layers are removed
2022  connect( project, static_cast < void ( QgsProject::* )( const QList<QgsMapLayer *>& layers ) > ( &QgsProject::layersWillBeRemoved ),
2023  this, &QgsLayoutItemMap::layersAboutToBeRemoved );
2024  // redraws the map AFTER layers are removed
2025  connect( project->layerTreeRoot(), &QgsLayerTree::layerOrderChanged, this, [ = ]
2026  {
2027  if ( layers().isEmpty() )
2028  {
2029  //using project layers, and layer order has changed
2030  invalidateCache();
2031  }
2032  } );
2033 
2034  connect( project, &QgsProject::crsChanged, this, [ = ]
2035  {
2036  if ( !mCrs.isValid() )
2037  {
2038  //using project CRS, which just changed....
2039  invalidateCache();
2040  emit crsChanged();
2041  }
2042  } );
2043 
2044  // If project colors change, we need to redraw the map, as layer symbols may rely on project colors
2045  connect( project, &QgsProject::projectColorsChanged, this, [ = ]
2046  {
2047  invalidateCache();
2048  } );
2049 
2050  connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsLayoutItemMap::mapThemeChanged );
2051  connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsLayoutItemMap::currentMapThemeRenamed );
2052  }
2053  connect( mLayout, &QgsLayout::refreshed, this, &QgsLayoutItemMap::invalidateCache );
2054  connect( &mLayout->renderContext(), &QgsLayoutRenderContext::predefinedScalesChanged, this, [ = ]
2055  {
2056  if ( mAtlasScalingMode == Predefined )
2057  updateAtlasFeature();
2058  } );
2059 }
2060 
2062 {
2063  QPolygonF thisExtent = calculateVisibleExtentPolygon( false );
2064  QTransform mapTransform;
2065  QPolygonF thisRectPoly = QPolygonF( QRectF( 0, 0, rect().width(), rect().height() ) );
2066  //workaround QT Bug #21329
2067  thisRectPoly.pop_back();
2068  thisExtent.pop_back();
2069 
2070  QPolygonF thisItemPolyInLayout = mapToScene( thisRectPoly );
2071 
2072  //create transform from layout coordinates to map coordinates
2073  QTransform::quadToQuad( thisItemPolyInLayout, thisExtent, mapTransform );
2074  return mapTransform;
2075 }
2076 
2077 QList<QgsLabelBlockingRegion> QgsLayoutItemMap::createLabelBlockingRegions( const QgsMapSettings & ) const
2078 {
2079  const QTransform mapTransform = layoutToMapCoordsTransform();
2080  QList< QgsLabelBlockingRegion > blockers;
2081  blockers.reserve( mBlockingLabelItems.count() );
2082  for ( const auto &item : std::as_const( mBlockingLabelItems ) )
2083  {
2084  // invisible items don't block labels!
2085  if ( !item )
2086  continue;
2087 
2088  // layout items may be temporarily hidden during layered exports
2089  if ( item->property( "wasVisible" ).isValid() )
2090  {
2091  if ( !item->property( "wasVisible" ).toBool() )
2092  continue;
2093  }
2094  else if ( !item->isVisible() )
2095  continue;
2096 
2097  QPolygonF itemRectInMapCoordinates = mapTransform.map( item->mapToScene( item->rect() ) );
2098  itemRectInMapCoordinates.append( itemRectInMapCoordinates.at( 0 ) ); //close polygon
2099  QgsGeometry blockingRegion = QgsGeometry::fromQPolygonF( itemRectInMapCoordinates );
2100  blockers << QgsLabelBlockingRegion( blockingRegion );
2101  }
2102  return blockers;
2103 }
2104 
2106 {
2107  return mLabelMargin;
2108 }
2109 
2111 {
2112  mLabelMargin = margin;
2113  refreshLabelMargin( false );
2114 }
2115 
2116 void QgsLayoutItemMap::updateToolTip()
2117 {
2118  setToolTip( displayName() );
2119 }
2120 
2121 QString QgsLayoutItemMap::themeToRender( const QgsExpressionContext &context ) const
2122 {
2123  QString presetName;
2124 
2125  if ( mFollowVisibilityPreset )
2126  {
2127  presetName = mFollowVisibilityPresetName;
2128  // preset name can be overridden by data-defined one
2129  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
2130  }
2131  else if ( !mExportThemes.empty() && mExportThemeIt != mExportThemes.end() )
2132  presetName = *mExportThemeIt;
2133  return presetName;
2134 }
2135 
2136 QList<QgsMapLayer *> QgsLayoutItemMap::layersToRender( const QgsExpressionContext *context ) const
2137 {
2138  QgsExpressionContext scopedContext;
2139  if ( !context )
2140  scopedContext = createExpressionContext();
2141  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
2142 
2143  QList<QgsMapLayer *> renderLayers;
2144 
2145  QString presetName = themeToRender( *evalContext );
2146  if ( !presetName.isEmpty() )
2147  {
2148  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
2149  renderLayers = mLayout->project()->mapThemeCollection()->mapThemeVisibleLayers( presetName );
2150  else // fallback to using map canvas layers
2151  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
2152  }
2153  else if ( !layers().isEmpty() )
2154  {
2155  renderLayers = layers();
2156  }
2157  else
2158  {
2159  renderLayers = mLayout->project()->mapThemeCollection()->masterVisibleLayers();
2160  }
2161 
2162  bool ok = false;
2163  QString ddLayers = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapLayers, *evalContext, QString(), &ok );
2164  if ( ok )
2165  {
2166  renderLayers.clear();
2167 
2168  const QStringList layerNames = ddLayers.split( '|' );
2169  //need to convert layer names to layer ids
2170  for ( const QString &name : layerNames )
2171  {
2172  const QList< QgsMapLayer * > matchingLayers = mLayout->project()->mapLayersByName( name );
2173  for ( QgsMapLayer *layer : matchingLayers )
2174  {
2175  renderLayers << layer;
2176  }
2177  }
2178  }
2179 
2180  //remove atlas coverage layer if required
2181  if ( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagHideCoverageLayer )
2182  {
2183  //hiding coverage layer
2184  int removeAt = renderLayers.indexOf( mLayout->reportContext().layer() );
2185  if ( removeAt != -1 )
2186  {
2187  renderLayers.removeAt( removeAt );
2188  }
2189  }
2190 
2191  // remove any invalid layers
2192  renderLayers.erase( std::remove_if( renderLayers.begin(), renderLayers.end(), []( QgsMapLayer * layer )
2193  {
2194  return !layer || !layer->isValid();
2195  } ), renderLayers.end() );
2196 
2197  return renderLayers;
2198 }
2199 
2200 QMap<QString, QString> QgsLayoutItemMap::layerStyleOverridesToRender( const QgsExpressionContext &context ) const
2201 {
2202  QString presetName = themeToRender( context );
2203  if ( !presetName.isEmpty() )
2204  {
2205  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
2206  {
2207  if ( presetName != mCachedLayerStyleOverridesPresetName )
2208  {
2209  // have to regenerate cache of style overrides
2210  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
2211  mCachedLayerStyleOverridesPresetName = presetName;
2212  }
2213 
2214  return mCachedPresetLayerStyleOverrides;
2215  }
2216  else
2217  return QMap<QString, QString>();
2218  }
2219  else if ( mFollowVisibilityPreset )
2220  {
2221  QString presetName = mFollowVisibilityPresetName;
2222  // data defined preset name?
2223  presetName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, presetName );
2224  if ( mLayout->project()->mapThemeCollection()->hasMapTheme( presetName ) )
2225  {
2226  if ( presetName.isEmpty() || presetName != mCachedLayerStyleOverridesPresetName )
2227  {
2228  // have to regenerate cache of style overrides
2229  mCachedPresetLayerStyleOverrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( presetName );
2230  mCachedLayerStyleOverridesPresetName = presetName;
2231  }
2232 
2233  return mCachedPresetLayerStyleOverrides;
2234  }
2235  else
2236  return QMap<QString, QString>();
2237  }
2238  else if ( mKeepLayerStyles )
2239  {
2240  return mLayerStyleOverrides;
2241  }
2242  else
2243  {
2244  return QMap<QString, QString>();
2245  }
2246 }
2247 
2248 QgsRectangle QgsLayoutItemMap::transformedExtent() const
2249 {
2250  double dx = mXOffset;
2251  double dy = mYOffset;
2252  transformShift( dx, dy );
2253  return QgsRectangle( mExtent.xMinimum() - dx, mExtent.yMinimum() - dy, mExtent.xMaximum() - dx, mExtent.yMaximum() - dy );
2254 }
2255 
2256 void QgsLayoutItemMap::mapPolygon( const QgsRectangle &extent, QPolygonF &poly ) const
2257 {
2258  poly.clear();
2259  if ( qgsDoubleNear( mEvaluatedMapRotation, 0.0 ) )
2260  {
2261  poly << QPointF( extent.xMinimum(), extent.yMaximum() );
2262  poly << QPointF( extent.xMaximum(), extent.yMaximum() );
2263  poly << QPointF( extent.xMaximum(), extent.yMinimum() );
2264  poly << QPointF( extent.xMinimum(), extent.yMinimum() );
2265  //ensure polygon is closed by readding first point
2266  poly << QPointF( poly.at( 0 ) );
2267  return;
2268  }
2269 
2270  //there is rotation
2271  QgsPointXY rotationPoint( ( extent.xMaximum() + extent.xMinimum() ) / 2.0, ( extent.yMaximum() + extent.yMinimum() ) / 2.0 );
2272  double dx, dy; //x-, y- shift from rotation point to corner point
2273 
2274  //top left point
2275  dx = rotationPoint.x() - extent.xMinimum();
2276  dy = rotationPoint.y() - extent.yMaximum();
2277  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2278  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2279 
2280  //top right point
2281  dx = rotationPoint.x() - extent.xMaximum();
2282  dy = rotationPoint.y() - extent.yMaximum();
2283  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2284  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2285 
2286  //bottom right point
2287  dx = rotationPoint.x() - extent.xMaximum();
2288  dy = rotationPoint.y() - extent.yMinimum();
2289  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2290  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2291 
2292  //bottom left point
2293  dx = rotationPoint.x() - extent.xMinimum();
2294  dy = rotationPoint.y() - extent.yMinimum();
2295  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dx, dy );
2296  poly << QPointF( rotationPoint.x() - dx, rotationPoint.y() - dy );
2297 
2298  //ensure polygon is closed by readding first point
2299  poly << QPointF( poly.at( 0 ) );
2300 }
2301 
2302 void QgsLayoutItemMap::transformShift( double &xShift, double &yShift ) const
2303 {
2304  double mmToMapUnits = 1.0 / mapUnitsToLayoutUnits();
2305  double dxScaled = xShift * mmToMapUnits;
2306  double dyScaled = - yShift * mmToMapUnits;
2307 
2308  QgsLayoutUtils::rotate( mEvaluatedMapRotation, dxScaled, dyScaled );
2309 
2310  xShift = dxScaled;
2311  yShift = dyScaled;
2312 }
2313 
2314 void QgsLayoutItemMap::drawAnnotations( QPainter *painter )
2315 {
2316  if ( !mLayout || !mLayout->project() || !mDrawAnnotations )
2317  {
2318  return;
2319  }
2320 
2321  const QList< QgsAnnotation * > annotations = mLayout->project()->annotationManager()->annotations();
2322  if ( annotations.isEmpty() )
2323  return;
2324 
2326  rc.setForceVectorOutput( true );
2328  QList< QgsMapLayer * > layers = layersToRender( &rc.expressionContext() );
2329 
2330  for ( QgsAnnotation *annotation : annotations )
2331  {
2332  if ( !annotation || !annotation->isVisible() )
2333  {
2334  continue;
2335  }
2336  if ( annotation->mapLayer() && !layers.contains( annotation->mapLayer() ) )
2337  continue;
2338 
2339  drawAnnotation( annotation, rc );
2340  }
2341 }
2342 
2343 void QgsLayoutItemMap::drawAnnotation( const QgsAnnotation *annotation, QgsRenderContext &context )
2344 {
2345  if ( !annotation || !annotation->isVisible() || !context.painter() || !context.painter()->device() )
2346  {
2347  return;
2348  }
2349 
2350  QgsScopedQPainterState painterState( context.painter() );
2351  context.setPainterFlagsUsingContext();
2352 
2353  double itemX, itemY;
2354  if ( annotation->hasFixedMapPosition() )
2355  {
2356  QPointF mapPos = layoutMapPosForItem( annotation );
2357  itemX = mapPos.x();
2358  itemY = mapPos.y();
2359  }
2360  else
2361  {
2362  itemX = annotation->relativePosition().x() * rect().width();
2363  itemY = annotation->relativePosition().y() * rect().height();
2364  }
2365  context.painter()->translate( itemX, itemY );
2366 
2367  //setup painter scaling to dots so that symbology is drawn to scale
2368  double dotsPerMM = context.painter()->device()->logicalDpiX() / 25.4;
2369  context.painter()->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
2370 
2371  annotation->render( context );
2372 }
2373 
2374 QPointF QgsLayoutItemMap::layoutMapPosForItem( const QgsAnnotation *annotation ) const
2375 {
2376  if ( !annotation )
2377  return QPointF( 0, 0 );
2378 
2379  double mapX = 0.0;
2380  double mapY = 0.0;
2381 
2382  mapX = annotation->mapPosition().x();
2383  mapY = annotation->mapPosition().y();
2384  QgsCoordinateReferenceSystem annotationCrs = annotation->mapPositionCrs();
2385 
2386  if ( annotationCrs != crs() )
2387  {
2388  //need to reproject
2389  QgsCoordinateTransform t( annotationCrs, crs(), mLayout->project() );
2390  double z = 0.0;
2391  try
2392  {
2393  t.transformInPlace( mapX, mapY, z );
2394  }
2395  catch ( const QgsCsException & )
2396  {
2397  }
2398  }
2399 
2400  return mapToItemCoords( QPointF( mapX, mapY ) );
2401 }
2402 
2403 void QgsLayoutItemMap::drawMapFrame( QPainter *p )
2404 {
2405  if ( frameEnabled() && p )
2406  {
2409 
2411  }
2412 }
2413 
2414 void QgsLayoutItemMap::drawMapBackground( QPainter *p )
2415 {
2416  if ( hasBackground() && p )
2417  {
2420 
2422  }
2423 }
2424 
2425 bool QgsLayoutItemMap::shouldDrawPart( QgsLayoutItemMap::PartType part ) const
2426 {
2427  if ( mCurrentExportPart == NotLayered )
2428  {
2429  //all parts of the map are visible
2430  return true;
2431  }
2432 
2433  switch ( part )
2434  {
2435  case NotLayered:
2436  return true;
2437 
2438  case Start:
2439  return false;
2440 
2441  case Background:
2442  return mCurrentExportPart == Background && hasBackground();
2443 
2444  case Layer:
2445  return mCurrentExportPart == Layer;
2446 
2447  case Grid:
2448  return mCurrentExportPart == Grid && mGridStack->hasEnabledItems();
2449 
2450  case OverviewMapExtent:
2451  return mCurrentExportPart == OverviewMapExtent && mOverviewStack->hasEnabledItems();
2452 
2453  case Frame:
2454  return mCurrentExportPart == Frame && frameEnabled();
2455 
2456  case SelectionBoxes:
2457  return mCurrentExportPart == SelectionBoxes && isSelected();
2458 
2459  case End:
2460  return false;
2461  }
2462 
2463  return false;
2464 }
2465 
2466 void QgsLayoutItemMap::refreshMapExtents( const QgsExpressionContext *context )
2467 {
2468  QgsExpressionContext scopedContext;
2469  if ( !context )
2470  scopedContext = createExpressionContext();
2471 
2472  bool ok = false;
2473  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
2474 
2475 
2476  //data defined map extents set?
2477  QgsRectangle newExtent = extent();
2478  bool useDdXMin = false;
2479  bool useDdXMax = false;
2480  bool useDdYMin = false;
2481  bool useDdYMax = false;
2482  double minXD = 0;
2483  double minYD = 0;
2484  double maxXD = 0;
2485  double maxYD = 0;
2486 
2487  minXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMin, *evalContext, 0.0, &ok );
2488  if ( ok )
2489  {
2490  useDdXMin = true;
2491  newExtent.setXMinimum( minXD );
2492  }
2493  minYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMin, *evalContext, 0.0, &ok );
2494  if ( ok )
2495  {
2496  useDdYMin = true;
2497  newExtent.setYMinimum( minYD );
2498  }
2499  maxXD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapXMax, *evalContext, 0.0, &ok );
2500  if ( ok )
2501  {
2502  useDdXMax = true;
2503  newExtent.setXMaximum( maxXD );
2504  }
2505  maxYD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapYMax, *evalContext, 0.0, &ok );
2506  if ( ok )
2507  {
2508  useDdYMax = true;
2509  newExtent.setYMaximum( maxYD );
2510  }
2511 
2512  if ( newExtent != mExtent )
2513  {
2514  //calculate new extents to fit data defined extents
2515 
2516  //Make sure the width/height ratio is the same as in current map extent.
2517  //This is to keep the map item frame and the page layout fixed
2518  double currentWidthHeightRatio = mExtent.width() / mExtent.height();
2519  double newWidthHeightRatio = newExtent.width() / newExtent.height();
2520 
2521  if ( currentWidthHeightRatio < newWidthHeightRatio )
2522  {
2523  //enlarge height of new extent, ensuring the map center stays the same
2524  double newHeight = newExtent.width() / currentWidthHeightRatio;
2525  double deltaHeight = newHeight - newExtent.height();
2526  newExtent.setYMinimum( newExtent.yMinimum() - deltaHeight / 2 );
2527  newExtent.setYMaximum( newExtent.yMaximum() + deltaHeight / 2 );
2528  }
2529  else
2530  {
2531  //enlarge width of new extent, ensuring the map center stays the same
2532  double newWidth = currentWidthHeightRatio * newExtent.height();
2533  double deltaWidth = newWidth - newExtent.width();
2534  newExtent.setXMinimum( newExtent.xMinimum() - deltaWidth / 2 );
2535  newExtent.setXMaximum( newExtent.xMaximum() + deltaWidth / 2 );
2536  }
2537 
2538  mExtent = newExtent;
2539  }
2540 
2541  //now refresh scale, as this potentially overrides extents
2542 
2543  //data defined map scale set?
2544  double scaleD = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapScale, *evalContext, 0.0, &ok );
2545  if ( ok )
2546  {
2547  setScale( scaleD, false );
2548  newExtent = mExtent;
2549  }
2550 
2551  if ( useDdXMax || useDdXMin || useDdYMax || useDdYMin )
2552  {
2553  //if only one of min/max was set for either x or y, then make sure our extent is locked on that value
2554  //as we can do this without altering the scale
2555  if ( useDdXMin && !useDdXMax )
2556  {
2557  double xMax = mExtent.xMaximum() - ( mExtent.xMinimum() - minXD );
2558  newExtent.setXMinimum( minXD );
2559  newExtent.setXMaximum( xMax );
2560  }
2561  else if ( !useDdXMin && useDdXMax )
2562  {
2563  double xMin = mExtent.xMinimum() - ( mExtent.xMaximum() - maxXD );
2564  newExtent.setXMinimum( xMin );
2565  newExtent.setXMaximum( maxXD );
2566  }
2567  if ( useDdYMin && !useDdYMax )
2568  {
2569  double yMax = mExtent.yMaximum() - ( mExtent.yMinimum() - minYD );
2570  newExtent.setYMinimum( minYD );
2571  newExtent.setYMaximum( yMax );
2572  }
2573  else if ( !useDdYMin && useDdYMax )
2574  {
2575  double yMin = mExtent.yMinimum() - ( mExtent.yMaximum() - maxYD );
2576  newExtent.setYMinimum( yMin );
2577  newExtent.setYMaximum( maxYD );
2578  }
2579 
2580  if ( newExtent != mExtent )
2581  {
2582  mExtent = newExtent;
2583  }
2584  }
2585 
2586  //lastly, map rotation overrides all
2587  double mapRotation = mMapRotation;
2588 
2589  //data defined map rotation set?
2591 
2592  if ( !qgsDoubleNear( mEvaluatedMapRotation, mapRotation ) )
2593  {
2594  mEvaluatedMapRotation = mapRotation;
2596  }
2597 }
2598 
2599 void QgsLayoutItemMap::refreshLabelMargin( bool updateItem )
2600 {
2601  //data defined label margin set?
2603  mEvaluatedLabelMargin.setLength( labelMargin );
2604  mEvaluatedLabelMargin.setUnits( mLabelMargin.units() );
2605 
2606  if ( updateItem )
2607  {
2608  update();
2609  }
2610 }
2611 
2612 void QgsLayoutItemMap::updateAtlasFeature()
2613 {
2614  if ( !atlasDriven() || !mLayout->reportContext().layer() )
2615  return; // nothing to do
2616 
2617  QgsRectangle bounds = computeAtlasRectangle();
2618  if ( bounds.isNull() )
2619  return;
2620 
2621  double xa1 = bounds.xMinimum();
2622  double xa2 = bounds.xMaximum();
2623  double ya1 = bounds.yMinimum();
2624  double ya2 = bounds.yMaximum();
2625  QgsRectangle newExtent = bounds;
2626  QgsRectangle originalExtent = mExtent;
2627 
2628  //sanity check - only allow fixed scale mode for point layers
2629  bool isPointLayer = QgsWkbTypes::geometryType( mLayout->reportContext().layer()->wkbType() ) == QgsWkbTypes::PointGeometry;
2630 
2631  if ( mAtlasScalingMode == Fixed || mAtlasScalingMode == Predefined || isPointLayer )
2632  {
2633  QgsScaleCalculator calc;
2634  calc.setMapUnits( crs().mapUnits() );
2635  calc.setDpi( 25.4 );
2636  double originalScale = calc.calculate( originalExtent, rect().width() );
2637  double geomCenterX = ( xa1 + xa2 ) / 2.0;
2638  double geomCenterY = ( ya1 + ya2 ) / 2.0;
2639  QVector<qreal> scales;
2641  if ( !mLayout->reportContext().predefinedScales().empty() ) // remove when deprecated method is removed
2642  scales = mLayout->reportContext().predefinedScales();
2643  else
2644  scales = mLayout->renderContext().predefinedScales();
2646  if ( mAtlasScalingMode == Fixed || scales.isEmpty() || ( isPointLayer && mAtlasScalingMode != Predefined ) )
2647  {
2648  // only translate, keep the original scale (i.e. width x height)
2649  double xMin = geomCenterX - originalExtent.width() / 2.0;
2650  double yMin = geomCenterY - originalExtent.height() / 2.0;
2651  newExtent = QgsRectangle( xMin,
2652  yMin,
2653  xMin + originalExtent.width(),
2654  yMin + originalExtent.height() );
2655 
2656  //scale newExtent to match original scale of map
2657  //this is required for geographic coordinate systems, where the scale varies by extent
2658  double newScale = calc.calculate( newExtent, rect().width() );
2659  newExtent.scale( originalScale / newScale );
2660  }
2661  else if ( mAtlasScalingMode == Predefined )
2662  {
2663  // choose one of the predefined scales
2664  double newWidth = originalExtent.width();
2665  double newHeight = originalExtent.height();
2666  for ( int i = 0; i < scales.size(); i++ )
2667  {
2668  double ratio = scales[i] / originalScale;
2669  newWidth = originalExtent.width() * ratio;
2670  newHeight = originalExtent.height() * ratio;
2671 
2672  // compute new extent, centered on feature
2673  double xMin = geomCenterX - newWidth / 2.0;
2674  double yMin = geomCenterY - newHeight / 2.0;
2675  newExtent = QgsRectangle( xMin,
2676  yMin,
2677  xMin + newWidth,
2678  yMin + newHeight );
2679 
2680  //scale newExtent to match desired map scale
2681  //this is required for geographic coordinate systems, where the scale varies by extent
2682  double newScale = calc.calculate( newExtent, rect().width() );
2683  newExtent.scale( scales[i] / newScale );
2684 
2685  if ( ( newExtent.width() >= bounds.width() ) && ( newExtent.height() >= bounds.height() ) )
2686  {
2687  // this is the smallest extent that embeds the feature, stop here
2688  break;
2689  }
2690  }
2691  }
2692  }
2693  else if ( mAtlasScalingMode == Auto )
2694  {
2695  // auto scale
2696 
2697  double geomRatio = bounds.width() / bounds.height();
2698  double mapRatio = originalExtent.width() / originalExtent.height();
2699 
2700  // geometry height is too big
2701  if ( geomRatio < mapRatio )
2702  {
2703  // extent the bbox's width
2704  double adjWidth = ( mapRatio * bounds.height() - bounds.width() ) / 2.0;
2705  xa1 -= adjWidth;
2706  xa2 += adjWidth;
2707  }
2708  // geometry width is too big
2709  else if ( geomRatio > mapRatio )
2710  {
2711  // extent the bbox's height
2712  double adjHeight = ( bounds.width() / mapRatio - bounds.height() ) / 2.0;
2713  ya1 -= adjHeight;
2714  ya2 += adjHeight;
2715  }
2716  newExtent = QgsRectangle( xa1, ya1, xa2, ya2 );
2717 
2718  const double evaluatedAtlasMargin = atlasMargin();
2719  if ( evaluatedAtlasMargin > 0.0 )
2720  {
2721  newExtent.scale( 1 + evaluatedAtlasMargin );
2722  }
2723  }
2724 
2725  // set the new extent (and render)
2726  setExtent( newExtent );
2727  emit preparedForAtlas();
2728 }
2729 
2730 QgsRectangle QgsLayoutItemMap::computeAtlasRectangle()
2731 {
2732  // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
2733  // We have to transform the geometry to the destination CRS and ask for the bounding box
2734  // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
2735  QgsGeometry g = mLayout->reportContext().currentGeometry( crs() );
2736  // Rotating the geometry, so the bounding box is correct wrt map rotation
2737  if ( mEvaluatedMapRotation != 0.0 )
2738  {
2739  QgsPointXY prevCenter = g.boundingBox().center();
2740  g.rotate( mEvaluatedMapRotation, g.boundingBox().center() );
2741  // Rotation center will be still the bounding box center of an unrotated geometry.
2742  // Which means, if the center of bbox moves after rotation, the viewport will
2743  // also be offset, and part of the geometry will fall out of bounds.
2744  // Here we compensate for that roughly: by extending the rotated bounds
2745  // so that its center is the same as the original.
2746  QgsRectangle bounds = g.boundingBox();
2747  double dx = std::max( std::abs( prevCenter.x() - bounds.xMinimum() ),
2748  std::abs( prevCenter.x() - bounds.xMaximum() ) );
2749  double dy = std::max( std::abs( prevCenter.y() - bounds.yMinimum() ),
2750  std::abs( prevCenter.y() - bounds.yMaximum() ) );
2751  QgsPointXY center = g.boundingBox().center();
2752  return QgsRectangle( center.x() - dx, center.y() - dy,
2753  center.x() + dx, center.y() + dy );
2754  }
2755  else
2756  {
2757  return g.boundingBox();
2758  }
2759 }
2760 
2761 void QgsLayoutItemMap::createStagedRenderJob( const QgsRectangle &extent, const QSizeF size, double dpi )
2762 {
2763  QgsMapSettings settings = mapSettings( extent, size, dpi, true );
2764  settings.setLayers( mOverviewStack->modifyMapLayerList( settings.layers() ) );
2765 
2766  mStagedRendererJob = std::make_unique< QgsMapRendererStagedRenderJob >( settings,
2769  : QgsMapRendererStagedRenderJob::Flags() );
2770  mStagedRendererJob->start();
2771 }
2772 
2773 
2774 
2775 //
2776 // QgsLayoutItemMapAtlasClippingSettings
2777 //
2778 
2780  : QObject( map )
2781  , mMap( map )
2782 {
2783  if ( mMap->layout() && mMap->layout()->project() )
2784  {
2785  connect( mMap->layout()->project(), static_cast < void ( QgsProject::* )( const QList<QgsMapLayer *>& layers ) > ( &QgsProject::layersWillBeRemoved ),
2786  this, &QgsLayoutItemMapAtlasClippingSettings::layersAboutToBeRemoved );
2787  }
2788 }
2789 
2791 {
2792  return mClipToAtlasFeature;
2793 }
2794 
2796 {
2797  if ( enabled == mClipToAtlasFeature )
2798  return;
2799 
2800  mClipToAtlasFeature = enabled;
2801  emit changed();
2802 }
2803 
2805 {
2806  return mFeatureClippingType;
2807 }
2808 
2810 {
2811  if ( mFeatureClippingType == type )
2812  return;
2813 
2814  mFeatureClippingType = type;
2815  emit changed();
2816 }
2817 
2819 {
2820  return mForceLabelsInsideFeature;
2821 }
2822 
2824 {
2825  if ( forceInside == mForceLabelsInsideFeature )
2826  return;
2827 
2828  mForceLabelsInsideFeature = forceInside;
2829  emit changed();
2830 }
2831 
2833 {
2834  return mRestrictToLayers;
2835 }
2836 
2838 {
2839  if ( mRestrictToLayers == enabled )
2840  return;
2841 
2842  mRestrictToLayers = enabled;
2843  emit changed();
2844 }
2845 
2847 {
2848  return _qgis_listRefToRaw( mLayersToClip );
2849 }
2850 
2851 void QgsLayoutItemMapAtlasClippingSettings::setLayersToClip( const QList< QgsMapLayer * > &layersToClip )
2852 {
2853  mLayersToClip = _qgis_listRawToRef( layersToClip );
2854  emit changed();
2855 }
2856 
2857 bool QgsLayoutItemMapAtlasClippingSettings::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
2858 {
2859  QDomElement settingsElem = document.createElement( QStringLiteral( "atlasClippingSettings" ) );
2860  settingsElem.setAttribute( QStringLiteral( "enabled" ), mClipToAtlasFeature ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2861  settingsElem.setAttribute( QStringLiteral( "forceLabelsInside" ), mForceLabelsInsideFeature ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2862  settingsElem.setAttribute( QStringLiteral( "clippingType" ), QString::number( static_cast<int>( mFeatureClippingType ) ) );
2863  settingsElem.setAttribute( QStringLiteral( "restrictLayers" ), mRestrictToLayers ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2864 
2865  //layer set
2866  QDomElement layerSetElem = document.createElement( QStringLiteral( "layersToClip" ) );
2867  for ( const QgsMapLayerRef &layerRef : mLayersToClip )
2868  {
2869  if ( !layerRef )
2870  continue;
2871  QDomElement layerElem = document.createElement( QStringLiteral( "Layer" ) );
2872  QDomText layerIdText = document.createTextNode( layerRef.layerId );
2873  layerElem.appendChild( layerIdText );
2874 
2875  layerElem.setAttribute( QStringLiteral( "name" ), layerRef.name );
2876  layerElem.setAttribute( QStringLiteral( "source" ), layerRef.source );
2877  layerElem.setAttribute( QStringLiteral( "provider" ), layerRef.provider );
2878 
2879  layerSetElem.appendChild( layerElem );
2880  }
2881  settingsElem.appendChild( layerSetElem );
2882 
2883  element.appendChild( settingsElem );
2884  return true;
2885 }
2886 
2887 bool QgsLayoutItemMapAtlasClippingSettings::readXml( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext & )
2888 {
2889  const QDomElement settingsElem = element.firstChildElement( QStringLiteral( "atlasClippingSettings" ) );
2890 
2891  mClipToAtlasFeature = settingsElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
2892  mForceLabelsInsideFeature = settingsElem.attribute( QStringLiteral( "forceLabelsInside" ), QStringLiteral( "0" ) ).toInt();
2893  mFeatureClippingType = static_cast< QgsMapClippingRegion::FeatureClippingType >( settingsElem.attribute( QStringLiteral( "clippingType" ), QStringLiteral( "0" ) ).toInt() );
2894  mRestrictToLayers = settingsElem.attribute( QStringLiteral( "restrictLayers" ), QStringLiteral( "0" ) ).toInt();
2895 
2896  mLayersToClip.clear();
2897  QDomNodeList layerSetNodeList = settingsElem.elementsByTagName( QStringLiteral( "layersToClip" ) );
2898  if ( !layerSetNodeList.isEmpty() )
2899  {
2900  QDomElement layerSetElem = layerSetNodeList.at( 0 ).toElement();
2901  QDomNodeList layerIdNodeList = layerSetElem.elementsByTagName( QStringLiteral( "Layer" ) );
2902  mLayersToClip.reserve( layerIdNodeList.size() );
2903  for ( int i = 0; i < layerIdNodeList.size(); ++i )
2904  {
2905  QDomElement layerElem = layerIdNodeList.at( i ).toElement();
2906  QString layerId = layerElem.text();
2907  QString layerName = layerElem.attribute( QStringLiteral( "name" ) );
2908  QString layerSource = layerElem.attribute( QStringLiteral( "source" ) );
2909  QString layerProvider = layerElem.attribute( QStringLiteral( "provider" ) );
2910 
2911  QgsMapLayerRef ref( layerId, layerName, layerSource, layerProvider );
2912  if ( mMap->layout() && mMap->layout()->project() )
2913  ref.resolveWeakly( mMap->layout()->project() );
2914  mLayersToClip << ref;
2915  }
2916  }
2917 
2918  return true;
2919 }
2920 
2921 void QgsLayoutItemMapAtlasClippingSettings::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
2922 {
2923  if ( !mLayersToClip.isEmpty() )
2924  {
2925  _qgis_removeLayers( mLayersToClip, layers );
2926  }
2927 }
2928 
2929 //
2930 // QgsLayoutItemMapItemClipPathSettings
2931 //
2933  : QObject( map )
2934  , mMap( map )
2935 {
2936 }
2937 
2939 {
2940  return mEnabled && mClipPathSource;
2941 }
2942 
2944 {
2945  return mEnabled;
2946 }
2947 
2949 {
2950  if ( enabled == mEnabled )
2951  return;
2952 
2953  mEnabled = enabled;
2954 
2955  if ( mClipPathSource )
2956  {
2957  // may need to refresh the clip source in order to get it to render/not render depending on enabled state
2958  mClipPathSource->refresh();
2959  }
2960  emit changed();
2961 }
2962 
2964 {
2965  if ( isActive() )
2966  {
2967  QgsGeometry clipGeom( mClipPathSource->clipPath() );
2968  clipGeom.transform( mMap->layoutToMapCoordsTransform() );
2969  return clipGeom;
2970  }
2971  return QgsGeometry();
2972 }
2973 
2975 {
2976  if ( isActive() )
2977  {
2978  QgsGeometry clipGeom( mClipPathSource->clipPath() );
2979  clipGeom.transform( mMap->sceneTransform().inverted() );
2980  return clipGeom;
2981  }
2982  return QgsGeometry();
2983 }
2984 
2986 {
2988  region.setFeatureClip( mFeatureClippingType );
2989  return region;
2990 }
2991 
2993 {
2994  if ( mClipPathSource == item )
2995  return;
2996 
2997  if ( mClipPathSource )
2998  {
2999  disconnect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::refresh );
3000  disconnect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::refresh );
3001  disconnect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::extentChanged );
3002  disconnect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::extentChanged );
3003  }
3004 
3005  QgsLayoutItem *oldItem = mClipPathSource;
3006  mClipPathSource = item;
3007 
3008  if ( mClipPathSource )
3009  {
3010  // if item size or rotation changes, we need to redraw this map
3011  connect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::refresh );
3012  connect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::refresh );
3013  // and if clip item size or rotation changes, then effectively we've changed the visible extent of the map
3014  connect( mClipPathSource, &QgsLayoutItem::clipPathChanged, mMap, &QgsLayoutItemMap::extentChanged );
3015  connect( mClipPathSource, &QgsLayoutItem::rotationChanged, mMap, &QgsLayoutItemMap::extentChanged );
3016  // trigger a redraw of the clip source, so that it becomes invisible
3017  mClipPathSource->refresh();
3018  }
3019 
3020  if ( oldItem )
3021  {
3022  // may need to refresh the previous item in order to get it to render
3023  oldItem->refresh();
3024  }
3025 
3026  emit changed();
3027 }
3028 
3030 {
3031  return mClipPathSource;
3032 }
3033 
3035 {
3036  return mFeatureClippingType;
3037 }
3038 
3040 {
3041  if ( mFeatureClippingType == type )
3042  return;
3043 
3044  mFeatureClippingType = type;
3045  emit changed();
3046 }
3047 
3049 {
3050  return mForceLabelsInsideClipPath;
3051 }
3052 
3054 {
3055  if ( forceInside == mForceLabelsInsideClipPath )
3056  return;
3057 
3058  mForceLabelsInsideClipPath = forceInside;
3059  emit changed();
3060 }
3061 
3062 bool QgsLayoutItemMapItemClipPathSettings::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
3063 {
3064  QDomElement settingsElem = document.createElement( QStringLiteral( "itemClippingSettings" ) );
3065  settingsElem.setAttribute( QStringLiteral( "enabled" ), mEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
3066  settingsElem.setAttribute( QStringLiteral( "forceLabelsInside" ), mForceLabelsInsideClipPath ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
3067  settingsElem.setAttribute( QStringLiteral( "clippingType" ), QString::number( static_cast<int>( mFeatureClippingType ) ) );
3068  if ( mClipPathSource )
3069  settingsElem.setAttribute( QStringLiteral( "clipSource" ), mClipPathSource->uuid() );
3070  else
3071  settingsElem.setAttribute( QStringLiteral( "clipSource" ), QString() );
3072 
3073  element.appendChild( settingsElem );
3074  return true;
3075 }
3076 
3077 bool QgsLayoutItemMapItemClipPathSettings::readXml( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext & )
3078 {
3079  const QDomElement settingsElem = element.firstChildElement( QStringLiteral( "itemClippingSettings" ) );
3080 
3081  mEnabled = settingsElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
3082  mForceLabelsInsideClipPath = settingsElem.attribute( QStringLiteral( "forceLabelsInside" ), QStringLiteral( "0" ) ).toInt();
3083  mFeatureClippingType = static_cast< QgsMapClippingRegion::FeatureClippingType >( settingsElem.attribute( QStringLiteral( "clippingType" ), QStringLiteral( "0" ) ).toInt() );
3084  mClipPathUuid = settingsElem.attribute( QStringLiteral( "clipSource" ) );
3085 
3086  return true;
3087 }
3088 
3090 {
3091  if ( !mClipPathUuid.isEmpty() )
3092  {
3093  if ( QgsLayoutItem *item = mMap->layout()->itemByUuid( mClipPathUuid, true ) )
3094  {
3095  setSourceItem( item );
3096  }
3097  }
3098 }
@ DrawEditingInfo
Enable drawing of vertex markers for layers in editing mode.
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
@ ForceVectorOutput
Vector graphics should not be cached and drawn as raster images.
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ DrawSelection
Whether vector selections should be shown in the rendered map.
@ Antialiasing
Enable anti-aliasing for map rendering.
@ UseAdvancedEffects
Enable layer opacity and blending effects.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
virtual QPainterPath asQPainterPath() const =0
Returns the geometry represented as a QPainterPath.
QDateTime valueAsDateTime(int key, const QgsExpressionContext &context, const QDateTime &defaultDateTime=QDateTime(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a datetime.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
Abstract base class for annotation items which are drawn over a map.
Definition: qgsannotation.h:54
bool hasFixedMapPosition
Definition: qgsannotation.h:72
QgsCoordinateReferenceSystem mapPositionCrs() const
Returns the CRS of the map position, or an invalid CRS if the annotation does not have a fixed map po...
QgsPointXY mapPosition
Definition: qgsannotation.h:73
void render(QgsRenderContext &context) const
Renders the annotation to a target render context.
bool isVisible() const
Returns true if the annotation is visible and should be rendered.
Definition: qgsannotation.h:95
QPointF relativePosition() const
Returns the relative position of the annotation, if it is not attached to a fixed map position.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsCoordinateReferenceSystemRegistry * coordinateReferenceSystemRegistry()
Returns the application's coordinate reference system (CRS) registry, which handles known CRS definit...
void userCrsChanged(const QString &id)
Emitted whenever an existing user CRS definition is changed.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QString toProj() const
Returns a Proj string representation of this CRS.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
QString 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.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered in the map.
void setSelectionColor(const QColor &color)
Sets the color that is used for drawing of selected vector features.
void setSimplifyMethod(const QgsVectorSimplifyMethod &method)
Sets the simplification setting to use when rendering vector layers.
QPolygonF visiblePolygon() const
Returns the visible area as a polygon (may be rotated)
void addRenderedFeatureHandler(QgsRenderedFeatureHandlerInterface *handler)
Adds a rendered feature handler to use while rendering the map settings.
void setTextRenderFormat(Qgis::TextRenderFormat format)
Sets the text render format, which dictates how text is rendered (e.g.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
void setDpiTarget(double dpi)
Sets the target dpi (dots per inch) to be taken into consideration when rendering.
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
QgsRectangle extent() const
Returns geographical coordinates of the rectangle that should be rendered.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the map of map layer style overrides (key: layer ID, value: style name) where a different style ...
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
void setRotation(double rotation)
Sets the rotation of the resulting map image, in degrees clockwise.
void setPathResolver(const QgsPathResolver &resolver)
Sets the path resolver for conversion between relative and absolute paths during rendering operations...
void setLabelBoundaryGeometry(const QgsGeometry &boundary)
Sets the label boundary geometry, which restricts where in the rendered map labels are permitted to b...
void setLabelBlockingRegions(const QList< QgsLabelBlockingRegion > &regions)
Sets a list of regions to avoid placing labels within.
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
void mapThemeRenamed(const QString &name, const QString &newName)
Emitted when a map theme within the collection is renamed.
void mapThemeChanged(const QString &theme)
Emitted when a map theme changes definition.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
QString description() const
Description.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:101
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
void crsChanged()
Emitted when the CRS of the project has changed.
QgsMapThemeCollection * mapThemeCollection
Definition: qgsproject.h:109
void projectColorsChanged()
Emitted whenever the project's color scheme has been changed.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:256
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
static QgsRectangle fromCenterAndSize(const QgsPointXY &center, double width, double height)
Creates a new rectangle, given the specified center point and width and height.
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
An interface for classes which provider custom handlers for features rendered as part of a map render...
Calculates scale for a given combination of canvas size, map extent, and monitor dpi.
double calculate(const QgsRectangle &mapExtent, double canvasWidth) const
Calculate the scale denominator.
void setDpi(double dpi)
Sets the dpi (dots per inch) for the output resolution, to be used in scale calculations.
void setMapUnits(QgsUnitTypes::DistanceUnit mapUnits)
Set the map units.
Scoped object for saving and restoring a QPainter object's state.
An interface for classes which can visit style entity (e.g.
@ LayoutItem
Individual item in a print layout.
virtual bool visitExit(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor stops visiting a node.
virtual bool visitEnter(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor starts visiting a node.
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
bool isTemporal() const
Returns true if the object's temporal range is enabled, and the object will be filtered when renderin...
void setIsTemporal(bool enabled)
Sets whether the temporal range is enabled (i.e.
void setTemporalRange(const QgsDateTimeRange &range)
Sets the temporal range for the object.
@ LayoutInches
Inches.
Definition: qgsunittypes.h:186
@ LayoutMillimeters
Millimeters.
Definition: qgsunittypes.h:183
static Q_INVOKABLE QString toString(QgsUnitTypes::DistanceUnit unit)
Returns a translated string representing a distance unit.
@ NoSimplification
No simplification can be applied.
static 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:938
#define FALLTHROUGH
Definition: qgis.h:2027
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:2000
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:1456
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:1999
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1504
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
Definition: qgsmaplayer.h:2133
const QgsCoordinateReferenceSystem & crs
Single variable definition for use within a QgsExpressionContextScope.
Contains details of a particular export layer relating to a layout item.
QPainter::CompositionMode compositionMode
Associated composition mode if this layer is associated with a map layer.
QString mapLayerId
Associated map layer ID, or an empty string if this export layer is not associated with a map layer.
double opacity
Associated opacity, if this layer is associated with a map layer.
QString name
User-friendly name for the export layer.
QString mapTheme
Associated map theme, or an empty string if this export layer does not need to be associated with a m...
Contains information relating to a node (i.e.
QString source
Weak reference to layer public source.
QString name
Weak reference to layer name.
TYPE * resolve(const QgsProject *project)
Resolves the map layer by attempting to find a layer with matching ID within a project.
QString provider
Weak reference to layer provider.
TYPE * resolveWeakly(const QgsProject *project, MatchType matchType=MatchType::All)
Resolves the map layer by attempting to find a matching layer in a project using a weak match.
QString layerId
Original layer ID.