QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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 mRenderer->render( plotRc, plotArea.width(), plotArea.height(), xMinimum(), xMaximum(), yMinimum(), yMaximum(), source );
121 plotPainter.end();
122
123 mCachedImages.insert( source, plot );
124 }
125 rc.painter()->drawImage( static_cast<int>( plotArea.left() ), static_cast<int>( plotArea.top() ), plot );
126 }
127 }
128
129 private:
130 QgsQuickElevationProfileCanvas *mCanvas = nullptr;
131 QgsProfilePlotRenderer *mRenderer = nullptr;
132
133 QRectF mPlotArea;
134 QMap<QString, QImage> mCachedImages;
135};
137
138
140 : QQuickItem( parent )
141{
142 // updating the profile plot is deferred on a timer, so that we don't trigger it too often
143 mDeferredRegenerationTimer = new QTimer( this );
144 mDeferredRegenerationTimer->setSingleShot( true );
145 mDeferredRegenerationTimer->stop();
146 connect( mDeferredRegenerationTimer, &QTimer::timeout, this, &QgsQuickElevationProfileCanvas::startDeferredRegeneration );
147
148 mDeferredRedrawTimer = new QTimer( this );
149 mDeferredRedrawTimer->setSingleShot( true );
150 mDeferredRedrawTimer->stop();
151 connect( mDeferredRedrawTimer, &QTimer::timeout, this, &QgsQuickElevationProfileCanvas::startDeferredRedraw );
152
153 mPlotItem = new QgsElevationProfilePlotItem( this );
154
155 setTransformOrigin( QQuickItem::TopLeft );
156 setFlags( QQuickItem::ItemHasContents );
157}
158
160{
161 if ( mCurrentJob )
162 {
163 mPlotItem->setRenderer( nullptr );
164 mCurrentJob->deleteLater();
165 mCurrentJob = nullptr;
166 }
167}
168
170{
171 if ( mCurrentJob )
172 {
173 mPlotItem->setRenderer( nullptr );
174 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
175 mCurrentJob->cancelGeneration();
176 mCurrentJob->deleteLater();
177 mCurrentJob = nullptr;
178 }
179}
180
181void QgsQuickElevationProfileCanvas::setupLayerConnections( QgsMapLayer *layer, bool isDisconnect )
182{
183 if ( !layer )
184 return;
185
186 if ( isDisconnect )
187 {
188 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsQuickElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
189 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsQuickElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
190 disconnect( layer, &QgsMapLayer::dataChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
191 }
192 else
193 {
194 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsQuickElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
195 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsQuickElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
196 connect( layer, &QgsMapLayer::dataChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
197 }
198
199 switch ( layer->type() )
200 {
201 case Qgis::LayerType::Vector:
202 {
203 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
204 if ( isDisconnect )
205 {
206 disconnect( vl, &QgsVectorLayer::featureAdded, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
207 disconnect( vl, &QgsVectorLayer::featureDeleted, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
208 disconnect( vl, &QgsVectorLayer::geometryChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
209 disconnect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
210 }
211 else
212 {
213 connect( vl, &QgsVectorLayer::featureAdded, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
214 connect( vl, &QgsVectorLayer::featureDeleted, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
215 connect( vl, &QgsVectorLayer::geometryChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
216 connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsQuickElevationProfileCanvas::regenerateResultsForLayer );
217 }
218 break;
219 }
220 case Qgis::LayerType::Raster:
221 case Qgis::LayerType::Plugin:
222 case Qgis::LayerType::Mesh:
223 case Qgis::LayerType::VectorTile:
224 case Qgis::LayerType::Annotation:
225 case Qgis::LayerType::PointCloud:
226 case Qgis::LayerType::Group:
227 break;
228 }
229}
230
232{
233 return mCurrentJob && mCurrentJob->isActive();
234}
235
237{
238 if ( !mCrs.isValid() || !mProject || mProfileCurve.isEmpty() )
239 return;
240
241 if ( mCurrentJob )
242 {
243 mPlotItem->setRenderer( nullptr );
244 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
245 mCurrentJob->deleteLater();
246 mCurrentJob = nullptr;
247 }
248
249 QgsProfileRequest request( static_cast<QgsCurve *>( mProfileCurve.get()->clone() ) );
250 request.setCrs( mCrs );
251 request.setTolerance( mTolerance );
252 request.setTransformContext( mProject->transformContext() );
253 request.setTerrainProvider( mProject->elevationProperties()->terrainProvider() ? mProject->elevationProperties()->terrainProvider()->clone() : nullptr );
254
255 QgsExpressionContext context;
258 request.setExpressionContext( context );
259
260 const QList<QgsMapLayer *> layersToGenerate = layers();
261 QList<QgsAbstractProfileSource *> sources;
262 sources.reserve( layersToGenerate.size() );
263 for ( QgsMapLayer *layer : layersToGenerate )
264 {
265 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
266 sources.append( source );
267 }
268
269 mCurrentJob = new QgsProfilePlotRenderer( sources, request );
270 connect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
271
272 QgsProfileGenerationContext generationContext;
273 generationContext.setDpi( window()->screen()->physicalDotsPerInch() * window()->screen()->devicePixelRatio() );
274 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( mProfileCurve.get()->length() ) / mPlotItem->plotArea().width() );
275 generationContext.setMapUnitsPerDistancePixel( mProfileCurve.get()->length() / mPlotItem->plotArea().width() );
276 mCurrentJob->setContext( generationContext );
277
278 mPlotItem->updatePlot();
279 mCurrentJob->startGeneration();
280 mPlotItem->setRenderer( mCurrentJob );
281
282 emit activeJobCountChanged( 1 );
283 emit isRenderingChanged();
284}
285
286void QgsQuickElevationProfileCanvas::generationFinished()
287{
288 if ( !mCurrentJob )
289 return;
290
291 emit activeJobCountChanged( 0 );
292
293 if ( mZoomFullWhenJobFinished )
294 {
295 mZoomFullWhenJobFinished = false;
296 zoomFull();
297 }
298
299 QRectF rect = boundingRect();
300 const float devicePixelRatio = static_cast<float>( window()->screen()->devicePixelRatio() );
301 mImage = QImage( static_cast<int>( rect.width() * devicePixelRatio ), static_cast<int>( rect.height() * devicePixelRatio ), QImage::Format_ARGB32_Premultiplied );
302 mImage.setDevicePixelRatio( devicePixelRatio );
303 mImage.fill( Qt::transparent );
304
305 QPainter imagePainter( &mImage );
306 imagePainter.setRenderHint( QPainter::Antialiasing, true );
308 rc.setDevicePixelRatio( devicePixelRatio );
309
312
313 mPlotItem->calculateOptimisedIntervals( rc );
314 mPlotItem->render( rc );
315 imagePainter.end();
316
317 mDirty = true;
318 update();
319
320 if ( mForceRegenerationAfterCurrentJobCompletes )
321 {
322 mForceRegenerationAfterCurrentJobCompletes = false;
323 mCurrentJob->invalidateAllRefinableSources();
324 scheduleDeferredRegeneration();
325 }
326 else
327 {
328 emit isRenderingChanged();
329 }
330}
331
332void QgsQuickElevationProfileCanvas::onLayerProfileGenerationPropertyChanged()
333{
334 // TODO -- handle nicely when existing job is in progress
335 if ( !mCurrentJob || mCurrentJob->isActive() )
336 return;
337
338 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
339 if ( !properties )
340 return;
341
342 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
343 {
344 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
345 {
346 if ( mCurrentJob->invalidateResults( source ) )
347 scheduleDeferredRegeneration();
348 }
349 }
350}
351
352void QgsQuickElevationProfileCanvas::onLayerProfileRendererPropertyChanged()
353{
354 // TODO -- handle nicely when existing job is in progress
355 if ( !mCurrentJob || mCurrentJob->isActive() )
356 return;
357
358 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
359 if ( !properties )
360 return;
361
362 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
363 {
364 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
365 {
366 mCurrentJob->replaceSource( source );
367 }
368 if ( mPlotItem->redrawResults( layer->id() ) )
369 scheduleDeferredRedraw();
370 }
371}
372
373void QgsQuickElevationProfileCanvas::regenerateResultsForLayer()
374{
375 if ( !mCurrentJob )
376 return;
377
378 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() ) )
379 {
380 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
381 {
382 if ( mCurrentJob->invalidateResults( source ) )
383 scheduleDeferredRegeneration();
384 }
385 }
386}
387
388void QgsQuickElevationProfileCanvas::scheduleDeferredRegeneration()
389{
390 if ( !mDeferredRegenerationScheduled )
391 {
392 mDeferredRegenerationTimer->start( 1 );
393 mDeferredRegenerationScheduled = true;
394 }
395}
396
397void QgsQuickElevationProfileCanvas::scheduleDeferredRedraw()
398{
399 if ( !mDeferredRedrawScheduled )
400 {
401 mDeferredRedrawTimer->start( 1 );
402 mDeferredRedrawScheduled = true;
403 }
404}
405
406void QgsQuickElevationProfileCanvas::startDeferredRegeneration()
407{
408 if ( mCurrentJob && !mCurrentJob->isActive() )
409 {
410 emit activeJobCountChanged( 1 );
411 mCurrentJob->regenerateInvalidatedResults();
412 }
413 else if ( mCurrentJob )
414 {
415 mForceRegenerationAfterCurrentJobCompletes = true;
416 }
417
418 mDeferredRegenerationScheduled = false;
419}
420
421void QgsQuickElevationProfileCanvas::startDeferredRedraw()
422{
423 refresh();
424 mDeferredRedrawScheduled = false;
425}
426
427void QgsQuickElevationProfileCanvas::refineResults()
428{
429 if ( mCurrentJob )
430 {
432 context.setDpi( window()->screen()->physicalDotsPerInch() * window()->screen()->devicePixelRatio() );
433 const double plotDistanceRange = mPlotItem->xMaximum() - mPlotItem->xMinimum();
434 const double plotElevationRange = mPlotItem->yMaximum() - mPlotItem->yMinimum();
435 const double plotDistanceUnitsPerPixel = plotDistanceRange / mPlotItem->plotArea().width();
436
437 // we round the actual desired map error down to just one significant figure, to avoid tiny differences
438 // as the plot is panned
439 const double targetMaxErrorInMapUnits = MAX_ERROR_PIXELS * plotDistanceUnitsPerPixel;
440 const double factor = std::pow( 10.0, 1 - std::ceil( std::log10( std::fabs( targetMaxErrorInMapUnits ) ) ) );
441 const double roundedErrorInMapUnits = std::floor( targetMaxErrorInMapUnits * factor ) / factor;
442 context.setMaximumErrorMapUnits( roundedErrorInMapUnits );
443
444 context.setMapUnitsPerDistancePixel( plotDistanceUnitsPerPixel );
445
446 // for similar reasons we round the minimum distance off to multiples of the maximum error in map units
447 const double distanceMin = std::floor( ( mPlotItem->xMinimum() - plotDistanceRange * 0.05 ) / context.maximumErrorMapUnits() ) * context.maximumErrorMapUnits();
448 context.setDistanceRange( QgsDoubleRange( std::max( 0.0, distanceMin ),
449 mPlotItem->xMaximum() + plotDistanceRange * 0.05 ) );
450
451 context.setElevationRange( QgsDoubleRange( mPlotItem->yMinimum() - plotElevationRange * 0.05,
452 mPlotItem->yMaximum() + plotElevationRange * 0.05 ) );
453 mCurrentJob->setContext( context );
454 }
455 scheduleDeferredRegeneration();
456}
457
459{
460 if ( mProject == project )
461 return;
462
463 mProject = project;
464
465 emit projectChanged();
466}
467
469{
470 if ( mCrs == crs )
471 return;
472
473 mCrs = crs;
474
475 emit crsChanged();
476}
477
479{
480 if ( mProfileCurve.equals( curve ) )
481 return;
482
483 mProfileCurve = curve.type() == Qgis::GeometryType::Line ? curve : QgsGeometry();
484
485 emit profileCurveChanged();
486}
487
489{
490 if ( mTolerance == tolerance )
491 return;
492
493 mTolerance = tolerance;
494
495 emit toleranceChanged();
496}
497
499{
500 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
501 {
502 setupLayerConnections( layer, true );
503 }
504
505 if ( !mProject )
506 {
507 mLayers.clear();
508 return;
509 }
510
511 const QList<QgsMapLayer *> projectLayers = QgsProject::instance()->layers<QgsMapLayer *>().toList();
512 // sort layers so that types which are more likely to obscure others are rendered below
513 // e.g. vector features should be drawn above raster DEMS, or the DEM line may completely obscure
514 // the vector feature
515 QList<QgsMapLayer *> sortedLayers = QgsMapLayerUtils::sortLayersByType( projectLayers,
516 {
517 Qgis::LayerType::Raster,
518 Qgis::LayerType::Mesh,
519 Qgis::LayerType::Vector,
520 Qgis::LayerType::PointCloud
521 } );
522
523 // filter list, removing null layers and invalid layers
524 auto filteredList = sortedLayers;
525 filteredList.erase( std::remove_if( filteredList.begin(), filteredList.end(),
526 []( QgsMapLayer * layer )
527 {
528 return !layer || !layer->isValid() || !layer->elevationProperties() || !layer->elevationProperties()->showByDefaultInElevationProfilePlots();
529 } ),
530 filteredList.end() );
531
532 mLayers = _qgis_listRawToQPointer( filteredList );
533 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
534 {
535 setupLayerConnections( layer, false );
536 }
537}
538
539QList<QgsMapLayer *> QgsQuickElevationProfileCanvas::layers() const
540{
541 return _qgis_listQPointerToRaw( mLayers );
542}
543
544#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
545void QgsQuickElevationProfileCanvas::geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry )
546{
547 QQuickItem::geometryChanged( newGeometry, oldGeometry );
548#else
549void QgsQuickElevationProfileCanvas::geometryChange( const QRectF &newGeometry, const QRectF &oldGeometry )
550{
551 QQuickItem::geometryChange( newGeometry, oldGeometry );
552#endif
553 mPlotItem->updateRect();
554 mDirty = true;
555 refresh();
556}
557
558QSGNode *QgsQuickElevationProfileCanvas::updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * )
559{
560 if ( mDirty )
561 {
562 delete oldNode;
563 oldNode = nullptr;
564 mDirty = false;
565 }
566
567 QSGNode *newNode = nullptr;
568 if ( !mImage.isNull() )
569 {
570 QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>( oldNode );
571 if ( !node )
572 {
573 node = new QSGSimpleTextureNode();
574 QSGTexture *texture = window()->createTextureFromImage( mImage );
575 node->setTexture( texture );
576 node->setOwnsTexture( true );
577 }
578
579 QRectF rect( boundingRect() );
580 QSizeF size = mImage.size();
581 if ( !size.isEmpty() )
582 size /= window()->screen()->devicePixelRatio();
583
584 // Check for resizes that change the w/h ratio
585 if ( !rect.isEmpty() && !size.isEmpty() && !qgsDoubleNear( rect.width() / rect.height(), ( size.width() ) / static_cast<double>( size.height() ), 3 ) )
586 {
587 if ( qgsDoubleNear( rect.height(), mImage.height() ) )
588 {
589 rect.setHeight( rect.width() / size.width() * size.height() );
590 }
591 else
592 {
593 rect.setWidth( rect.height() / size.height() * size.width() );
594 }
595 }
596 node->setRect( rect );
597 newNode = node;
598 }
599 else
600 {
601 QSGSimpleRectNode *node = static_cast<QSGSimpleRectNode *>( oldNode );
602 if ( !node )
603 {
604 node = new QSGSimpleRectNode();
605 node->setColor( Qt::transparent );
606 }
607 node->setRect( boundingRect() );
608 newNode = node;
609 }
610
611 return newNode;
612}
613
615{
616 if ( !mCurrentJob )
617 return;
618
619 const QgsDoubleRange zRange = mCurrentJob->zRange();
620
621 if ( zRange.upper() < zRange.lower() )
622 {
623 // invalid range, e.g. no features found in plot!
624 mPlotItem->setYMinimum( 0 );
625 mPlotItem->setYMaximum( 10 );
626 }
627 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
628 {
629 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
630 mPlotItem->setYMinimum( zRange.lower() - 5 );
631 mPlotItem->setYMaximum( zRange.lower() + 5 );
632 }
633 else
634 {
635 // add 5% margin to height range
636 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
637 mPlotItem->setYMinimum( zRange.lower() - margin );
638 mPlotItem->setYMaximum( zRange.upper() + margin );
639 }
640
641 const double profileLength = mProfileCurve.get()->length();
642 mPlotItem->setXMinimum( 0 );
643 // just 2% margin to max distance -- any more is overkill and wasted space
644 mPlotItem->setXMaximum( profileLength * 1.02 );
645
646 refineResults();
647}
648
650{
651 if ( !mCurrentJob )
652 return;
653
654 const QgsDoubleRange zRange = mCurrentJob->zRange();
655 double xLength = mProfileCurve.get()->length();
656 double yLength = zRange.upper() - zRange.lower();
657 qDebug() << yLength;
658 if ( yLength < 0.0 )
659 {
660 // invalid range, e.g. no features found in plot!
661 mPlotItem->setYMinimum( 0 );
662 mPlotItem->setYMaximum( 10 );
663
664 mPlotItem->setXMinimum( 0 );
665 // just 2% margin to max distance -- any more is overkill and wasted space
666 mPlotItem->setXMaximum( xLength * 1.02 );
667 }
668 else
669 {
670 double yInRatioLength = xLength * mPlotItem->size().height() / mPlotItem->size().width();
671 double xInRatioLength = yLength * mPlotItem->size().width() / mPlotItem->size().height();
672 if ( yInRatioLength > yLength )
673 {
674 qDebug() << "yInRatioLength";
675 mPlotItem->setYMinimum( zRange.lower() - ( yInRatioLength / 2 ) );
676 qDebug() << mPlotItem->yMinimum();
677 mPlotItem->setYMaximum( zRange.upper() + ( yInRatioLength / 2 ) );
678 qDebug() << mPlotItem->yMaximum();
679
680 mPlotItem->setXMinimum( 0 );
681 // just 2% margin to max distance -- any more is overkill and wasted space
682 mPlotItem->setXMaximum( xLength * 1.02 );
683 }
684 else
685 {
686 qDebug() << "xInRatioLength";
687 // add 5% margin to height range
688 const double margin = yLength * 0.05;
689 mPlotItem->setYMinimum( zRange.lower() - margin );
690 qDebug() << mPlotItem->yMinimum();
691 mPlotItem->setYMaximum( zRange.upper() + margin );
692 qDebug() << mPlotItem->yMaximum();
693
694 mPlotItem->setXMinimum( 0 - ( xInRatioLength / 2 ) );
695 mPlotItem->setXMaximum( xLength + ( xInRatioLength / 2 ) );
696 }
697 }
698
699 refineResults();
700}
701
702void QgsQuickElevationProfileCanvas::setVisiblePlotRange( double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation )
703{
704 mPlotItem->setYMinimum( minimumElevation );
705 mPlotItem->setYMaximum( maximumElevation );
706 mPlotItem->setXMinimum( minimumDistance );
707 mPlotItem->setXMaximum( maximumDistance );
708 refineResults();
709}
710
712{
713 return QgsDoubleRange( mPlotItem->xMinimum(), mPlotItem->xMaximum() );
714}
715
717{
718 return QgsDoubleRange( mPlotItem->yMinimum(), mPlotItem->yMaximum() );
719}
720
722{
724 if ( mCurrentJob )
725 {
726 mPlotItem->setRenderer( nullptr );
727 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
728 mCurrentJob->deleteLater();
729 mCurrentJob = nullptr;
730 }
731
732 mZoomFullWhenJobFinished = true;
733
734 mImage = QImage();
735 mDirty = true;
736 update();
737}
Base class for 2-dimensional plot/chart/graphs.
Definition: qgsplot.h:235
void calculateOptimisedIntervals(QgsRenderContext &context)
Automatically sets the grid and label intervals to optimal values for display in the given render con...
Definition: qgsplot.cpp:424
double yMaximum() const
Returns the maximum value of the y axis.
Definition: qgsplot.h:347
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:370
double xMaximum() const
Returns the maximum value of the x axis.
Definition: qgsplot.h:333
void setYMaximum(double maximum)
Sets the maximum value of the y axis.
Definition: qgsplot.h:354
double yMinimum() const
Returns the minimum value of the y axis.
Definition: qgsplot.h:319
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:375
void setYMinimum(double minimum)
Sets the minimum value of the y axis.
Definition: qgsplot.h:326
virtual void renderContent(QgsRenderContext &context, const QRectF &plotArea)
Renders the plot content.
Definition: qgsplot.cpp:358
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:36
QgsRange which stores a range of double values.
Definition: qgsrange.h:203
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:164
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:167
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:73
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Qgis::LayerType type
Definition: qgsmaplayer.h:80
void dataChanged()
Data of layer changed.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Definition: qgsmaplayer.h:1530
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:105
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:477
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:1194
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:111
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:66
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:73
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.
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:3509
const QgsCoordinateReferenceSystem & crs