QGIS API Documentation 3.41.0-Master (af5edcb665c)
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
19#include "moc_qgslayoutitemelevationprofile.cpp"
21#include "qgsplot.h"
22#include "qgslayout.h"
23#include "qgsmessagelog.h"
25#include "qgscurve.h"
26#include "qgsprofilerequest.h"
28#include "qgsterrainprovider.h"
29#include "qgsprofilerenderer.h"
30#include "qgslayoututils.h"
31#include "qgsvectorlayer.h"
35
36#include <QTimer>
37
38#define CACHE_SIZE_LIMIT 5000
39
41class QgsLayoutItemElevationProfilePlot : public Qgs2DPlot
42{
43 public:
44
45 QgsLayoutItemElevationProfilePlot()
46 {
47 }
48
49 void setRenderer( QgsProfilePlotRenderer *renderer )
50 {
51 // cppcheck-suppress danglingLifetime
52 mRenderer = renderer;
53 }
54
55 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
56 {
57 if ( mRenderer )
58 {
59 rc.painter()->translate( plotArea.left(), plotArea.top() );
60 mRenderer->render( rc, plotArea.width(), plotArea.height(), xMinimum() * xScale, xMaximum() * xScale, yMinimum(), yMaximum() );
61 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
62 }
63 }
64
65 double xScale = 1;
66
67 private:
68
69 QgsProfilePlotRenderer *mRenderer = nullptr;
70
71};
73
75 : QgsLayoutItem( layout )
76 , mPlot( std::make_unique< QgsLayoutItemElevationProfilePlot >() )
77{
78 mBackgroundUpdateTimer = new QTimer( this );
79 mBackgroundUpdateTimer->setSingleShot( true );
80 connect( mBackgroundUpdateTimer, &QTimer::timeout, this, &QgsLayoutItemElevationProfile::recreateCachedImageInBackground );
81
82 setCacheMode( QGraphicsItem::NoCache );
83
84 if ( mLayout )
85 {
87 }
88
89 connect( this, &QgsLayoutItem::sizePositionChanged, this, [this]
90 {
92 } );
93
94 //default to no background
95 setBackgroundEnabled( false );
96}
97
99{
100 if ( mRenderJob )
101 {
102 disconnect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
104 mRenderJob->cancelGeneration(); // blocks
105 mPainter->end();
106 }
107}
108
113
118
120{
121 return QgsApplication::getThemeIcon( QStringLiteral( "mLayoutItemElevationProfile.svg" ) );
122}
123
125{
127
128 bool forceUpdate = false;
129
132 {
133 double value = mTolerance;
134
135 bool ok = false;
137
138 if ( !ok )
139 {
140 QgsMessageLog::logMessage( tr( "Elevation profile tolerance expression eval error" ) );
141 }
142 else
143 {
144 mTolerance = value;
145 }
146
147 forceUpdate = true;
148 }
149
152 {
153 double value = mPlot->xMinimum();
154
155 bool ok = false;
157
158 if ( !ok )
159 {
160 QgsMessageLog::logMessage( tr( "Elevation profile minimum distance expression eval error" ) );
161 }
162 else
163 {
164 mPlot->setXMinimum( value );
165 }
166
167 forceUpdate = true;
168 }
169
172 {
173 double value = mPlot->xMaximum();
174
175 bool ok = false;
177
178 if ( !ok )
179 {
180 QgsMessageLog::logMessage( tr( "Elevation profile maximum distance expression eval error" ) );
181 }
182 else
183 {
184 mPlot->setXMaximum( value );
185 }
186
187 forceUpdate = true;
188 }
189
192 {
193 double value = mPlot->yMinimum();
194
195 bool ok = false;
197
198 if ( !ok )
199 {
200 QgsMessageLog::logMessage( tr( "Elevation profile minimum elevation expression eval error" ) );
201 }
202 else
203 {
204 mPlot->setYMinimum( value );
205 }
206
207 forceUpdate = true;
208 }
209
212 {
213 double value = mPlot->yMaximum();
214
215 bool ok = false;
217
218 if ( !ok )
219 {
220 QgsMessageLog::logMessage( tr( "Elevation profile maximum elevation expression eval error" ) );
221 }
222 else
223 {
224 mPlot->setYMaximum( value );
225 }
226
227 forceUpdate = true;
228 }
229
232 {
233 double value = mPlot->xAxis().gridIntervalMajor();
234
235 bool ok = false;
237
238 if ( !ok )
239 {
240 QgsMessageLog::logMessage( tr( "Elevation profile distance axis major interval expression eval error" ) );
241 }
242 else
243 {
244 mPlot->xAxis().setGridIntervalMajor( value );
245 }
246
247 forceUpdate = true;
248 }
249
252 {
253 double value = mPlot->xAxis().gridIntervalMinor();
254
255 bool ok = false;
257
258 if ( !ok )
259 {
260 QgsMessageLog::logMessage( tr( "Elevation profile distance axis minor interval expression eval error" ) );
261 }
262 else
263 {
264 mPlot->xAxis().setGridIntervalMinor( value );
265 }
266
267 forceUpdate = true;
268 }
269
272 {
273 double value = mPlot->xAxis().labelInterval();
274
275 bool ok = false;
277
278 if ( !ok )
279 {
280 QgsMessageLog::logMessage( tr( "Elevation profile distance axis label interval expression eval error" ) );
281 }
282 else
283 {
284 mPlot->xAxis().setLabelInterval( value );
285 }
286
287 forceUpdate = true;
288 }
289
292 {
293 double value = mPlot->yAxis().gridIntervalMajor();
294
295 bool ok = false;
297
298 if ( !ok )
299 {
300 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis major interval expression eval error" ) );
301 }
302 else
303 {
304 mPlot->yAxis().setGridIntervalMajor( value );
305 }
306
307 forceUpdate = true;
308 }
309
312 {
313 double value = mPlot->yAxis().gridIntervalMinor();
314
315 bool ok = false;
317
318 if ( !ok )
319 {
320 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis minor interval expression eval error" ) );
321 }
322 else
323 {
324 mPlot->yAxis().setGridIntervalMinor( value );
325 }
326
327 forceUpdate = true;
328 }
329
332 {
333 double value = mPlot->yAxis().labelInterval();
334
335 bool ok = false;
337
338 if ( !ok )
339 {
340 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis label interval expression eval error" ) );
341 }
342 else
343 {
344 mPlot->yAxis().setLabelInterval( value );
345 }
346
347 forceUpdate = true;
348 }
349
352 {
353 double value = mPlot->margins().left();
354
355 bool ok = false;
357
358 if ( !ok )
359 {
360 QgsMessageLog::logMessage( tr( "Elevation profile left margin expression eval error" ) );
361 }
362 else
363 {
364 QgsMargins margins = mPlot->margins();
365 margins.setLeft( value );
366 mPlot->setMargins( margins );
367 }
368
369 forceUpdate = true;
370 }
371
374 {
375 double value = mPlot->margins().right();
376
377 bool ok = false;
379
380 if ( !ok )
381 {
382 QgsMessageLog::logMessage( tr( "Elevation profile right margin expression eval error" ) );
383 }
384 else
385 {
386 QgsMargins margins = mPlot->margins();
387 margins.setRight( value );
388 mPlot->setMargins( margins );
389 }
390
391 forceUpdate = true;
392 }
393
396 {
397 double value = mPlot->margins().top();
398
399 bool ok = false;
401
402 if ( !ok )
403 {
404 QgsMessageLog::logMessage( tr( "Elevation profile top margin expression eval error" ) );
405 }
406 else
407 {
408 QgsMargins margins = mPlot->margins();
409 margins.setTop( value );
410 mPlot->setMargins( margins );
411 }
412
413 forceUpdate = true;
414 }
415
418 {
419 double value = mPlot->margins().bottom();
420
421 bool ok = false;
423
424 if ( !ok )
425 {
426 QgsMessageLog::logMessage( tr( "Elevation profile bottom margin expression eval error" ) );
427 }
428 else
429 {
430 QgsMargins margins = mPlot->margins();
431 margins.setBottom( value );
432 mPlot->setMargins( margins );
433 }
434
435 forceUpdate = true;
436 }
437
438 if ( forceUpdate )
439 {
440 mCacheInvalidated = true;
441
443 update();
444 }
445
447}
448
453
455{
456 return blendMode() != QPainter::CompositionMode_SourceOver;
457}
458
460{
461 return mEvaluatedOpacity < 1.0;
462}
463
465{
466 return mPlot.get();
467}
468
470{
471 return mPlot.get();
472}
473
474QList<QgsMapLayer *> QgsLayoutItemElevationProfile::layers() const
475{
476 return _qgis_listRefToRaw( mLayers );
477}
478
479void QgsLayoutItemElevationProfile::setLayers( const QList<QgsMapLayer *> &layers )
480{
481 if ( layers == _qgis_listRefToRaw( mLayers ) )
482 return;
483
484 mLayers = _qgis_listRawToRef( layers );
486}
487
489{
490 mCurve.reset( curve );
492}
493
495{
496 return mCurve.get();
497}
498
500{
501 if ( mCrs == crs )
502 return;
503
504 mCrs = crs;
506}
507
512
514{
515 if ( mTolerance == tolerance )
516 return;
517
518 mTolerance = tolerance;
520}
521
523{
524 return mTolerance;
525}
526
528{
529 mAtlasDriven = enabled;
530}
531
533{
534 QgsProfileRequest req( mCurve ? mCurve.get()->clone() : nullptr );
535
536 req.setCrs( mCrs );
537 req.setTolerance( mTolerance );
539 if ( mLayout )
540 {
541 if ( QgsProject *project = mLayout->project() )
542 {
543 req.setTransformContext( project->transformContext() );
544 if ( QgsAbstractTerrainProvider *provider = project->elevationProperties()->terrainProvider() )
545 {
546 req.setTerrainProvider( provider->clone() );
547 }
548 }
549 }
550 return req;
551}
552
553void QgsLayoutItemElevationProfile::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * )
554{
555 if ( !mLayout || !painter || !painter->device() || !mUpdatesEnabled )
556 {
557 return;
558 }
559 if ( !shouldDrawItem() )
560 {
561 return;
562 }
563
564 QRectF thisPaintRect = rect();
565 if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
566 return;
567
568 if ( mLayout->renderContext().isPreviewRender() )
569 {
572
573 QgsScopedQPainterState painterState( painter );
574 painter->setClipRect( thisPaintRect );
575 if ( !mCacheFinalImage || mCacheFinalImage->isNull() )
576 {
577 // No initial render available - so draw some preview text alerting user
578 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
579 painter->drawRect( thisPaintRect );
580 painter->setBrush( Qt::NoBrush );
581 QFont messageFont;
582 messageFont.setPointSize( 12 );
583 painter->setFont( messageFont );
584 painter->setPen( QColor( 255, 255, 255, 255 ) );
585 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering profile" ) );
586
587 if (
588 ( mRenderJob && mCacheInvalidated && !mDrawingPreview ) // current job was invalidated - start a new one
589 ||
590 ( !mRenderJob && !mDrawingPreview ) // this is the profiles's very first paint - trigger a cache update
591 )
592 {
593
594 mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
595 mBackgroundUpdateTimer->start( 1 );
596 }
597 }
598 else
599 {
600 if ( mCacheInvalidated && !mDrawingPreview )
601 {
602 // cache was invalidated - trigger a background update
603 mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
604 mBackgroundUpdateTimer->start( 1 );
605 }
606
607 //Background color is already included in cached image, so no need to draw
608
609 double imagePixelWidth = mCacheFinalImage->width(); //how many pixels of the image are for the map extent?
610 double scale = rect().width() / imagePixelWidth;
611
612 QgsScopedQPainterState rotatedPainterState( painter );
613
614 painter->scale( scale, scale );
615 painter->setCompositionMode( blendModeForRender() );
616 painter->drawImage( 0, 0, *mCacheFinalImage );
617 }
618
619 painter->setClipRect( thisPaintRect, Qt::NoClip );
620
621 if ( frameEnabled() )
622 {
624 }
625 }
626 else
627 {
628 if ( mDrawing )
629 return;
630
631 mDrawing = true;
632 QPaintDevice *paintDevice = painter->device();
633 if ( !paintDevice )
634 return;
635
636 QSizeF layoutSize = mLayout->convertToLayoutUnits( sizeWithUnits() );
637
638 if ( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering )
639 painter->setRenderHint( QPainter::LosslessImageRendering, true );
640
641 mPlot->xScale = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
642
643 if ( !qgsDoubleNear( layoutSize.width(), 0.0 ) && !qgsDoubleNear( layoutSize.height(), 0.0 ) )
644 {
645 if ( ( containsAdvancedEffects() || ( blendModeForRender() != QPainter::CompositionMode_SourceOver ) )
646 && ( !( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagForceVectorOutput ) ) )
647 {
648 // rasterize
649 double destinationDpi = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter ) * 25.4;
650 double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, Qgis::LayoutUnit::Inches ).length() : 1;
651 int widthInPixels = static_cast< int >( std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi ) );
652 int heightInPixels = static_cast< int >( std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi ) );
653 QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
654
655 image.fill( Qt::transparent );
656 image.setDotsPerMeterX( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
657 image.setDotsPerMeterY( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
658 double dotsPerMM = destinationDpi / 25.4;
659 layoutSize *= dotsPerMM; // output size will be in dots (pixels)
660 QPainter p( &image );
661 preparePainter( &p );
662
665
666 p.scale( dotsPerMM, dotsPerMM );
667 if ( hasBackground() )
668 {
670 }
671
672 p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
673
674 const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale / layoutSize.width();
675 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
676
677 QList< QgsAbstractProfileSource * > sources;
679 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
680 {
681 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
682 sources.append( source );
683 }
684
685 QgsProfilePlotRenderer renderer( sources, profileRequest() );
686
687 renderer.generateSynchronously();
688 mPlot->setRenderer( &renderer );
689
690 // size must be in pixels, not layout units
691 mPlot->setSize( layoutSize );
692
693 mPlot->render( rc );
694
695 mPlot->setRenderer( nullptr );
696
697 p.scale( dotsPerMM, dotsPerMM );
698
699 if ( frameEnabled() )
700 {
702 }
703
704 QgsScopedQPainterState painterState( painter );
705 painter->setCompositionMode( blendModeForRender() );
706 painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
707 painter->drawImage( 0, 0, image );
708 painter->scale( dotsPerMM, dotsPerMM );
709 }
710 else
711 {
714
715 // Fill with background color
716 if ( hasBackground() )
717 {
719 }
720
721 QgsScopedQPainterState painterState( painter );
722 QgsScopedQPainterState stagedPainterState( painter );
723 double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
724 layoutSize *= dotsPerMM; // output size will be in dots (pixels)
725 painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
726
727 const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale / layoutSize.width();
728 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
729
730 QList< QgsAbstractProfileSource * > sources;
732 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
733 {
734 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
735 sources.append( source );
736 }
737
738 QgsProfilePlotRenderer renderer( sources, profileRequest() );
739
740
741 // TODO
742 // we should be able to call renderer.start()/renderer.waitForFinished() here and
743 // benefit from parallel source generation. BUT
744 // for some reason the QtConcurrent::map call in start() never triggers
745 // the actual background thread execution.
746 // So for now just generate the results one by one
747 renderer.generateSynchronously();
748 mPlot->setRenderer( &renderer );
749
750 // size must be in pixels, not layout units
751 mPlot->setSize( layoutSize );
752
753 mPlot->render( rc );
754
755 mPlot->setRenderer( nullptr );
756
757 painter->setClipRect( thisPaintRect, Qt::NoClip );
758
759 if ( frameEnabled() )
760 {
762 }
763 }
764 }
765
766 mDrawing = false;
767 }
768}
769
771{
772 if ( mAtlasDriven && mLayout && mLayout->reportContext().layer() )
773 {
774 if ( QgsVectorLayer *layer = mLayout->reportContext().layer() )
775 {
776 mCrs = layer->crs();
777 }
778 const QgsGeometry curveGeom( mLayout->reportContext().currentGeometry( mCrs ) );
779 if ( const QgsAbstractGeometry *geom = curveGeom.constGet() )
780 {
781 if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( geom->simplifiedTypeRef() ) )
782 {
783 mCurve.reset( curve->clone() );
784 }
785 }
786 }
789}
790
792{
793 if ( mDrawing )
794 return;
795
796 mCacheInvalidated = true;
797 update();
798}
799
803
804bool QgsLayoutItemElevationProfile::writePropertiesToElement( QDomElement &layoutProfileElem, QDomDocument &doc, const QgsReadWriteContext &rwContext ) const
805{
806 {
807 QDomElement plotElement = doc.createElement( QStringLiteral( "plot" ) );
808 mPlot->writeXml( plotElement, doc, rwContext );
809 layoutProfileElem.appendChild( plotElement );
810 }
811
812 layoutProfileElem.setAttribute( QStringLiteral( "distanceUnit" ), qgsEnumValueToKey( mDistanceUnit ) );
813
814 layoutProfileElem.setAttribute( QStringLiteral( "tolerance" ), mTolerance );
815 layoutProfileElem.setAttribute( QStringLiteral( "atlasDriven" ), mAtlasDriven ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
816 if ( mCrs.isValid() )
817 {
818 QDomElement crsElem = doc.createElement( QStringLiteral( "crs" ) );
819 mCrs.writeXml( crsElem, doc );
820 layoutProfileElem.appendChild( crsElem );
821 }
822 if ( mCurve )
823 {
824 QDomElement curveElem = doc.createElement( QStringLiteral( "curve" ) );
825 curveElem.appendChild( doc.createTextNode( mCurve->asWkt( ) ) );
826 layoutProfileElem.appendChild( curveElem );
827 }
828
829 {
830 QDomElement layersElement = doc.createElement( QStringLiteral( "layers" ) );
831 for ( const QgsMapLayerRef &layer : mLayers )
832 {
833 QDomElement layerElement = doc.createElement( QStringLiteral( "layer" ) );
834 layer.writeXml( layerElement, rwContext );
835 layersElement.appendChild( layerElement );
836 }
837 layoutProfileElem.appendChild( layersElement );
838 }
839
840 return true;
841}
842
843bool QgsLayoutItemElevationProfile::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
844{
845 const QDomElement plotElement = itemElem.firstChildElement( QStringLiteral( "plot" ) );
846 if ( !plotElement.isNull() )
847 {
848 mPlot->readXml( plotElement, context );
849 }
850
851 const QDomNodeList crsNodeList = itemElem.elementsByTagName( QStringLiteral( "crs" ) );
853 if ( !crsNodeList.isEmpty() )
854 {
855 const QDomElement crsElem = crsNodeList.at( 0 ).toElement();
856 crs.readXml( crsElem );
857 }
858 mCrs = crs;
859
860 setDistanceUnit( qgsEnumKeyToValue( itemElem.attribute( QStringLiteral( "distanceUnit" ) ), mCrs.mapUnits() ) );
861
862 const QDomNodeList curveNodeList = itemElem.elementsByTagName( QStringLiteral( "curve" ) );
863 if ( !curveNodeList.isEmpty() )
864 {
865 const QDomElement curveElem = curveNodeList.at( 0 ).toElement();
866 const QgsGeometry curve = QgsGeometry::fromWkt( curveElem.text() );
867 if ( const QgsCurve *curveGeom = qgsgeometry_cast< const QgsCurve * >( curve.constGet() ) )
868 {
869 mCurve.reset( curveGeom->clone() );
870 }
871 else
872 {
873 mCurve.reset();
874 }
875 }
876
877 mTolerance = itemElem.attribute( QStringLiteral( "tolerance" ) ).toDouble();
878 mAtlasDriven = static_cast< bool >( itemElem.attribute( QStringLiteral( "atlasDriven" ), QStringLiteral( "0" ) ).toInt() );
879
880 {
881 mLayers.clear();
882 const QDomElement layersElement = itemElem.firstChildElement( QStringLiteral( "layers" ) );
883 QDomElement layerElement = layersElement.firstChildElement( QStringLiteral( "layer" ) );
884 while ( !layerElement.isNull() )
885 {
886 QgsMapLayerRef ref;
887 ref.readXml( layerElement, context );
888 ref.resolveWeakly( mLayout->project() );
889 mLayers.append( ref );
890
891 layerElement = layerElement.nextSiblingElement( QStringLiteral( "layer" ) );
892 }
893 }
894
895 return true;
896}
897
898void QgsLayoutItemElevationProfile::recreateCachedImageInBackground()
899{
900 if ( mRenderJob )
901 {
902 disconnect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
903 QgsProfilePlotRenderer *oldJob = mRenderJob.release();
904 QPainter *oldPainter = mPainter.release();
905 QImage *oldImage = mCacheRenderingImage.release();
906 connect( oldJob, &QgsProfilePlotRenderer::generationFinished, this, [oldPainter, oldJob, oldImage]
907 {
908 oldJob->deleteLater();
909 delete oldPainter;
910 delete oldImage;
911 } );
913 }
914 else
915 {
916 mCacheRenderingImage.reset( nullptr );
918 }
919
920 Q_ASSERT( !mRenderJob );
921 Q_ASSERT( !mPainter );
922 Q_ASSERT( !mCacheRenderingImage );
923
924 const QSizeF layoutSize = mLayout->convertToLayoutUnits( sizeWithUnits() );
925 double widthLayoutUnits = layoutSize.width();
926 double heightLayoutUnits = layoutSize.height();
927
928 int w = static_cast< int >( std::round( widthLayoutUnits * mPreviewScaleFactor ) );
929 int h = static_cast< int >( std::round( heightLayoutUnits * mPreviewScaleFactor ) );
930
931 // limit size of image for better performance
932 if ( w > 5000 || h > 5000 )
933 {
934 if ( w > h )
935 {
936 w = 5000;
937 h = static_cast< int>( std::round( w * heightLayoutUnits / widthLayoutUnits ) );
938 }
939 else
940 {
941 h = 5000;
942 w = static_cast< int >( std::round( h * widthLayoutUnits / heightLayoutUnits ) );
943 }
944 }
945
946 if ( w <= 0 || h <= 0 )
947 return;
948
949 mCacheRenderingImage.reset( new QImage( w, h, QImage::Format_ARGB32 ) );
950
951 // set DPI of the image
952 mCacheRenderingImage->setDotsPerMeterX( static_cast< int >( std::round( 1000 * w / widthLayoutUnits ) ) );
953 mCacheRenderingImage->setDotsPerMeterY( static_cast< int >( std::round( 1000 * h / heightLayoutUnits ) ) );
954
955 //start with empty fill to avoid artifacts
956 mCacheRenderingImage->fill( Qt::transparent );
957 if ( hasBackground() )
958 {
959 //Initially fill image with specified background color
960 mCacheRenderingImage->fill( backgroundColor().rgba() );
961 }
962
963 mCacheInvalidated = false;
964 mPainter.reset( new QPainter( mCacheRenderingImage.get() ) );
965
966 QList< QgsAbstractProfileSource * > sources;
968 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
969 {
970 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
971 sources.append( source );
972 }
973
974 mRenderJob = std::make_unique< QgsProfilePlotRenderer >( sources, profileRequest() );
975 connect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
976 mRenderJob->startGeneration();
977
978 mDrawingPreview = false;
979}
980
981void QgsLayoutItemElevationProfile::profileGenerationFinished()
982{
983 mPlot->setRenderer( mRenderJob.get() );
984
986
987 mPlot->xScale = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
988
989 const double mapUnitsPerPixel = static_cast< double >( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale /
990 mCacheRenderingImage->size().width();
991 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
992
993 // size must be in pixels, not layout units
994 mPlot->setSize( mCacheRenderingImage->size() );
995
996 mPlot->render( rc );
997
998 mPlot->setRenderer( nullptr );
999
1000 mPainter->end();
1001 mRenderJob.reset( nullptr );
1002 mPainter.reset( nullptr );
1003 mCacheFinalImage = std::move( mCacheRenderingImage );
1005 update();
1006 emit previewRefreshed();
1007}
1008
1010{
1011 return mDistanceUnit;
1012}
1013
1015{
1016 mDistanceUnit = unit;
1017
1018 switch ( mDistanceUnit )
1019 {
1068 mPlot->xAxis().setLabelSuffix( QStringLiteral( " %1" ).arg( QgsUnitTypes::toAbbreviatedString( mDistanceUnit ) ) );
1069 break;
1070
1072 mPlot->xAxis().setLabelSuffix( QObject::tr( "°" ) );
1073 break;
1074
1076 mPlot->xAxis().setLabelSuffix( QString() );
1077 break;
1078 }
1079}
1080
DistanceUnit
Units of distance.
Definition qgis.h:4740
@ YardsBritishSears1922Truncated
British yards (Sears 1922 truncated)
@ Feet
Imperial feet.
@ MilesUSSurvey
US Survey miles.
@ LinksBritishSears1922
British links (Sears 1922)
@ YardsBritishBenoit1895A
British yards (Benoit 1895 A)
@ LinksBritishBenoit1895A
British links (Benoit 1895 A)
@ Centimeters
Centimeters.
@ YardsIndian1975
Indian yards (1975)
@ FeetUSSurvey
US Survey feet.
@ Millimeters
Millimeters.
@ FeetBritishSears1922
British feet (Sears 1922)
@ YardsClarkes
Clarke's yards.
@ YardsIndian
Indian yards.
@ FeetBritishBenoit1895B
British feet (Benoit 1895 B)
@ Miles
Terrestrial miles.
@ LinksUSSurvey
US Survey links.
@ ChainsUSSurvey
US Survey chains.
@ FeetClarkes
Clarke's feet.
@ Unknown
Unknown distance unit.
@ Yards
Imperial yards.
@ FeetBritish1936
British feet (1936)
@ FeetIndian1962
Indian feet (1962)
@ YardsBritishSears1922
British yards (Sears 1922)
@ FeetIndian1937
Indian feet (1937)
@ YardsIndian1937
Indian yards (1937)
@ Degrees
Degrees, for planar geographic CRS distance measurements.
@ ChainsBritishBenoit1895B
British chains (Benoit 1895 B)
@ LinksBritishSears1922Truncated
British links (Sears 1922 truncated)
@ ChainsBritishBenoit1895A
British chains (Benoit 1895 A)
@ YardsBritishBenoit1895B
British yards (Benoit 1895 B)
@ FeetBritish1865
British feet (1865)
@ YardsIndian1962
Indian yards (1962)
@ FeetBritishSears1922Truncated
British feet (Sears 1922 truncated)
@ MetersGermanLegal
German legal meter.
@ LinksBritishBenoit1895B
British links (Benoit 1895 B)
@ ChainsInternational
International chains.
@ LinksInternational
International links.
@ ChainsBritishSears1922Truncated
British chains (Sears 1922 truncated)
@ FeetIndian
Indian (geodetic) feet.
@ NauticalMiles
Nautical miles.
@ ChainsClarkes
Clarke's chains.
@ LinksClarkes
Clarke's links.
@ ChainsBritishSears1922
British chains (Sears 1922)
@ Kilometers
Kilometers.
@ FeetIndian1975
Indian feet (1975)
@ FeetGoldCoast
Gold Coast feet.
@ FeetBritishBenoit1895A
British feet (Benoit 1895 A)
Base class for 2-dimensional plot/chart/graphs.
Definition qgsplot.h:273
double yMaximum() const
Returns the maximum value of the y axis.
Definition qgsplot.h:383
double xMaximum() const
Returns the maximum value of the x axis.
Definition qgsplot.h:369
double yMinimum() const
Returns the minimum value of the y axis.
Definition qgsplot.h:355
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.
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.
@ ElevationProfileElevationMinorInterval
Minor grid line interval for elevation profile elevation axis.
@ ElevationProfileDistanceMinorInterval
Minor grid line interval for elevation profile distance axis.
@ ElevationProfileMinimumDistance
Minimum distance value for elevation profile.
@ ElevationProfileMaximumElevation
Maximum elevation value for elevation profile.
@ ElevationProfileDistanceLabelInterval
Label interval for elevation profile distance axis.
@ ElevationProfileTolerance
Tolerance distance for elevation profiles.
@ ElevationProfileMinimumElevation
Minimum elevation value for elevation profile.
@ ElevationProfileElevationLabelInterval
Label interval for elevation profile elevation axis.
@ ElevationProfileDistanceMajorInterval
Major grid line interval for elevation profile distance axis.
@ ElevationProfileElevationMajorInterval
Major grid line interval for elevation profile elevation axis.
@ AllProperties
All properties for item.
@ 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:6276
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6257
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6066
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.