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