QGIS API Documentation 3.39.0-Master (47f7b3a4989)
Loading...
Searching...
No Matches
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"
34
35#include <QTimer>
36
37#define CACHE_SIZE_LIMIT 5000
38
40class QgsLayoutItemElevationProfilePlot : public Qgs2DPlot
41{
42 public:
43
44 QgsLayoutItemElevationProfilePlot()
45 {
46 }
47
48 void setRenderer( QgsProfilePlotRenderer *renderer )
49 {
50 // cppcheck-suppress danglingLifetime
51 mRenderer = renderer;
52 }
53
54 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
55 {
56 if ( mRenderer )
57 {
58 rc.painter()->translate( plotArea.left(), plotArea.top() );
59 mRenderer->render( rc, plotArea.width(), plotArea.height(), xMinimum() * xScale, xMaximum() * xScale, yMinimum(), yMaximum() );
60 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
61 }
62 }
63
64 double xScale = 1;
65
66 private:
67
68 QgsProfilePlotRenderer *mRenderer = nullptr;
69
70};
72
74 : QgsLayoutItem( layout )
75 , mPlot( std::make_unique< QgsLayoutItemElevationProfilePlot >() )
76{
77 mBackgroundUpdateTimer = new QTimer( this );
78 mBackgroundUpdateTimer->setSingleShot( true );
79 connect( mBackgroundUpdateTimer, &QTimer::timeout, this, &QgsLayoutItemElevationProfile::recreateCachedImageInBackground );
80
81 setCacheMode( QGraphicsItem::NoCache );
82
83 if ( mLayout )
84 {
86 }
87
88 connect( this, &QgsLayoutItem::sizePositionChanged, this, [this]
89 {
91 } );
92
93 //default to no background
94 setBackgroundEnabled( false );
95}
96
98{
99 if ( mRenderJob )
100 {
101 disconnect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
103 mRenderJob->cancelGeneration(); // blocks
104 mPainter->end();
105 }
106}
107
112
117
119{
120 return QgsApplication::getThemeIcon( QStringLiteral( "mLayoutItemElevationProfile.svg" ) );
121}
122
124{
126
127 bool forceUpdate = false;
128
131 {
132 double value = mTolerance;
133
134 bool ok = false;
136
137 if ( !ok )
138 {
139 QgsMessageLog::logMessage( tr( "Elevation profile tolerance expression eval error" ) );
140 }
141 else
142 {
143 mTolerance = value;
144 }
145
146 forceUpdate = true;
147 }
148
151 {
152 double value = mPlot->xMinimum();
153
154 bool ok = false;
156
157 if ( !ok )
158 {
159 QgsMessageLog::logMessage( tr( "Elevation profile minimum distance expression eval error" ) );
160 }
161 else
162 {
163 mPlot->setXMinimum( value );
164 }
165
166 forceUpdate = true;
167 }
168
171 {
172 double value = mPlot->xMaximum();
173
174 bool ok = false;
176
177 if ( !ok )
178 {
179 QgsMessageLog::logMessage( tr( "Elevation profile maximum distance expression eval error" ) );
180 }
181 else
182 {
183 mPlot->setXMaximum( value );
184 }
185
186 forceUpdate = true;
187 }
188
191 {
192 double value = mPlot->yMinimum();
193
194 bool ok = false;
196
197 if ( !ok )
198 {
199 QgsMessageLog::logMessage( tr( "Elevation profile minimum elevation expression eval error" ) );
200 }
201 else
202 {
203 mPlot->setYMinimum( value );
204 }
205
206 forceUpdate = true;
207 }
208
211 {
212 double value = mPlot->yMaximum();
213
214 bool ok = false;
216
217 if ( !ok )
218 {
219 QgsMessageLog::logMessage( tr( "Elevation profile maximum elevation expression eval error" ) );
220 }
221 else
222 {
223 mPlot->setYMaximum( value );
224 }
225
226 forceUpdate = true;
227 }
228
231 {
232 double value = mPlot->xAxis().gridIntervalMajor();
233
234 bool ok = false;
236
237 if ( !ok )
238 {
239 QgsMessageLog::logMessage( tr( "Elevation profile distance axis major interval expression eval error" ) );
240 }
241 else
242 {
243 mPlot->xAxis().setGridIntervalMajor( value );
244 }
245
246 forceUpdate = true;
247 }
248
251 {
252 double value = mPlot->xAxis().gridIntervalMinor();
253
254 bool ok = false;
256
257 if ( !ok )
258 {
259 QgsMessageLog::logMessage( tr( "Elevation profile distance axis minor interval expression eval error" ) );
260 }
261 else
262 {
263 mPlot->xAxis().setGridIntervalMinor( value );
264 }
265
266 forceUpdate = true;
267 }
268
271 {
272 double value = mPlot->xAxis().labelInterval();
273
274 bool ok = false;
276
277 if ( !ok )
278 {
279 QgsMessageLog::logMessage( tr( "Elevation profile distance axis label interval expression eval error" ) );
280 }
281 else
282 {
283 mPlot->xAxis().setLabelInterval( value );
284 }
285
286 forceUpdate = true;
287 }
288
291 {
292 double value = mPlot->yAxis().gridIntervalMajor();
293
294 bool ok = false;
296
297 if ( !ok )
298 {
299 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis major interval expression eval error" ) );
300 }
301 else
302 {
303 mPlot->yAxis().setGridIntervalMajor( value );
304 }
305
306 forceUpdate = true;
307 }
308
311 {
312 double value = mPlot->yAxis().gridIntervalMinor();
313
314 bool ok = false;
316
317 if ( !ok )
318 {
319 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis minor interval expression eval error" ) );
320 }
321 else
322 {
323 mPlot->yAxis().setGridIntervalMinor( value );
324 }
325
326 forceUpdate = true;
327 }
328
331 {
332 double value = mPlot->yAxis().labelInterval();
333
334 bool ok = false;
336
337 if ( !ok )
338 {
339 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis label interval expression eval error" ) );
340 }
341 else
342 {
343 mPlot->yAxis().setLabelInterval( value );
344 }
345
346 forceUpdate = true;
347 }
348
351 {
352 double value = mPlot->margins().left();
353
354 bool ok = false;
356
357 if ( !ok )
358 {
359 QgsMessageLog::logMessage( tr( "Elevation profile left margin expression eval error" ) );
360 }
361 else
362 {
363 QgsMargins margins = mPlot->margins();
364 margins.setLeft( value );
365 mPlot->setMargins( margins );
366 }
367
368 forceUpdate = true;
369 }
370
373 {
374 double value = mPlot->margins().right();
375
376 bool ok = false;
378
379 if ( !ok )
380 {
381 QgsMessageLog::logMessage( tr( "Elevation profile right margin expression eval error" ) );
382 }
383 else
384 {
385 QgsMargins margins = mPlot->margins();
386 margins.setRight( value );
387 mPlot->setMargins( margins );
388 }
389
390 forceUpdate = true;
391 }
392
395 {
396 double value = mPlot->margins().top();
397
398 bool ok = false;
400
401 if ( !ok )
402 {
403 QgsMessageLog::logMessage( tr( "Elevation profile top margin expression eval error" ) );
404 }
405 else
406 {
407 QgsMargins margins = mPlot->margins();
408 margins.setTop( value );
409 mPlot->setMargins( margins );
410 }
411
412 forceUpdate = true;
413 }
414
417 {
418 double value = mPlot->margins().bottom();
419
420 bool ok = false;
422
423 if ( !ok )
424 {
425 QgsMessageLog::logMessage( tr( "Elevation profile bottom margin expression eval error" ) );
426 }
427 else
428 {
429 QgsMargins margins = mPlot->margins();
430 margins.setBottom( value );
431 mPlot->setMargins( margins );
432 }
433
434 forceUpdate = true;
435 }
436
437 if ( forceUpdate )
438 {
439 mCacheInvalidated = true;
440
442 update();
443 }
444
446}
447
452
454{
455 return blendMode() != QPainter::CompositionMode_SourceOver;
456}
457
459{
460 return mEvaluatedOpacity < 1.0;
461}
462
464{
465 return mPlot.get();
466}
467
469{
470 return mPlot.get();
471}
472
473QList<QgsMapLayer *> QgsLayoutItemElevationProfile::layers() const
474{
475 return _qgis_listRefToRaw( mLayers );
476}
477
478void QgsLayoutItemElevationProfile::setLayers( const QList<QgsMapLayer *> &layers )
479{
480 if ( layers == _qgis_listRefToRaw( mLayers ) )
481 return;
482
483 mLayers = _qgis_listRawToRef( layers );
485}
486
488{
489 mCurve.reset( curve );
491}
492
494{
495 return mCurve.get();
496}
497
499{
500 if ( mCrs == crs )
501 return;
502
503 mCrs = crs;
505}
506
511
513{
514 if ( mTolerance == tolerance )
515 return;
516
517 mTolerance = tolerance;
519}
520
522{
523 return mTolerance;
524}
525
527{
528 mAtlasDriven = enabled;
529}
530
532{
533 QgsProfileRequest req( mCurve ? mCurve.get()->clone() : nullptr );
534
535 req.setCrs( mCrs );
536 req.setTolerance( mTolerance );
538 if ( mLayout )
539 {
540 if ( QgsProject *project = mLayout->project() )
541 {
542 req.setTransformContext( project->transformContext() );
543 if ( QgsAbstractTerrainProvider *provider = project->elevationProperties()->terrainProvider() )
544 {
545 req.setTerrainProvider( provider->clone() );
546 }
547 }
548 }
549 return req;
550}
551
552void QgsLayoutItemElevationProfile::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * )
553{
554 if ( !mLayout || !painter || !painter->device() || !mUpdatesEnabled )
555 {
556 return;
557 }
558 if ( !shouldDrawItem() )
559 {
560 return;
561 }
562
563 QRectF thisPaintRect = rect();
564 if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
565 return;
566
567 if ( mLayout->renderContext().isPreviewRender() )
568 {
571
572 QgsScopedQPainterState painterState( painter );
573 painter->setClipRect( thisPaintRect );
574 if ( !mCacheFinalImage || mCacheFinalImage->isNull() )
575 {
576 // No initial render available - so draw some preview text alerting user
577 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
578 painter->drawRect( thisPaintRect );
579 painter->setBrush( Qt::NoBrush );
580 QFont messageFont;
581 messageFont.setPointSize( 12 );
582 painter->setFont( messageFont );
583 painter->setPen( QColor( 255, 255, 255, 255 ) );
584 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering profile" ) );
585
586 if (
587 ( mRenderJob && mCacheInvalidated && !mDrawingPreview ) // current job was invalidated - start a new one
588 ||
589 ( !mRenderJob && !mDrawingPreview ) // this is the profiles's very first paint - trigger a cache update
590 )
591 {
592
593 mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
594 mBackgroundUpdateTimer->start( 1 );
595 }
596 }
597 else
598 {
599 if ( mCacheInvalidated && !mDrawingPreview )
600 {
601 // cache was invalidated - trigger a background update
602 mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
603 mBackgroundUpdateTimer->start( 1 );
604 }
605
606 //Background color is already included in cached image, so no need to draw
607
608 double imagePixelWidth = mCacheFinalImage->width(); //how many pixels of the image are for the map extent?
609 double scale = rect().width() / imagePixelWidth;
610
611 QgsScopedQPainterState rotatedPainterState( painter );
612
613 painter->scale( scale, scale );
614 painter->setCompositionMode( blendModeForRender() );
615 painter->drawImage( 0, 0, *mCacheFinalImage );
616 }
617
618 painter->setClipRect( thisPaintRect, Qt::NoClip );
619
620 if ( frameEnabled() )
621 {
623 }
624 }
625 else
626 {
627 if ( mDrawing )
628 return;
629
630 mDrawing = true;
631 QPaintDevice *paintDevice = painter->device();
632 if ( !paintDevice )
633 return;
634
635 QSizeF layoutSize = mLayout->convertToLayoutUnits( sizeWithUnits() );
636
637 if ( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering )
638 painter->setRenderHint( QPainter::LosslessImageRendering, true );
639
640 mPlot->xScale = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
641
642 if ( !qgsDoubleNear( layoutSize.width(), 0.0 ) && !qgsDoubleNear( layoutSize.height(), 0.0 ) )
643 {
644 if ( ( containsAdvancedEffects() || ( blendModeForRender() != QPainter::CompositionMode_SourceOver ) )
645 && ( !( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagForceVectorOutput ) ) )
646 {
647 // rasterize
648 double destinationDpi = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter ) * 25.4;
649 double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, Qgis::LayoutUnit::Inches ).length() : 1;
650 int widthInPixels = static_cast< int >( std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi ) );
651 int heightInPixels = static_cast< int >( std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi ) );
652 QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
653
654 image.fill( Qt::transparent );
655 image.setDotsPerMeterX( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
656 image.setDotsPerMeterY( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
657 double dotsPerMM = destinationDpi / 25.4;
658 layoutSize *= dotsPerMM; // output size will be in dots (pixels)
659 QPainter p( &image );
660 preparePainter( &p );
661
664
665 p.scale( dotsPerMM, dotsPerMM );
666 if ( hasBackground() )
667 {
669 }
670
671 p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
672
673 const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale / layoutSize.width();
674 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
675
676 QList< QgsAbstractProfileSource * > sources;
678 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
679 {
680 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
681 sources.append( source );
682 }
683
684 QgsProfilePlotRenderer renderer( sources, profileRequest() );
685
686 renderer.generateSynchronously();
687 mPlot->setRenderer( &renderer );
688
689 // size must be in pixels, not layout units
690 mPlot->setSize( layoutSize );
691
692 mPlot->render( rc );
693
694 mPlot->setRenderer( nullptr );
695
696 p.scale( dotsPerMM, dotsPerMM );
697
698 if ( frameEnabled() )
699 {
701 }
702
703 QgsScopedQPainterState painterState( painter );
704 painter->setCompositionMode( blendModeForRender() );
705 painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
706 painter->drawImage( 0, 0, image );
707 painter->scale( dotsPerMM, dotsPerMM );
708 }
709 else
710 {
713
714 // Fill with background color
715 if ( hasBackground() )
716 {
718 }
719
720 QgsScopedQPainterState painterState( painter );
721 QgsScopedQPainterState stagedPainterState( painter );
722 double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
723 layoutSize *= dotsPerMM; // output size will be in dots (pixels)
724 painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
725
726 const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale / layoutSize.width();
727 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
728
729 QList< QgsAbstractProfileSource * > sources;
731 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
732 {
733 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
734 sources.append( source );
735 }
736
737 QgsProfilePlotRenderer renderer( sources, profileRequest() );
738
739
740 // TODO
741 // we should be able to call renderer.start()/renderer.waitForFinished() here and
742 // benefit from parallel source generation. BUT
743 // for some reason the QtConcurrent::map call in start() never triggers
744 // the actual background thread execution.
745 // So for now just generate the results one by one
746 renderer.generateSynchronously();
747 mPlot->setRenderer( &renderer );
748
749 // size must be in pixels, not layout units
750 mPlot->setSize( layoutSize );
751
752 mPlot->render( rc );
753
754 mPlot->setRenderer( nullptr );
755
756 painter->setClipRect( thisPaintRect, Qt::NoClip );
757
758 if ( frameEnabled() )
759 {
761 }
762 }
763 }
764
765 mDrawing = false;
766 }
767}
768
770{
771 if ( mAtlasDriven && mLayout && mLayout->reportContext().layer() )
772 {
773 if ( QgsVectorLayer *layer = mLayout->reportContext().layer() )
774 {
775 mCrs = layer->crs();
776 }
777 const QgsGeometry curveGeom( mLayout->reportContext().currentGeometry( mCrs ) );
778 if ( const QgsAbstractGeometry *geom = curveGeom.constGet() )
779 {
780 if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( geom->simplifiedTypeRef() ) )
781 {
782 mCurve.reset( curve->clone() );
783 }
784 }
785 }
788}
789
791{
792 if ( mDrawing )
793 return;
794
795 mCacheInvalidated = true;
796 update();
797}
798
802
803bool QgsLayoutItemElevationProfile::writePropertiesToElement( QDomElement &layoutProfileElem, QDomDocument &doc, const QgsReadWriteContext &rwContext ) const
804{
805 {
806 QDomElement plotElement = doc.createElement( QStringLiteral( "plot" ) );
807 mPlot->writeXml( plotElement, doc, rwContext );
808 layoutProfileElem.appendChild( plotElement );
809 }
810
811 layoutProfileElem.setAttribute( QStringLiteral( "distanceUnit" ), qgsEnumValueToKey( mDistanceUnit ) );
812
813 layoutProfileElem.setAttribute( QStringLiteral( "tolerance" ), mTolerance );
814 layoutProfileElem.setAttribute( QStringLiteral( "atlasDriven" ), mAtlasDriven ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
815 if ( mCrs.isValid() )
816 {
817 QDomElement crsElem = doc.createElement( QStringLiteral( "crs" ) );
818 mCrs.writeXml( crsElem, doc );
819 layoutProfileElem.appendChild( crsElem );
820 }
821 if ( mCurve )
822 {
823 QDomElement curveElem = doc.createElement( QStringLiteral( "curve" ) );
824 curveElem.appendChild( doc.createTextNode( mCurve->asWkt( ) ) );
825 layoutProfileElem.appendChild( curveElem );
826 }
827
828 {
829 QDomElement layersElement = doc.createElement( QStringLiteral( "layers" ) );
830 for ( const QgsMapLayerRef &layer : mLayers )
831 {
832 QDomElement layerElement = doc.createElement( QStringLiteral( "layer" ) );
833 layer.writeXml( layerElement, rwContext );
834 layersElement.appendChild( layerElement );
835 }
836 layoutProfileElem.appendChild( layersElement );
837 }
838
839 return true;
840}
841
842bool QgsLayoutItemElevationProfile::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
843{
844 const QDomElement plotElement = itemElem.firstChildElement( QStringLiteral( "plot" ) );
845 if ( !plotElement.isNull() )
846 {
847 mPlot->readXml( plotElement, context );
848 }
849
850 const QDomNodeList crsNodeList = itemElem.elementsByTagName( QStringLiteral( "crs" ) );
852 if ( !crsNodeList.isEmpty() )
853 {
854 const QDomElement crsElem = crsNodeList.at( 0 ).toElement();
855 crs.readXml( crsElem );
856 }
857 mCrs = crs;
858
859 setDistanceUnit( qgsEnumKeyToValue( itemElem.attribute( QStringLiteral( "distanceUnit" ) ), mCrs.mapUnits() ) );
860
861 const QDomNodeList curveNodeList = itemElem.elementsByTagName( QStringLiteral( "curve" ) );
862 if ( !curveNodeList.isEmpty() )
863 {
864 const QDomElement curveElem = curveNodeList.at( 0 ).toElement();
865 const QgsGeometry curve = QgsGeometry::fromWkt( curveElem.text() );
866 if ( const QgsCurve *curveGeom = qgsgeometry_cast< const QgsCurve * >( curve.constGet() ) )
867 {
868 mCurve.reset( curveGeom->clone() );
869 }
870 else
871 {
872 mCurve.reset();
873 }
874 }
875
876 mTolerance = itemElem.attribute( QStringLiteral( "tolerance" ) ).toDouble();
877 mAtlasDriven = static_cast< bool >( itemElem.attribute( QStringLiteral( "atlasDriven" ), QStringLiteral( "0" ) ).toInt() );
878
879 {
880 mLayers.clear();
881 const QDomElement layersElement = itemElem.firstChildElement( QStringLiteral( "layers" ) );
882 QDomElement layerElement = layersElement.firstChildElement( QStringLiteral( "layer" ) );
883 while ( !layerElement.isNull() )
884 {
885 QgsMapLayerRef ref;
886 ref.readXml( layerElement, context );
887 ref.resolveWeakly( mLayout->project() );
888 mLayers.append( ref );
889
890 layerElement = layerElement.nextSiblingElement( QStringLiteral( "layer" ) );
891 }
892 }
893
894 return true;
895}
896
897void QgsLayoutItemElevationProfile::recreateCachedImageInBackground()
898{
899 if ( mRenderJob )
900 {
901 disconnect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
902 QgsProfilePlotRenderer *oldJob = mRenderJob.release();
903 QPainter *oldPainter = mPainter.release();
904 QImage *oldImage = mCacheRenderingImage.release();
905 connect( oldJob, &QgsProfilePlotRenderer::generationFinished, this, [oldPainter, oldJob, oldImage]
906 {
907 oldJob->deleteLater();
908 delete oldPainter;
909 delete oldImage;
910 } );
912 }
913 else
914 {
915 mCacheRenderingImage.reset( nullptr );
917 }
918
919 Q_ASSERT( !mRenderJob );
920 Q_ASSERT( !mPainter );
921 Q_ASSERT( !mCacheRenderingImage );
922
923 const QSizeF layoutSize = mLayout->convertToLayoutUnits( sizeWithUnits() );
924 double widthLayoutUnits = layoutSize.width();
925 double heightLayoutUnits = layoutSize.height();
926
927 int w = static_cast< int >( std::round( widthLayoutUnits * mPreviewScaleFactor ) );
928 int h = static_cast< int >( std::round( heightLayoutUnits * mPreviewScaleFactor ) );
929
930 // limit size of image for better performance
931 if ( w > 5000 || h > 5000 )
932 {
933 if ( w > h )
934 {
935 w = 5000;
936 h = static_cast< int>( std::round( w * heightLayoutUnits / widthLayoutUnits ) );
937 }
938 else
939 {
940 h = 5000;
941 w = static_cast< int >( std::round( h * widthLayoutUnits / heightLayoutUnits ) );
942 }
943 }
944
945 if ( w <= 0 || h <= 0 )
946 return;
947
948 mCacheRenderingImage.reset( new QImage( w, h, QImage::Format_ARGB32 ) );
949
950 // set DPI of the image
951 mCacheRenderingImage->setDotsPerMeterX( static_cast< int >( std::round( 1000 * w / widthLayoutUnits ) ) );
952 mCacheRenderingImage->setDotsPerMeterY( static_cast< int >( std::round( 1000 * h / heightLayoutUnits ) ) );
953
954 //start with empty fill to avoid artifacts
955 mCacheRenderingImage->fill( Qt::transparent );
956 if ( hasBackground() )
957 {
958 //Initially fill image with specified background color
959 mCacheRenderingImage->fill( backgroundColor().rgba() );
960 }
961
962 mCacheInvalidated = false;
963 mPainter.reset( new QPainter( mCacheRenderingImage.get() ) );
964
965 QList< QgsAbstractProfileSource * > sources;
967 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
968 {
969 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
970 sources.append( source );
971 }
972
973 mRenderJob = std::make_unique< QgsProfilePlotRenderer >( sources, profileRequest() );
974 connect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
975 mRenderJob->startGeneration();
976
977 mDrawingPreview = false;
978}
979
980void QgsLayoutItemElevationProfile::profileGenerationFinished()
981{
982 mPlot->setRenderer( mRenderJob.get() );
983
985
986 mPlot->xScale = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
987
988 const double mapUnitsPerPixel = static_cast< double >( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale /
989 mCacheRenderingImage->size().width();
990 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
991
992 // size must be in pixels, not layout units
993 mPlot->setSize( mCacheRenderingImage->size() );
994
995 mPlot->render( rc );
996
997 mPlot->setRenderer( nullptr );
998
999 mPainter->end();
1000 mRenderJob.reset( nullptr );
1001 mPainter.reset( nullptr );
1002 mCacheFinalImage = std::move( mCacheRenderingImage );
1004 update();
1005 emit previewRefreshed();
1006}
1007
1009{
1010 return mDistanceUnit;
1011}
1012
1014{
1015 mDistanceUnit = unit;
1016
1017 switch ( mDistanceUnit )
1018 {
1028 mPlot->xAxis().setLabelSuffix( QStringLiteral( " %1" ).arg( QgsUnitTypes::toAbbreviatedString( mDistanceUnit ) ) );
1029 break;
1030
1032 mPlot->xAxis().setLabelSuffix( QObject::tr( "°" ) );
1033 break;
1034
1036 mPlot->xAxis().setLabelSuffix( QString() );
1037 break;
1038 }
1039}
1040
DistanceUnit
Units of distance.
Definition qgis.h:4363
@ 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.
static QgsProfileSourceRegistry * profileSourceRegistry()
Returns registry of available profile source implementations.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
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.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static Q_INVOKABLE 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.
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.
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.
QList< QgsAbstractProfileSource * > profileSources() const
Returns a list of registered profile sources.
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:5675
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:5656
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5465
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.