QGIS API Documentation 3.43.0-Master (c67cf405802)
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 "qgslinesymbol.h"
22#include "qgsplot.h"
23#include "qgslayout.h"
24#include "qgsmessagelog.h"
26#include "qgscurve.h"
27#include "qgsprofilerequest.h"
29#include "qgsterrainprovider.h"
30#include "qgsprofilerenderer.h"
31#include "qgslayoututils.h"
32#include "qgsvectorlayer.h"
36#include "qgssymbollayerutils.h"
37
38#include <QTimer>
39#include <memory>
40
41#define CACHE_SIZE_LIMIT 5000
42
44class QgsLayoutItemElevationProfilePlot : public Qgs2DPlot
45{
46 public:
47
48 QgsLayoutItemElevationProfilePlot()
49 {
50 }
51
52 void setRenderer( QgsProfilePlotRenderer *renderer )
53 {
54 // cppcheck-suppress danglingLifetime
55 mRenderer = renderer;
56 }
57
58 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
59 {
60 if ( mRenderer )
61 {
62 const double distanceMin = xMinimum() * xScale;
63 const double distanceMax = xMaximum() * xScale;
64 rc.painter()->translate( plotArea.left(), plotArea.top() );
65 mRenderer->render( rc, plotArea.width(), plotArea.height(), distanceMin, distanceMax, yMinimum(), yMaximum() );
66 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
67 mRenderer->renderSubsectionsIndicator( rc, plotArea, distanceMin, distanceMax, yMinimum(), yMaximum() );
68 }
69 }
70
71 double xScale = 1;
72
73 private:
74
75 QgsProfilePlotRenderer *mRenderer = nullptr;
76
77};
79
81 : QgsLayoutItem( layout )
82 , mPlot( std::make_unique< QgsLayoutItemElevationProfilePlot >() )
83{
84 mBackgroundUpdateTimer = new QTimer( this );
85 mBackgroundUpdateTimer->setSingleShot( true );
86 connect( mBackgroundUpdateTimer, &QTimer::timeout, this, &QgsLayoutItemElevationProfile::recreateCachedImageInBackground );
87
88 setCacheMode( QGraphicsItem::NoCache );
89
90 if ( mLayout )
91 {
93 }
94
95 connect( this, &QgsLayoutItem::sizePositionChanged, this, [this]
96 {
98 } );
99
100 //default to no background
101 setBackgroundEnabled( false );
102}
103
105{
106 if ( mRenderJob )
107 {
108 disconnect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
110 mRenderJob->cancelGeneration(); // blocks
111 mPainter->end();
112 }
113}
114
119
124
126{
127 return QgsApplication::getThemeIcon( QStringLiteral( "mLayoutItemElevationProfile.svg" ) );
128}
129
131{
133
134 bool forceUpdate = false;
135
138 {
139 double value = mTolerance;
140
141 bool ok = false;
143
144 if ( !ok )
145 {
146 QgsMessageLog::logMessage( tr( "Elevation profile tolerance expression eval error" ) );
147 }
148 else
149 {
150 mTolerance = value;
151 }
152
153 forceUpdate = true;
154 }
155
158 {
159 double value = mPlot->xMinimum();
160
161 bool ok = false;
163
164 if ( !ok )
165 {
166 QgsMessageLog::logMessage( tr( "Elevation profile minimum distance expression eval error" ) );
167 }
168 else
169 {
170 mPlot->setXMinimum( value );
171 }
172
173 forceUpdate = true;
174 }
175
178 {
179 double value = mPlot->xMaximum();
180
181 bool ok = false;
183
184 if ( !ok )
185 {
186 QgsMessageLog::logMessage( tr( "Elevation profile maximum distance expression eval error" ) );
187 }
188 else
189 {
190 mPlot->setXMaximum( value );
191 }
192
193 forceUpdate = true;
194 }
195
198 {
199 double value = mPlot->yMinimum();
200
201 bool ok = false;
203
204 if ( !ok )
205 {
206 QgsMessageLog::logMessage( tr( "Elevation profile minimum elevation expression eval error" ) );
207 }
208 else
209 {
210 mPlot->setYMinimum( value );
211 }
212
213 forceUpdate = true;
214 }
215
218 {
219 double value = mPlot->yMaximum();
220
221 bool ok = false;
223
224 if ( !ok )
225 {
226 QgsMessageLog::logMessage( tr( "Elevation profile maximum elevation expression eval error" ) );
227 }
228 else
229 {
230 mPlot->setYMaximum( value );
231 }
232
233 forceUpdate = true;
234 }
235
238 {
239 double value = mPlot->xAxis().gridIntervalMajor();
240
241 bool ok = false;
243
244 if ( !ok )
245 {
246 QgsMessageLog::logMessage( tr( "Elevation profile distance axis major interval expression eval error" ) );
247 }
248 else
249 {
250 mPlot->xAxis().setGridIntervalMajor( value );
251 }
252
253 forceUpdate = true;
254 }
255
258 {
259 double value = mPlot->xAxis().gridIntervalMinor();
260
261 bool ok = false;
263
264 if ( !ok )
265 {
266 QgsMessageLog::logMessage( tr( "Elevation profile distance axis minor interval expression eval error" ) );
267 }
268 else
269 {
270 mPlot->xAxis().setGridIntervalMinor( value );
271 }
272
273 forceUpdate = true;
274 }
275
278 {
279 double value = mPlot->xAxis().labelInterval();
280
281 bool ok = false;
283
284 if ( !ok )
285 {
286 QgsMessageLog::logMessage( tr( "Elevation profile distance axis label interval expression eval error" ) );
287 }
288 else
289 {
290 mPlot->xAxis().setLabelInterval( value );
291 }
292
293 forceUpdate = true;
294 }
295
298 {
299 double value = mPlot->yAxis().gridIntervalMajor();
300
301 bool ok = false;
303
304 if ( !ok )
305 {
306 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis major interval expression eval error" ) );
307 }
308 else
309 {
310 mPlot->yAxis().setGridIntervalMajor( value );
311 }
312
313 forceUpdate = true;
314 }
315
318 {
319 double value = mPlot->yAxis().gridIntervalMinor();
320
321 bool ok = false;
323
324 if ( !ok )
325 {
326 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis minor interval expression eval error" ) );
327 }
328 else
329 {
330 mPlot->yAxis().setGridIntervalMinor( value );
331 }
332
333 forceUpdate = true;
334 }
335
338 {
339 double value = mPlot->yAxis().labelInterval();
340
341 bool ok = false;
343
344 if ( !ok )
345 {
346 QgsMessageLog::logMessage( tr( "Elevation profile elevation axis label interval expression eval error" ) );
347 }
348 else
349 {
350 mPlot->yAxis().setLabelInterval( value );
351 }
352
353 forceUpdate = true;
354 }
355
358 {
359 double value = mPlot->margins().left();
360
361 bool ok = false;
363
364 if ( !ok )
365 {
366 QgsMessageLog::logMessage( tr( "Elevation profile left margin expression eval error" ) );
367 }
368 else
369 {
370 QgsMargins margins = mPlot->margins();
371 margins.setLeft( value );
372 mPlot->setMargins( margins );
373 }
374
375 forceUpdate = true;
376 }
377
380 {
381 double value = mPlot->margins().right();
382
383 bool ok = false;
385
386 if ( !ok )
387 {
388 QgsMessageLog::logMessage( tr( "Elevation profile right margin expression eval error" ) );
389 }
390 else
391 {
392 QgsMargins margins = mPlot->margins();
393 margins.setRight( value );
394 mPlot->setMargins( margins );
395 }
396
397 forceUpdate = true;
398 }
399
402 {
403 double value = mPlot->margins().top();
404
405 bool ok = false;
407
408 if ( !ok )
409 {
410 QgsMessageLog::logMessage( tr( "Elevation profile top margin expression eval error" ) );
411 }
412 else
413 {
414 QgsMargins margins = mPlot->margins();
415 margins.setTop( value );
416 mPlot->setMargins( margins );
417 }
418
419 forceUpdate = true;
420 }
421
424 {
425 double value = mPlot->margins().bottom();
426
427 bool ok = false;
429
430 if ( !ok )
431 {
432 QgsMessageLog::logMessage( tr( "Elevation profile bottom margin expression eval error" ) );
433 }
434 else
435 {
436 QgsMargins margins = mPlot->margins();
437 margins.setBottom( value );
438 mPlot->setMargins( margins );
439 }
440
441 forceUpdate = true;
442 }
443
444 if ( forceUpdate )
445 {
446 mCacheInvalidated = true;
447
449 update();
450 }
451
453}
454
459
461{
462 return blendMode() != QPainter::CompositionMode_SourceOver;
463}
464
466{
467 return mEvaluatedOpacity < 1.0;
468}
469
471{
472 return mPlot.get();
473}
474
476{
477 return mPlot.get();
478}
479
480QList<QgsMapLayer *> QgsLayoutItemElevationProfile::layers() const
481{
482 return _qgis_listRefToRaw( mLayers );
483}
484
485void QgsLayoutItemElevationProfile::setLayers( const QList<QgsMapLayer *> &layers )
486{
487 if ( layers == _qgis_listRefToRaw( mLayers ) )
488 return;
489
490 mLayers = _qgis_listRawToRef( layers );
492}
493
495{
496 mCurve.reset( curve );
498}
499
501{
502 return mCurve.get();
503}
504
506{
507 if ( mCrs == crs )
508 return;
509
510 mCrs = crs;
512}
513
518
520{
521 if ( mTolerance == tolerance )
522 return;
523
524 mTolerance = tolerance;
526}
527
529{
530 return mTolerance;
531}
532
534{
535 mAtlasDriven = enabled;
536}
537
539{
540 QgsProfileRequest req( mCurve ? mCurve.get()->clone() : nullptr );
541
542 req.setCrs( mCrs );
543 req.setTolerance( mTolerance );
545 if ( mLayout )
546 {
547 if ( QgsProject *project = mLayout->project() )
548 {
549 req.setTransformContext( project->transformContext() );
550 if ( QgsAbstractTerrainProvider *provider = project->elevationProperties()->terrainProvider() )
551 {
552 req.setTerrainProvider( provider->clone() );
553 }
554 }
555 }
556 return req;
557}
558
559void QgsLayoutItemElevationProfile::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * )
560{
561 if ( !mLayout || !painter || !painter->device() || !mUpdatesEnabled )
562 {
563 return;
564 }
565 if ( !shouldDrawItem() )
566 {
567 return;
568 }
569
570 QRectF thisPaintRect = rect();
571 if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
572 return;
573
574 if ( mLayout->renderContext().isPreviewRender() )
575 {
578
579 QgsScopedQPainterState painterState( painter );
580 painter->setClipRect( thisPaintRect );
581 if ( !mCacheFinalImage || mCacheFinalImage->isNull() )
582 {
583 // No initial render available - so draw some preview text alerting user
584 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
585 painter->drawRect( thisPaintRect );
586 painter->setBrush( Qt::NoBrush );
587 QFont messageFont;
588 messageFont.setPointSize( 12 );
589 painter->setFont( messageFont );
590 painter->setPen( QColor( 255, 255, 255, 255 ) );
591 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering profile" ) );
592
593 if (
594 ( mRenderJob && mCacheInvalidated && !mDrawingPreview ) // current job was invalidated - start a new one
595 ||
596 ( !mRenderJob && !mDrawingPreview ) // this is the profiles's very first paint - trigger a cache update
597 )
598 {
599
600 mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
601 mBackgroundUpdateTimer->start( 1 );
602 }
603 }
604 else
605 {
606 if ( mCacheInvalidated && !mDrawingPreview )
607 {
608 // cache was invalidated - trigger a background update
609 mPreviewScaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
610 mBackgroundUpdateTimer->start( 1 );
611 }
612
613 //Background color is already included in cached image, so no need to draw
614
615 double imagePixelWidth = mCacheFinalImage->width(); //how many pixels of the image are for the map extent?
616 double scale = rect().width() / imagePixelWidth;
617
618 QgsScopedQPainterState rotatedPainterState( painter );
619
620 painter->scale( scale, scale );
621 painter->setCompositionMode( blendModeForRender() );
622 painter->drawImage( 0, 0, *mCacheFinalImage );
623 }
624
625 painter->setClipRect( thisPaintRect, Qt::NoClip );
626
627 if ( frameEnabled() )
628 {
630 }
631 }
632 else
633 {
634 if ( mDrawing )
635 return;
636
637 mDrawing = true;
638 QPaintDevice *paintDevice = painter->device();
639 if ( !paintDevice )
640 return;
641
642 QSizeF layoutSize = mLayout->convertToLayoutUnits( sizeWithUnits() );
643
644 if ( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering )
645 painter->setRenderHint( QPainter::LosslessImageRendering, true );
646
647 mPlot->xScale = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
648
649 if ( !qgsDoubleNear( layoutSize.width(), 0.0 ) && !qgsDoubleNear( layoutSize.height(), 0.0 ) )
650 {
651 if ( ( containsAdvancedEffects() || ( blendModeForRender() != QPainter::CompositionMode_SourceOver ) )
652 && ( !( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagForceVectorOutput ) ) )
653 {
654 // rasterize
655 double destinationDpi = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter ) * 25.4;
656 double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, Qgis::LayoutUnit::Inches ).length() : 1;
657 int widthInPixels = static_cast< int >( std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi ) );
658 int heightInPixels = static_cast< int >( std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi ) );
659 QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
660
661 image.fill( Qt::transparent );
662 image.setDotsPerMeterX( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
663 image.setDotsPerMeterY( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
664 double dotsPerMM = destinationDpi / 25.4;
665 layoutSize *= dotsPerMM; // output size will be in dots (pixels)
666 QPainter p( &image );
667 preparePainter( &p );
668
671
672 p.scale( dotsPerMM, dotsPerMM );
673 if ( hasBackground() )
674 {
676 }
677
678 p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
679
680 const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale / layoutSize.width();
681 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
682
683 QList< QgsAbstractProfileSource * > sources;
685 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
686 {
687 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
688 sources.append( source );
689 }
690
691 QgsProfilePlotRenderer renderer( sources, profileRequest() );
692 std::unique_ptr<QgsLineSymbol> rendererSubSectionsSymbol( subsectionsSymbol() ? subsectionsSymbol()->clone() : nullptr );
693 renderer.setSubsectionsSymbol( rendererSubSectionsSymbol.release() );
694
695 renderer.generateSynchronously();
696 mPlot->setRenderer( &renderer );
697
698 // size must be in pixels, not layout units
699 mPlot->setSize( layoutSize );
700
701 mPlot->render( rc );
702
703 mPlot->setRenderer( nullptr );
704
705 p.scale( dotsPerMM, dotsPerMM );
706
707 if ( frameEnabled() )
708 {
710 }
711
712 QgsScopedQPainterState painterState( painter );
713 painter->setCompositionMode( blendModeForRender() );
714 painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
715 painter->drawImage( 0, 0, image );
716 painter->scale( dotsPerMM, dotsPerMM );
717 }
718 else
719 {
722
723 // Fill with background color
724 if ( hasBackground() )
725 {
727 }
728
729 QgsScopedQPainterState painterState( painter );
730 QgsScopedQPainterState stagedPainterState( painter );
731 double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
732 layoutSize *= dotsPerMM; // output size will be in dots (pixels)
733 painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
734
735 const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale / layoutSize.width();
736 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
737
738 QList< QgsAbstractProfileSource * > sources;
740 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
741 {
742 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
743 sources.append( source );
744 }
745
746 QgsProfilePlotRenderer renderer( sources, profileRequest() );
747 std::unique_ptr<QgsLineSymbol> rendererSubSectionsSymbol( subsectionsSymbol() ? subsectionsSymbol()->clone() : nullptr );
748 renderer.setSubsectionsSymbol( rendererSubSectionsSymbol.release() );
749
750 // TODO
751 // we should be able to call renderer.start()/renderer.waitForFinished() here and
752 // benefit from parallel source generation. BUT
753 // for some reason the QtConcurrent::map call in start() never triggers
754 // the actual background thread execution.
755 // So for now just generate the results one by one
756 renderer.generateSynchronously();
757 mPlot->setRenderer( &renderer );
758
759 // size must be in pixels, not layout units
760 mPlot->setSize( layoutSize );
761
762 mPlot->render( rc );
763
764 mPlot->setRenderer( nullptr );
765
766 painter->setClipRect( thisPaintRect, Qt::NoClip );
767
768 if ( frameEnabled() )
769 {
771 }
772 }
773 }
774
775 mDrawing = false;
776 }
777}
778
780{
781 if ( mAtlasDriven && mLayout && mLayout->reportContext().layer() )
782 {
783 if ( QgsVectorLayer *layer = mLayout->reportContext().layer() )
784 {
785 mCrs = layer->crs();
786 }
787 const QgsGeometry curveGeom( mLayout->reportContext().currentGeometry( mCrs ) );
788 if ( const QgsAbstractGeometry *geom = curveGeom.constGet() )
789 {
790 if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( geom->simplifiedTypeRef() ) )
791 {
792 mCurve.reset( curve->clone() );
793 }
794 }
795 }
798}
799
801{
802 if ( mDrawing )
803 return;
804
805 mCacheInvalidated = true;
806 update();
807}
808
812
813bool QgsLayoutItemElevationProfile::writePropertiesToElement( QDomElement &layoutProfileElem, QDomDocument &doc, const QgsReadWriteContext &rwContext ) const
814{
815 {
816 QDomElement plotElement = doc.createElement( QStringLiteral( "plot" ) );
817 mPlot->writeXml( plotElement, doc, rwContext );
818 layoutProfileElem.appendChild( plotElement );
819 }
820
821 layoutProfileElem.setAttribute( QStringLiteral( "distanceUnit" ), qgsEnumValueToKey( mDistanceUnit ) );
822
823 layoutProfileElem.setAttribute( QStringLiteral( "tolerance" ), mTolerance );
824 layoutProfileElem.setAttribute( QStringLiteral( "atlasDriven" ), mAtlasDriven ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
825 if ( mCrs.isValid() )
826 {
827 QDomElement crsElem = doc.createElement( QStringLiteral( "crs" ) );
828 mCrs.writeXml( crsElem, doc );
829 layoutProfileElem.appendChild( crsElem );
830 }
831 if ( mCurve )
832 {
833 QDomElement curveElem = doc.createElement( QStringLiteral( "curve" ) );
834 curveElem.appendChild( doc.createTextNode( mCurve->asWkt( ) ) );
835 layoutProfileElem.appendChild( curveElem );
836 }
837
838 {
839 QDomElement layersElement = doc.createElement( QStringLiteral( "layers" ) );
840 for ( const QgsMapLayerRef &layer : mLayers )
841 {
842 QDomElement layerElement = doc.createElement( QStringLiteral( "layer" ) );
843 layer.writeXml( layerElement, rwContext );
844 layersElement.appendChild( layerElement );
845 }
846 layoutProfileElem.appendChild( layersElement );
847 }
848
849 if ( mSubsectionsSymbol )
850 {
851 QDomElement subsectionsElement = doc.createElement( QStringLiteral( "subsections" ) );
852 const QDomElement symbolElement = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "subsections" ), mSubsectionsSymbol.get(), doc, rwContext );
853 subsectionsElement.appendChild( symbolElement );
854 layoutProfileElem.appendChild( subsectionsElement );
855 }
856
857 return true;
858}
859
860bool QgsLayoutItemElevationProfile::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
861{
862 const QDomElement plotElement = itemElem.firstChildElement( QStringLiteral( "plot" ) );
863 if ( !plotElement.isNull() )
864 {
865 mPlot->readXml( plotElement, context );
866 }
867
868 const QDomNodeList crsNodeList = itemElem.elementsByTagName( QStringLiteral( "crs" ) );
870 if ( !crsNodeList.isEmpty() )
871 {
872 const QDomElement crsElem = crsNodeList.at( 0 ).toElement();
873 crs.readXml( crsElem );
874 }
875 mCrs = crs;
876
877 setDistanceUnit( qgsEnumKeyToValue( itemElem.attribute( QStringLiteral( "distanceUnit" ) ), mCrs.mapUnits() ) );
878
879 const QDomNodeList curveNodeList = itemElem.elementsByTagName( QStringLiteral( "curve" ) );
880 if ( !curveNodeList.isEmpty() )
881 {
882 const QDomElement curveElem = curveNodeList.at( 0 ).toElement();
883 const QgsGeometry curve = QgsGeometry::fromWkt( curveElem.text() );
884 if ( const QgsCurve *curveGeom = qgsgeometry_cast< const QgsCurve * >( curve.constGet() ) )
885 {
886 mCurve.reset( curveGeom->clone() );
887 }
888 else
889 {
890 mCurve.reset();
891 }
892 }
893
894 mTolerance = itemElem.attribute( QStringLiteral( "tolerance" ) ).toDouble();
895 mAtlasDriven = static_cast< bool >( itemElem.attribute( QStringLiteral( "atlasDriven" ), QStringLiteral( "0" ) ).toInt() );
896
897 {
898 mLayers.clear();
899 const QDomElement layersElement = itemElem.firstChildElement( QStringLiteral( "layers" ) );
900 QDomElement layerElement = layersElement.firstChildElement( QStringLiteral( "layer" ) );
901 while ( !layerElement.isNull() )
902 {
903 QgsMapLayerRef ref;
904 ref.readXml( layerElement, context );
905 ref.resolveWeakly( mLayout->project() );
906 mLayers.append( ref );
907
908 layerElement = layerElement.nextSiblingElement( QStringLiteral( "layer" ) );
909 }
910 }
911
912 const QDomElement subsectionsElement = itemElem.firstChildElement( QStringLiteral( "subsections" ) );
913 const QDomElement symbolsElement = subsectionsElement.firstChildElement( QStringLiteral( "symbol" ) );
914 if ( !symbolsElement.isNull() )
915 {
916 std::unique_ptr< QgsLineSymbol > subSectionsSymbol = QgsSymbolLayerUtils::loadSymbol<QgsLineSymbol >( symbolsElement, context );
917 if ( subSectionsSymbol )
918 {
919 setSubsectionsSymbol( subSectionsSymbol.release() );
920 }
921 }
922
923
924 return true;
925}
926
927void QgsLayoutItemElevationProfile::recreateCachedImageInBackground()
928{
929 if ( mRenderJob )
930 {
931 disconnect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
932 QgsProfilePlotRenderer *oldJob = mRenderJob.release();
933 QPainter *oldPainter = mPainter.release();
934 QImage *oldImage = mCacheRenderingImage.release();
935 connect( oldJob, &QgsProfilePlotRenderer::generationFinished, this, [oldPainter, oldJob, oldImage]
936 {
937 oldJob->deleteLater();
938 delete oldPainter;
939 delete oldImage;
940 } );
942 }
943 else
944 {
945 mCacheRenderingImage.reset( nullptr );
947 }
948
949 Q_ASSERT( !mRenderJob );
950 Q_ASSERT( !mPainter );
951 Q_ASSERT( !mCacheRenderingImage );
952
953 const QSizeF layoutSize = mLayout->convertToLayoutUnits( sizeWithUnits() );
954 double widthLayoutUnits = layoutSize.width();
955 double heightLayoutUnits = layoutSize.height();
956
957 int w = static_cast< int >( std::round( widthLayoutUnits * mPreviewScaleFactor ) );
958 int h = static_cast< int >( std::round( heightLayoutUnits * mPreviewScaleFactor ) );
959
960 // limit size of image for better performance
961 if ( w > 5000 || h > 5000 )
962 {
963 if ( w > h )
964 {
965 w = 5000;
966 h = static_cast< int>( std::round( w * heightLayoutUnits / widthLayoutUnits ) );
967 }
968 else
969 {
970 h = 5000;
971 w = static_cast< int >( std::round( h * widthLayoutUnits / heightLayoutUnits ) );
972 }
973 }
974
975 if ( w <= 0 || h <= 0 )
976 return;
977
978 mCacheRenderingImage.reset( new QImage( w, h, QImage::Format_ARGB32 ) );
979
980 // set DPI of the image
981 mCacheRenderingImage->setDotsPerMeterX( static_cast< int >( std::round( 1000 * w / widthLayoutUnits ) ) );
982 mCacheRenderingImage->setDotsPerMeterY( static_cast< int >( std::round( 1000 * h / heightLayoutUnits ) ) );
983
984 //start with empty fill to avoid artifacts
985 mCacheRenderingImage->fill( Qt::transparent );
986 if ( hasBackground() )
987 {
988 //Initially fill image with specified background color
989 mCacheRenderingImage->fill( backgroundColor().rgba() );
990 }
991
992 mCacheInvalidated = false;
993 mPainter.reset( new QPainter( mCacheRenderingImage.get() ) );
994
995 QList< QgsAbstractProfileSource * > sources;
997 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
998 {
999 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
1000 sources.append( source );
1001 }
1002
1003 mRenderJob = std::make_unique< QgsProfilePlotRenderer >( sources, profileRequest() );
1004 std::unique_ptr<QgsLineSymbol> rendererSubSectionsSymbol( subsectionsSymbol() ? subsectionsSymbol()->clone() : nullptr );
1005 mRenderJob->setSubsectionsSymbol( rendererSubSectionsSymbol.release() );
1006 connect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
1007 mRenderJob->startGeneration();
1008
1009 mDrawingPreview = false;
1010}
1011
1012void QgsLayoutItemElevationProfile::profileGenerationFinished()
1013{
1014 mPlot->setRenderer( mRenderJob.get() );
1015
1017
1018 mPlot->xScale = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
1019
1020 const double mapUnitsPerPixel = static_cast< double >( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale /
1021 mCacheRenderingImage->size().width();
1022 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
1023
1024 // size must be in pixels, not layout units
1025 mPlot->setSize( mCacheRenderingImage->size() );
1026
1027 mPlot->render( rc );
1028
1029 mPlot->setRenderer( nullptr );
1030
1031 mPainter->end();
1032 mRenderJob.reset( nullptr );
1033 mPainter.reset( nullptr );
1034 mCacheFinalImage = std::move( mCacheRenderingImage );
1036 update();
1037 emit previewRefreshed();
1038}
1039
1041{
1042 return mDistanceUnit;
1043}
1044
1046{
1047 mDistanceUnit = unit;
1048
1049 switch ( mDistanceUnit )
1050 {
1099 mPlot->xAxis().setLabelSuffix( QStringLiteral( " %1" ).arg( QgsUnitTypes::toAbbreviatedString( mDistanceUnit ) ) );
1100 break;
1101
1103 mPlot->xAxis().setLabelSuffix( QObject::tr( "°" ) );
1104 break;
1105
1107 mPlot->xAxis().setLabelSuffix( QString() );
1108 break;
1109 }
1110}
1111
1113{
1114 mSubsectionsSymbol.reset( symbol );
1115}
DistanceUnit
Units of distance.
Definition qgis.h:4843
@ 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 xMinimum() const
Returns the minimum value of the x axis.
Definition qgsplot.h:341
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.
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.
void setSubsectionsSymbol(QgsLineSymbol *symbol)
Sets the symbol used to draw the subsections.
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).
QgsLineSymbol * subsectionsSymbol()
Returns the symbol used to draw the subsections.
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.
A line symbol type, for rendering LineString and MultiLineString geometries.
Perform transforms between map coordinates and device coordinates.
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, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
Generates and renders elevation profile plots.
void setSubsectionsSymbol(QgsLineSymbol *symbol)
Sets the symbol used to draw the subsections.
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.
A container for the context for various read/write operations on 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 QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
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 dataset.
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:6496
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6477
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6286
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.