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