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