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