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