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