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