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