QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
•All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 {
205 case Qgis::LayerType::Vector:
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 }
224 case Qgis::LayerType::Raster:
225 case Qgis::LayerType::Plugin:
226 case Qgis::LayerType::Mesh:
227 case Qgis::LayerType::VectorTile:
228 case Qgis::LayerType::Annotation:
229 case Qgis::LayerType::PointCloud:
230 case Qgis::LayerType::Group:
231 break;
232 }
233}
234
236{
237 return mCurrentJob && mCurrentJob->isActive();
238}
239
241{
242 if ( !mCrs.isValid() || !mProject || mProfileCurve.isEmpty() )
243 return;
244
245 if ( mCurrentJob )
246 {
247 mPlotItem->setRenderer( nullptr );
248 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
249 mCurrentJob->deleteLater();
250 mCurrentJob = nullptr;
251 }
252
253 QgsProfileRequest request( static_cast<QgsCurve *>( mProfileCurve.get()->clone() ) );
254 request.setCrs( mCrs );
255 request.setTolerance( mTolerance );
256 request.setTransformContext( mProject->transformContext() );
257 request.setTerrainProvider( mProject->elevationProperties()->terrainProvider() ? mProject->elevationProperties()->terrainProvider()->clone() : nullptr );
258
259 QgsExpressionContext context;
262 request.setExpressionContext( context );
263
264 const QList<QgsMapLayer *> layersToGenerate = layers();
265 QList<QgsAbstractProfileSource *> sources;
266 sources.reserve( layersToGenerate.size() );
267 for ( QgsMapLayer *layer : layersToGenerate )
268 {
269 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
270 sources.append( source );
271 }
272
273 mCurrentJob = new QgsProfilePlotRenderer( sources, request );
274 connect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
275
276 QgsProfileGenerationContext generationContext;
277 generationContext.setDpi( window()->screen()->physicalDotsPerInch() * window()->screen()->devicePixelRatio() );
278 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( mProfileCurve.get()->length() ) / mPlotItem->plotArea().width() );
279 generationContext.setMapUnitsPerDistancePixel( mProfileCurve.get()->length() / mPlotItem->plotArea().width() );
280 mCurrentJob->setContext( generationContext );
281
282 mPlotItem->updatePlot();
283 mCurrentJob->startGeneration();
284 mPlotItem->setRenderer( mCurrentJob );
285
286 emit activeJobCountChanged( 1 );
287 emit isRenderingChanged();
288}
289
290void QgsQuickElevationProfileCanvas::generationFinished()
291{
292 if ( !mCurrentJob )
293 return;
294
295 emit activeJobCountChanged( 0 );
296
297 if ( mZoomFullWhenJobFinished )
298 {
299 mZoomFullWhenJobFinished = false;
300 zoomFull();
301 }
302
303 QRectF rect = boundingRect();
304 const float devicePixelRatio = static_cast<float>( window()->screen()->devicePixelRatio() );
305 mImage = QImage( static_cast<int>( rect.width() * devicePixelRatio ), static_cast<int>( rect.height() * devicePixelRatio ), QImage::Format_ARGB32_Premultiplied );
306 mImage.setDevicePixelRatio( devicePixelRatio );
307 mImage.fill( Qt::transparent );
308
309 QPainter imagePainter( &mImage );
310 imagePainter.setRenderHint( QPainter::Antialiasing, true );
312 rc.setDevicePixelRatio( devicePixelRatio );
313
316
317 mPlotItem->calculateOptimisedIntervals( rc );
318 mPlotItem->render( rc );
319 imagePainter.end();
320
321 mDirty = true;
322 update();
323
324 if ( mForceRegenerationAfterCurrentJobCompletes )
325 {
326 mForceRegenerationAfterCurrentJobCompletes = false;
327 mCurrentJob->invalidateAllRefinableSources();
328 scheduleDeferredRegeneration();
329 }
330 else
331 {
332 emit isRenderingChanged();
333 }
334}
335
336void QgsQuickElevationProfileCanvas::onLayerProfileGenerationPropertyChanged()
337{
338 // TODO -- handle nicely when existing job is in progress
339 if ( !mCurrentJob || mCurrentJob->isActive() )
340 return;
341
342 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
343 if ( !properties )
344 return;
345
346 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
347 {
348 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
349 {
350 if ( mCurrentJob->invalidateResults( source ) )
351 scheduleDeferredRegeneration();
352 }
353 }
354}
355
356void QgsQuickElevationProfileCanvas::onLayerProfileRendererPropertyChanged()
357{
358 // TODO -- handle nicely when existing job is in progress
359 if ( !mCurrentJob || mCurrentJob->isActive() )
360 return;
361
362 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
363 if ( !properties )
364 return;
365
366 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
367 {
368 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
369 {
370 mCurrentJob->replaceSource( source );
371 }
372 if ( mPlotItem->redrawResults( layer->id() ) )
373 scheduleDeferredRedraw();
374 }
375}
376
377void QgsQuickElevationProfileCanvas::regenerateResultsForLayer()
378{
379 if ( !mCurrentJob )
380 return;
381
382 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() ) )
383 {
384 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
385 {
386 if ( mCurrentJob->invalidateResults( source ) )
387 scheduleDeferredRegeneration();
388 }
389 }
390}
391
392void QgsQuickElevationProfileCanvas::scheduleDeferredRegeneration()
393{
394 if ( !mDeferredRegenerationScheduled )
395 {
396 mDeferredRegenerationTimer->start( 1 );
397 mDeferredRegenerationScheduled = true;
398 }
399}
400
401void QgsQuickElevationProfileCanvas::scheduleDeferredRedraw()
402{
403 if ( !mDeferredRedrawScheduled )
404 {
405 mDeferredRedrawTimer->start( 1 );
406 mDeferredRedrawScheduled = true;
407 }
408}
409
410void QgsQuickElevationProfileCanvas::startDeferredRegeneration()
411{
412 if ( mCurrentJob && !mCurrentJob->isActive() )
413 {
414 emit activeJobCountChanged( 1 );
415 mCurrentJob->regenerateInvalidatedResults();
416 }
417 else if ( mCurrentJob )
418 {
419 mForceRegenerationAfterCurrentJobCompletes = true;
420 }
421
422 mDeferredRegenerationScheduled = false;
423}
424
425void QgsQuickElevationProfileCanvas::startDeferredRedraw()
426{
427 refresh();
428 mDeferredRedrawScheduled = false;
429}
430
431void QgsQuickElevationProfileCanvas::refineResults()
432{
433 if ( mCurrentJob )
434 {
436 context.setDpi( window()->screen()->physicalDotsPerInch() * window()->screen()->devicePixelRatio() );
437 const double plotDistanceRange = mPlotItem->xMaximum() - mPlotItem->xMinimum();
438 const double plotElevationRange = mPlotItem->yMaximum() - mPlotItem->yMinimum();
439 const double plotDistanceUnitsPerPixel = plotDistanceRange / mPlotItem->plotArea().width();
440
441 // we round the actual desired map error down to just one significant figure, to avoid tiny differences
442 // as the plot is panned
443 const double targetMaxErrorInMapUnits = MAX_ERROR_PIXELS * plotDistanceUnitsPerPixel;
444 const double factor = std::pow( 10.0, 1 - std::ceil( std::log10( std::fabs( targetMaxErrorInMapUnits ) ) ) );
445 const double roundedErrorInMapUnits = std::floor( targetMaxErrorInMapUnits * factor ) / factor;
446 context.setMaximumErrorMapUnits( roundedErrorInMapUnits );
447
448 context.setMapUnitsPerDistancePixel( plotDistanceUnitsPerPixel );
449
450 // for similar reasons we round the minimum distance off to multiples of the maximum error in map units
451 const double distanceMin = std::floor( ( mPlotItem->xMinimum() - plotDistanceRange * 0.05 ) / context.maximumErrorMapUnits() ) * context.maximumErrorMapUnits();
452 context.setDistanceRange( QgsDoubleRange( std::max( 0.0, distanceMin ),
453 mPlotItem->xMaximum() + plotDistanceRange * 0.05 ) );
454
455 context.setElevationRange( QgsDoubleRange( mPlotItem->yMinimum() - plotElevationRange * 0.05,
456 mPlotItem->yMaximum() + plotElevationRange * 0.05 ) );
457 mCurrentJob->setContext( context );
458 }
459 scheduleDeferredRegeneration();
460}
461
463{
464 if ( mProject == project )
465 return;
466
467 mProject = project;
468
469 emit projectChanged();
470}
471
473{
474 if ( mCrs == crs )
475 return;
476
477 mCrs = crs;
478
479 emit crsChanged();
480}
481
483{
484 if ( mProfileCurve.equals( curve ) )
485 return;
486
487 mProfileCurve = curve.type() == Qgis::GeometryType::Line ? curve : QgsGeometry();
488
489 emit profileCurveChanged();
490}
491
493{
494 if ( mTolerance == tolerance )
495 return;
496
497 mTolerance = tolerance;
498
499 emit toleranceChanged();
500}
501
503{
504 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
505 {
506 setupLayerConnections( layer, true );
507 }
508
509 if ( !mProject )
510 {
511 mLayers.clear();
512 return;
513 }
514
515 const QList<QgsMapLayer *> projectLayers = QgsProject::instance()->layers<QgsMapLayer *>().toList();
516 // sort layers so that types which are more likely to obscure others are rendered below
517 // e.g. vector features should be drawn above raster DEMS, or the DEM line may completely obscure
518 // the vector feature
519 QList<QgsMapLayer *> sortedLayers = QgsMapLayerUtils::sortLayersByType( projectLayers,
520 {
521 Qgis::LayerType::Raster,
522 Qgis::LayerType::Mesh,
523 Qgis::LayerType::Vector,
524 Qgis::LayerType::PointCloud
525 } );
526
527 // filter list, removing null layers and invalid layers
528 auto filteredList = sortedLayers;
529 filteredList.erase( std::remove_if( filteredList.begin(), filteredList.end(),
530 []( QgsMapLayer * layer )
531 {
532 return !layer || !layer->isValid() || !layer->elevationProperties() || !layer->elevationProperties()->showByDefaultInElevationProfilePlots();
533 } ),
534 filteredList.end() );
535
536 mLayers = _qgis_listRawToQPointer( filteredList );
537 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
538 {
539 setupLayerConnections( layer, false );
540 }
541}
542
543QList<QgsMapLayer *> QgsQuickElevationProfileCanvas::layers() const
544{
545 return _qgis_listQPointerToRaw( mLayers );
546}
547
548#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
549void QgsQuickElevationProfileCanvas::geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry )
550{
551 QQuickItem::geometryChanged( newGeometry, oldGeometry );
552#else
553void QgsQuickElevationProfileCanvas::geometryChange( const QRectF &newGeometry, const QRectF &oldGeometry )
554{
555 QQuickItem::geometryChange( newGeometry, oldGeometry );
556#endif
557 mPlotItem->updateRect();
558 mDirty = true;
559 refresh();
560}
561
562QSGNode *QgsQuickElevationProfileCanvas::updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * )
563{
564 if ( mDirty )
565 {
566 delete oldNode;
567 oldNode = nullptr;
568 mDirty = false;
569 }
570
571 QSGNode *newNode = nullptr;
572 if ( !mImage.isNull() )
573 {
574 QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>( oldNode );
575 if ( !node )
576 {
577 node = new QSGSimpleTextureNode();
578 QSGTexture *texture = window()->createTextureFromImage( mImage );
579 node->setTexture( texture );
580 node->setOwnsTexture( true );
581 }
582
583 QRectF rect( boundingRect() );
584 QSizeF size = mImage.size();
585 if ( !size.isEmpty() )
586 size /= window()->screen()->devicePixelRatio();
587
588 // Check for resizes that change the w/h ratio
589 if ( !rect.isEmpty() && !size.isEmpty() && !qgsDoubleNear( rect.width() / rect.height(), ( size.width() ) / static_cast<double>( size.height() ), 3 ) )
590 {
591 if ( qgsDoubleNear( rect.height(), mImage.height() ) )
592 {
593 rect.setHeight( rect.width() / size.width() * size.height() );
594 }
595 else
596 {
597 rect.setWidth( rect.height() / size.height() * size.width() );
598 }
599 }
600 node->setRect( rect );
601 newNode = node;
602 }
603 else
604 {
605 QSGSimpleRectNode *node = static_cast<QSGSimpleRectNode *>( oldNode );
606 if ( !node )
607 {
608 node = new QSGSimpleRectNode();
609 node->setColor( Qt::transparent );
610 }
611 node->setRect( boundingRect() );
612 newNode = node;
613 }
614
615 return newNode;
616}
617
619{
620 if ( !mCurrentJob )
621 return;
622
623 const QgsDoubleRange zRange = mCurrentJob->zRange();
624
625 if ( zRange.upper() < zRange.lower() )
626 {
627 // invalid range, e.g. no features found in plot!
628 mPlotItem->setYMinimum( 0 );
629 mPlotItem->setYMaximum( 10 );
630 }
631 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
632 {
633 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
634 mPlotItem->setYMinimum( zRange.lower() - 5 );
635 mPlotItem->setYMaximum( zRange.lower() + 5 );
636 }
637 else
638 {
639 // add 5% margin to height range
640 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
641 mPlotItem->setYMinimum( zRange.lower() - margin );
642 mPlotItem->setYMaximum( zRange.upper() + margin );
643 }
644
645 const double profileLength = mProfileCurve.get()->length();
646 mPlotItem->setXMinimum( 0 );
647 // just 2% margin to max distance -- any more is overkill and wasted space
648 mPlotItem->setXMaximum( profileLength * 1.02 );
649
650 refineResults();
651}
652
654{
655 if ( !mCurrentJob )
656 return;
657
658 const QgsDoubleRange zRange = mCurrentJob->zRange();
659 double xLength = mProfileCurve.get()->length();
660 double yLength = zRange.upper() - zRange.lower();
661 qDebug() << yLength;
662 if ( yLength < 0.0 )
663 {
664 // invalid range, e.g. no features found in plot!
665 mPlotItem->setYMinimum( 0 );
666 mPlotItem->setYMaximum( 10 );
667
668 mPlotItem->setXMinimum( 0 );
669 // just 2% margin to max distance -- any more is overkill and wasted space
670 mPlotItem->setXMaximum( xLength * 1.02 );
671 }
672 else
673 {
674 double yInRatioLength = xLength * mPlotItem->size().height() / mPlotItem->size().width();
675 double xInRatioLength = yLength * mPlotItem->size().width() / mPlotItem->size().height();
676 if ( yInRatioLength > yLength )
677 {
678 qDebug() << "yInRatioLength";
679 mPlotItem->setYMinimum( zRange.lower() - ( yInRatioLength / 2 ) );
680 qDebug() << mPlotItem->yMinimum();
681 mPlotItem->setYMaximum( zRange.upper() + ( yInRatioLength / 2 ) );
682 qDebug() << mPlotItem->yMaximum();
683
684 mPlotItem->setXMinimum( 0 );
685 // just 2% margin to max distance -- any more is overkill and wasted space
686 mPlotItem->setXMaximum( xLength * 1.02 );
687 }
688 else
689 {
690 qDebug() << "xInRatioLength";
691 // add 5% margin to height range
692 const double margin = yLength * 0.05;
693 mPlotItem->setYMinimum( zRange.lower() - margin );
694 qDebug() << mPlotItem->yMinimum();
695 mPlotItem->setYMaximum( zRange.upper() + margin );
696 qDebug() << mPlotItem->yMaximum();
697
698 mPlotItem->setXMinimum( 0 - ( xInRatioLength / 2 ) );
699 mPlotItem->setXMaximum( xLength + ( xInRatioLength / 2 ) );
700 }
701 }
702
703 refineResults();
704}
705
706void QgsQuickElevationProfileCanvas::setVisiblePlotRange( double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation )
707{
708 mPlotItem->setYMinimum( minimumElevation );
709 mPlotItem->setYMaximum( maximumElevation );
710 mPlotItem->setXMinimum( minimumDistance );
711 mPlotItem->setXMaximum( maximumDistance );
712 refineResults();
713}
714
716{
717 return QgsDoubleRange( mPlotItem->xMinimum(), mPlotItem->xMaximum() );
718}
719
721{
722 return QgsDoubleRange( mPlotItem->yMinimum(), mPlotItem->yMaximum() );
723}
724
726{
728 if ( mCurrentJob )
729 {
730 mPlotItem->setRenderer( nullptr );
731 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsQuickElevationProfileCanvas::generationFinished );
732 mCurrentJob->deleteLater();
733 mCurrentJob = nullptr;
734 }
735
736 mZoomFullWhenJobFinished = true;
737
738 mImage = QImage();
739 mDirty = true;
740 update();
741}
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:599
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:482
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:487
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:470
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:1533
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:484
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:1211
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: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.
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:3988
const QgsCoordinateReferenceSystem & crs