QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgslayoutitemelevationprofile.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutitemelevationprofile.cpp
3 ---------------------------------
4 begin : January 2023
5 copyright : (C) 2023 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
20#include "qgsplot.h"
21#include "qgslayout.h"
22#include "qgsmessagelog.h"
24#include "qgscurve.h"
25#include "qgsprofilerequest.h"
27#include "qgsterrainprovider.h"
28#include "qgsprofilerenderer.h"
29#include "qgslayoututils.h"
30#include "qgsvectorlayer.h"
33
34#include <QTimer>
35
36#define CACHE_SIZE_LIMIT 5000
37
39class QgsLayoutItemElevationProfilePlot : public Qgs2DPlot
40{
41 public:
42
43 QgsLayoutItemElevationProfilePlot()
44 {
45 }
46
47 void setRenderer( QgsProfilePlotRenderer *renderer )
48 {
49 // cppcheck-suppress danglingLifetime
50 mRenderer = renderer;
51 }
52
53 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
54 {
55 if ( mRenderer )
56 {
57 rc.painter()->translate( plotArea.left(), plotArea.top() );
58 mRenderer->render( rc, plotArea.width(), plotArea.height(), xMinimum() * xScale, xMaximum() * xScale, yMinimum(), yMaximum() );
59 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
60 }
61 }
62
63 double xScale = 1;
64
65 private:
66
67 QgsProfilePlotRenderer *mRenderer = nullptr;
68
69};
71
73 : QgsLayoutItem( layout )
74 , mPlot( std::make_unique< QgsLayoutItemElevationProfilePlot >() )
75{
76 mBackgroundUpdateTimer = new QTimer( this );
77 mBackgroundUpdateTimer->setSingleShot( true );
78 connect( mBackgroundUpdateTimer, &QTimer::timeout, this, &QgsLayoutItemElevationProfile::recreateCachedImageInBackground );
79
80 setCacheMode( QGraphicsItem::NoCache );
81
82 if ( mLayout )
83 {
85 }
86
87 connect( this, &QgsLayoutItem::sizePositionChanged, this, [this]
88 {
90 } );
91
92 //default to no background
93 setBackgroundEnabled( false );
94}
95
97{
98 if ( mRenderJob )
99 {
100 disconnect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
102 mRenderJob->cancelGeneration(); // blocks
103 mPainter->end();
104 }
105}
106
108{
110}
111
113{
115}
116
118{
119 return QgsApplication::getThemeIcon( QStringLiteral( "mLayoutItemElevationProfile.svg" ) );
120}
121
123{
125
126 bool forceUpdate = false;
127
130 {
131 double value = mTolerance;
132
133 bool ok = false;
135
136 if ( !ok )
137 {
138 QgsMessageLog::logMessage( tr( "Elevation profile tolerance expression eval error" ) );
139 }
140 else
141 {
142 mTolerance = value;
143 }
144
145 forceUpdate = true;
146 }
147
150 {
151 double value = mPlot->xMinimum();
152
153 bool ok = false;
155
156 if ( !ok )
157 {
158 QgsMessageLog::logMessage( tr( "Elevation profile minimum distance expression eval error" ) );
159 }
160 else
161 {
162 mPlot->setXMinimum( value );
163 }
164
165 forceUpdate = true;
166 }
167
170 {
171 double value = mPlot->xMaximum();
172
173 bool ok = false;
175
176 if ( !ok )
177 {
178 QgsMessageLog::logMessage( tr( "Elevation profile maximum distance expression eval error" ) );
179 }
180 else
181 {
182 mPlot->setXMaximum( value );
183 }
184
185 forceUpdate = true;
186 }
187
190 {
191 double value = mPlot->yMinimum();
192
193 bool ok = false;
195
196 if ( !ok )
197 {
198 QgsMessageLog::logMessage( tr( "Elevation profile minimum elevation expression eval error" ) );
199 }
200 else
201 {
202 mPlot->setYMinimum( value );
203 }
204
205 forceUpdate = true;
206 }
207
210 {
211 double value = mPlot->yMaximum();
212
213 bool ok = false;
215
216 if ( !ok )
217 {
218 QgsMessageLog::logMessage( tr( "Elevation profile maximum elevation expression eval error" ) );
219 }
220 else
221 {
222 mPlot->setYMaximum( value );
223 }
224
225 forceUpdate = true;
226 }
227
230 {
231 double value = mPlot->xAxis().gridIntervalMajor();
232
233 bool ok = false;
235
236 if ( !ok )
237 {
238 QgsMessageLog::logMessage( tr( "Elevation profile distance axis major interval expression eval error" ) );
239 }
240 else
241 {
242 mPlot->xAxis().setGridIntervalMajor( value );
243 }
244
245 forceUpdate = true;
246 }
247
250 {
251 double value = mPlot->xAxis().gridIntervalMinor();
252
253 bool ok = false;
255
256 if ( !ok )
257 {
258 QgsMessageLog::logMessage( tr( "Elevation profile distance axis minor interval expression eval error" ) );
259 }
260 else
261 {
262 mPlot->xAxis().setGridIntervalMinor( value );
263 }
264
265 forceUpdate = true;
266 }
267
270 {
271 double value = mPlot->xAxis().labelInterval();
272
273 bool ok = false;
275
276 if ( !ok )
277 {
278 QgsMessageLog::logMessage( tr( "Elevation profile distance axis label interval expression eval error" ) );
279 }
280 else
281 {
282 mPlot->xAxis().setLabelInterval( value );
283 }
284
285 forceUpdate = true;
286 }
287
290 {
291 double value = mPlot->yAxis().gridIntervalMajor();
292
293 bool ok = false;
295
296 if ( !ok )
297 {
298 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis major interval expression eval error" ) );
299 }
300 else
301 {
302 mPlot->yAxis().setGridIntervalMajor( value );
303 }
304
305 forceUpdate = true;
306 }
307
310 {
311 double value = mPlot->yAxis().gridIntervalMinor();
312
313 bool ok = false;
315
316 if ( !ok )
317 {
318 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis minor interval expression eval error" ) );
319 }
320 else
321 {
322 mPlot->yAxis().setGridIntervalMinor( value );
323 }
324
325 forceUpdate = true;
326 }
327
330 {
331 double value = mPlot->yAxis().labelInterval();
332
333 bool ok = false;
335
336 if ( !ok )
337 {
338 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis label interval expression eval error" ) );
339 }
340 else
341 {
342 mPlot->yAxis().setLabelInterval( value );
343 }
344
345 forceUpdate = true;
346 }
347
350 {
351 double value = mPlot->margins().left();
352
353 bool ok = false;
355
356 if ( !ok )
357 {
358 QgsMessageLog::logMessage( tr( "Elevation profile left margin expression eval error" ) );
359 }
360 else
361 {
362 QgsMargins margins = mPlot->margins();
363 margins.setLeft( value );
364 mPlot->setMargins( margins );
365 }
366
367 forceUpdate = true;
368 }
369
372 {
373 double value = mPlot->margins().right();
374
375 bool ok = false;
377
378 if ( !ok )
379 {
380 QgsMessageLog::logMessage( tr( "Elevation profile right margin expression eval error" ) );
381 }
382 else
383 {
384 QgsMargins margins = mPlot->margins();
385 margins.setRight( value );
386 mPlot->setMargins( margins );
387 }
388
389 forceUpdate = true;
390 }
391
394 {
395 double value = mPlot->margins().top();
396
397 bool ok = false;
399
400 if ( !ok )
401 {
402 QgsMessageLog::logMessage( tr( "Elevation profile top margin expression eval error" ) );
403 }
404 else
405 {
406 QgsMargins margins = mPlot->margins();
407 margins.setTop( value );
408 mPlot->setMargins( margins );
409 }
410
411 forceUpdate = true;
412 }
413
416 {
417 double value = mPlot->margins().bottom();
418
419 bool ok = false;
421
422 if ( !ok )
423 {
424 QgsMessageLog::logMessage( tr( "Elevation profile bottom margin expression eval error" ) );
425 }
426 else
427 {
428 QgsMargins margins = mPlot->margins();
429 margins.setBottom( value );
430 mPlot->setMargins( margins );
431 }
432
433 forceUpdate = true;
434 }
435
436 if ( forceUpdate )
437 {
438 mCacheInvalidated = true;
439
441 update();
442 }
443
445}
446
448{
450}
451
453{
454 return blendMode() != QPainter::CompositionMode_SourceOver;
455}
456
458{
459 return mEvaluatedOpacity < 1.0;
460}
461
463{
464 return mPlot.get();
465}
466
468{
469 return mPlot.get();
470}
471
472QList<QgsMapLayer *> QgsLayoutItemElevationProfile::layers() const
473{
474 return _qgis_listRefToRaw( mLayers );
475}
476
477void QgsLayoutItemElevationProfile::setLayers( const QList<QgsMapLayer *> &layers )
478{
479 if ( layers == _qgis_listRefToRaw( mLayers ) )
480 return;
481
482 mLayers = _qgis_listRawToRef( layers );
484}
485
487{
488 mCurve.reset( curve );
490}
491
493{
494 return mCurve.get();
495}
496
498{
499 if ( mCrs == crs )
500 return;
501
502 mCrs = crs;
504}
505
507{
508 return mCrs;
509}
510
512{
513 if ( mTolerance == tolerance )
514 return;
515
516 mTolerance = tolerance;
518}
519
521{
522 return mTolerance;
523}
524
526{
527 mAtlasDriven = enabled;
528}
529
531{
532 QgsProfileRequest req( mCurve ? mCurve.get()->clone() : nullptr );
533
534 req.setCrs( mCrs );
535 req.setTolerance( mTolerance );
537 if ( mLayout )
538 {
539 if ( QgsProject *project = mLayout->project() )
540 {
541 req.setTransformContext( project->transformContext() );
542 if ( QgsAbstractTerrainProvider *provider = project->elevationProperties()->terrainProvider() )
543 {
544 req.setTerrainProvider( provider->clone() );
545 }
546 }
547 }
548 return req;
549}
550
551void QgsLayoutItemElevationProfile::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * )
552{
553 if ( !mLayout || !painter || !painter->device() || !mUpdatesEnabled )
554 {
555 return;
556 }
557 if ( !shouldDrawItem() )
558 {
559 return;
560 }
561
562 QRectF thisPaintRect = rect();
563 if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
564 return;
565
566 if ( mLayout->renderContext().isPreviewRender() )
567 {
570
571 QgsScopedQPainterState painterState( painter );
572 painter->setClipRect( thisPaintRect );
573 if ( !mCacheFinalImage || mCacheFinalImage->isNull() )
574 {
575 // No initial render available - so draw some preview text alerting user
576 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
577 painter->drawRect( thisPaintRect );
578 painter->setBrush( Qt::NoBrush );
579 QFont messageFont;
580 messageFont.setPointSize( 12 );
581 painter->setFont( messageFont );
582 painter->setPen( QColor( 255, 255, 255, 255 ) );
583 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering profile" ) );
584
585 if (
586 ( mRenderJob && mCacheInvalidated && !mDrawingPreview ) // current job was invalidated - start a new one
587 ||
588 ( !mRenderJob && !mDrawingPreview ) // this is the profiles's very first paint - trigger a cache update
589 )
590 {
591
592 mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
593 mBackgroundUpdateTimer->start( 1 );
594 }
595 }
596 else
597 {
598 if ( mCacheInvalidated && !mDrawingPreview )
599 {
600 // cache was invalidated - trigger a background update
601 mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
602 mBackgroundUpdateTimer->start( 1 );
603 }
604
605 //Background color is already included in cached image, so no need to draw
606
607 double imagePixelWidth = mCacheFinalImage->width(); //how many pixels of the image are for the map extent?
608 double scale = rect().width() / imagePixelWidth;
609
610 QgsScopedQPainterState rotatedPainterState( painter );
611
612 painter->scale( scale, scale );
613 painter->setCompositionMode( blendModeForRender() );
614 painter->drawImage( 0, 0, *mCacheFinalImage );
615 }
616
617 painter->setClipRect( thisPaintRect, Qt::NoClip );
618
619 if ( frameEnabled() )
620 {
622 }
623 }
624 else
625 {
626 if ( mDrawing )
627 return;
628
629 mDrawing = true;
630 QPaintDevice *paintDevice = painter->device();
631 if ( !paintDevice )
632 return;
633
634 QSizeF layoutSize = mLayout->convertToLayoutUnits( sizeWithUnits() );
635
636 if ( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering )
637 painter->setRenderHint( QPainter::LosslessImageRendering, true );
638
639 mPlot->xScale = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
640
641 if ( !qgsDoubleNear( layoutSize.width(), 0.0 ) && !qgsDoubleNear( layoutSize.height(), 0.0 ) )
642 {
643 if ( ( containsAdvancedEffects() || ( blendModeForRender() != QPainter::CompositionMode_SourceOver ) )
644 && ( !( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagForceVectorOutput ) ) )
645 {
646 // rasterize
647 double destinationDpi = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter ) * 25.4;
648 double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, Qgis::LayoutUnit::Inches ).length() : 1;
649 int widthInPixels = static_cast< int >( std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi ) );
650 int heightInPixels = static_cast< int >( std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi ) );
651 QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
652
653 image.fill( Qt::transparent );
654 image.setDotsPerMeterX( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
655 image.setDotsPerMeterY( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
656 double dotsPerMM = destinationDpi / 25.4;
657 layoutSize *= dotsPerMM; // output size will be in dots (pixels)
658 QPainter p( &image );
659 preparePainter( &p );
660
663
664 p.scale( dotsPerMM, dotsPerMM );
665 if ( hasBackground() )
666 {
668 }
669
670 p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
671
672 const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale / layoutSize.width();
673 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
674
675 QList< QgsAbstractProfileSource * > sources;
676 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
677 {
678 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
679 sources.append( source );
680 }
681
682 QgsProfilePlotRenderer renderer( sources, profileRequest() );
683
684 renderer.generateSynchronously();
685 mPlot->setRenderer( &renderer );
686
687 // size must be in pixels, not layout units
688 mPlot->setSize( layoutSize );
689
690 mPlot->render( rc );
691
692 mPlot->setRenderer( nullptr );
693
694 p.scale( dotsPerMM, dotsPerMM );
695
696 if ( frameEnabled() )
697 {
699 }
700
701 QgsScopedQPainterState painterState( painter );
702 painter->setCompositionMode( blendModeForRender() );
703 painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
704 painter->drawImage( 0, 0, image );
705 painter->scale( dotsPerMM, dotsPerMM );
706 }
707 else
708 {
711
712 // Fill with background color
713 if ( hasBackground() )
714 {
716 }
717
718 QgsScopedQPainterState painterState( painter );
719 QgsScopedQPainterState stagedPainterState( painter );
720 double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
721 layoutSize *= dotsPerMM; // output size will be in dots (pixels)
722 painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
723
724 const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale / layoutSize.width();
725 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
726
727 QList< QgsAbstractProfileSource * > sources;
728 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
729 {
730 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
731 sources.append( source );
732 }
733
734 QgsProfilePlotRenderer renderer( sources, profileRequest() );
735
736
737 // TODO
738 // we should be able to call renderer.start()/renderer.waitForFinished() here and
739 // benefit from parallel source generation. BUT
740 // for some reason the QtConcurrent::map call in start() never triggers
741 // the actual background thread execution.
742 // So for now just generate the results one by one
743 renderer.generateSynchronously();
744 mPlot->setRenderer( &renderer );
745
746 // size must be in pixels, not layout units
747 mPlot->setSize( layoutSize );
748
749 mPlot->render( rc );
750
751 mPlot->setRenderer( nullptr );
752
753 painter->setClipRect( thisPaintRect, Qt::NoClip );
754
755 if ( frameEnabled() )
756 {
758 }
759 }
760 }
761
762 mDrawing = false;
763 }
764}
765
767{
768 if ( mAtlasDriven && mLayout && mLayout->reportContext().layer() )
769 {
770 if ( QgsVectorLayer *layer = mLayout->reportContext().layer() )
771 {
772 mCrs = layer->crs();
773 }
774 const QgsGeometry curveGeom( mLayout->reportContext().currentGeometry( mCrs ) );
775 if ( const QgsAbstractGeometry *geom = curveGeom.constGet() )
776 {
777 if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( geom->simplifiedTypeRef() ) )
778 {
779 mCurve.reset( curve->clone() );
780 }
781 }
782 }
785}
786
788{
789 if ( mDrawing )
790 return;
791
792 mCacheInvalidated = true;
793 update();
794}
795
797{
798}
799
800bool QgsLayoutItemElevationProfile::writePropertiesToElement( QDomElement &layoutProfileElem, QDomDocument &doc, const QgsReadWriteContext &rwContext ) const
801{
802 {
803 QDomElement plotElement = doc.createElement( QStringLiteral( "plot" ) );
804 mPlot->writeXml( plotElement, doc, rwContext );
805 layoutProfileElem.appendChild( plotElement );
806 }
807
808 layoutProfileElem.setAttribute( QStringLiteral( "distanceUnit" ), qgsEnumValueToKey( mDistanceUnit ) );
809
810 layoutProfileElem.setAttribute( QStringLiteral( "tolerance" ), mTolerance );
811 layoutProfileElem.setAttribute( QStringLiteral( "atlasDriven" ), mAtlasDriven ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
812 if ( mCrs.isValid() )
813 {
814 QDomElement crsElem = doc.createElement( QStringLiteral( "crs" ) );
815 mCrs.writeXml( crsElem, doc );
816 layoutProfileElem.appendChild( crsElem );
817 }
818 if ( mCurve )
819 {
820 QDomElement curveElem = doc.createElement( QStringLiteral( "curve" ) );
821 curveElem.appendChild( doc.createTextNode( mCurve->asWkt( ) ) );
822 layoutProfileElem.appendChild( curveElem );
823 }
824
825 {
826 QDomElement layersElement = doc.createElement( QStringLiteral( "layers" ) );
827 for ( const QgsMapLayerRef &layer : mLayers )
828 {
829 QDomElement layerElement = doc.createElement( QStringLiteral( "layer" ) );
830 layer.writeXml( layerElement, rwContext );
831 layersElement.appendChild( layerElement );
832 }
833 layoutProfileElem.appendChild( layersElement );
834 }
835
836 return true;
837}
838
839bool QgsLayoutItemElevationProfile::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
840{
841 const QDomElement plotElement = itemElem.firstChildElement( QStringLiteral( "plot" ) );
842 if ( !plotElement.isNull() )
843 {
844 mPlot->readXml( plotElement, context );
845 }
846
847 const QDomNodeList crsNodeList = itemElem.elementsByTagName( QStringLiteral( "crs" ) );
849 if ( !crsNodeList.isEmpty() )
850 {
851 const QDomElement crsElem = crsNodeList.at( 0 ).toElement();
852 crs.readXml( crsElem );
853 }
854 mCrs = crs;
855
856 setDistanceUnit( qgsEnumKeyToValue( itemElem.attribute( QStringLiteral( "distanceUnit" ) ), mCrs.mapUnits() ) );
857
858 const QDomNodeList curveNodeList = itemElem.elementsByTagName( QStringLiteral( "curve" ) );
859 if ( !curveNodeList.isEmpty() )
860 {
861 const QDomElement curveElem = curveNodeList.at( 0 ).toElement();
862 const QgsGeometry curve = QgsGeometry::fromWkt( curveElem.text() );
863 if ( const QgsCurve *curveGeom = qgsgeometry_cast< const QgsCurve * >( curve.constGet() ) )
864 {
865 mCurve.reset( curveGeom->clone() );
866 }
867 else
868 {
869 mCurve.reset();
870 }
871 }
872
873 mTolerance = itemElem.attribute( QStringLiteral( "tolerance" ) ).toDouble();
874 mAtlasDriven = static_cast< bool >( itemElem.attribute( QStringLiteral( "atlasDriven" ), QStringLiteral( "0" ) ).toInt() );
875
876 {
877 mLayers.clear();
878 const QDomElement layersElement = itemElem.firstChildElement( QStringLiteral( "layers" ) );
879 QDomElement layerElement = layersElement.firstChildElement( QStringLiteral( "layer" ) );
880 while ( !layerElement.isNull() )
881 {
882 QgsMapLayerRef ref;
883 ref.readXml( layerElement, context );
884 ref.resolveWeakly( mLayout->project() );
885 mLayers.append( ref );
886
887 layerElement = layerElement.nextSiblingElement( QStringLiteral( "layer" ) );
888 }
889 }
890
891 return true;
892}
893
894void QgsLayoutItemElevationProfile::recreateCachedImageInBackground()
895{
896 if ( mRenderJob )
897 {
898 disconnect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
899 QgsProfilePlotRenderer *oldJob = mRenderJob.release();
900 QPainter *oldPainter = mPainter.release();
901 QImage *oldImage = mCacheRenderingImage.release();
902 connect( oldJob, &QgsProfilePlotRenderer::generationFinished, this, [oldPainter, oldJob, oldImage]
903 {
904 oldJob->deleteLater();
905 delete oldPainter;
906 delete oldImage;
907 } );
909 }
910 else
911 {
912 mCacheRenderingImage.reset( nullptr );
914 }
915
916 Q_ASSERT( !mRenderJob );
917 Q_ASSERT( !mPainter );
918 Q_ASSERT( !mCacheRenderingImage );
919
920 const QSizeF layoutSize = mLayout->convertToLayoutUnits( sizeWithUnits() );
921 double widthLayoutUnits = layoutSize.width();
922 double heightLayoutUnits = layoutSize.height();
923
924 int w = static_cast< int >( std::round( widthLayoutUnits * mPreviewScaleFactor ) );
925 int h = static_cast< int >( std::round( heightLayoutUnits * mPreviewScaleFactor ) );
926
927 // limit size of image for better performance
928 if ( w > 5000 || h > 5000 )
929 {
930 if ( w > h )
931 {
932 w = 5000;
933 h = static_cast< int>( std::round( w * heightLayoutUnits / widthLayoutUnits ) );
934 }
935 else
936 {
937 h = 5000;
938 w = static_cast< int >( std::round( h * widthLayoutUnits / heightLayoutUnits ) );
939 }
940 }
941
942 if ( w <= 0 || h <= 0 )
943 return;
944
945 mCacheRenderingImage.reset( new QImage( w, h, QImage::Format_ARGB32 ) );
946
947 // set DPI of the image
948 mCacheRenderingImage->setDotsPerMeterX( static_cast< int >( std::round( 1000 * w / widthLayoutUnits ) ) );
949 mCacheRenderingImage->setDotsPerMeterY( static_cast< int >( std::round( 1000 * h / heightLayoutUnits ) ) );
950
951 //start with empty fill to avoid artifacts
952 mCacheRenderingImage->fill( Qt::transparent );
953 if ( hasBackground() )
954 {
955 //Initially fill image with specified background color
956 mCacheRenderingImage->fill( backgroundColor().rgba() );
957 }
958
959 mCacheInvalidated = false;
960 mPainter.reset( new QPainter( mCacheRenderingImage.get() ) );
961
962 QList< QgsAbstractProfileSource * > sources;
963 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
964 {
965 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
966 sources.append( source );
967 }
968
969 mRenderJob = std::make_unique< QgsProfilePlotRenderer >( sources, profileRequest() );
970 connect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
971 mRenderJob->startGeneration();
972
973 mDrawingPreview = false;
974}
975
976void QgsLayoutItemElevationProfile::profileGenerationFinished()
977{
978 mPlot->setRenderer( mRenderJob.get() );
979
981
982 mPlot->xScale = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
983
984 const double mapUnitsPerPixel = static_cast< double >( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale /
985 mCacheRenderingImage->size().width();
986 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
987
988 // size must be in pixels, not layout units
989 mPlot->setSize( mCacheRenderingImage->size() );
990
991 mPlot->render( rc );
992
993 mPlot->setRenderer( nullptr );
994
995 mPainter->end();
996 mRenderJob.reset( nullptr );
997 mPainter.reset( nullptr );
998 mCacheFinalImage = std::move( mCacheRenderingImage );
1000 update();
1001 emit previewRefreshed();
1002}
1003
1005{
1006 return mDistanceUnit;
1007}
1008
1010{
1011 mDistanceUnit = unit;
1012
1013 switch ( mDistanceUnit )
1014 {
1024 mPlot->xAxis().setLabelSuffix( QStringLiteral( " %1" ).arg( QgsUnitTypes::toAbbreviatedString( mDistanceUnit ) ) );
1025 break;
1026
1028 mPlot->xAxis().setLabelSuffix( QObject::tr( "°" ) );
1029 break;
1030
1032 mPlot->xAxis().setLabelSuffix( QString() );
1033 break;
1034 }
1035}
1036
DistanceUnit
Units of distance.
Definition: qgis.h:4124
@ Feet
Imperial feet.
@ Centimeters
Centimeters.
@ Millimeters
Millimeters.
@ Miles
Terrestrial miles.
@ Unknown
Unknown distance unit.
@ Yards
Imperial yards.
@ Degrees
Degrees, for planar geographic CRS distance measurements.
@ Inches
Inches (since QGIS 3.32)
@ NauticalMiles
Nautical miles.
@ Kilometers
Kilometers.
Base class for 2-dimensional plot/chart/graphs.
Definition: qgsplot.h:278
double yMaximum() const
Returns the maximum value of the y axis.
Definition: qgsplot.h:390
double xMaximum() const
Returns the maximum value of the x axis.
Definition: qgsplot.h:376
double yMinimum() const
Returns the minimum value of the y axis.
Definition: qgsplot.h:362
virtual void renderContent(QgsRenderContext &context, const QRectF &plotArea)
Renders the plot content.
Definition: qgsplot.cpp:479
Abstract base class for all geometries.
Interface for classes which can generate elevation profiles.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
Abstract base class for terrain providers.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
Q_GADGET Qgis::DistanceUnit mapUnits
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
Abstract base class for curved geometry type.
Definition: qgscurve.h:35
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
A layout item subclass for elevation profile plots.
static QgsLayoutItemElevationProfile * create(QgsLayout *layout)
Returns a new elevation profile item for the specified layout.
QgsCurve * profileCurve() const
Returns the cross section profile curve, which represents the line along which the profile should be ...
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::DataDefinedProperty::AllProperties) override
Refreshes a data defined property for the item by reevaluating the property's value and redrawing the...
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of map layers participating in the elevation profile.
QList< QgsMapLayer * > layers() const
Returns the list of map layers participating in the elevation profile.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the desired Coordinate Reference System (crs) for the profile.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
QgsLayoutItem::Flags itemFlags() const override
Returns the item's flags, which indicate how the item behaves.
void setDistanceUnit(Qgis::DistanceUnit unit)
Sets the unit for the distance axis.
Qgs2DPlot * plot()
Returns a reference to the elevation plot object, which can be used to set plot appearance and proper...
void setTolerance(double tolerance)
Sets the tolerance of the request (in crs() units).
QgsCoordinateReferenceSystem crs() const
Returns the desired Coordinate Reference System for the profile.
void setAtlasDriven(bool enabled)
Sets whether the profile curve will follow the current atlas feature.
double tolerance() const
Returns the tolerance of the request (in crs() units).
Qgis::DistanceUnit distanceUnit() const
Returns the units for the distance axis.
bool requiresRasterization() const override
Returns true if the item is drawn in such a way that forces the whole layout to be rasterized when ex...
bool containsAdvancedEffects() const override
Returns true if the item contains contents with blend modes or transparency effects which can only be...
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
QIcon icon() const override
Returns the item's icon.
void previewRefreshed()
Emitted whenever the item's preview has been refreshed.
void setProfileCurve(QgsCurve *curve)
Sets the cross section profile curve, which represents the line along which the profile should be gen...
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
QgsProfileRequest profileRequest() const
Returns the profile request used to generate the elevation profile.
@ LayoutElevationProfile
Elevation profile item (since QGIS 3.30)
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:43
Base class for graphical items within a QgsLayout.
virtual void drawFrame(QgsRenderContext &context)
Draws the frame around the item.
QColor backgroundColor(bool useDataDefined=true) const
Returns the background color for this item.
virtual void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::DataDefinedProperty::AllProperties)
Refreshes a data defined property for the item by reevaluating the property's value and redrawing the...
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
void refreshItemSize()
Refreshes an item's size by rechecking it against any possible item fixed or minimum sizes.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
virtual void drawBackground(QgsRenderContext &context)
Draws the background for the item.
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
@ FlagOverridesPaint
Item overrides the default layout item painting method.
@ FlagDisableSceneCaching
Item should not have QGraphicsItem caching enabled.
void sizePositionChanged()
Emitted when the item's size or position changes.
bool frameEnabled() const
Returns true if the item includes a frame.
bool hasBackground() const
Returns true if the item has a background.
QFlags< Flag > Flags
void refresh() override
Refreshes the item, causing a recalculation of any property overrides and recalculation of its positi...
friend class QgsLayoutItemElevationProfile
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
QPainter::CompositionMode blendMode() const
Returns the item's composition blending mode.
void backgroundTaskCountChanged(int count)
Emitted whenever the number of background tasks an item is executing changes.
QgsPropertyCollection mDataDefinedProperties
const QgsLayout * layout() const
Returns the layout the object is attached to.
QPointer< QgsLayout > mLayout
DataDefinedProperty
Data defined properties for different item types.
@ ElevationProfileMaximumDistance
Maximum distance value for elevation profile (since QGIS 3.30)
@ MarginBottom
Bottom margin (since QGIS 3.30)
@ ElevationProfileElevationMinorInterval
Minor grid line interval for elevation profile elevation axis (since QGIS 3.30)
@ ElevationProfileDistanceMinorInterval
Minor grid line interval for elevation profile distance axis (since QGIS 3.30)
@ ElevationProfileMinimumDistance
Minimum distance value for elevation profile (since QGIS 3.30)
@ ElevationProfileMaximumElevation
Maximum elevation value for elevation profile (since QGIS 3.30)
@ ElevationProfileDistanceLabelInterval
Label interval for elevation profile distance axis (since QGIS 3.30)
@ ElevationProfileTolerance
Tolerance distance for elevation profiles (since QGIS 3.30)
@ ElevationProfileMinimumElevation
Minimum elevation value for elevation profile (since QGIS 3.30)
@ MarginLeft
Left margin (since QGIS 3.30)
@ MarginRight
Right margin (since QGIS 3.30)
@ ElevationProfileElevationLabelInterval
Label interval for elevation profile elevation axis (since QGIS 3.30)
@ ElevationProfileDistanceMajorInterval
Major grid line interval for elevation profile distance axis (since QGIS 3.30)
@ ElevationProfileElevationMajorInterval
Major grid line interval for elevation profile elevation axis (since QGIS 3.30)
@ AllProperties
All properties for item.
@ MarginTop
Top margin (since QGIS 3.30)
@ FlagLosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ FlagForceVectorOutput
Force output in vector format where possible, even if items require rasterization to keep their corre...
static QgsRenderContext createRenderContextForLayout(QgsLayout *layout, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout and painter destination.
static Q_DECL_DEPRECATED double scaleFactorFromItemStyle(const QStyleOptionGraphicsItem *style)
Extracts the scale factor from an item style.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void refreshed()
Emitted when the layout has been refreshed and items should also be refreshed and updated.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
The QgsMargins class defines the four margins of a rectangle.
Definition: qgsmargins.h:37
void setBottom(double bottom)
Sets the bottom margin to bottom.
Definition: qgsmargins.h:113
void setLeft(double left)
Sets the left margin to left.
Definition: qgsmargins.h:95
void setRight(double right)
Sets the right margin to right.
Definition: qgsmargins.h:107
void setTop(double top)
Sets the top margin to top.
Definition: qgsmargins.h:101
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Generates and renders elevation profile plots.
void cancelGenerationWithoutBlocking()
Triggers cancellation of the generation job without blocking.
void generateSynchronously()
Generate the profile results synchronously in this thread.
void generationFinished()
Emitted when the profile generation is finished (or canceled).
Encapsulates properties and constraints relating to fetching elevation profiles from different source...
QgsProfileRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate expressions.
QgsProfileRequest & setTransformContext(const QgsCoordinateTransformContext &context)
Sets the transform context, for use when transforming coordinates from a source to the request's crs(...
QgsProfileRequest & setTerrainProvider(QgsAbstractTerrainProvider *provider)
Sets the terrain provider.
QgsProfileRequest & setTolerance(double tolerance)
Sets the tolerance of the request (in crs() units).
QgsProfileRequest & setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the desired Coordinate Reference System (crs) for the profile.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:107
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
The class is used as a container of context for various read/write operations on other objects.
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
Scoped object for saving and restoring a QPainter object's state.
static Q_INVOKABLE double fromUnitToUnitFactor(Qgis::DistanceUnit fromUnit, Qgis::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
static Q_INVOKABLE QString toAbbreviatedString(Qgis::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
Represents a vector layer which manages a vector based data sets.
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition: qgis.h:5417
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition: qgis.h:5398
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207
const QgsCoordinateReferenceSystem & crs
TYPE * resolveWeakly(const QgsProject *project, MatchType matchType=MatchType::All)
Resolves the map layer by attempting to find a matching layer in a project using a weak match.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the layer's properties from an XML element.