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