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