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