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