QGIS API Documentation 3.43.0-Master (8fc5848dca1)
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() & Qgis::LayoutRenderFlag::LosslessImageRendering )
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 const bool forceVector = mLayout && mLayout->renderContext().rasterizedRenderingPolicy() == Qgis::RasterizedRenderingPolicy::ForceVector;
652 if ( ( containsAdvancedEffects() || ( blendModeForRender() != QPainter::CompositionMode_SourceOver ) )
653 && !forceVector )
654 {
655 // rasterize
656 double destinationDpi = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter ) * 25.4;
657 double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, Qgis::LayoutUnit::Inches ).length() : 1;
658 int widthInPixels = static_cast< int >( std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi ) );
659 int heightInPixels = static_cast< int >( std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi ) );
660 QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
661
662 image.fill( Qt::transparent );
663 image.setDotsPerMeterX( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
664 image.setDotsPerMeterY( static_cast< int >( std::round( 1000 * destinationDpi / 25.4 ) ) );
665 double dotsPerMM = destinationDpi / 25.4;
666 layoutSize *= dotsPerMM; // output size will be in dots (pixels)
667 QPainter p( &image );
668 preparePainter( &p );
669
672
673 p.scale( dotsPerMM, dotsPerMM );
674 if ( hasBackground() )
675 {
677 }
678
679 p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
680
681 const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale / layoutSize.width();
682 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
683
684 QList< QgsAbstractProfileSource * > sources;
686 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
687 {
688 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
689 sources.append( source );
690 }
691
692 QgsProfilePlotRenderer renderer( sources, profileRequest() );
693 std::unique_ptr<QgsLineSymbol> rendererSubSectionsSymbol( subsectionsSymbol() ? subsectionsSymbol()->clone() : nullptr );
694 renderer.setSubsectionsSymbol( rendererSubSectionsSymbol.release() );
695
696 renderer.generateSynchronously();
697 mPlot->setRenderer( &renderer );
698
699 // size must be in pixels, not layout units
700 mPlot->setSize( layoutSize );
701
702 mPlot->render( rc );
703
704 mPlot->setRenderer( nullptr );
705
706 p.scale( dotsPerMM, dotsPerMM );
707
708 if ( frameEnabled() )
709 {
711 }
712
713 QgsScopedQPainterState painterState( painter );
714 painter->setCompositionMode( blendModeForRender() );
715 painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
716 painter->drawImage( 0, 0, image );
717 painter->scale( dotsPerMM, dotsPerMM );
718 }
719 else
720 {
723
724 // Fill with background color
725 if ( hasBackground() )
726 {
728 }
729
730 QgsScopedQPainterState painterState( painter );
731 QgsScopedQPainterState stagedPainterState( painter );
732 double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
733 layoutSize *= dotsPerMM; // output size will be in dots (pixels)
734 painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
735
736 const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale / layoutSize.width();
737 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
738
739 QList< QgsAbstractProfileSource * > sources;
741 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
742 {
743 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
744 sources.append( source );
745 }
746
747 QgsProfilePlotRenderer renderer( sources, profileRequest() );
748 std::unique_ptr<QgsLineSymbol> rendererSubSectionsSymbol( subsectionsSymbol() ? subsectionsSymbol()->clone() : nullptr );
749 renderer.setSubsectionsSymbol( rendererSubSectionsSymbol.release() );
750
751 // TODO
752 // we should be able to call renderer.start()/renderer.waitForFinished() here and
753 // benefit from parallel source generation. BUT
754 // for some reason the QtConcurrent::map call in start() never triggers
755 // the actual background thread execution.
756 // So for now just generate the results one by one
757 renderer.generateSynchronously();
758 mPlot->setRenderer( &renderer );
759
760 // size must be in pixels, not layout units
761 mPlot->setSize( layoutSize );
762
763 mPlot->render( rc );
764
765 mPlot->setRenderer( nullptr );
766
767 painter->setClipRect( thisPaintRect, Qt::NoClip );
768
769 if ( frameEnabled() )
770 {
772 }
773 }
774 }
775
776 mDrawing = false;
777 }
778}
779
781{
782 if ( mAtlasDriven && mLayout && mLayout->reportContext().layer() )
783 {
784 if ( QgsVectorLayer *layer = mLayout->reportContext().layer() )
785 {
786 mCrs = layer->crs();
787 }
788 const QgsGeometry curveGeom( mLayout->reportContext().currentGeometry( mCrs ) );
789 if ( const QgsAbstractGeometry *geom = curveGeom.constGet() )
790 {
791 if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( geom->simplifiedTypeRef() ) )
792 {
793 mCurve.reset( curve->clone() );
794 }
795 }
796 }
799}
800
802{
803 if ( mDrawing )
804 return;
805
806 mCacheInvalidated = true;
807 update();
808}
809
813
814bool QgsLayoutItemElevationProfile::writePropertiesToElement( QDomElement &layoutProfileElem, QDomDocument &doc, const QgsReadWriteContext &rwContext ) const
815{
816 {
817 QDomElement plotElement = doc.createElement( QStringLiteral( "plot" ) );
818 mPlot->writeXml( plotElement, doc, rwContext );
819 layoutProfileElem.appendChild( plotElement );
820 }
821
822 layoutProfileElem.setAttribute( QStringLiteral( "distanceUnit" ), qgsEnumValueToKey( mDistanceUnit ) );
823
824 layoutProfileElem.setAttribute( QStringLiteral( "tolerance" ), mTolerance );
825 layoutProfileElem.setAttribute( QStringLiteral( "atlasDriven" ), mAtlasDriven ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
826 if ( mCrs.isValid() )
827 {
828 QDomElement crsElem = doc.createElement( QStringLiteral( "crs" ) );
829 mCrs.writeXml( crsElem, doc );
830 layoutProfileElem.appendChild( crsElem );
831 }
832 if ( mCurve )
833 {
834 QDomElement curveElem = doc.createElement( QStringLiteral( "curve" ) );
835 curveElem.appendChild( doc.createTextNode( mCurve->asWkt( ) ) );
836 layoutProfileElem.appendChild( curveElem );
837 }
838
839 {
840 QDomElement layersElement = doc.createElement( QStringLiteral( "layers" ) );
841 for ( const QgsMapLayerRef &layer : mLayers )
842 {
843 QDomElement layerElement = doc.createElement( QStringLiteral( "layer" ) );
844 layer.writeXml( layerElement, rwContext );
845 layersElement.appendChild( layerElement );
846 }
847 layoutProfileElem.appendChild( layersElement );
848 }
849
850 if ( mSubsectionsSymbol )
851 {
852 QDomElement subsectionsElement = doc.createElement( QStringLiteral( "subsections" ) );
853 const QDomElement symbolElement = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "subsections" ), mSubsectionsSymbol.get(), doc, rwContext );
854 subsectionsElement.appendChild( symbolElement );
855 layoutProfileElem.appendChild( subsectionsElement );
856 }
857
858 return true;
859}
860
861bool QgsLayoutItemElevationProfile::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
862{
863 const QDomElement plotElement = itemElem.firstChildElement( QStringLiteral( "plot" ) );
864 if ( !plotElement.isNull() )
865 {
866 mPlot->readXml( plotElement, context );
867 }
868
869 const QDomNodeList crsNodeList = itemElem.elementsByTagName( QStringLiteral( "crs" ) );
871 if ( !crsNodeList.isEmpty() )
872 {
873 const QDomElement crsElem = crsNodeList.at( 0 ).toElement();
874 crs.readXml( crsElem );
875 }
876 mCrs = crs;
877
878 setDistanceUnit( qgsEnumKeyToValue( itemElem.attribute( QStringLiteral( "distanceUnit" ) ), mCrs.mapUnits() ) );
879
880 const QDomNodeList curveNodeList = itemElem.elementsByTagName( QStringLiteral( "curve" ) );
881 if ( !curveNodeList.isEmpty() )
882 {
883 const QDomElement curveElem = curveNodeList.at( 0 ).toElement();
884 const QgsGeometry curve = QgsGeometry::fromWkt( curveElem.text() );
885 if ( const QgsCurve *curveGeom = qgsgeometry_cast< const QgsCurve * >( curve.constGet() ) )
886 {
887 mCurve.reset( curveGeom->clone() );
888 }
889 else
890 {
891 mCurve.reset();
892 }
893 }
894
895 mTolerance = itemElem.attribute( QStringLiteral( "tolerance" ) ).toDouble();
896 mAtlasDriven = static_cast< bool >( itemElem.attribute( QStringLiteral( "atlasDriven" ), QStringLiteral( "0" ) ).toInt() );
897
898 {
899 mLayers.clear();
900 const QDomElement layersElement = itemElem.firstChildElement( QStringLiteral( "layers" ) );
901 QDomElement layerElement = layersElement.firstChildElement( QStringLiteral( "layer" ) );
902 while ( !layerElement.isNull() )
903 {
904 QgsMapLayerRef ref;
905 ref.readXml( layerElement, context );
906 ref.resolveWeakly( mLayout->project() );
907 mLayers.append( ref );
908
909 layerElement = layerElement.nextSiblingElement( QStringLiteral( "layer" ) );
910 }
911 }
912
913 const QDomElement subsectionsElement = itemElem.firstChildElement( QStringLiteral( "subsections" ) );
914 const QDomElement symbolsElement = subsectionsElement.firstChildElement( QStringLiteral( "symbol" ) );
915 if ( !symbolsElement.isNull() )
916 {
917 std::unique_ptr< QgsLineSymbol > subSectionsSymbol = QgsSymbolLayerUtils::loadSymbol<QgsLineSymbol >( symbolsElement, context );
918 if ( subSectionsSymbol )
919 {
920 setSubsectionsSymbol( subSectionsSymbol.release() );
921 }
922 }
923
924
925 return true;
926}
927
928void QgsLayoutItemElevationProfile::recreateCachedImageInBackground()
929{
930 if ( mRenderJob )
931 {
932 disconnect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
933 QgsProfilePlotRenderer *oldJob = mRenderJob.release();
934 QPainter *oldPainter = mPainter.release();
935 QImage *oldImage = mCacheRenderingImage.release();
936 connect( oldJob, &QgsProfilePlotRenderer::generationFinished, this, [oldPainter, oldJob, oldImage]
937 {
938 oldJob->deleteLater();
939 delete oldPainter;
940 delete oldImage;
941 } );
943 }
944 else
945 {
946 mCacheRenderingImage.reset( nullptr );
948 }
949
950 Q_ASSERT( !mRenderJob );
951 Q_ASSERT( !mPainter );
952 Q_ASSERT( !mCacheRenderingImage );
953
954 const QSizeF layoutSize = mLayout->convertToLayoutUnits( sizeWithUnits() );
955 double widthLayoutUnits = layoutSize.width();
956 double heightLayoutUnits = layoutSize.height();
957
958 int w = static_cast< int >( std::round( widthLayoutUnits * mPreviewScaleFactor ) );
959 int h = static_cast< int >( std::round( heightLayoutUnits * mPreviewScaleFactor ) );
960
961 // limit size of image for better performance
962 if ( w > 5000 || h > 5000 )
963 {
964 if ( w > h )
965 {
966 w = 5000;
967 h = static_cast< int>( std::round( w * heightLayoutUnits / widthLayoutUnits ) );
968 }
969 else
970 {
971 h = 5000;
972 w = static_cast< int >( std::round( h * widthLayoutUnits / heightLayoutUnits ) );
973 }
974 }
975
976 if ( w <= 0 || h <= 0 )
977 return;
978
979 mCacheRenderingImage.reset( new QImage( w, h, QImage::Format_ARGB32 ) );
980
981 // set DPI of the image
982 mCacheRenderingImage->setDotsPerMeterX( static_cast< int >( std::round( 1000 * w / widthLayoutUnits ) ) );
983 mCacheRenderingImage->setDotsPerMeterY( static_cast< int >( std::round( 1000 * h / heightLayoutUnits ) ) );
984
985 //start with empty fill to avoid artifacts
986 mCacheRenderingImage->fill( Qt::transparent );
987 if ( hasBackground() )
988 {
989 //Initially fill image with specified background color
990 mCacheRenderingImage->fill( backgroundColor().rgba() );
991 }
992
993 mCacheInvalidated = false;
994 mPainter.reset( new QPainter( mCacheRenderingImage.get() ) );
995
996 QList< QgsAbstractProfileSource * > sources;
998 for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
999 {
1000 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer.get() ) )
1001 sources.append( source );
1002 }
1003
1004 mRenderJob = std::make_unique< QgsProfilePlotRenderer >( sources, profileRequest() );
1005 std::unique_ptr<QgsLineSymbol> rendererSubSectionsSymbol( subsectionsSymbol() ? subsectionsSymbol()->clone() : nullptr );
1006 mRenderJob->setSubsectionsSymbol( rendererSubSectionsSymbol.release() );
1007 connect( mRenderJob.get(), &QgsProfilePlotRenderer::generationFinished, this, &QgsLayoutItemElevationProfile::profileGenerationFinished );
1008 mRenderJob->startGeneration();
1009
1010 mDrawingPreview = false;
1011}
1012
1013void QgsLayoutItemElevationProfile::profileGenerationFinished()
1014{
1015 mPlot->setRenderer( mRenderJob.get() );
1016
1018
1019 mPlot->xScale = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
1020
1021 const double mapUnitsPerPixel = static_cast< double >( mPlot->xMaximum() - mPlot->xMinimum() ) * mPlot->xScale /
1022 mCacheRenderingImage->size().width();
1023 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
1024
1025 // size must be in pixels, not layout units
1026 mPlot->setSize( mCacheRenderingImage->size() );
1027
1028 mPlot->render( rc );
1029
1030 mPlot->setRenderer( nullptr );
1031
1032 mPainter->end();
1033 mRenderJob.reset( nullptr );
1034 mPainter.reset( nullptr );
1035 mCacheFinalImage = std::move( mCacheRenderingImage );
1037 update();
1038 emit previewRefreshed();
1039}
1040
1042{
1043 return mDistanceUnit;
1044}
1045
1047{
1048 mDistanceUnit = unit;
1049
1050 switch ( mDistanceUnit )
1051 {
1100 mPlot->xAxis().setLabelSuffix( QStringLiteral( " %1" ).arg( QgsUnitTypes::toAbbreviatedString( mDistanceUnit ) ) );
1101 break;
1102
1104 mPlot->xAxis().setLabelSuffix( QObject::tr( "°" ) );
1105 break;
1106
1108 mPlot->xAxis().setLabelSuffix( QString() );
1109 break;
1110 }
1111}
1112
1114{
1115 mSubsectionsSymbol.reset( symbol );
1116}
@ ForceVector
Always force vector-based rendering, even when the result will be visually different to a raster-base...
DistanceUnit
Units of distance.
Definition qgis.h:4875
@ 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)
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
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.
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:6577
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6558
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6367
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.