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