QGIS API Documentation 3.99.0-Master (a5475b57e34)
Loading...
Searching...
No Matches
qgselevationprofilecanvas.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgselevationprofilecanvas.cpp
3 -----------------
4 begin : March 2022
5 copyright : (C) 2022 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
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
20
23#include "qgsapplication.h"
24#include "qgscurve.h"
26#include "qgsfillsymbol.h"
27#include "qgsgeos.h"
30#include "qgsnumericformat.h"
31#include "qgsplot.h"
32#include "qgsplotcanvasitem.h"
33#include "qgspoint.h"
34#include "qgsprofilerenderer.h"
35#include "qgsprofilerequest.h"
36#include "qgsprofilesnapping.h"
39#include "qgsscreenhelper.h"
40#include "qgsterrainprovider.h"
41
42#include <QPalette>
43#include <QString>
44#include <QTimer>
45#include <QWheelEvent>
46
47#include "moc_qgselevationprofilecanvas.cpp"
48
49using namespace Qt::StringLiterals;
50
52class QgsElevationProfilePlotItem : public Qgs2DXyPlot, public QgsPlotCanvasItem
53{
54 public:
55 QgsElevationProfilePlotItem( QgsElevationProfileCanvas *canvas )
56 : QgsPlotCanvasItem( canvas )
57 {
58 setYMinimum( 0 );
59 setYMaximum( 100 );
60
62 }
63
64 void setRenderer( QgsProfilePlotRenderer *renderer )
65 {
66 mRenderer = renderer;
67 }
68
69 void updateRect()
70 {
71 mRect = mCanvas->rect();
72 setSize( mRect.size() );
73
74 prepareGeometryChange();
75 setPos( mRect.topLeft() );
76
77 mImage = QImage();
78 mCachedImages.clear();
79 mPlotArea = QRectF();
80 update();
81 }
82
83 void updatePlot()
84 {
85 mImage = QImage();
86 mCachedImages.clear();
87 mPlotArea = QRectF();
88 update();
89 }
90
91 bool redrawResults( const QString &sourceId )
92 {
93 auto it = mCachedImages.find( sourceId );
94 if ( it == mCachedImages.end() )
95 return false;
96
97 mCachedImages.erase( it );
98 mImage = QImage();
99 return true;
100 }
101
102 QRectF boundingRect() const override
103 {
104 return mRect;
105 }
106
107 QString distanceSuffix() const
108 {
109 switch ( mDistanceUnit )
110 {
159 return u" %1"_s.arg( QgsUnitTypes::toAbbreviatedString( mDistanceUnit ) );
160
162 return QObject::tr( "°" );
164 return QString();
165 }
167 }
168
169 void setXAxisUnits( Qgis::DistanceUnit unit )
170 {
171 mDistanceUnit = unit;
172 xAxis().setLabelSuffix( distanceSuffix() );
173 update();
174 }
175
176 QRectF plotArea()
177 {
178 if ( !mPlotArea.isNull() )
179 return mPlotArea;
180
181 // force immediate recalculation of plot area
182 QgsRenderContext context;
183 if ( !scene()->views().isEmpty() )
184 context.setScaleFactor( scene()->views().at( 0 )->logicalDpiX() / 25.4 );
185
186 QgsPlotRenderContext plotContext;
187 calculateOptimisedIntervals( context, plotContext );
188 mPlotArea = interiorPlotArea( context, plotContext );
189 return mPlotArea;
190 }
191
192 QgsProfilePoint canvasPointToPlotPoint( QPointF point )
193 {
194 const QRectF area = plotArea();
195 if ( !area.contains( point.x(), point.y() ) )
196 return QgsProfilePoint();
197
198 const double distance = ( point.x() - area.left() ) / area.width() * ( xMaximum() - xMinimum() ) * mXScaleFactor + xMinimum() * mXScaleFactor;
199 const double elevation = ( area.bottom() - point.y() ) / area.height() * ( yMaximum() - yMinimum() ) + yMinimum();
200 return QgsProfilePoint( distance, elevation );
201 }
202
203 QgsPointXY plotPointToCanvasPoint( const QgsProfilePoint &point )
204 {
205 if ( point.distance() < xMinimum() * mXScaleFactor || point.distance() > xMaximum() * mXScaleFactor || point.elevation() < yMinimum() || point.elevation() > yMaximum() )
206 return QgsPointXY();
207
208 const QRectF area = plotArea();
209
210 const double x = ( point.distance() - xMinimum() * mXScaleFactor ) / ( ( xMaximum() - xMinimum() ) * mXScaleFactor ) * ( area.width() ) + area.left();
211 const double y = area.bottom() - ( point.elevation() - yMinimum() ) / ( yMaximum() - yMinimum() ) * ( area.height() );
212 return QgsPointXY( x, y );
213 }
214
215 void renderContent( QgsRenderContext &rc, QgsPlotRenderContext &, const QRectF &plotArea, const QgsPlotData & ) override
216 {
217 mPlotArea = plotArea;
218
219 if ( !mRenderer )
220 return;
221
222 const double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
223
224 const QStringList sourceIds = mRenderer->sourceIds();
225 for ( const QString &source : sourceIds )
226 {
227 QImage plot;
228 auto it = mCachedImages.constFind( source );
229 if ( it != mCachedImages.constEnd() )
230 {
231 plot = it.value();
232 }
233 else
234 {
235 plot = mRenderer->renderToImage( plotArea.width() * pixelRatio, plotArea.height() * pixelRatio, xMinimum() * mXScaleFactor, xMaximum() * mXScaleFactor, yMinimum(), yMaximum(), source, pixelRatio );
236 plot.setDevicePixelRatio( pixelRatio );
237 mCachedImages.insert( source, plot );
238 }
239 rc.painter()->drawImage( QPointF( plotArea.left(), plotArea.top() ), plot );
240 }
241 }
242
244 void paint( QPainter *painter ) override
245 {
246 // cache rendering to an image, so we don't need to redraw the plot
247 if ( !mImage.isNull() )
248 {
249 painter->drawImage( QPointF( 0, 0 ), mImage );
250 }
251 else
252 {
253 const double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
254 mImage = QImage( mRect.width() * pixelRatio, mRect.height() * pixelRatio, QImage::Format_ARGB32_Premultiplied );
255 mImage.setDevicePixelRatio( pixelRatio );
256 mImage.fill( Qt::transparent );
257
258 QPainter imagePainter( &mImage );
259 imagePainter.setRenderHint( QPainter::Antialiasing, true );
260 QgsRenderContext rc = QgsRenderContext::fromQPainter( &imagePainter );
261 rc.setDevicePixelRatio( pixelRatio );
262
263 const double mapUnitsPerPixel = ( xMaximum() - xMinimum() ) * mXScaleFactor / plotArea().width();
264 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
265
268
269 QgsPlotRenderContext plotContext;
270 calculateOptimisedIntervals( rc, plotContext );
271 render( rc, plotContext );
272 imagePainter.end();
273
274 painter->drawImage( QPointF( 0, 0 ), mImage );
275 }
276 }
277
278 void setSubsectionsSymbol( QgsLineSymbol *symbol )
279 {
280 if ( mRenderer )
281 {
282 mRenderer->setSubsectionsSymbol( symbol );
283 updatePlot();
284 }
285 }
286
287 QgsProject *mProject = nullptr;
288 double mXScaleFactor = 1.0;
289
291
292 private:
293 QImage mImage;
294
295 QMap<QString, QImage> mCachedImages;
296
297 QRectF mRect;
298 QRectF mPlotArea;
299 QgsProfilePlotRenderer *mRenderer = nullptr;
300};
301
302class QgsElevationProfileCrossHairsItem : public QgsPlotCanvasItem
303{
304 public:
305 QgsElevationProfileCrossHairsItem( QgsElevationProfileCanvas *canvas, QgsElevationProfilePlotItem *plotItem )
306 : QgsPlotCanvasItem( canvas )
307 , mPlotItem( plotItem )
308 {
309 }
310
311 void updateRect()
312 {
313 mRect = mCanvas->rect();
314
315 prepareGeometryChange();
316 setPos( mRect.topLeft() );
317 update();
318 }
319
320 void setPoint( const QgsProfilePoint &point )
321 {
322 mPoint = point;
323 update();
324 }
325
326 QRectF boundingRect() const override
327 {
328 return mRect;
329 }
330
332 void paint( QPainter *painter ) override
333 {
334 const QgsPointXY crossHairPlotPoint = mPlotItem->plotPointToCanvasPoint( mPoint );
335 if ( crossHairPlotPoint.isEmpty() )
336 return;
337
338 painter->save();
339 painter->setBrush( Qt::NoBrush );
340 QPen crossHairPen;
341 crossHairPen.setCosmetic( true );
342 crossHairPen.setWidthF( 1 );
343 crossHairPen.setStyle( Qt::DashLine );
344 crossHairPen.setCapStyle( Qt::FlatCap );
345 const QPalette scenePalette = mPlotItem->scene()->palette();
346 QColor penColor = scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text );
347 penColor.setAlpha( 150 );
348 crossHairPen.setColor( penColor );
349 painter->setPen( crossHairPen );
350 painter->drawLine( QPointF( mPlotItem->plotArea().left(), crossHairPlotPoint.y() ), QPointF( mPlotItem->plotArea().right(), crossHairPlotPoint.y() ) );
351 painter->drawLine( QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().top() ), QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().bottom() ) );
352
353 // also render current point text
354 QgsNumericFormatContext numericContext;
355
356 const QString xCoordinateText = mPlotItem->xAxis().numericFormat()->formatDouble( mPoint.distance() / mPlotItem->mXScaleFactor, numericContext )
357 + mPlotItem->distanceSuffix();
358
359 const QString yCoordinateText = mPlotItem->yAxis().numericFormat()->formatDouble( mPoint.elevation(), numericContext );
360
361 QFont font;
362 const QFontMetrics fm( font );
363 const double height = fm.capHeight();
364 const double xWidth = fm.horizontalAdvance( xCoordinateText );
365 const double yWidth = fm.horizontalAdvance( yCoordinateText );
366 const double textAxisMargin = fm.horizontalAdvance( ' ' );
367
368 QPointF xCoordOrigin;
369 QPointF yCoordOrigin;
370
371 if ( mPoint.distance() < ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5 * mPlotItem->mXScaleFactor )
372 {
373 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
374 {
375 // render x coordinate on right top (left top align)
376 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
377 // render y coordinate on right top (right bottom align)
378 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
379 }
380 else
381 {
382 // render x coordinate on right bottom (left bottom align)
383 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
384 // render y coordinate on right bottom (right top align)
385 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
386 }
387 }
388 else
389 {
390 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
391 {
392 // render x coordinate on left top (right top align)
393 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
394 // render y coordinate on left top (left bottom align)
395 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
396 }
397 else
398 {
399 // render x coordinate on left bottom (right bottom align)
400 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
401 // render y coordinate on left bottom (left top align)
402 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
403 }
404 }
405
406 // semi opaque background color brush
407 QColor backgroundColor = mPlotItem->chartBackgroundSymbol()->color();
408 backgroundColor.setAlpha( 220 );
409 painter->setBrush( QBrush( backgroundColor ) );
410 painter->setPen( Qt::NoPen );
411 painter->drawRect( QRectF( xCoordOrigin.x() - textAxisMargin + 1, xCoordOrigin.y() - textAxisMargin - height + 1, xWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
412 painter->drawRect( QRectF( yCoordOrigin.x() - textAxisMargin + 1, yCoordOrigin.y() - textAxisMargin - height + 1, yWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
413
414 painter->setBrush( Qt::NoBrush );
415 painter->setPen( scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
416
417 painter->drawText( xCoordOrigin, xCoordinateText );
418 painter->drawText( yCoordOrigin, yCoordinateText );
419 painter->restore();
420 }
421
422 private:
423 QRectF mRect;
424 QgsProfilePoint mPoint;
425 QgsElevationProfilePlotItem *mPlotItem = nullptr;
426};
428
429
431 : QgsPlotCanvas( parent )
432{
433 mScreenHelper = new QgsScreenHelper( this );
434
435 mPlotItem = new QgsElevationProfilePlotItem( this );
436
437 // follow system color scheme by default
438 setBackgroundColor( QColor() );
439
440 mCrossHairsItem = new QgsElevationProfileCrossHairsItem( this, mPlotItem );
441 mCrossHairsItem->setZValue( 100 );
442 mCrossHairsItem->hide();
443
444 // updating the profile plot is deferred on a timer, so that we don't trigger it too often
445 mDeferredRegenerationTimer = new QTimer( this );
446 mDeferredRegenerationTimer->setSingleShot( true );
447 mDeferredRegenerationTimer->stop();
448 connect( mDeferredRegenerationTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRegeneration );
449
450 mDeferredRedrawTimer = new QTimer( this );
451 mDeferredRedrawTimer->setSingleShot( true );
452 mDeferredRedrawTimer->stop();
453 connect( mDeferredRedrawTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRedraw );
454
455 connect( QgsApplication::profileSourceRegistry(), &QgsProfileSourceRegistry::profileSourceRegistered, this, &QgsElevationProfileCanvas::setSourcesPrivate );
456 connect( QgsApplication::profileSourceRegistry(), &QgsProfileSourceRegistry::profileSourceUnregistered, this, &QgsElevationProfileCanvas::setSourcesPrivate );
457
458 setSourcesPrivate(); // Initialize with registered sources
459}
460
462{
463 if ( mCurrentJob )
464 {
465 mPlotItem->setRenderer( nullptr );
466 mCurrentJob->deleteLater();
467 mCurrentJob = nullptr;
468 }
469}
470
472{
473 if ( mCurrentJob )
474 {
475 mPlotItem->setRenderer( nullptr );
476 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
477 mCurrentJob->cancelGeneration();
478 mCurrentJob->deleteLater();
479 mCurrentJob = nullptr;
480 }
481}
482
484{
485 const double dxPercent = dx / mPlotItem->plotArea().width();
486 const double dyPercent = dy / mPlotItem->plotArea().height();
487
488 // these look backwards, but we are dragging the paper, not the view!
489 const double dxPlot = -dxPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
490 const double dyPlot = dyPercent * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
491
492 // no need to handle axis scale lock here, we aren't changing scales
493 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
494 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
495 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
496 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
497
498 refineResults();
499
500 mPlotItem->updatePlot();
501 emit plotAreaChanged();
502}
503
505{
506 if ( !mPlotItem->plotArea().contains( x, y ) )
507 return;
508
509 const double newCenterX = mPlotItem->xMinimum() + ( x - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
510 const double newCenterY = mPlotItem->yMinimum() + ( mPlotItem->plotArea().bottom() - y ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
511
512 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
513 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
514
515 // no need to handle axis scale lock here, we aren't changing scales
516 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
517 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
518 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
519 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
520
521 refineResults();
522
523 mPlotItem->updatePlot();
524 emit plotAreaChanged();
525}
526
528{
529 scalePlot( factor, factor );
530 emit plotAreaChanged();
531}
532
533QgsProfileSnapContext QgsElevationProfileCanvas::snapContext() const
534{
535 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
536 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
537 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
538
539 QgsProfileSnapContext context;
540 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
541 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
542 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
543 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
544 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
545 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) );
546
547 return context;
548}
549
550QgsProfileIdentifyContext QgsElevationProfileCanvas::identifyContext() const
551{
552 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
553 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
554 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
555
556 QgsProfileIdentifyContext context;
557 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
558 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
559 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
560 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
561 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
562 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) );
563
564 context.project = mProject;
565
566 return context;
567}
568
569void QgsElevationProfileCanvas::setupLayerConnections( QgsMapLayer *layer, bool isDisconnect )
570{
571 if ( isDisconnect )
572 {
573 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
574 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
575 disconnect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
576 }
577 else
578 {
579 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
580 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
581 connect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
582 }
583
584 switch ( layer->type() )
585 {
587 {
588 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
589 if ( isDisconnect )
590 {
591 disconnect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
592 disconnect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
593 disconnect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
594 disconnect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
595 }
596 else
597 {
598 connect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
599 connect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
600 connect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
601 connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
602 }
603 break;
604 }
613 break;
614 }
615}
616
617void QgsElevationProfileCanvas::adjustRangeForAxisScaleLock( double &xMinimum, double &xMaximum, double &yMinimum, double &yMaximum ) const
618{
619 // ensures that we always "zoom out" to match horizontal/vertical scales
620 const double horizontalScale = ( xMaximum - xMinimum ) / mPlotItem->plotArea().width();
621 const double verticalScale = ( yMaximum - yMinimum ) / mPlotItem->plotArea().height();
622
623 const double currentRatio = horizontalScale / verticalScale;
624
625 if ( currentRatio <= mLockedAxisScale )
626 {
627 const double height = horizontalScale * mPlotItem->plotArea().height() / mLockedAxisScale;
628 const double deltaHeight = ( yMaximum - yMinimum ) - height;
629 yMinimum += deltaHeight / 2;
630 yMaximum -= deltaHeight / 2;
631 }
632 else
633 {
634 const double width = verticalScale * mPlotItem->plotArea().width() * mLockedAxisScale;
635 const double deltaWidth = ( ( xMaximum - xMinimum ) - width );
636 xMinimum += deltaWidth / 2;
637 xMaximum -= deltaWidth / 2;
638 }
639}
640
642{
643 return mDistanceUnit;
644}
645
647{
648 mDistanceUnit = unit;
649 const double oldMin = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
650 const double oldMax = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
651 mPlotItem->mXScaleFactor = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
652 mPlotItem->setXAxisUnits( mDistanceUnit );
653 mPlotItem->setXMinimum( oldMin / mPlotItem->mXScaleFactor );
654 mPlotItem->setXMaximum( oldMax / mPlotItem->mXScaleFactor );
655 mPlotItem->updatePlot();
656 emit scaleChanged();
657}
658
660{
661 if ( !color.isValid() )
662 {
663 QPalette customPalette = qApp->palette();
664 const QColor baseColor = qApp->palette().color( QPalette::ColorRole::Base );
665 const QColor windowColor = qApp->palette().color( QPalette::ColorRole::Window );
666 customPalette.setColor( QPalette::ColorRole::Base, windowColor );
667 customPalette.setColor( QPalette::ColorRole::Window, baseColor );
668 setPalette( customPalette );
669 scene()->setPalette( customPalette );
670 }
671 else
672 {
673 // build custom palette
674 const bool isDarkTheme = color.lightnessF() < 0.5;
675 QPalette customPalette = qApp->palette();
676 customPalette.setColor( QPalette::ColorRole::Window, color );
677 if ( isDarkTheme )
678 {
679 customPalette.setColor( QPalette::ColorRole::Text, QColor( 255, 255, 255 ) );
680 customPalette.setColor( QPalette::ColorRole::Base, color.lighter( 120 ) );
681 }
682 else
683 {
684 customPalette.setColor( QPalette::ColorRole::Text, QColor( 0, 0, 0 ) );
685 customPalette.setColor( QPalette::ColorRole::Base, color.darker( 120 ) );
686 }
687
688 setPalette( customPalette );
689 scene()->setPalette( customPalette );
690 }
691
692 updateChartFromPalette();
693}
694
696{
697 return mLockAxisScales;
698}
699
701{
702 mLockAxisScales = lock;
703 if ( mLockAxisScales )
704 {
705 double xMinimum = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
706 double xMaximum = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
707 double yMinimum = mPlotItem->yMinimum();
708 double yMaximum = mPlotItem->yMaximum();
709 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
710 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
711 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
712 mPlotItem->setYMinimum( yMinimum );
713 mPlotItem->setYMaximum( yMaximum );
714
715 refineResults();
716 mPlotItem->updatePlot();
717 emit plotAreaChanged();
718 emit scaleChanged();
719 }
720}
721
723{
724 const double horizontalScale = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / mPlotItem->plotArea().width();
725 const double verticalScale = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / mPlotItem->plotArea().height();
726 return horizontalScale / verticalScale;
727}
728
730{
731 mLockedAxisScale = scale;
732
733 double xMinimum = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
734 double xMaximum = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
735 double yMinimum = mPlotItem->yMinimum();
736 double yMaximum = mPlotItem->yMaximum();
737 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
738 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
739 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
740 mPlotItem->setYMinimum( yMinimum );
741 mPlotItem->setYMaximum( yMaximum );
742
743 refineResults();
744 mPlotItem->updatePlot();
745 emit plotAreaChanged();
746 emit scaleChanged();
747}
748
750{
751 if ( !mCurrentJob || !mSnappingEnabled )
752 return QgsPointXY();
753
754 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
755
756 const QgsProfileSnapResult snappedPoint = mCurrentJob->snapPoint( plotPoint, snapContext() );
757 if ( !snappedPoint.isValid() )
758 return QgsPointXY();
759
760 return plotPointToCanvasPoint( snappedPoint.snappedPoint );
761}
762
763void QgsElevationProfileCanvas::scalePlot( double xFactor, double yFactor )
764{
765 if ( mLockAxisScales )
766 yFactor = xFactor;
767
768 const double currentWidth = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor;
769 const double currentHeight = mPlotItem->yMaximum() - mPlotItem->yMinimum();
770
771 const double newWidth = currentWidth / xFactor;
772 const double newHeight = currentHeight / yFactor;
773
774 const double currentCenterX = ( mPlotItem->xMinimum() + mPlotItem->xMaximum() ) * 0.5 * mPlotItem->mXScaleFactor;
775 const double currentCenterY = ( mPlotItem->yMinimum() + mPlotItem->yMaximum() ) * 0.5;
776
777 double xMinimum = currentCenterX - newWidth * 0.5;
778 double xMaximum = currentCenterX + newWidth * 0.5;
779 double yMinimum = currentCenterY - newHeight * 0.5;
780 double yMaximum = currentCenterY + newHeight * 0.5;
781 if ( mLockAxisScales )
782 {
783 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
784 }
785
786 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
787 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
788 mPlotItem->setYMinimum( yMinimum );
789 mPlotItem->setYMaximum( yMaximum );
790
791 refineResults();
792 mPlotItem->updatePlot();
793 emit plotAreaChanged();
794 emit scaleChanged();
795}
796
798{
799 const QRectF intersected = rect.intersected( mPlotItem->plotArea() );
800
801 double minX = ( intersected.left() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
802 double maxX = ( intersected.right() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
803 double minY = ( mPlotItem->plotArea().bottom() - intersected.bottom() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
804 double maxY = ( mPlotItem->plotArea().bottom() - intersected.top() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
805
806 if ( mLockAxisScales )
807 {
808 adjustRangeForAxisScaleLock( minX, maxX, minY, maxY );
809 }
810
811 mPlotItem->setXMinimum( minX / mPlotItem->mXScaleFactor );
812 mPlotItem->setXMaximum( maxX / mPlotItem->mXScaleFactor );
813 mPlotItem->setYMinimum( minY );
814 mPlotItem->setYMaximum( maxY );
815
816 refineResults();
817 mPlotItem->updatePlot();
818 emit plotAreaChanged();
819 emit scaleChanged();
820}
821
823{
824 //get mouse wheel zoom behavior settings
825 QgsSettings settings;
826 double zoomFactor = settings.value( u"qgis/zoom_factor"_s, 2 ).toDouble();
827 bool reverseZoom = settings.value( u"qgis/reverse_wheel_zoom"_s, false ).toBool();
828 bool zoomIn = reverseZoom ? event->angleDelta().y() < 0 : event->angleDelta().y() > 0;
829
830 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
831 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
832
833 if ( event->modifiers() & Qt::ControlModifier )
834 {
835 //holding ctrl while wheel zooming results in a finer zoom
836 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
837 }
838
839 //calculate zoom scale factor
840 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
841
842 QRectF viewportRect = mPlotItem->plotArea();
843
844 if ( viewportRect.contains( event->position() ) )
845 {
846 //adjust view center
847 const double oldCenterX = 0.5 * ( mPlotItem->xMaximum() + mPlotItem->xMinimum() );
848 const double oldCenterY = 0.5 * ( mPlotItem->yMaximum() + mPlotItem->yMinimum() );
849
850 const double eventPosX = ( event->position().x() - viewportRect.left() ) / viewportRect.width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
851 const double eventPosY = ( viewportRect.bottom() - event->position().y() ) / viewportRect.height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
852
853 const double newCenterX = eventPosX + ( ( oldCenterX - eventPosX ) * scaleFactor );
854 const double newCenterY = eventPosY + ( ( oldCenterY - eventPosY ) * scaleFactor );
855
856 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
857 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
858
859 // don't need to handle axis scale lock here, we are always changing axis by the same scale
860 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
861 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
862 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
863 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
864 }
865
866 //zoom plot
867 if ( zoomIn )
868 {
869 scalePlot( zoomFactor );
870 }
871 else
872 {
873 scalePlot( 1 / zoomFactor );
874 }
875 emit plotAreaChanged();
876 emit scaleChanged();
877}
878
880{
882 if ( e->isAccepted() )
883 {
884 mCrossHairsItem->hide();
885 return;
886 }
887
888 QgsProfilePoint plotPoint = canvasPointToPlotPoint( e->pos() );
889 if ( mCurrentJob && mSnappingEnabled && !plotPoint.isEmpty() )
890 {
891 const QgsProfileSnapResult snapResult = mCurrentJob->snapPoint( plotPoint, snapContext() );
892 if ( snapResult.isValid() )
893 plotPoint = snapResult.snappedPoint;
894 }
895
896 if ( plotPoint.isEmpty() )
897 {
898 mCrossHairsItem->hide();
899 }
900 else
901 {
902 mCrossHairsItem->setPoint( plotPoint );
903 mCrossHairsItem->show();
904 }
905 emit canvasPointHovered( e->pos(), plotPoint );
906}
907
909{
910 return mPlotItem->plotArea();
911}
912
914{
915 if ( !mProject || !profileCurve() )
916 return;
917
918 cancelJobs();
919
920 QgsProfileRequest request( profileCurve()->clone() );
921 request.setCrs( mCrs );
922 request.setTolerance( mTolerance );
923 request.setTransformContext( mProject->transformContext() );
924 request.setTerrainProvider( mProject->elevationProperties()->terrainProvider() ? mProject->elevationProperties()->terrainProvider()->clone() : nullptr );
925 QgsExpressionContext context;
928 request.setExpressionContext( context );
929
930 QList< QgsAbstractProfileSource *> sourcesToRender = sources();
931 std::reverse( sourcesToRender.begin(), sourcesToRender.end() ); // sources are rendered from bottom to top
932
933 mCurrentJob = new QgsProfilePlotRenderer( sourcesToRender, request );
934 connect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
935
936 QgsProfileGenerationContext generationContext;
937 generationContext.setDpi( mScreenHelper->screenDpi() );
938 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( mProfileCurve->length() ) / mPlotItem->plotArea().width() );
939 generationContext.setMapUnitsPerDistancePixel( mProfileCurve->length() / mPlotItem->plotArea().width() );
940 mCurrentJob->setContext( generationContext );
941
942 if ( mSubsectionsSymbol )
943 {
944 mCurrentJob->setSubsectionsSymbol( mSubsectionsSymbol->clone() );
945 }
946
947 mCurrentJob->startGeneration();
948 mPlotItem->setRenderer( mCurrentJob );
949
950 emit activeJobCountChanged( 1 );
951}
952
954{
955 mZoomFullWhenJobFinished = true;
956}
957
958void QgsElevationProfileCanvas::generationFinished()
959{
960 if ( !mCurrentJob )
961 return;
962
963 emit activeJobCountChanged( 0 );
964
965 if ( mZoomFullWhenJobFinished )
966 {
967 // we only zoom full for the initial generation
968 mZoomFullWhenJobFinished = false;
969 zoomFull();
970 }
971 else
972 {
973 // here we should invalidate cached results only for the layers which have been refined
974
975 // and if no layers are being refeined, don't invalidate anything
976
977 mPlotItem->updatePlot();
978 }
979
980 if ( mForceRegenerationAfterCurrentJobCompletes )
981 {
982 mForceRegenerationAfterCurrentJobCompletes = false;
983 mCurrentJob->invalidateAllRefinableSources();
984 scheduleDeferredRegeneration();
985 }
986}
987
988void QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged()
989{
990 // TODO -- handle nicely when existing job is in progress
991 if ( !mCurrentJob || mCurrentJob->isActive() )
992 return;
993
994 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
995 if ( !properties )
996 return;
997
998 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
999 {
1000 if ( QgsAbstractProfileSource *source = layer->profileSource() )
1001 {
1002 if ( mCurrentJob->invalidateResults( source ) )
1003 scheduleDeferredRegeneration();
1004 }
1005 }
1006}
1007
1008void QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged()
1009{
1010 // TODO -- handle nicely when existing job is in progress
1011 if ( !mCurrentJob || mCurrentJob->isActive() )
1012 return;
1013
1014 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
1015 if ( !properties )
1016 return;
1017
1018 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
1019 {
1020 if ( QgsAbstractProfileSource *source = layer->profileSource() )
1021 {
1022 mCurrentJob->replaceSource( source );
1023 }
1024 if ( mPlotItem->redrawResults( layer->id() ) )
1025 scheduleDeferredRedraw();
1026 }
1027}
1028
1029void QgsElevationProfileCanvas::regenerateResultsForLayer()
1030{
1031 if ( !mCurrentJob )
1032 return;
1033
1034 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() ) )
1035 {
1036 if ( QgsAbstractProfileSource *source = layer->profileSource() )
1037 {
1038 if ( mCurrentJob->invalidateResults( source ) )
1039 scheduleDeferredRegeneration();
1040 }
1041 }
1042}
1043
1044void QgsElevationProfileCanvas::scheduleDeferredRegeneration()
1045{
1046 if ( !mDeferredRegenerationScheduled )
1047 {
1048 mDeferredRegenerationTimer->start( 1 );
1049 mDeferredRegenerationScheduled = true;
1050 }
1051}
1052
1053void QgsElevationProfileCanvas::scheduleDeferredRedraw()
1054{
1055 if ( !mDeferredRedrawScheduled )
1056 {
1057 mDeferredRedrawTimer->start( 1 );
1058 mDeferredRedrawScheduled = true;
1059 }
1060}
1061
1062void QgsElevationProfileCanvas::startDeferredRegeneration()
1063{
1064 if ( mCurrentJob && !mCurrentJob->isActive() )
1065 {
1066 emit activeJobCountChanged( 1 );
1067 mCurrentJob->regenerateInvalidatedResults();
1068 }
1069 else if ( mCurrentJob )
1070 {
1071 mForceRegenerationAfterCurrentJobCompletes = true;
1072 }
1073
1074 mDeferredRegenerationScheduled = false;
1075}
1076
1077void QgsElevationProfileCanvas::startDeferredRedraw()
1078{
1079 mPlotItem->update();
1080 mDeferredRedrawScheduled = false;
1081}
1082
1083void QgsElevationProfileCanvas::refineResults()
1084{
1085 if ( mCurrentJob )
1086 {
1087 QgsProfileGenerationContext context;
1088 context.setDpi( mScreenHelper->screenDpi() );
1089 const double plotDistanceRange = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor;
1090 const double plotElevationRange = mPlotItem->yMaximum() - mPlotItem->yMinimum();
1091 const double plotDistanceUnitsPerPixel = plotDistanceRange / mPlotItem->plotArea().width();
1092
1093 // we round the actual desired map error down to just one significant figure, to avoid tiny differences
1094 // as the plot is panned
1095 const double targetMaxErrorInMapUnits = MAX_ERROR_PIXELS * plotDistanceUnitsPerPixel;
1096 const double factor = std::pow( 10.0, 1 - std::ceil( std::log10( std::fabs( targetMaxErrorInMapUnits ) ) ) );
1097 const double roundedErrorInMapUnits = std::floor( targetMaxErrorInMapUnits * factor ) / factor;
1098 context.setMaximumErrorMapUnits( roundedErrorInMapUnits );
1099
1100 context.setMapUnitsPerDistancePixel( plotDistanceUnitsPerPixel );
1101
1102 // for similar reasons we round the minimum distance off to multiples of the maximum error in map units
1103 const double distanceMin = std::floor( ( mPlotItem->xMinimum() * mPlotItem->mXScaleFactor - plotDistanceRange * 0.05 ) / context.maximumErrorMapUnits() ) * context.maximumErrorMapUnits();
1104 context.setDistanceRange( QgsDoubleRange( std::max( 0.0, distanceMin ), mPlotItem->xMaximum() * mPlotItem->mXScaleFactor + plotDistanceRange * 0.05 ) );
1105
1106 context.setElevationRange( QgsDoubleRange( mPlotItem->yMinimum() - plotElevationRange * 0.05, mPlotItem->yMaximum() + plotElevationRange * 0.05 ) );
1107 mCurrentJob->setContext( context );
1108 }
1109 scheduleDeferredRegeneration();
1110}
1111
1112void QgsElevationProfileCanvas::updateChartFromPalette()
1113{
1114 const QPalette chartPalette = palette();
1115 setBackgroundBrush( QBrush( chartPalette.color( QPalette::ColorRole::Base ) ) );
1116 {
1117 QgsTextFormat textFormat = mPlotItem->xAxis().textFormat();
1118 textFormat.setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
1119 mPlotItem->xAxis().setTextFormat( textFormat );
1120 mPlotItem->yAxis().setTextFormat( textFormat );
1121 }
1122 {
1123 std::unique_ptr<QgsFillSymbol> chartFill( mPlotItem->chartBackgroundSymbol()->clone() );
1124 chartFill->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Window ) );
1125 mPlotItem->setChartBackgroundSymbol( chartFill.release() );
1126 }
1127 {
1128 std::unique_ptr<QgsFillSymbol> chartBorder( mPlotItem->chartBorderSymbol()->clone() );
1129 chartBorder->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text ) );
1130 mPlotItem->setChartBorderSymbol( chartBorder.release() );
1131 }
1132 {
1133 std::unique_ptr<QgsLineSymbol> chartMajorSymbol( mPlotItem->xAxis().gridMajorSymbol()->clone() );
1134 QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
1135 c.setAlpha( 150 );
1136 chartMajorSymbol->setColor( c );
1137 mPlotItem->xAxis().setGridMajorSymbol( chartMajorSymbol->clone() );
1138 mPlotItem->yAxis().setGridMajorSymbol( chartMajorSymbol.release() );
1139 }
1140 {
1141 std::unique_ptr<QgsLineSymbol> chartMinorSymbol( mPlotItem->xAxis().gridMinorSymbol()->clone() );
1142 QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
1143 c.setAlpha( 50 );
1144 chartMinorSymbol->setColor( c );
1145 mPlotItem->xAxis().setGridMinorSymbol( chartMinorSymbol->clone() );
1146 mPlotItem->yAxis().setGridMinorSymbol( chartMinorSymbol.release() );
1147 }
1148 mPlotItem->updatePlot();
1149}
1150
1152{
1153 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1154 return QgsProfilePoint();
1155
1156 return mPlotItem->canvasPointToPlotPoint( point );
1157}
1158
1160{
1161 return mPlotItem->plotPointToCanvasPoint( point );
1162}
1163
1165{
1166 mProject = project;
1167 mPlotItem->mProject = project;
1168}
1169
1174
1176{
1177 mProfileCurve.reset( curve );
1178}
1179
1181{
1182 return mProfileCurve.get();
1183}
1184
1186{
1187 mTolerance = tolerance;
1188}
1189
1194
1195void QgsElevationProfileCanvas::setLayers( const QList<QgsMapLayer *> &layers )
1196{
1197 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
1198 {
1199 setupLayerConnections( layer, true );
1200 }
1201
1202 // filter list, removing null layers and invalid layers
1203 auto filteredList = layers;
1204 filteredList.erase( std::remove_if( filteredList.begin(), filteredList.end(), []( QgsMapLayer *layer ) {
1205 return !layer || !layer->isValid();
1206 } ),
1207 filteredList.end() );
1208
1209 mLayers = _qgis_listRawToQPointer( filteredList );
1210 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
1211 {
1212 setupLayerConnections( layer, false );
1213 }
1214}
1215
1216QList<QgsMapLayer *> QgsElevationProfileCanvas::layers() const
1217{
1218 return _qgis_listQPointerToRaw( mLayers );
1219}
1220
1221void QgsElevationProfileCanvas::setSources( const QList<QgsAbstractProfileSource *> &sources )
1222{
1223 mSources.clear();
1224 mSources.reserve( sources.count() );
1225 for ( QgsAbstractProfileSource *profileSource : sources )
1226 {
1227 if ( auto layer = dynamic_cast<QgsMapLayer *>( profileSource ) )
1228 {
1229 mSources << QgsWeakMapLayerPointer( layer );
1230 }
1231 else if ( QgsApplication::profileSourceRegistry()->findSourceById( profileSource->profileSourceId() ) )
1232 {
1233 mSources << profileSource;
1234 }
1235 }
1236}
1237
1238QList<QgsAbstractProfileSource *> QgsElevationProfileCanvas::sources() const
1239{
1240 if ( mSources.isEmpty() && !mLayers.isEmpty() )
1241 {
1242 // Legacy: If we have layers, extract their sources and return them.
1243 // We don't set mSources here, because we want the previous check to
1244 // continue failing if only layers are set.
1245 // TODO: Remove in QGIS 5.0.
1246 QList< QgsAbstractProfileSource * > sources;
1247 const QList<QgsMapLayer *> layersToGenerate = layers();
1248 sources.reserve( layersToGenerate.size() );
1249
1250 for ( QgsMapLayer *layer : layersToGenerate )
1251 {
1252 if ( QgsAbstractProfileSource *source = layer->profileSource() )
1253 sources << source;
1254 }
1255
1256 // More legacy: canvas layers are in the same direction as the renderer
1257 // expects, but since we reverse sources() (i.e., layers) before passing
1258 // them to the renderer, then we need to reverse them here first.
1259 std::reverse( sources.begin(), sources.end() );
1260 return sources;
1261 }
1262
1263 QList< QgsAbstractProfileSource * > sources;
1264 sources.reserve( mSources.count() );
1265 for ( const auto &source : mSources )
1266 {
1267 if ( const QgsWeakMapLayerPointer *weakLayerPointer = std::get_if< QgsWeakMapLayerPointer >( &source ) )
1268 {
1269 if ( QgsMapLayer *layer = weakLayerPointer->data() )
1270 {
1271 sources << layer->profileSource();
1272 }
1273 }
1274 else if ( auto profileSource = std::get_if< QgsAbstractProfileSource * >( &source ) )
1275 {
1276 if ( QgsApplication::profileSourceRegistry()->findSourceById( ( *profileSource )->profileSourceId() ) )
1277 {
1278 sources << *profileSource;
1279 }
1280 }
1281 }
1282
1283 return sources;
1284}
1285
1287{
1289
1290 if ( mLockAxisScales )
1291 {
1292 double xMinimum = mPlotItem->xMinimum();
1293 double xMaximum = mPlotItem->xMaximum();
1294 double yMinimum = mPlotItem->yMinimum();
1295 double yMaximum = mPlotItem->yMaximum();
1296 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1297 mPlotItem->setXMinimum( xMinimum );
1298 mPlotItem->setXMaximum( xMaximum );
1299 mPlotItem->setYMinimum( yMinimum );
1300 mPlotItem->setYMaximum( yMaximum );
1301 }
1302
1303 mPlotItem->updateRect();
1304 mCrossHairsItem->updateRect();
1305
1306 emit scaleChanged();
1307}
1308
1310{
1311 QgsPlotCanvas::paintEvent( event );
1312
1313 if ( !mFirstDrawOccurred )
1314 {
1315 // on first show we need to update the visible rect of the plot. (Not sure why this doesn't work in showEvent, but it doesn't).
1316 mFirstDrawOccurred = true;
1317 mPlotItem->updateRect();
1318 mCrossHairsItem->updateRect();
1319 }
1320}
1321
1323{
1324 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1325 return QgsPoint();
1326
1327 if ( !mProfileCurve )
1328 return QgsPoint();
1329
1330 const double dx = point.x() - mPlotItem->plotArea().left();
1331
1332 const double distanceAlongPlotPercent = dx / mPlotItem->plotArea().width();
1333 double distanceAlongCurveLength = distanceAlongPlotPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
1334
1335 std::unique_ptr<QgsPoint> mapXyPoint( mProfileCurve->interpolatePoint( distanceAlongCurveLength ) );
1336 if ( !mapXyPoint )
1337 return QgsPoint();
1338
1339 const double mapZ = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * ( mPlotItem->plotArea().bottom() - point.y() ) + mPlotItem->yMinimum();
1340
1341 return QgsPoint( mapXyPoint->x(), mapXyPoint->y(), mapZ );
1342}
1343
1345{
1346 if ( !mProfileCurve )
1347 return QgsPointXY();
1348
1349 QgsGeos geos( mProfileCurve.get() );
1350 QString error;
1351 const double distanceAlongCurve = geos.lineLocatePoint( point, &error );
1352
1353 const double distanceAlongCurveOnPlot = distanceAlongCurve - mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
1354 const double distanceAlongCurvePercent = distanceAlongCurveOnPlot / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor );
1355 const double distanceAlongPlotRect = distanceAlongCurvePercent * mPlotItem->plotArea().width();
1356
1357 const double canvasX = mPlotItem->plotArea().left() + distanceAlongPlotRect;
1358
1359 double canvasY = 0;
1360 if ( std::isnan( point.z() ) || point.z() < mPlotItem->yMinimum() )
1361 {
1362 canvasY = mPlotItem->plotArea().top();
1363 }
1364 else if ( point.z() > mPlotItem->yMaximum() )
1365 {
1366 canvasY = mPlotItem->plotArea().bottom();
1367 }
1368 else
1369 {
1370 const double yPercent = ( point.z() - mPlotItem->yMinimum() ) / ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
1371 canvasY = mPlotItem->plotArea().bottom() - mPlotItem->plotArea().height() * yPercent;
1372 }
1373
1374 return QgsPointXY( canvasX, canvasY );
1375}
1376
1378{
1379 if ( !mCurrentJob )
1380 return;
1381
1382 const QgsDoubleRange zRange = mCurrentJob->zRange();
1383
1384 double yMinimum = 0;
1385 double yMaximum = 0;
1386
1387 if ( zRange.upper() < zRange.lower() )
1388 {
1389 // invalid range, e.g. no features found in plot!
1390 yMinimum = 0;
1391 yMaximum = 10;
1392 }
1393 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
1394 {
1395 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
1396 yMinimum = zRange.lower() - 5;
1397 yMaximum = zRange.lower() + 5;
1398 }
1399 else
1400 {
1401 // add 5% margin to height range
1402 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
1403 yMinimum = zRange.lower() - margin;
1404 yMaximum = zRange.upper() + margin;
1405 }
1406
1407 const double profileLength = profileCurve()->length();
1408 double xMinimum = 0;
1409 // just 2% margin to max distance -- any more is overkill and wasted space
1410 double xMaximum = profileLength * 1.02;
1411
1412 if ( mLockAxisScales )
1413 {
1414 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1415 }
1416
1417 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
1418 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
1419 mPlotItem->setYMinimum( yMinimum );
1420 mPlotItem->setYMaximum( yMaximum );
1421
1422 refineResults();
1423 mPlotItem->updatePlot();
1424 emit plotAreaChanged();
1425 emit scaleChanged();
1426}
1427
1428void QgsElevationProfileCanvas::setVisiblePlotRange( double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation )
1429{
1430 if ( mLockAxisScales )
1431 {
1432 adjustRangeForAxisScaleLock( minimumDistance, maximumDistance, minimumElevation, maximumElevation );
1433 }
1434
1435 mPlotItem->setYMinimum( minimumElevation );
1436 mPlotItem->setYMaximum( maximumElevation );
1437 mPlotItem->setXMinimum( minimumDistance / mPlotItem->mXScaleFactor );
1438 mPlotItem->setXMaximum( maximumDistance / mPlotItem->mXScaleFactor );
1439 refineResults();
1440 mPlotItem->updatePlot();
1441 emit plotAreaChanged();
1442 emit scaleChanged();
1443}
1444
1446{
1447 return QgsDoubleRange( mPlotItem->xMinimum() * mPlotItem->mXScaleFactor, mPlotItem->xMaximum() * mPlotItem->mXScaleFactor );
1448}
1449
1451{
1452 return QgsDoubleRange( mPlotItem->yMinimum(), mPlotItem->yMaximum() );
1453}
1454
1456{
1457 return *mPlotItem;
1458}
1459
1461class QgsElevationProfilePlot : public Qgs2DXyPlot
1462{
1463 public:
1464 QgsElevationProfilePlot( QgsProfilePlotRenderer *renderer )
1465 : mRenderer( renderer )
1466 {
1467 }
1468
1469 void renderContent( QgsRenderContext &rc, QgsPlotRenderContext &, const QRectF &plotArea, const QgsPlotData & ) override
1470 {
1471 if ( !mRenderer )
1472 return;
1473
1474 rc.painter()->translate( plotArea.left(), plotArea.top() );
1475 mRenderer->render( rc, plotArea.width(), plotArea.height(), xMinimum() * mXScale, xMaximum() * mXScale, yMinimum(), yMaximum() );
1476 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
1477 }
1478
1479 double mXScale = 1;
1480
1481 private:
1482 QgsProfilePlotRenderer *mRenderer = nullptr;
1483};
1485
1486void QgsElevationProfileCanvas::render( QgsRenderContext &context, double width, double height, const Qgs2DXyPlot &plotSettings )
1487{
1488 if ( !mCurrentJob )
1489 return;
1490
1493
1494 QgsElevationProfilePlot profilePlot( mCurrentJob );
1495
1496 // quick and nasty way to transfer settings from another plot class -- in future we probably want to improve this, but let's let the API settle first...
1497 QDomDocument doc;
1498 QDomElement elem = doc.createElement( u"plot"_s );
1499 QgsReadWriteContext rwContext;
1500 plotSettings.writeXml( elem, doc, rwContext );
1501 profilePlot.readXml( elem, rwContext );
1502
1503 profilePlot.mXScale = mPlotItem->mXScaleFactor;
1504 profilePlot.xAxis().setLabelSuffix( mPlotItem->xAxis().labelSuffix() );
1505 profilePlot.xAxis().setLabelSuffixPlacement( mPlotItem->xAxis().labelSuffixPlacement() );
1506
1507 profilePlot.setSize( QSizeF( width, height ) );
1508 QgsPlotRenderContext plotContext;
1509 profilePlot.render( context, plotContext );
1510}
1511
1512QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( QPointF point )
1513{
1514 if ( !mCurrentJob )
1515 return {};
1516
1517 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
1518
1519 return mCurrentJob->identify( plotPoint, identifyContext() );
1520}
1521
1522QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( const QRectF &rect )
1523{
1524 if ( !mCurrentJob )
1525 return {};
1526
1527 const QgsProfilePoint topLeftPlotPoint = canvasPointToPlotPoint( rect.topLeft() );
1528 const QgsProfilePoint bottomRightPlotPoint = canvasPointToPlotPoint( rect.bottomRight() );
1529
1530 double distance1 = topLeftPlotPoint.distance();
1531 double distance2 = bottomRightPlotPoint.distance();
1532 if ( distance2 < distance1 )
1533 std::swap( distance1, distance2 );
1534
1535 double elevation1 = topLeftPlotPoint.elevation();
1536 double elevation2 = bottomRightPlotPoint.elevation();
1537 if ( elevation2 < elevation1 )
1538 std::swap( elevation1, elevation2 );
1539
1540 return mCurrentJob->identify( QgsDoubleRange( distance1, distance2 ), QgsDoubleRange( elevation1, elevation2 ), identifyContext() );
1541}
1542
1544{
1545 setProfileCurve( nullptr );
1546 cancelJobs();
1547 mPlotItem->updatePlot();
1548}
1549
1551{
1552 mSnappingEnabled = enabled;
1553}
1554
1556{
1557 mSubsectionsSymbol.reset( symbol );
1558 std::unique_ptr<QgsLineSymbol> plotItemSymbol( mSubsectionsSymbol ? mSubsectionsSymbol->clone() : nullptr );
1559 mPlotItem->setSubsectionsSymbol( plotItemSymbol.release() );
1560}
1561
1562void QgsElevationProfileCanvas::setSourcesPrivate()
1563{
1564 mSources.clear();
1565 mSources.reserve( QgsApplication::profileSourceRegistry()->profileSources().count() );
1566 for ( QgsAbstractProfileSource *source : QgsApplication::profileSourceRegistry()->profileSources() )
1567 mSources << source;
1568}
@ FirstAndLastLabels
Place suffix after the first and last label values only.
Definition qgis.h:3409
DistanceUnit
Units of distance.
Definition qgis.h:5135
@ YardsBritishSears1922Truncated
British yards (Sears 1922 truncated).
Definition qgis.h:5175
@ Feet
Imperial feet.
Definition qgis.h:5138
@ MilesUSSurvey
US Survey miles.
Definition qgis.h:5182
@ LinksBritishSears1922
British links (Sears 1922).
Definition qgis.h:5170
@ YardsBritishBenoit1895A
British yards (Benoit 1895 A).
Definition qgis.h:5173
@ LinksBritishBenoit1895A
British links (Benoit 1895 A).
Definition qgis.h:5167
@ Centimeters
Centimeters.
Definition qgis.h:5143
@ YardsIndian1975
Indian yards (1975).
Definition qgis.h:5181
@ FeetUSSurvey
US Survey feet.
Definition qgis.h:5165
@ Millimeters
Millimeters.
Definition qgis.h:5144
@ FeetBritishSears1922
British feet (Sears 1922).
Definition qgis.h:5158
@ YardsClarkes
Clarke's yards.
Definition qgis.h:5177
@ YardsIndian
Indian yards.
Definition qgis.h:5178
@ FeetBritishBenoit1895B
British feet (Benoit 1895 B).
Definition qgis.h:5156
@ Miles
Terrestrial miles.
Definition qgis.h:5141
@ LinksUSSurvey
US Survey links.
Definition qgis.h:5172
@ Meters
Meters.
Definition qgis.h:5136
@ ChainsUSSurvey
US Survey chains.
Definition qgis.h:5152
@ FeetClarkes
Clarke's feet.
Definition qgis.h:5159
@ Unknown
Unknown distance unit.
Definition qgis.h:5185
@ Yards
Imperial yards.
Definition qgis.h:5140
@ FeetBritish1936
British feet (1936).
Definition qgis.h:5154
@ FeetIndian1962
Indian feet (1962).
Definition qgis.h:5163
@ YardsBritishSears1922
British yards (Sears 1922).
Definition qgis.h:5176
@ FeetIndian1937
Indian feet (1937).
Definition qgis.h:5162
@ YardsIndian1937
Indian yards (1937).
Definition qgis.h:5179
@ Degrees
Degrees, for planar geographic CRS distance measurements.
Definition qgis.h:5142
@ ChainsBritishBenoit1895B
British chains (Benoit 1895 B).
Definition qgis.h:5148
@ LinksBritishSears1922Truncated
British links (Sears 1922 truncated).
Definition qgis.h:5169
@ ChainsBritishBenoit1895A
British chains (Benoit 1895 A).
Definition qgis.h:5147
@ YardsBritishBenoit1895B
British yards (Benoit 1895 B).
Definition qgis.h:5174
@ FeetBritish1865
British feet (1865).
Definition qgis.h:5153
@ YardsIndian1962
Indian yards (1962).
Definition qgis.h:5180
@ FeetBritishSears1922Truncated
British feet (Sears 1922 truncated).
Definition qgis.h:5157
@ MetersGermanLegal
German legal meter.
Definition qgis.h:5184
@ LinksBritishBenoit1895B
British links (Benoit 1895 B).
Definition qgis.h:5168
@ ChainsInternational
International chains.
Definition qgis.h:5146
@ Inches
Inches.
Definition qgis.h:5145
@ Fathoms
Fathoms.
Definition qgis.h:5183
@ LinksInternational
International links.
Definition qgis.h:5166
@ ChainsBritishSears1922Truncated
British chains (Sears 1922 truncated).
Definition qgis.h:5149
@ FeetIndian
Indian (geodetic) feet.
Definition qgis.h:5161
@ NauticalMiles
Nautical miles.
Definition qgis.h:5139
@ ChainsClarkes
Clarke's chains.
Definition qgis.h:5151
@ LinksClarkes
Clarke's links.
Definition qgis.h:5171
@ ChainsBritishSears1922
British chains (Sears 1922).
Definition qgis.h:5150
@ Kilometers
Kilometers.
Definition qgis.h:5137
@ FeetIndian1975
Indian feet (1975).
Definition qgis.h:5164
@ FeetGoldCoast
Gold Coast feet.
Definition qgis.h:5160
@ FeetBritishBenoit1895A
British feet (Benoit 1895 A).
Definition qgis.h:5155
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:212
@ Plugin
Plugin based layer.
Definition qgis.h:207
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:213
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:210
@ Vector
Vector layer.
Definition qgis.h:205
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:209
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:208
@ Raster
Raster layer.
Definition qgis.h:206
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:211
virtual void renderContent(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QRectF &plotArea, const QgsPlotData &plotData=QgsPlotData())
Renders the plot content.
Definition qgsplot.cpp:267
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:278
Base class for 2-dimensional plot/chart/graphs with an X and Y axes.
Definition qgsplot.h:661
double yMaximum() const
Returns the maximum value of the y axis.
Definition qgsplot.h:744
void setYMinimum(double minimum)
Sets the minimum value of the y axis.
Definition qgsplot.h:723
double xMinimum() const
Returns the minimum value of the x axis.
Definition qgsplot.h:702
void render(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData=QgsPlotData()) override
Renders the plot.
Definition qgsplot.cpp:420
void calculateOptimisedIntervals(QgsRenderContext &context, QgsPlotRenderContext &plotContext)
Automatically sets the grid and label intervals to optimal values for display in the given render con...
Definition qgsplot.cpp:966
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:758
double yMinimum() const
Returns the minimum value of the y axis.
Definition qgsplot.h:716
QRectF interiorPlotArea(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData=QgsPlotData()) const override
Returns the area of the plot which corresponds to the actual plot content (excluding all titles and o...
Definition qgsplot.cpp:795
void setYMaximum(double maximum)
Sets the maximum value of the y axis.
Definition qgsplot.h:751
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:368
double xMaximum() const
Returns the maximum value of the x axis.
Definition qgsplot.h:730
virtual double length() const
Returns the planar, 2-dimensional length of the geometry.
Interface for classes which can generate elevation profiles.
static QgsProfileSourceRegistry * profileSourceRegistry()
Returns registry of available profile source implementations.
Represents a coordinate reference system (CRS).
Abstract base class for curved geometry type.
Definition qgscurve.h:36
QgsRange which stores a range of double values.
Definition qgsrange.h:236
QgsDoubleRange visibleElevationRange() const
Returns the elevation range currently visible in the plot.
QgsCurve * profileCurve() const
Returns the profile curve.
void setTolerance(double tolerance)
Sets the profile tolerance (in crs() units).
void setSources(const QList< QgsAbstractProfileSource * > &sources)
Sets the list of sources to include in the profile.
void setLockAxisScales(bool lock)
Sets whether the distance and elevation scales are locked to each other.
void setProfileCurve(QgsCurve *curve)
Sets the profile curve.
void zoomToRect(const QRectF &rect) override
Zooms the plot to the specified rect in canvas units.
void activeJobCountChanged(int count)
Emitted when the number of active background jobs changes.
QgsElevationProfileCanvas(QWidget *parent=nullptr)
Constructor for QgsElevationProfileCanvas, with the specified parent widget.
void scalePlot(double factor) override
Scales the plot by a specified scale factor.
void paintEvent(QPaintEvent *event) override
QgsDoubleRange visibleDistanceRange() const
Returns the distance range currently visible in the plot.
void cancelJobs() override
Cancel any rendering job, in a blocking way.
QgsCoordinateReferenceSystem crs() const override
Returns the coordinate reference system (CRS) for map coordinates used by the canvas.
void clear()
Clears the current profile.
void setDistanceUnit(Qgis::DistanceUnit unit)
Sets the distance unit used by the canvas.
QgsProfilePoint canvasPointToPlotPoint(QPointF point) const
Converts a canvas point to the equivalent plot point.
void setBackgroundColor(const QColor &color)
Sets the background color to use for the profile canvas.
void render(QgsRenderContext &context, double width, double height, const Qgs2DXyPlot &plotSettings)
Renders a portion of the profile using the specified render context.
QgsPointXY plotPointToCanvasPoint(const QgsProfilePoint &point) const
Converts a plot point to the equivalent canvas point.
QgsPoint toMapCoordinates(const QgsPointXY &point) const override
Converts a point on the canvas to the associated map coordinate.
bool lockAxisScales() const
Returns true if the distance and elevation scales are locked to each other.
void setAxisScaleRatio(double scale)
Sets the ratio of horizontal (distance) to vertical (elevation) scale for the plot.
void setVisiblePlotRange(double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation)
Sets the visible area of the plot.
void canvasPointHovered(const QgsPointXY &point, const QgsProfilePoint &profilePoint)
Emitted when the mouse hovers over the specified point (in canvas coordinates).
const Qgs2DXyPlot & plot() const
Returns a reference to the 2D plot used by the widget.
void setSubsectionsSymbol(QgsLineSymbol *symbol)
Sets the symbol used to draw the subsections.
QgsPointXY snapToPlot(QPoint point) override
Snap a canvas point to the plot.
void setProject(QgsProject *project)
Sets the project associated with the profile.
QList< QgsMapLayer * > layers() const
Returns the list of layers included in the profile.
void resizeEvent(QResizeEvent *event) override
void centerPlotOn(double x, double y) override
Centers the plot on the plot point corresponding to x, y in canvas units.
void wheelZoom(QWheelEvent *event) override
Zoom plot from a mouse wheel event.
void refresh() override
Triggers a complete regeneration of the profile, causing the profile extraction to perform in the bac...
Qgis::DistanceUnit distanceUnit() const
Returns the distance unit used by the canvas.
double tolerance() const
Returns the tolerance of the profile (in crs() units).
void mouseMoveEvent(QMouseEvent *e) override
void panContentsBy(double dx, double dy) override
Pans the plot contents by dx, dy in canvas units.
void invalidateCurrentPlotExtent()
Invalidates the current plot extent, which means that the visible plot area will be recalculated and ...
QgsPointXY toCanvasCoordinates(const QgsPoint &point) const override
Converts a point in map coordinates to the associated canvas point.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the crs associated with the canvas' map coordinates.
QList< QgsAbstractProfileSource * > sources() const
Returns the list of sources included in the profile.
double axisScaleRatio() const
Returns the current ratio of horizontal (distance) to vertical (elevation) scale for the plot.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to include in the profile.
void zoomFull()
Zooms to the full extent of the profile.
void scaleChanged()
Emitted when the plot scale is changed.
void setSnappingEnabled(bool enabled)
Sets whether snapping of cursor points is enabled.
QVector< QgsProfileIdentifyResults > identify(QPointF point)
Identify results visible at the specified plot point.
QRectF plotArea() const
Returns the interior rectangle representing the surface of the plot, in canvas coordinates.
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.
Does vector analysis using the GEOS library and handles import, export, and exception handling.
Definition qgsgeos.h:141
A line symbol type, for rendering LineString and MultiLineString geometries.
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...
Base class for all map layer types.
Definition qgsmaplayer.h:83
virtual QgsAbstractProfileSource * profileSource()
Returns the layer's profile source if it has profile capabilities.
QString id
Definition qgsmaplayer.h:86
Qgis::LayerType type
Definition qgsmaplayer.h:93
void dataChanged()
Data of layer changed.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
void setLabelSuffixPlacement(Qgis::PlotAxisSuffixPlacement placement)
Sets the placement for the axis label suffixes.
Definition qgsplot.cpp:193
void setLabelSuffix(const QString &suffix)
Sets the axis label suffix.
Definition qgsplot.cpp:183
An abstract class for items that can be placed on a QgsPlotCanvas.
virtual void paint(QPainter *painter)=0
Paints the item.
bool event(QEvent *e) override
QgsPlotCanvas(QWidget *parent=nullptr)
Constructor for QgsPlotCanvas, with the specified parent widget.
void plotAreaChanged()
Emitted whenever the visible area of the plot is changed.
void mouseMoveEvent(QMouseEvent *e) override
void resizeEvent(QResizeEvent *e) override
Contains information about the context of a plot rendering operation.
Definition qgsplot.h:184
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
bool isEmpty() const
Returns true if the geometry is empty.
Definition qgspointxy.h:244
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
double z
Definition qgspoint.h:58
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.
Encapsulates the context of identifying profile results.
double maximumPointElevationDelta
Maximum allowed snapping delta for the elevation values when identifying a point.
double maximumPointDistanceDelta
Maximum allowed snapping delta for the distance values when identifying a point.
QgsProject * project
Associated project.
double displayRatioElevationVsDistance
Display ratio of elevation vs distance units.
double maximumSurfaceDistanceDelta
Maximum allowed snapping delta for the distance values when identifying a continuous elevation surfac...
double maximumSurfaceElevationDelta
Maximum allowed snapping delta for the elevation values when identifying a continuous elevation surfa...
Generates and renders elevation profile plots.
void generationFinished()
Emitted when the profile generation is finished (or canceled).
Encapsulates a point on a distance-elevation profile.
double elevation() const
Returns the elevation of the point.
double distance() const
Returns the distance of the point.
bool isEmpty() const
Returns true if the point is empty.
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.
Encapsulates the context of snapping a profile point.
double maximumPointDistanceDelta
Maximum allowed snapping delta for the distance values when snapping to a point.
double maximumSurfaceElevationDelta
Maximum allowed snapping delta for the elevation values when snapping to a continuous elevation surfa...
double maximumPointElevationDelta
Maximum allowed snapping delta for the elevation values when snapping to a point.
double maximumSurfaceDistanceDelta
Maximum allowed snapping delta for the distance values when snapping to a continuous elevation surfac...
double displayRatioElevationVsDistance
Display ratio of elevation vs distance units.
Encapsulates results of snapping a profile point.
bool isValid() const
Returns true if the result is a valid point.
QgsProfilePoint snappedPoint
Snapped point.
void profileSourceUnregistered(const QString &sourceId)
Signal emitted once a profile source is unregistered.
void profileSourceRegistered(const QString &sourceId, const QString &sourceName)
Signal emitted once a profile source is registered.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:113
T lower() const
Returns the lower bound of the range.
Definition qgsrange.h:81
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:88
A container for the context for various read/write operations on objects.
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.
A utility class for dynamic handling of changes to screen properties.
Stores settings for use within QGIS.
Definition qgssettings.h:68
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
static Q_INVOKABLE double fromUnitToUnitFactor(Qgis::DistanceUnit fromUnit, Qgis::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
static Q_INVOKABLE QString toAbbreviatedString(Qgis::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
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.
Contains geos related utilities and functions.
Definition qgsgeos.h:77
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define BUILTIN_UNREACHABLE
Definition qgis.h:7539
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6950
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.