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