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