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