QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsquickelevationprofilecanvas.cpp
Go to the documentation of this file.
1/***************************************************************************
2 QgsQuickElevationProfileCanvas.cpp
3 -----------------
4 begin : October 2022
5 copyright : (C) 2022 by Mathieu Pellerin
6 email : mathieu at opengis dot ch
7***************************************************************************/
8
9
10/***************************************************************************
11 * *
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
16 * *
17 ***************************************************************************/
18
23#include "qgsmaplayerutils.h"
25#include "qgsplot.h"
26#include "qgsprofilerenderer.h"
27#include "qgsprofilerequest.h"
30#include "qgsterrainprovider.h"
31
32#include <QQuickWindow>
33#include <QSGSimpleRectNode>
34#include <QSGSimpleTextureNode>
35#include <QScreen>
36#include <QTimer>
37
38
40class QgsElevationProfilePlotItem : public Qgs2DPlot
41{
42 public:
43 explicit QgsElevationProfilePlotItem( QgsQuickElevationProfileCanvas *canvas )
44 : mCanvas( canvas )
45 {
46 setYMinimum( 0 );
47 setYMaximum( 100 );
48 setSize( mCanvas->boundingRect().size() );
49 }
50
51 void setRenderer( QgsProfilePlotRenderer *renderer )
52 {
53 mRenderer = renderer;
54 }
55
56 void updateRect()
57 {
58 setSize( mCanvas->boundingRect().size() );
59 mCachedImages.clear();
60 mPlotArea = QRectF();
61 }
62
63 void updatePlot()
64 {
65 mCachedImages.clear();
66 mPlotArea = QRectF();
67 }
68
69 bool redrawResults( const QString &sourceId )
70 {
71 auto it = mCachedImages.find( sourceId );
72 if ( it == mCachedImages.end() )
73 return false;
74
75 mCachedImages.erase( it );
76 return true;
77 }
78
79 QRectF plotArea()
80 {
81 if ( !mPlotArea.isNull() )
82 return mPlotArea;
83
84 // force immediate recalculation of plot area
85 QgsRenderContext context;
86 context.setScaleFactor( ( mCanvas->window()->screen()->physicalDotsPerInch() * mCanvas->window()->screen()->devicePixelRatio() ) / 25.4 );
87
89 mPlotArea = interiorPlotArea( context );
90 return mPlotArea;
91 }
92
93 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
94 {
95 mPlotArea = plotArea;
96
97 if ( !mRenderer )
98 return;
99
100 const QStringList sourceIds = mRenderer->sourceIds();
101 for ( const QString &source : sourceIds )
102 {
103 QImage plot;
104 auto it = mCachedImages.constFind( source );
105 if ( it != mCachedImages.constEnd() )
106 {
107 plot = it.value();
108 }
109 else
110 {
111 const float devicePixelRatio = static_cast<float>( mCanvas->window()->screen()->devicePixelRatio() );
112 plot = QImage( static_cast<int>( plotArea.width() * devicePixelRatio ), static_cast<int>( plotArea.height() * devicePixelRatio ), QImage::Format_ARGB32_Premultiplied );
113 plot.setDevicePixelRatio( devicePixelRatio );
114 plot.fill( Qt::transparent );
115
116 QPainter plotPainter( &plot );
117 plotPainter.setRenderHint( QPainter::Antialiasing, true );
118 QgsRenderContext plotRc = QgsRenderContext::fromQPainter( &plotPainter );
119 plotRc.setDevicePixelRatio( devicePixelRatio );
120
121 const double mapUnitsPerPixel = ( xMaximum() - xMinimum() ) / plotArea.width();
122 plotRc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
123
124 mRenderer->render( plotRc, plotArea.width(), plotArea.height(), xMinimum(), xMaximum(), yMinimum(), yMaximum(), source );
125 plotPainter.end();
126
127 mCachedImages.insert( source, plot );
128 }
129 rc.painter()->drawImage( static_cast<int>( plotArea.left() ), static_cast<int>( plotArea.top() ), plot );
130 }
131 }
132
133 private:
134 QgsQuickElevationProfileCanvas *mCanvas = nullptr;
135 QgsProfilePlotRenderer *mRenderer = nullptr;
136
137 QRectF mPlotArea;
138 QMap<QString, QImage> mCachedImages;
139};
141
142
144 : QQuickItem( parent )
145{
146 // updating the profile plot is deferred on a timer, so that we don't trigger it too often
147 mDeferredRegenerationTimer = new QTimer( this );
148 mDeferredRegenerationTimer->setSingleShot( true );
149 mDeferredRegenerationTimer->stop();
150 connect( mDeferredRegenerationTimer, &QTimer::timeout, this, &QgsQuickElevationProfileCanvas::startDeferredRegeneration );
151
152 mDeferredRedrawTimer = new QTimer( this );
153 mDeferredRedrawTimer->setSingleShot( true );
154 mDeferredRedrawTimer->stop();
155 connect( mDeferredRedrawTimer, &QTimer::timeout, this, &QgsQuickElevationProfileCanvas::startDeferredRedraw );
156
157 mPlotItem = new QgsElevationProfilePlotItem( this );
158
159 setTransformOrigin( QQuickItem::TopLeft );
160 setFlags( QQuickItem::ItemHasContents );
161}
162
164{
165 if ( mCurrentJob )
166 {
167 mPlotItem->setRenderer( nullptr );
168 mCurrentJob->deleteLater();
169 mCurrentJob = nullptr;
170 }
171}
172
174{
175 if ( mCurrentJob )
176 {
177 mPlotItem->setRenderer( nullptr );
178 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
179 mCurrentJob->cancelGeneration();
180 mCurrentJob->deleteLater();
181 mCurrentJob = nullptr;
182 }
183}
184
185void QgsQuickElevationProfileCanvas::setupLayerConnections( QgsMapLayer *layer, bool isDisconnect )
186{
187 if ( !layer )
188 return;
189
190 if ( isDisconnect )
191 {
192 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsQuickElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
193 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsQuickElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
194 disconnect( layer, &QgsMapLayer::dataChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
195 }
196 else
197 {
198 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsQuickElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
199 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsQuickElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
200 connect( layer, &QgsMapLayer::dataChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
201 }
202
203 switch ( layer->type() )
204 {
206 {
207 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
208 if ( isDisconnect )
209 {
210 disconnect( vl, &QgsVectorLayer::featureAdded, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
211 disconnect( vl, &QgsVectorLayer::featureDeleted, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
212 disconnect( vl, &QgsVectorLayer::geometryChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
213 disconnect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
214 }
215 else
216 {
217 connect( vl, &QgsVectorLayer::featureAdded, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
218 connect( vl, &QgsVectorLayer::featureDeleted, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
219 connect( vl, &QgsVectorLayer::geometryChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
220 connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
221 }
222 break;
223 }
232 break;
233 }
234}
235
237{
238 return mCurrentJob && mCurrentJob->isActive();
239}
240
242{
243 if ( !mCrs.isValid() || !mProject || mProfileCurve.isEmpty() )
244 return;
245
246 if ( mCurrentJob )
247 {
248 mPlotItem->setRenderer( nullptr );
249 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
250 mCurrentJob->deleteLater();
251 mCurrentJob = nullptr;
252 }
253
254 QgsProfileRequest request( static_cast<QgsCurve *>( mProfileCurve.get()->clone() ) );
255 request.setCrs( mCrs );
256 request.setTolerance( mTolerance );
257 request.setTransformContext( mProject->transformContext() );
258 request.setTerrainProvider( mProject->elevationProperties()->terrainProvider() ? mProject->elevationProperties()->terrainProvider()->clone() : nullptr );
259
260 QgsExpressionContext context;
263 request.setExpressionContext( context );
264
265 const QList<QgsMapLayer *> layersToGenerate = layers();
266 QList<QgsAbstractProfileSource *> sources;
267 sources.reserve( layersToGenerate.size() );
268 for ( QgsMapLayer *layer : layersToGenerate )
269 {
270 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
271 sources.append( source );
272 }
273
274 mCurrentJob = new QgsProfilePlotRenderer( sources, request );
275 connect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
276
277 QgsProfileGenerationContext generationContext;
278 generationContext.setDpi( window()->screen()->physicalDotsPerInch() * window()->screen()->devicePixelRatio() );
279 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( mProfileCurve.get()->length() ) / mPlotItem->plotArea().width() );
280 generationContext.setMapUnitsPerDistancePixel( mProfileCurve.get()->length() / mPlotItem->plotArea().width() );
281 mCurrentJob->setContext( generationContext );
282
283 mPlotItem->updatePlot();
284 mCurrentJob->startGeneration();
285 mPlotItem->setRenderer( mCurrentJob );
286
287 emit activeJobCountChanged( 1 );
288 emit isRenderingChanged();
289}
290
291void QgsQuickElevationProfileCanvas::generationFinished()
292{
293 if ( !mCurrentJob )
294 return;
295
296 emit activeJobCountChanged( 0 );
297
298 if ( mZoomFullWhenJobFinished )
299 {
300 mZoomFullWhenJobFinished = false;
301 zoomFull();
302 }
303
304 QRectF rect = boundingRect();
305 const float devicePixelRatio = static_cast<float>( window()->screen()->devicePixelRatio() );
306 mImage = QImage( static_cast<int>( rect.width() * devicePixelRatio ), static_cast<int>( rect.height() * devicePixelRatio ), QImage::Format_ARGB32_Premultiplied );
307 mImage.setDevicePixelRatio( devicePixelRatio );
308 mImage.fill( Qt::transparent );
309
310 QPainter imagePainter( &mImage );
311 imagePainter.setRenderHint( QPainter::Antialiasing, true );
313 rc.setDevicePixelRatio( devicePixelRatio );
314
317
318 mPlotItem->calculateOptimisedIntervals( rc );
319 mPlotItem->render( rc );
320 imagePainter.end();
321
322 mDirty = true;
323 update();
324
325 if ( mForceRegenerationAfterCurrentJobCompletes )
326 {
327 mForceRegenerationAfterCurrentJobCompletes = false;
328 mCurrentJob->invalidateAllRefinableSources();
329 scheduleDeferredRegeneration();
330 }
331 else
332 {
333 emit isRenderingChanged();
334 }
335}
336
337void QgsQuickElevationProfileCanvas::onLayerProfileGenerationPropertyChanged()
338{
339 // TODO -- handle nicely when existing job is in progress
340 if ( !mCurrentJob || mCurrentJob->isActive() )
341 return;
342
343 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
344 if ( !properties )
345 return;
346
347 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
348 {
349 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
350 {
351 if ( mCurrentJob->invalidateResults( source ) )
352 scheduleDeferredRegeneration();
353 }
354 }
355}
356
357void QgsQuickElevationProfileCanvas::onLayerProfileRendererPropertyChanged()
358{
359 // TODO -- handle nicely when existing job is in progress
360 if ( !mCurrentJob || mCurrentJob->isActive() )
361 return;
362
363 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
364 if ( !properties )
365 return;
366
367 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
368 {
369 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
370 {
371 mCurrentJob->replaceSource( source );
372 }
373 if ( mPlotItem->redrawResults( layer->id() ) )
374 scheduleDeferredRedraw();
375 }
376}
377
378void QgsQuickElevationProfileCanvas::regenerateResultsForLayer()
379{
380 if ( !mCurrentJob )
381 return;
382
383 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() ) )
384 {
385 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
386 {
387 if ( mCurrentJob->invalidateResults( source ) )
388 scheduleDeferredRegeneration();
389 }
390 }
391}
392
393void QgsQuickElevationProfileCanvas::scheduleDeferredRegeneration()
394{
395 if ( !mDeferredRegenerationScheduled )
396 {
397 mDeferredRegenerationTimer->start( 1 );
398 mDeferredRegenerationScheduled = true;
399 }
400}
401
402void QgsQuickElevationProfileCanvas::scheduleDeferredRedraw()
403{
404 if ( !mDeferredRedrawScheduled )
405 {
406 mDeferredRedrawTimer->start( 1 );
407 mDeferredRedrawScheduled = true;
408 }
409}
410
411void QgsQuickElevationProfileCanvas::startDeferredRegeneration()
412{
413 if ( mCurrentJob && !mCurrentJob->isActive() )
414 {
415 emit activeJobCountChanged( 1 );
416 mCurrentJob->regenerateInvalidatedResults();
417 }
418 else if ( mCurrentJob )
419 {
420 mForceRegenerationAfterCurrentJobCompletes = true;
421 }
422
423 mDeferredRegenerationScheduled = false;
424}
425
426void QgsQuickElevationProfileCanvas::startDeferredRedraw()
427{
428 refresh();
429 mDeferredRedrawScheduled = false;
430}
431
432void QgsQuickElevationProfileCanvas::refineResults()
433{
434 if ( mCurrentJob )
435 {
437 context.setDpi( window()->screen()->physicalDotsPerInch() * window()->screen()->devicePixelRatio() );
438 const double plotDistanceRange = mPlotItem->xMaximum() - mPlotItem->xMinimum();
439 const double plotElevationRange = mPlotItem->yMaximum() - mPlotItem->yMinimum();
440 const double plotDistanceUnitsPerPixel = plotDistanceRange / mPlotItem->plotArea().width();
441
442 // we round the actual desired map error down to just one significant figure, to avoid tiny differences
443 // as the plot is panned
444 const double targetMaxErrorInMapUnits = MAX_ERROR_PIXELS * plotDistanceUnitsPerPixel;
445 const double factor = std::pow( 10.0, 1 - std::ceil( std::log10( std::fabs( targetMaxErrorInMapUnits ) ) ) );
446 const double roundedErrorInMapUnits = std::floor( targetMaxErrorInMapUnits * factor ) / factor;
447 context.setMaximumErrorMapUnits( roundedErrorInMapUnits );
448
449 context.setMapUnitsPerDistancePixel( plotDistanceUnitsPerPixel );
450
451 // for similar reasons we round the minimum distance off to multiples of the maximum error in map units
452 const double distanceMin = std::floor( ( mPlotItem->xMinimum() - plotDistanceRange * 0.05 ) / context.maximumErrorMapUnits() ) * context.maximumErrorMapUnits();
453 context.setDistanceRange( QgsDoubleRange( std::max( 0.0, distanceMin ),
454 mPlotItem->xMaximum() + plotDistanceRange * 0.05 ) );
455
456 context.setElevationRange( QgsDoubleRange( mPlotItem->yMinimum() - plotElevationRange * 0.05,
457 mPlotItem->yMaximum() + plotElevationRange * 0.05 ) );
458 mCurrentJob->setContext( context );
459 }
460 scheduleDeferredRegeneration();
461}
462
464{
465 if ( mProject == project )
466 return;
467
468 mProject = project;
469
470 emit projectChanged();
471}
472
474{
475 if ( mCrs == crs )
476 return;
477
478 mCrs = crs;
479
480 emit crsChanged();
481}
482
484{
485 if ( mProfileCurve.equals( curve ) )
486 return;
487
488 mProfileCurve = curve.type() == Qgis::GeometryType::Line ? curve : QgsGeometry();
489
490 emit profileCurveChanged();
491}
492
494{
495 if ( mTolerance == tolerance )
496 return;
497
498 mTolerance = tolerance;
499
500 emit toleranceChanged();
501}
502
504{
505 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
506 {
507 setupLayerConnections( layer, true );
508 }
509
510 if ( !mProject )
511 {
512 mLayers.clear();
513 return;
514 }
515
516 const QList<QgsMapLayer *> projectLayers = QgsProject::instance()->layers<QgsMapLayer *>().toList();
517 // sort layers so that types which are more likely to obscure others are rendered below
518 // e.g. vector features should be drawn above raster DEMS, or the DEM line may completely obscure
519 // the vector feature
520 QList<QgsMapLayer *> sortedLayers = QgsMapLayerUtils::sortLayersByType( projectLayers,
521 {
526 } );
527
528 // filter list, removing null layers and invalid layers
529 auto filteredList = sortedLayers;
530 filteredList.erase( std::remove_if( filteredList.begin(), filteredList.end(),
531 []( QgsMapLayer * layer )
532 {
533 return !layer || !layer->isValid() || !layer->elevationProperties() || !layer->elevationProperties()->showByDefaultInElevationProfilePlots();
534 } ),
535 filteredList.end() );
536
537 mLayers = _qgis_listRawToQPointer( filteredList );
538 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
539 {
540 setupLayerConnections( layer, false );
541 }
542}
543
544QList<QgsMapLayer *> QgsQuickElevationProfileCanvas::layers() const
545{
546 return _qgis_listQPointerToRaw( mLayers );
547}
548
549#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
550void QgsQuickElevationProfileCanvas::geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry )
551{
552 QQuickItem::geometryChanged( newGeometry, oldGeometry );
553#else
554void QgsQuickElevationProfileCanvas::geometryChange( const QRectF &newGeometry, const QRectF &oldGeometry )
555{
556 QQuickItem::geometryChange( newGeometry, oldGeometry );
557#endif
558 mPlotItem->updateRect();
559 mDirty = true;
560 refresh();
561}
562
563QSGNode *QgsQuickElevationProfileCanvas::updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * )
564{
565 if ( mDirty )
566 {
567 delete oldNode;
568 oldNode = nullptr;
569 mDirty = false;
570 }
571
572 QSGNode *newNode = nullptr;
573 if ( !mImage.isNull() )
574 {
575 QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>( oldNode );
576 if ( !node )
577 {
578 node = new QSGSimpleTextureNode();
579 QSGTexture *texture = window()->createTextureFromImage( mImage );
580 node->setTexture( texture );
581 node->setOwnsTexture( true );
582 }
583
584 QRectF rect( boundingRect() );
585 QSizeF size = mImage.size();
586 if ( !size.isEmpty() )
587 size /= window()->screen()->devicePixelRatio();
588
589 // Check for resizes that change the w/h ratio
590 if ( !rect.isEmpty() && !size.isEmpty() && !qgsDoubleNear( rect.width() / rect.height(), ( size.width() ) / static_cast<double>( size.height() ), 3 ) )
591 {
592 if ( qgsDoubleNear( rect.height(), mImage.height() ) )
593 {
594 rect.setHeight( rect.width() / size.width() * size.height() );
595 }
596 else
597 {
598 rect.setWidth( rect.height() / size.height() * size.width() );
599 }
600 }
601 node->setRect( rect );
602 newNode = node;
603 }
604 else
605 {
606 QSGSimpleRectNode *node = static_cast<QSGSimpleRectNode *>( oldNode );
607 if ( !node )
608 {
609 node = new QSGSimpleRectNode();
610 node->setColor( Qt::transparent );
611 }
612 node->setRect( boundingRect() );
613 newNode = node;
614 }
615
616 return newNode;
617}
618
620{
621 if ( !mCurrentJob )
622 return;
623
624 const QgsDoubleRange zRange = mCurrentJob->zRange();
625
626 if ( zRange.upper() < zRange.lower() )
627 {
628 // invalid range, e.g. no features found in plot!
629 mPlotItem->setYMinimum( 0 );
630 mPlotItem->setYMaximum( 10 );
631 }
632 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
633 {
634 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
635 mPlotItem->setYMinimum( zRange.lower() - 5 );
636 mPlotItem->setYMaximum( zRange.lower() + 5 );
637 }
638 else
639 {
640 // add 5% margin to height range
641 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
642 mPlotItem->setYMinimum( zRange.lower() - margin );
643 mPlotItem->setYMaximum( zRange.upper() + margin );
644 }
645
646 const double profileLength = mProfileCurve.get()->length();
647 mPlotItem->setXMinimum( 0 );
648 // just 2% margin to max distance -- any more is overkill and wasted space
649 mPlotItem->setXMaximum( profileLength * 1.02 );
650
651 refineResults();
652}
653
655{
656 if ( !mCurrentJob )
657 return;
658
659 const QgsDoubleRange zRange = mCurrentJob->zRange();
660 double xLength = mProfileCurve.get()->length();
661 double yLength = zRange.upper() - zRange.lower();
662 qDebug() << yLength;
663 if ( yLength < 0.0 )
664 {
665 // invalid range, e.g. no features found in plot!
666 mPlotItem->setYMinimum( 0 );
667 mPlotItem->setYMaximum( 10 );
668
669 mPlotItem->setXMinimum( 0 );
670 // just 2% margin to max distance -- any more is overkill and wasted space
671 mPlotItem->setXMaximum( xLength * 1.02 );
672 }
673 else
674 {
675 double yInRatioLength = xLength * mPlotItem->size().height() / mPlotItem->size().width();
676 double xInRatioLength = yLength * mPlotItem->size().width() / mPlotItem->size().height();
677 if ( yInRatioLength > yLength )
678 {
679 qDebug() << "yInRatioLength";
680 mPlotItem->setYMinimum( zRange.lower() - ( yInRatioLength / 2 ) );
681 qDebug() << mPlotItem->yMinimum();
682 mPlotItem->setYMaximum( zRange.upper() + ( yInRatioLength / 2 ) );
683 qDebug() << mPlotItem->yMaximum();
684
685 mPlotItem->setXMinimum( 0 );
686 // just 2% margin to max distance -- any more is overkill and wasted space
687 mPlotItem->setXMaximum( xLength * 1.02 );
688 }
689 else
690 {
691 qDebug() << "xInRatioLength";
692 // add 5% margin to height range
693 const double margin = yLength * 0.05;
694 mPlotItem->setYMinimum( zRange.lower() - margin );
695 qDebug() << mPlotItem->yMinimum();
696 mPlotItem->setYMaximum( zRange.upper() + margin );
697 qDebug() << mPlotItem->yMaximum();
698
699 mPlotItem->setXMinimum( 0 - ( xInRatioLength / 2 ) );
700 mPlotItem->setXMaximum( xLength + ( xInRatioLength / 2 ) );
701 }
702 }
703
704 refineResults();
705}
706
707void QgsQuickElevationProfileCanvas::setVisiblePlotRange( double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation )
708{
709 mPlotItem->setYMinimum( minimumElevation );
710 mPlotItem->setYMaximum( maximumElevation );
711 mPlotItem->setXMinimum( minimumDistance );
712 mPlotItem->setXMaximum( maximumDistance );
713 refineResults();
714}
715
717{
718 return QgsDoubleRange( mPlotItem->xMinimum(), mPlotItem->xMaximum() );
719}
720
722{
723 return QgsDoubleRange( mPlotItem->yMinimum(), mPlotItem->yMaximum() );
724}
725
727{
729 if ( mCurrentJob )
730 {
731 mPlotItem->setRenderer( nullptr );
732 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
733 mCurrentJob->deleteLater();
734 mCurrentJob = nullptr;
735 }
736
737 mZoomFullWhenJobFinished = true;
738
739 mImage = QImage();
740 mDirty = true;
741 update();
742}
@ Group
Composite group layer. Added in QGIS 3.24.
@ Plugin
Plugin based layer.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ Vector
Vector layer.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ Mesh
Mesh layer. Added in QGIS 3.2.
@ Raster
Raster layer.
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Base class for 2-dimensional plot/chart/graphs.
Definition: qgsplot.h:278
void calculateOptimisedIntervals(QgsRenderContext &context)
Automatically sets the grid and label intervals to optimal values for display in the given render con...
Definition: qgsplot.cpp:611
double yMaximum() const
Returns the maximum value of the y axis.
Definition: qgsplot.h:390
void setSize(QSizeF size)
Sets the overall size of the plot (including titles and over components which sit outside the plot ar...
Definition: qgsplot.cpp:491
double xMaximum() const
Returns the maximum value of the x axis.
Definition: qgsplot.h:376
void setYMaximum(double maximum)
Sets the maximum value of the y axis.
Definition: qgsplot.h:397
double xMinimum() const
Returns the minimum value of the x axis.
Definition: qgsplot.h:348
double yMinimum() const
Returns the minimum value of the y axis.
Definition: qgsplot.h:362
QRectF interiorPlotArea(QgsRenderContext &context) const
Returns the area of the plot which corresponds to the actual plot content (excluding all titles and o...
Definition: qgsplot.cpp:496
void setYMinimum(double minimum)
Sets the minimum value of the y axis.
Definition: qgsplot.h:369
virtual void renderContent(QgsRenderContext &context, const QRectF &plotArea)
Renders the plot content.
Definition: qgsplot.cpp:479
virtual double length() const
Returns the planar, 2-dimensional length of the geometry.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
Interface for classes which can generate elevation profiles.
virtual QgsAbstractTerrainProvider * clone() const =0
Creates a clone of the provider and returns the new object.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
Abstract base class for curved geometry type.
Definition: qgscurve.h:35
QgsRange which stores a range of double values.
Definition: qgsrange.h:231
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
bool equals(const QgsGeometry &geometry) const
Test if this geometry is exactly equal to another geometry.
Qgis::GeometryType type
Definition: qgsgeometry.h:165
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
Base class for storage of map layer elevation properties.
void profileGenerationPropertyChanged()
Emitted when any of the elevation properties which relate solely to generation of elevation profiles ...
void profileRenderingPropertyChanged()
Emitted when any of the elevation properties which relate solely to presentation of elevation results...
static QList< QgsMapLayer * > sortLayersByType(const QList< QgsMapLayer * > &layers, const QList< Qgis::LayerType > &order)
Sorts a list of map layers by their layer type, respecting the order of types specified.
Base class for all map layer types.
Definition: qgsmaplayer.h:75
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Qgis::LayerType type
Definition: qgsmaplayer.h:82
void dataChanged()
Data of layer changed.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Definition: qgsmaplayer.h:1633
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
Encapsulates the context in which an elevation profile is to be generated.
double maximumErrorMapUnits() const
Returns the maximum allowed error in the generated result, in profile curve map units.
void setDpi(double dpi)
Sets the dpi (dots per inch) for the profie, to be used in size conversions.
void setMaximumErrorMapUnits(double error)
Sets the maximum allowed error in the generated result, in profile curve map units.
void setDistanceRange(const QgsDoubleRange &range)
Sets the range of distances to include in the generation.
void setElevationRange(const QgsDoubleRange &range)
Sets the range of elevations to include in the generation.
void setMapUnitsPerDistancePixel(double units)
Sets the number of map units per pixel in the distance dimension.
Generates and renders elevation profile plots.
void regenerateInvalidatedResults()
Starts a background regeneration of any invalidated results and immediately returns.
void invalidateAllRefinableSources()
Invalidates previous results from all refinable sources.
void cancelGeneration()
Stop the generation job - does not return until the job has terminated.
void startGeneration()
Start the generation job and immediately return.
QgsDoubleRange zRange() const
Returns the limits of the retrieved elevation values.
bool isActive() const
Returns true if the generation job is currently running in background.
bool invalidateResults(QgsAbstractProfileSource *source)
Invalidates the profile results from the source with matching ID.
void replaceSource(QgsAbstractProfileSource *source)
Replaces the existing source with matching ID.
void setContext(const QgsProfileGenerationContext &context)
Sets the context in which the profile generation will occur.
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.
QgsAbstractTerrainProvider * terrainProvider()
Returns the project's terrain provider.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:107
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
const QgsProjectElevationProperties * elevationProperties() const
Returns the project's elevation properties, which contains the project's elevation related settings.
QVector< T > layers() const
Returns a list of registered map layers with a specified layer type.
Definition: qgsproject.h:1181
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:113
This class implements a visual Qt Quick Item that does elevation profile rendering according to the c...
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override
void cancelJobs()
Cancel any rendering job in a blocking way.
void setTolerance(double tolerance)
Sets the profile tolerance (in crs() units).
QgsQuickElevationProfileCanvas(QQuickItem *parent=nullptr)
Constructor for QgsElevationProfileCanvas, with the specified parent widget.
void activeJobCountChanged(int count)
Emitted when the number of active background jobs changes.
void setProfileCurve(QgsGeometry curve)
Sets the profile curve geometry.
void crsChanged()
Emitted when the CRS linked to the profile curve geometry changes.
void setVisiblePlotRange(double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation)
Sets the visible area of the plot.
bool isRendering
The isRendering property is set to true while a rendering job is pending for this elevation profile c...
void setProject(QgsProject *project)
Sets the project associated with the profile.
void profileCurveChanged()
Emitted when the profile curve geometry changes.
QList< QgsMapLayer * > layers() const
Returns the list of layers included in the profile.
QgsDoubleRange visibleDistanceRange() const
Returns the distance range currently visible in the plot.
Q_INVOKABLE void refresh()
Triggers a complete regeneration of the profile, causing the profile extraction to perform in the bac...
QSGNode * updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) override
QgsDoubleRange visibleElevationRange() const
Returns the elevation range currently visible in the plot.
Q_INVOKABLE void populateLayersFromProject()
Populates the current profile with elevation-enabled layers from the associated project.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the crs associated with the map coordinates.
Q_INVOKABLE void clear()
Clears the current profile.
void projectChanged()
Emitted when the associated project changes.
void toleranceChanged()
Emitted when the tolerance changes.
Q_INVOKABLE void zoomFull()
Zooms to the full extent of the profile.
void isRenderingChanged()
The isRendering property is set to true while a rendering job is pending for this elevation profile c...
Q_INVOKABLE void zoomFullInRatio()
Zooms to the full extent of the profile while maintaining X and Y axes' length ratio.
T lower() const
Returns the lower bound of the range.
Definition: qgsrange.h:78
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:85
Contains information about the context of a rendering operation.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
void setDevicePixelRatio(float ratio)
Sets the device pixel ratio.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
Represents a vector layer which manages a vector based data sets.
void attributeValueChanged(QgsFeatureId fid, int idx, const QVariant &value)
Emitted whenever an attribute value change is done in the edit buffer.
void featureAdded(QgsFeatureId fid)
Emitted when a new feature has been added to the layer.
void featureDeleted(QgsFeatureId fid)
Emitted when a feature has been deleted.
void geometryChanged(QgsFeatureId fid, const QgsGeometry &geometry)
Emitted whenever a geometry change is done in the edit buffer.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207
const QgsCoordinateReferenceSystem & crs