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