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