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