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