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