QGIS API Documentation 3.43.0-Master (b60ef06885e)
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
19#include "qgsapplication.h"
21#include "moc_qgselevationprofilecanvas.cpp"
23#include "qgsplotcanvasitem.h"
24#include "qgsprofilerequest.h"
26#include "qgscurve.h"
28#include "qgsterrainprovider.h"
30#include "qgsprofilerenderer.h"
31#include "qgspoint.h"
32#include "qgsgeos.h"
33#include "qgsplot.h"
34#include "qgsnumericformat.h"
36#include "qgsprofilesnapping.h"
38#include "qgsscreenhelper.h"
39#include "qgsfillsymbol.h"
41
42#include <QWheelEvent>
43#include <QTimer>
44#include <QPalette>
45
47class QgsElevationProfilePlotItem : public Qgs2DPlot, public QgsPlotCanvasItem
48{
49 public:
50 QgsElevationProfilePlotItem( QgsElevationProfileCanvas *canvas )
51 : QgsPlotCanvasItem( canvas )
52 {
53 setYMinimum( 0 );
54 setYMaximum( 100 );
55
57 }
58
59 void setRenderer( QgsProfilePlotRenderer *renderer )
60 {
61 mRenderer = renderer;
62 }
63
64 void updateRect()
65 {
66 mRect = mCanvas->rect();
67 setSize( mRect.size() );
68
69 prepareGeometryChange();
70 setPos( mRect.topLeft() );
71
72 mImage = QImage();
73 mCachedImages.clear();
74 mPlotArea = QRectF();
75 update();
76 }
77
78 void updatePlot()
79 {
80 mImage = QImage();
81 mCachedImages.clear();
82 mPlotArea = QRectF();
83 update();
84 }
85
86 bool redrawResults( const QString &sourceId )
87 {
88 auto it = mCachedImages.find( sourceId );
89 if ( it == mCachedImages.end() )
90 return false;
91
92 mCachedImages.erase( it );
93 mImage = QImage();
94 return true;
95 }
96
97 QRectF boundingRect() const override
98 {
99 return mRect;
100 }
101
102 QString distanceSuffix() const
103 {
104 switch ( mDistanceUnit )
105 {
154 return QStringLiteral( " %1" ).arg( QgsUnitTypes::toAbbreviatedString( mDistanceUnit ) );
155
157 return QObject::tr( "°" );
159 return QString();
160 }
162 }
163
164 void setXAxisUnits( Qgis::DistanceUnit unit )
165 {
166 mDistanceUnit = unit;
167 xAxis().setLabelSuffix( distanceSuffix() );
168 update();
169 }
170
171 QRectF plotArea()
172 {
173 if ( !mPlotArea.isNull() )
174 return mPlotArea;
175
176 // force immediate recalculation of plot area
177 QgsRenderContext context;
178 if ( !scene()->views().isEmpty() )
179 context.setScaleFactor( scene()->views().at( 0 )->logicalDpiX() / 25.4 );
180
182 mPlotArea = interiorPlotArea( context );
183 return mPlotArea;
184 }
185
186 QgsProfilePoint canvasPointToPlotPoint( QPointF point )
187 {
188 const QRectF area = plotArea();
189 if ( !area.contains( point.x(), point.y() ) )
190 return QgsProfilePoint();
191
192 const double distance = ( point.x() - area.left() ) / area.width() * ( xMaximum() - xMinimum() ) * mXScaleFactor + xMinimum() * mXScaleFactor;
193 const double elevation = ( area.bottom() - point.y() ) / area.height() * ( yMaximum() - yMinimum() ) + yMinimum();
194 return QgsProfilePoint( distance, elevation );
195 }
196
197 QgsPointXY plotPointToCanvasPoint( const QgsProfilePoint &point )
198 {
199 if ( point.distance() < xMinimum() * mXScaleFactor || point.distance() > xMaximum() * mXScaleFactor || point.elevation() < yMinimum() || point.elevation() > yMaximum() )
200 return QgsPointXY();
201
202 const QRectF area = plotArea();
203
204 const double x = ( point.distance() - xMinimum() * mXScaleFactor ) / ( ( xMaximum() - xMinimum() ) * mXScaleFactor ) * ( area.width() ) + area.left();
205 const double y = area.bottom() - ( point.elevation() - yMinimum() ) / ( yMaximum() - yMinimum() ) * ( area.height() );
206 return QgsPointXY( x, y );
207 }
208
209 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
210 {
211 mPlotArea = plotArea;
212
213 if ( !mRenderer )
214 return;
215
216 const double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
217
218 const QStringList sourceIds = mRenderer->sourceIds();
219 for ( const QString &source : sourceIds )
220 {
221 QImage plot;
222 auto it = mCachedImages.constFind( source );
223 if ( it != mCachedImages.constEnd() )
224 {
225 plot = it.value();
226 }
227 else
228 {
229 plot = mRenderer->renderToImage( plotArea.width() * pixelRatio, plotArea.height() * pixelRatio, xMinimum() * mXScaleFactor, xMaximum() * mXScaleFactor, yMinimum(), yMaximum(), source, pixelRatio );
230 plot.setDevicePixelRatio( pixelRatio );
231 mCachedImages.insert( source, plot );
232 }
233 rc.painter()->drawImage( QPointF( plotArea.left(), plotArea.top() ), plot );
234 }
235 }
236
237 void paint( QPainter *painter ) override
238 {
239 // cache rendering to an image, so we don't need to redraw the plot
240 if ( !mImage.isNull() )
241 {
242 painter->drawImage( QPointF( 0, 0 ), mImage );
243 }
244 else
245 {
246 const double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
247 mImage = QImage( mRect.width() * pixelRatio, mRect.height() * pixelRatio, QImage::Format_ARGB32_Premultiplied );
248 mImage.setDevicePixelRatio( pixelRatio );
249 mImage.fill( Qt::transparent );
250
251 QPainter imagePainter( &mImage );
252 imagePainter.setRenderHint( QPainter::Antialiasing, true );
254 rc.setDevicePixelRatio( pixelRatio );
255
256 const double mapUnitsPerPixel = ( xMaximum() - xMinimum() ) * mXScaleFactor / plotArea().width();
257 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
258
261
263 render( rc );
264 imagePainter.end();
265
266 painter->drawImage( QPointF( 0, 0 ), mImage );
267 }
268 }
269
270 void setSubsectionsSymbol( QgsLineSymbol *symbol )
271 {
272 if ( mRenderer )
273 {
274 mRenderer->setSubsectionsSymbol( symbol );
275 updatePlot();
276 }
277 }
278
279 QgsProject *mProject = nullptr;
280 double mXScaleFactor = 1.0;
281
283
284 private:
285 QImage mImage;
286
287 QMap<QString, QImage> mCachedImages;
288
289 QRectF mRect;
290 QRectF mPlotArea;
291 QgsProfilePlotRenderer *mRenderer = nullptr;
292};
293
294class QgsElevationProfileCrossHairsItem : public QgsPlotCanvasItem
295{
296 public:
297 QgsElevationProfileCrossHairsItem( QgsElevationProfileCanvas *canvas, QgsElevationProfilePlotItem *plotItem )
298 : QgsPlotCanvasItem( canvas )
299 , mPlotItem( plotItem )
300 {
301 }
302
303 void updateRect()
304 {
305 mRect = mCanvas->rect();
306
307 prepareGeometryChange();
308 setPos( mRect.topLeft() );
309 update();
310 }
311
312 void setPoint( const QgsProfilePoint &point )
313 {
314 mPoint = point;
315 update();
316 }
317
318 QRectF boundingRect() const override
319 {
320 return mRect;
321 }
322
323 void paint( QPainter *painter ) override
324 {
325 const QgsPointXY crossHairPlotPoint = mPlotItem->plotPointToCanvasPoint( mPoint );
326 if ( crossHairPlotPoint.isEmpty() )
327 return;
328
329 painter->save();
330 painter->setBrush( Qt::NoBrush );
331 QPen crossHairPen;
332 crossHairPen.setCosmetic( true );
333 crossHairPen.setWidthF( 1 );
334 crossHairPen.setStyle( Qt::DashLine );
335 crossHairPen.setCapStyle( Qt::FlatCap );
336 const QPalette scenePalette = mPlotItem->scene()->palette();
337 QColor penColor = scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text );
338 penColor.setAlpha( 150 );
339 crossHairPen.setColor( penColor );
340 painter->setPen( crossHairPen );
341 painter->drawLine( QPointF( mPlotItem->plotArea().left(), crossHairPlotPoint.y() ), QPointF( mPlotItem->plotArea().right(), crossHairPlotPoint.y() ) );
342 painter->drawLine( QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().top() ), QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().bottom() ) );
343
344 // also render current point text
345 QgsNumericFormatContext numericContext;
346
347 const QString xCoordinateText = mPlotItem->xAxis().numericFormat()->formatDouble( mPoint.distance() / mPlotItem->mXScaleFactor, numericContext )
348 + mPlotItem->distanceSuffix();
349
350 const QString yCoordinateText = mPlotItem->yAxis().numericFormat()->formatDouble( mPoint.elevation(), numericContext );
351
352 QFont font;
353 const QFontMetrics fm( font );
354 const double height = fm.capHeight();
355 const double xWidth = fm.horizontalAdvance( xCoordinateText );
356 const double yWidth = fm.horizontalAdvance( yCoordinateText );
357 const double textAxisMargin = fm.horizontalAdvance( ' ' );
358
359 QPointF xCoordOrigin;
360 QPointF yCoordOrigin;
361
362 if ( mPoint.distance() < ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5 * mPlotItem->mXScaleFactor )
363 {
364 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
365 {
366 // render x coordinate on right top (left top align)
367 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
368 // render y coordinate on right top (right bottom align)
369 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
370 }
371 else
372 {
373 // render x coordinate on right bottom (left bottom align)
374 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
375 // render y coordinate on right bottom (right top align)
376 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
377 }
378 }
379 else
380 {
381 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
382 {
383 // render x coordinate on left top (right top align)
384 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
385 // render y coordinate on left top (left bottom align)
386 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
387 }
388 else
389 {
390 // render x coordinate on left bottom (right bottom align)
391 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
392 // render y coordinate on left bottom (left top align)
393 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
394 }
395 }
396
397 // semi opaque background color brush
398 QColor backgroundColor = mPlotItem->chartBackgroundSymbol()->color();
399 backgroundColor.setAlpha( 220 );
400 painter->setBrush( QBrush( backgroundColor ) );
401 painter->setPen( Qt::NoPen );
402 painter->drawRect( QRectF( xCoordOrigin.x() - textAxisMargin + 1, xCoordOrigin.y() - textAxisMargin - height + 1, xWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
403 painter->drawRect( QRectF( yCoordOrigin.x() - textAxisMargin + 1, yCoordOrigin.y() - textAxisMargin - height + 1, yWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
404
405 painter->setBrush( Qt::NoBrush );
406 painter->setPen( scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
407
408 painter->drawText( xCoordOrigin, xCoordinateText );
409 painter->drawText( yCoordOrigin, yCoordinateText );
410 painter->restore();
411 }
412
413 private:
414 QRectF mRect;
415 QgsProfilePoint mPoint;
416 QgsElevationProfilePlotItem *mPlotItem = nullptr;
417};
419
420
422 : QgsPlotCanvas( parent )
423{
424 mScreenHelper = new QgsScreenHelper( this );
425
426 mPlotItem = new QgsElevationProfilePlotItem( this );
427
428 // follow system color scheme by default
429 setBackgroundColor( QColor() );
430
431 mCrossHairsItem = new QgsElevationProfileCrossHairsItem( this, mPlotItem );
432 mCrossHairsItem->setZValue( 100 );
433 mCrossHairsItem->hide();
434
435 // updating the profile plot is deferred on a timer, so that we don't trigger it too often
436 mDeferredRegenerationTimer = new QTimer( this );
437 mDeferredRegenerationTimer->setSingleShot( true );
438 mDeferredRegenerationTimer->stop();
439 connect( mDeferredRegenerationTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRegeneration );
440
441 mDeferredRedrawTimer = new QTimer( this );
442 mDeferredRedrawTimer->setSingleShot( true );
443 mDeferredRedrawTimer->stop();
444 connect( mDeferredRedrawTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRedraw );
445}
446
448{
449 if ( mCurrentJob )
450 {
451 mPlotItem->setRenderer( nullptr );
452 mCurrentJob->deleteLater();
453 mCurrentJob = nullptr;
454 }
455}
456
458{
459 if ( mCurrentJob )
460 {
461 mPlotItem->setRenderer( nullptr );
462 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
463 mCurrentJob->cancelGeneration();
464 mCurrentJob->deleteLater();
465 mCurrentJob = nullptr;
466 }
467}
468
470{
471 const double dxPercent = dx / mPlotItem->plotArea().width();
472 const double dyPercent = dy / mPlotItem->plotArea().height();
473
474 // these look backwards, but we are dragging the paper, not the view!
475 const double dxPlot = -dxPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
476 const double dyPlot = dyPercent * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
477
478 // no need to handle axis scale lock here, we aren't changing scales
479 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
480 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
481 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
482 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
483
484 refineResults();
485
486 mPlotItem->updatePlot();
487 emit plotAreaChanged();
488}
489
491{
492 if ( !mPlotItem->plotArea().contains( x, y ) )
493 return;
494
495 const double newCenterX = mPlotItem->xMinimum() + ( x - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
496 const double newCenterY = mPlotItem->yMinimum() + ( mPlotItem->plotArea().bottom() - y ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
497
498 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
499 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
500
501 // no need to handle axis scale lock here, we aren't changing scales
502 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
503 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
504 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
505 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
506
507 refineResults();
508
509 mPlotItem->updatePlot();
510 emit plotAreaChanged();
511}
512
514{
515 scalePlot( factor, factor );
516 emit plotAreaChanged();
517}
518
519QgsProfileSnapContext QgsElevationProfileCanvas::snapContext() const
520{
521 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
522 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
523 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
524
525 QgsProfileSnapContext context;
526 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
527 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
528 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
529 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
530 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
531 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) );
532
533 return context;
534}
535
536QgsProfileIdentifyContext QgsElevationProfileCanvas::identifyContext() const
537{
538 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
539 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
540 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
541
543 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
544 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
545 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
546 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
547 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
548 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) );
549
550 context.project = mProject;
551
552 return context;
553}
554
555void QgsElevationProfileCanvas::setupLayerConnections( QgsMapLayer *layer, bool isDisconnect )
556{
557 if ( isDisconnect )
558 {
559 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
560 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
561 disconnect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
562 }
563 else
564 {
565 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
566 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
567 connect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
568 }
569
570 switch ( layer->type() )
571 {
573 {
574 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
575 if ( isDisconnect )
576 {
577 disconnect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
578 disconnect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
579 disconnect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
580 disconnect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
581 }
582 else
583 {
584 connect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
585 connect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
586 connect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
587 connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
588 }
589 break;
590 }
599 break;
600 }
601}
602
603void QgsElevationProfileCanvas::adjustRangeForAxisScaleLock( double &xMinimum, double &xMaximum, double &yMinimum, double &yMaximum ) const
604{
605 // ensures that we always "zoom out" to match horizontal/vertical scales
606 const double horizontalScale = ( xMaximum - xMinimum ) / mPlotItem->plotArea().width();
607 const double verticalScale = ( yMaximum - yMinimum ) / mPlotItem->plotArea().height();
608 if ( horizontalScale > verticalScale )
609 {
610 const double height = horizontalScale * mPlotItem->plotArea().height();
611 const double deltaHeight = ( yMaximum - yMinimum ) - height;
612 yMinimum += deltaHeight / 2;
613 yMaximum -= deltaHeight / 2;
614 }
615 else
616 {
617 const double width = verticalScale * mPlotItem->plotArea().width();
618 const double deltaWidth = ( ( xMaximum - xMinimum ) - width );
619 xMinimum += deltaWidth / 2;
620 xMaximum -= deltaWidth / 2;
621 }
622}
623
625{
626 return mDistanceUnit;
627}
628
630{
631 mDistanceUnit = unit;
632 const double oldMin = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
633 const double oldMax = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
634 mPlotItem->mXScaleFactor = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
635 mPlotItem->setXAxisUnits( mDistanceUnit );
636 mPlotItem->setXMinimum( oldMin / mPlotItem->mXScaleFactor );
637 mPlotItem->setXMaximum( oldMax / mPlotItem->mXScaleFactor );
638 mPlotItem->updatePlot();
639}
640
642{
643 if ( !color.isValid() )
644 {
645 QPalette customPalette = qApp->palette();
646 const QColor baseColor = qApp->palette().color( QPalette::ColorRole::Base );
647 const QColor windowColor = qApp->palette().color( QPalette::ColorRole::Window );
648 customPalette.setColor( QPalette::ColorRole::Base, windowColor );
649 customPalette.setColor( QPalette::ColorRole::Window, baseColor );
650 setPalette( customPalette );
651 scene()->setPalette( customPalette );
652 }
653 else
654 {
655 // build custom palette
656 const bool isDarkTheme = color.lightnessF() < 0.5;
657 QPalette customPalette = qApp->palette();
658 customPalette.setColor( QPalette::ColorRole::Window, color );
659 if ( isDarkTheme )
660 {
661 customPalette.setColor( QPalette::ColorRole::Text, QColor( 255, 255, 255 ) );
662 customPalette.setColor( QPalette::ColorRole::Base, color.lighter( 120 ) );
663 }
664 else
665 {
666 customPalette.setColor( QPalette::ColorRole::Text, QColor( 0, 0, 0 ) );
667 customPalette.setColor( QPalette::ColorRole::Base, color.darker( 120 ) );
668 }
669
670 setPalette( customPalette );
671 scene()->setPalette( customPalette );
672 }
673
674 updateChartFromPalette();
675}
676
678{
679 return mLockAxisScales;
680}
681
683{
684 mLockAxisScales = lock;
685 if ( mLockAxisScales )
686 {
687 double xMinimum = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
688 double xMaximum = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
689 double yMinimum = mPlotItem->yMinimum();
690 double yMaximum = mPlotItem->yMaximum();
691 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
692 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
693 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
694 mPlotItem->setYMinimum( yMinimum );
695 mPlotItem->setYMaximum( yMaximum );
696
697 refineResults();
698 mPlotItem->updatePlot();
699 emit plotAreaChanged();
700 }
701}
702
704{
705 if ( !mCurrentJob || !mSnappingEnabled )
706 return QgsPointXY();
707
708 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
709
710 const QgsProfileSnapResult snappedPoint = mCurrentJob->snapPoint( plotPoint, snapContext() );
711 if ( !snappedPoint.isValid() )
712 return QgsPointXY();
713
714 return plotPointToCanvasPoint( snappedPoint.snappedPoint );
715}
716
717void QgsElevationProfileCanvas::scalePlot( double xFactor, double yFactor )
718{
719 if ( mLockAxisScales )
720 yFactor = xFactor;
721
722 const double currentWidth = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor;
723 const double currentHeight = mPlotItem->yMaximum() - mPlotItem->yMinimum();
724
725 const double newWidth = currentWidth / xFactor;
726 const double newHeight = currentHeight / yFactor;
727
728 const double currentCenterX = ( mPlotItem->xMinimum() + mPlotItem->xMaximum() ) * 0.5 * mPlotItem->mXScaleFactor;
729 const double currentCenterY = ( mPlotItem->yMinimum() + mPlotItem->yMaximum() ) * 0.5;
730
731 double xMinimum = currentCenterX - newWidth * 0.5;
732 double xMaximum = currentCenterX + newWidth * 0.5;
733 double yMinimum = currentCenterY - newHeight * 0.5;
734 double yMaximum = currentCenterY + newHeight * 0.5;
735 if ( mLockAxisScales )
736 {
737 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
738 }
739
740 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
741 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
742 mPlotItem->setYMinimum( yMinimum );
743 mPlotItem->setYMaximum( yMaximum );
744
745 refineResults();
746 mPlotItem->updatePlot();
747 emit plotAreaChanged();
748}
749
751{
752 const QRectF intersected = rect.intersected( mPlotItem->plotArea() );
753
754 double minX = ( intersected.left() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
755 double maxX = ( intersected.right() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
756 double minY = ( mPlotItem->plotArea().bottom() - intersected.bottom() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
757 double maxY = ( mPlotItem->plotArea().bottom() - intersected.top() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
758
759 if ( mLockAxisScales )
760 {
761 adjustRangeForAxisScaleLock( minX, maxX, minY, maxY );
762 }
763
764 mPlotItem->setXMinimum( minX / mPlotItem->mXScaleFactor );
765 mPlotItem->setXMaximum( maxX / mPlotItem->mXScaleFactor );
766 mPlotItem->setYMinimum( minY );
767 mPlotItem->setYMaximum( maxY );
768
769 refineResults();
770 mPlotItem->updatePlot();
771 emit plotAreaChanged();
772}
773
774void QgsElevationProfileCanvas::wheelZoom( QWheelEvent *event )
775{
776 //get mouse wheel zoom behavior settings
777 QgsSettings settings;
778 double zoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
779 bool reverseZoom = settings.value( QStringLiteral( "qgis/reverse_wheel_zoom" ), false ).toBool();
780 bool zoomIn = reverseZoom ? event->angleDelta().y() < 0 : event->angleDelta().y() > 0;
781
782 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
783 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
784
785 if ( event->modifiers() & Qt::ControlModifier )
786 {
787 //holding ctrl while wheel zooming results in a finer zoom
788 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
789 }
790
791 //calculate zoom scale factor
792 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
793
794 QRectF viewportRect = mPlotItem->plotArea();
795
796 if ( viewportRect.contains( event->position() ) )
797 {
798 //adjust view center
799 const double oldCenterX = 0.5 * ( mPlotItem->xMaximum() + mPlotItem->xMinimum() );
800 const double oldCenterY = 0.5 * ( mPlotItem->yMaximum() + mPlotItem->yMinimum() );
801
802 const double eventPosX = ( event->position().x() - viewportRect.left() ) / viewportRect.width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
803 const double eventPosY = ( viewportRect.bottom() - event->position().y() ) / viewportRect.height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
804
805 const double newCenterX = eventPosX + ( ( oldCenterX - eventPosX ) * scaleFactor );
806 const double newCenterY = eventPosY + ( ( oldCenterY - eventPosY ) * scaleFactor );
807
808 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
809 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
810
811 // don't need to handle axis scale lock here, we are always changing axis by the same scale
812 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
813 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
814 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
815 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
816 }
817
818 //zoom plot
819 if ( zoomIn )
820 {
821 scalePlot( zoomFactor );
822 }
823 else
824 {
825 scalePlot( 1 / zoomFactor );
826 }
827 emit plotAreaChanged();
828}
829
831{
833 if ( e->isAccepted() )
834 {
835 mCrossHairsItem->hide();
836 return;
837 }
838
839 QgsProfilePoint plotPoint = canvasPointToPlotPoint( e->pos() );
840 if ( mCurrentJob && mSnappingEnabled && !plotPoint.isEmpty() )
841 {
842 const QgsProfileSnapResult snapResult = mCurrentJob->snapPoint( plotPoint, snapContext() );
843 if ( snapResult.isValid() )
844 plotPoint = snapResult.snappedPoint;
845 }
846
847 if ( plotPoint.isEmpty() )
848 {
849 mCrossHairsItem->hide();
850 }
851 else
852 {
853 mCrossHairsItem->setPoint( plotPoint );
854 mCrossHairsItem->show();
855 }
856 emit canvasPointHovered( e->pos(), plotPoint );
857}
858
860{
861 return mPlotItem->plotArea();
862}
863
865{
866 if ( !mProject || !profileCurve() )
867 return;
868
869 cancelJobs();
870
871 QgsProfileRequest request( profileCurve()->clone() );
872 request.setCrs( mCrs );
873 request.setTolerance( mTolerance );
874 request.setTransformContext( mProject->transformContext() );
875 request.setTerrainProvider( mProject->elevationProperties()->terrainProvider() ? mProject->elevationProperties()->terrainProvider()->clone() : nullptr );
876 QgsExpressionContext context;
879 request.setExpressionContext( context );
880
881 const QList<QgsMapLayer *> layersToGenerate = layers();
882 QList<QgsAbstractProfileSource *> sources;
883 const QList<QgsAbstractProfileSource *> registrySources = QgsApplication::profileSourceRegistry()->profileSources();
884 sources.reserve( layersToGenerate.size() + registrySources.size() );
885
886 sources << registrySources;
887 for ( QgsMapLayer *layer : layersToGenerate )
888 {
889 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
890 sources.append( source );
891 }
892
893 mCurrentJob = new QgsProfilePlotRenderer( sources, request );
894 connect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
895
896 QgsProfileGenerationContext generationContext;
897 generationContext.setDpi( mScreenHelper->screenDpi() );
898 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( mProfileCurve->length() ) / mPlotItem->plotArea().width() );
899 generationContext.setMapUnitsPerDistancePixel( mProfileCurve->length() / mPlotItem->plotArea().width() );
900 mCurrentJob->setContext( generationContext );
901
902 if ( mSubsectionsSymbol )
903 {
904 mCurrentJob->setSubsectionsSymbol( mSubsectionsSymbol->clone() );
905 }
906
907 mCurrentJob->startGeneration();
908 mPlotItem->setRenderer( mCurrentJob );
909
910 emit activeJobCountChanged( 1 );
911}
912
914{
915 mZoomFullWhenJobFinished = true;
916}
917
918void QgsElevationProfileCanvas::generationFinished()
919{
920 if ( !mCurrentJob )
921 return;
922
923 emit activeJobCountChanged( 0 );
924
925 if ( mZoomFullWhenJobFinished )
926 {
927 // we only zoom full for the initial generation
928 mZoomFullWhenJobFinished = false;
929 zoomFull();
930 }
931 else
932 {
933 // here we should invalidate cached results only for the layers which have been refined
934
935 // and if no layers are being refeined, don't invalidate anything
936
937 mPlotItem->updatePlot();
938 }
939
940 if ( mForceRegenerationAfterCurrentJobCompletes )
941 {
942 mForceRegenerationAfterCurrentJobCompletes = false;
943 mCurrentJob->invalidateAllRefinableSources();
944 scheduleDeferredRegeneration();
945 }
946}
947
948void QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged()
949{
950 // TODO -- handle nicely when existing job is in progress
951 if ( !mCurrentJob || mCurrentJob->isActive() )
952 return;
953
954 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
955 if ( !properties )
956 return;
957
958 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
959 {
960 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
961 {
962 if ( mCurrentJob->invalidateResults( source ) )
963 scheduleDeferredRegeneration();
964 }
965 }
966}
967
968void QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged()
969{
970 // TODO -- handle nicely when existing job is in progress
971 if ( !mCurrentJob || mCurrentJob->isActive() )
972 return;
973
974 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
975 if ( !properties )
976 return;
977
978 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
979 {
980 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
981 {
982 mCurrentJob->replaceSource( source );
983 }
984 if ( mPlotItem->redrawResults( layer->id() ) )
985 scheduleDeferredRedraw();
986 }
987}
988
989void QgsElevationProfileCanvas::regenerateResultsForLayer()
990{
991 if ( !mCurrentJob )
992 return;
993
994 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() ) )
995 {
996 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
997 {
998 if ( mCurrentJob->invalidateResults( source ) )
999 scheduleDeferredRegeneration();
1000 }
1001 }
1002}
1003
1004void QgsElevationProfileCanvas::scheduleDeferredRegeneration()
1005{
1006 if ( !mDeferredRegenerationScheduled )
1007 {
1008 mDeferredRegenerationTimer->start( 1 );
1009 mDeferredRegenerationScheduled = true;
1010 }
1011}
1012
1013void QgsElevationProfileCanvas::scheduleDeferredRedraw()
1014{
1015 if ( !mDeferredRedrawScheduled )
1016 {
1017 mDeferredRedrawTimer->start( 1 );
1018 mDeferredRedrawScheduled = true;
1019 }
1020}
1021
1022void QgsElevationProfileCanvas::startDeferredRegeneration()
1023{
1024 if ( mCurrentJob && !mCurrentJob->isActive() )
1025 {
1026 emit activeJobCountChanged( 1 );
1027 mCurrentJob->regenerateInvalidatedResults();
1028 }
1029 else if ( mCurrentJob )
1030 {
1031 mForceRegenerationAfterCurrentJobCompletes = true;
1032 }
1033
1034 mDeferredRegenerationScheduled = false;
1035}
1036
1037void QgsElevationProfileCanvas::startDeferredRedraw()
1038{
1039 mPlotItem->update();
1040 mDeferredRedrawScheduled = false;
1041}
1042
1043void QgsElevationProfileCanvas::refineResults()
1044{
1045 if ( mCurrentJob )
1046 {
1048 context.setDpi( mScreenHelper->screenDpi() );
1049 const double plotDistanceRange = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor;
1050 const double plotElevationRange = mPlotItem->yMaximum() - mPlotItem->yMinimum();
1051 const double plotDistanceUnitsPerPixel = plotDistanceRange / mPlotItem->plotArea().width();
1052
1053 // we round the actual desired map error down to just one significant figure, to avoid tiny differences
1054 // as the plot is panned
1055 const double targetMaxErrorInMapUnits = MAX_ERROR_PIXELS * plotDistanceUnitsPerPixel;
1056 const double factor = std::pow( 10.0, 1 - std::ceil( std::log10( std::fabs( targetMaxErrorInMapUnits ) ) ) );
1057 const double roundedErrorInMapUnits = std::floor( targetMaxErrorInMapUnits * factor ) / factor;
1058 context.setMaximumErrorMapUnits( roundedErrorInMapUnits );
1059
1060 context.setMapUnitsPerDistancePixel( plotDistanceUnitsPerPixel );
1061
1062 // for similar reasons we round the minimum distance off to multiples of the maximum error in map units
1063 const double distanceMin = std::floor( ( mPlotItem->xMinimum() * mPlotItem->mXScaleFactor - plotDistanceRange * 0.05 ) / context.maximumErrorMapUnits() ) * context.maximumErrorMapUnits();
1064 context.setDistanceRange( QgsDoubleRange( std::max( 0.0, distanceMin ), mPlotItem->xMaximum() * mPlotItem->mXScaleFactor + plotDistanceRange * 0.05 ) );
1065
1066 context.setElevationRange( QgsDoubleRange( mPlotItem->yMinimum() - plotElevationRange * 0.05, mPlotItem->yMaximum() + plotElevationRange * 0.05 ) );
1067 mCurrentJob->setContext( context );
1068 }
1069 scheduleDeferredRegeneration();
1070}
1071
1072void QgsElevationProfileCanvas::updateChartFromPalette()
1073{
1074 const QPalette chartPalette = palette();
1075 setBackgroundBrush( QBrush( chartPalette.color( QPalette::ColorRole::Base ) ) );
1076 {
1077 QgsTextFormat textFormat = mPlotItem->xAxis().textFormat();
1078 textFormat.setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
1079 mPlotItem->xAxis().setTextFormat( textFormat );
1080 mPlotItem->yAxis().setTextFormat( textFormat );
1081 }
1082 {
1083 std::unique_ptr<QgsFillSymbol> chartFill( mPlotItem->chartBackgroundSymbol()->clone() );
1084 chartFill->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Window ) );
1085 mPlotItem->setChartBackgroundSymbol( chartFill.release() );
1086 }
1087 {
1088 std::unique_ptr<QgsFillSymbol> chartBorder( mPlotItem->chartBorderSymbol()->clone() );
1089 chartBorder->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text ) );
1090 mPlotItem->setChartBorderSymbol( chartBorder.release() );
1091 }
1092 {
1093 std::unique_ptr<QgsLineSymbol> chartMajorSymbol( mPlotItem->xAxis().gridMajorSymbol()->clone() );
1094 QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
1095 c.setAlpha( 150 );
1096 chartMajorSymbol->setColor( c );
1097 mPlotItem->xAxis().setGridMajorSymbol( chartMajorSymbol->clone() );
1098 mPlotItem->yAxis().setGridMajorSymbol( chartMajorSymbol.release() );
1099 }
1100 {
1101 std::unique_ptr<QgsLineSymbol> chartMinorSymbol( mPlotItem->xAxis().gridMinorSymbol()->clone() );
1102 QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
1103 c.setAlpha( 50 );
1104 chartMinorSymbol->setColor( c );
1105 mPlotItem->xAxis().setGridMinorSymbol( chartMinorSymbol->clone() );
1106 mPlotItem->yAxis().setGridMinorSymbol( chartMinorSymbol.release() );
1107 }
1108 mPlotItem->updatePlot();
1109}
1110
1112{
1113 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1114 return QgsProfilePoint();
1115
1116 return mPlotItem->canvasPointToPlotPoint( point );
1117}
1118
1120{
1121 return mPlotItem->plotPointToCanvasPoint( point );
1122}
1123
1125{
1126 mProject = project;
1127 mPlotItem->mProject = project;
1128}
1129
1134
1136{
1137 mProfileCurve.reset( curve );
1138}
1139
1141{
1142 return mProfileCurve.get();
1143}
1144
1146{
1147 mTolerance = tolerance;
1148}
1149
1154
1155void QgsElevationProfileCanvas::setLayers( const QList<QgsMapLayer *> &layers )
1156{
1157 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
1158 {
1159 setupLayerConnections( layer, true );
1160 }
1161
1162 // filter list, removing null layers and invalid layers
1163 auto filteredList = layers;
1164 filteredList.erase( std::remove_if( filteredList.begin(), filteredList.end(), []( QgsMapLayer *layer ) {
1165 return !layer || !layer->isValid();
1166 } ),
1167 filteredList.end() );
1168
1169 mLayers = _qgis_listRawToQPointer( filteredList );
1170 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
1171 {
1172 setupLayerConnections( layer, false );
1173 }
1174}
1175
1176QList<QgsMapLayer *> QgsElevationProfileCanvas::layers() const
1177{
1178 return _qgis_listQPointerToRaw( mLayers );
1179}
1180
1182{
1184
1185 if ( mLockAxisScales )
1186 {
1187 double xMinimum = mPlotItem->xMinimum();
1188 double xMaximum = mPlotItem->xMaximum();
1189 double yMinimum = mPlotItem->yMinimum();
1190 double yMaximum = mPlotItem->yMaximum();
1191 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1192 mPlotItem->setXMinimum( xMinimum );
1193 mPlotItem->setXMaximum( xMaximum );
1194 mPlotItem->setYMinimum( yMinimum );
1195 mPlotItem->setYMaximum( yMaximum );
1196 }
1197
1198 mPlotItem->updateRect();
1199 mCrossHairsItem->updateRect();
1200}
1201
1203{
1204 QgsPlotCanvas::paintEvent( event );
1205
1206 if ( !mFirstDrawOccurred )
1207 {
1208 // 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).
1209 mFirstDrawOccurred = true;
1210 mPlotItem->updateRect();
1211 mCrossHairsItem->updateRect();
1212 }
1213}
1214
1216{
1217 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1218 return QgsPoint();
1219
1220 if ( !mProfileCurve )
1221 return QgsPoint();
1222
1223 const double dx = point.x() - mPlotItem->plotArea().left();
1224
1225 const double distanceAlongPlotPercent = dx / mPlotItem->plotArea().width();
1226 double distanceAlongCurveLength = distanceAlongPlotPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
1227
1228 std::unique_ptr<QgsPoint> mapXyPoint( mProfileCurve->interpolatePoint( distanceAlongCurveLength ) );
1229 if ( !mapXyPoint )
1230 return QgsPoint();
1231
1232 const double mapZ = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * ( mPlotItem->plotArea().bottom() - point.y() ) + mPlotItem->yMinimum();
1233
1234 return QgsPoint( mapXyPoint->x(), mapXyPoint->y(), mapZ );
1235}
1236
1238{
1239 if ( !mProfileCurve )
1240 return QgsPointXY();
1241
1242 QgsGeos geos( mProfileCurve.get() );
1243 QString error;
1244 const double distanceAlongCurve = geos.lineLocatePoint( point, &error );
1245
1246 const double distanceAlongCurveOnPlot = distanceAlongCurve - mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
1247 const double distanceAlongCurvePercent = distanceAlongCurveOnPlot / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor );
1248 const double distanceAlongPlotRect = distanceAlongCurvePercent * mPlotItem->plotArea().width();
1249
1250 const double canvasX = mPlotItem->plotArea().left() + distanceAlongPlotRect;
1251
1252 double canvasY = 0;
1253 if ( std::isnan( point.z() ) || point.z() < mPlotItem->yMinimum() )
1254 {
1255 canvasY = mPlotItem->plotArea().top();
1256 }
1257 else if ( point.z() > mPlotItem->yMaximum() )
1258 {
1259 canvasY = mPlotItem->plotArea().bottom();
1260 }
1261 else
1262 {
1263 const double yPercent = ( point.z() - mPlotItem->yMinimum() ) / ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
1264 canvasY = mPlotItem->plotArea().bottom() - mPlotItem->plotArea().height() * yPercent;
1265 }
1266
1267 return QgsPointXY( canvasX, canvasY );
1268}
1269
1271{
1272 if ( !mCurrentJob )
1273 return;
1274
1275 const QgsDoubleRange zRange = mCurrentJob->zRange();
1276
1277 double yMinimum = 0;
1278 double yMaximum = 0;
1279
1280 if ( zRange.upper() < zRange.lower() )
1281 {
1282 // invalid range, e.g. no features found in plot!
1283 yMinimum = 0;
1284 yMaximum = 10;
1285 }
1286 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
1287 {
1288 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
1289 yMinimum = zRange.lower() - 5;
1290 yMaximum = zRange.lower() + 5;
1291 }
1292 else
1293 {
1294 // add 5% margin to height range
1295 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
1296 yMinimum = zRange.lower() - margin;
1297 yMaximum = zRange.upper() + margin;
1298 }
1299
1300 const double profileLength = profileCurve()->length();
1301 double xMinimum = 0;
1302 // just 2% margin to max distance -- any more is overkill and wasted space
1303 double xMaximum = profileLength * 1.02;
1304
1305 if ( mLockAxisScales )
1306 {
1307 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1308 }
1309
1310 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
1311 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
1312 mPlotItem->setYMinimum( yMinimum );
1313 mPlotItem->setYMaximum( yMaximum );
1314
1315 refineResults();
1316 mPlotItem->updatePlot();
1317 emit plotAreaChanged();
1318}
1319
1320void QgsElevationProfileCanvas::setVisiblePlotRange( double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation )
1321{
1322 if ( mLockAxisScales )
1323 {
1324 adjustRangeForAxisScaleLock( minimumDistance, maximumDistance, minimumElevation, maximumElevation );
1325 }
1326
1327 mPlotItem->setYMinimum( minimumElevation );
1328 mPlotItem->setYMaximum( maximumElevation );
1329 mPlotItem->setXMinimum( minimumDistance / mPlotItem->mXScaleFactor );
1330 mPlotItem->setXMaximum( maximumDistance / mPlotItem->mXScaleFactor );
1331 refineResults();
1332 mPlotItem->updatePlot();
1333 emit plotAreaChanged();
1334}
1335
1337{
1338 return QgsDoubleRange( mPlotItem->xMinimum() * mPlotItem->mXScaleFactor, mPlotItem->xMaximum() * mPlotItem->mXScaleFactor );
1339}
1340
1342{
1343 return QgsDoubleRange( mPlotItem->yMinimum(), mPlotItem->yMaximum() );
1344}
1345
1347{
1348 return *mPlotItem;
1349}
1350
1352class QgsElevationProfilePlot : public Qgs2DPlot
1353{
1354 public:
1355 QgsElevationProfilePlot( QgsProfilePlotRenderer *renderer )
1356 : mRenderer( renderer )
1357 {
1358 }
1359
1360 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
1361 {
1362 if ( !mRenderer )
1363 return;
1364
1365 rc.painter()->translate( plotArea.left(), plotArea.top() );
1366 mRenderer->render( rc, plotArea.width(), plotArea.height(), xMinimum() * mXScale, xMaximum() * mXScale, yMinimum(), yMaximum() );
1367 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
1368 }
1369
1370 double mXScale = 1;
1371
1372 private:
1373 QgsProfilePlotRenderer *mRenderer = nullptr;
1374};
1376
1377void QgsElevationProfileCanvas::render( QgsRenderContext &context, double width, double height, const Qgs2DPlot &plotSettings )
1378{
1379 if ( !mCurrentJob )
1380 return;
1381
1384
1385 QgsElevationProfilePlot profilePlot( mCurrentJob );
1386
1387 // 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...
1388 QDomDocument doc;
1389 QDomElement elem = doc.createElement( QStringLiteral( "plot" ) );
1390 QgsReadWriteContext rwContext;
1391 plotSettings.writeXml( elem, doc, rwContext );
1392 profilePlot.readXml( elem, rwContext );
1393
1394 profilePlot.mXScale = mPlotItem->mXScaleFactor;
1395 profilePlot.xAxis().setLabelSuffix( mPlotItem->xAxis().labelSuffix() );
1396 profilePlot.xAxis().setLabelSuffixPlacement( mPlotItem->xAxis().labelSuffixPlacement() );
1397
1398 profilePlot.setSize( QSizeF( width, height ) );
1399 profilePlot.render( context );
1400}
1401
1402QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( QPointF point )
1403{
1404 if ( !mCurrentJob )
1405 return {};
1406
1407 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
1408
1409 return mCurrentJob->identify( plotPoint, identifyContext() );
1410}
1411
1412QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( const QRectF &rect )
1413{
1414 if ( !mCurrentJob )
1415 return {};
1416
1417 const QgsProfilePoint topLeftPlotPoint = canvasPointToPlotPoint( rect.topLeft() );
1418 const QgsProfilePoint bottomRightPlotPoint = canvasPointToPlotPoint( rect.bottomRight() );
1419
1420 double distance1 = topLeftPlotPoint.distance();
1421 double distance2 = bottomRightPlotPoint.distance();
1422 if ( distance2 < distance1 )
1423 std::swap( distance1, distance2 );
1424
1425 double elevation1 = topLeftPlotPoint.elevation();
1426 double elevation2 = bottomRightPlotPoint.elevation();
1427 if ( elevation2 < elevation1 )
1428 std::swap( elevation1, elevation2 );
1429
1430 return mCurrentJob->identify( QgsDoubleRange( distance1, distance2 ), QgsDoubleRange( elevation1, elevation2 ), identifyContext() );
1431}
1432
1434{
1435 setProfileCurve( nullptr );
1436 cancelJobs();
1437 mPlotItem->updatePlot();
1438}
1439
1441{
1442 mSnappingEnabled = enabled;
1443}
1444
1446{
1447 mSubsectionsSymbol.reset( symbol );
1448 std::unique_ptr<QgsLineSymbol> plotItemSymbol( mSubsectionsSymbol ? mSubsectionsSymbol->clone() : nullptr );
1449 mPlotItem->setSubsectionsSymbol( plotItemSymbol.release() );
1450}
@ FirstAndLastLabels
Place suffix after the first and last label values only.
DistanceUnit
Units of distance.
Definition qgis.h:4843
@ YardsBritishSears1922Truncated
British yards (Sears 1922 truncated)
@ Feet
Imperial feet.
@ MilesUSSurvey
US Survey miles.
@ LinksBritishSears1922
British links (Sears 1922)
@ YardsBritishBenoit1895A
British yards (Benoit 1895 A)
@ LinksBritishBenoit1895A
British links (Benoit 1895 A)
@ Centimeters
Centimeters.
@ YardsIndian1975
Indian yards (1975)
@ FeetUSSurvey
US Survey feet.
@ Millimeters
Millimeters.
@ FeetBritishSears1922
British feet (Sears 1922)
@ YardsClarkes
Clarke's yards.
@ YardsIndian
Indian yards.
@ FeetBritishBenoit1895B
British feet (Benoit 1895 B)
@ Miles
Terrestrial miles.
@ LinksUSSurvey
US Survey links.
@ ChainsUSSurvey
US Survey chains.
@ FeetClarkes
Clarke's feet.
@ Unknown
Unknown distance unit.
@ Yards
Imperial yards.
@ FeetBritish1936
British feet (1936)
@ FeetIndian1962
Indian feet (1962)
@ YardsBritishSears1922
British yards (Sears 1922)
@ FeetIndian1937
Indian feet (1937)
@ YardsIndian1937
Indian yards (1937)
@ Degrees
Degrees, for planar geographic CRS distance measurements.
@ ChainsBritishBenoit1895B
British chains (Benoit 1895 B)
@ LinksBritishSears1922Truncated
British links (Sears 1922 truncated)
@ ChainsBritishBenoit1895A
British chains (Benoit 1895 A)
@ YardsBritishBenoit1895B
British yards (Benoit 1895 B)
@ FeetBritish1865
British feet (1865)
@ YardsIndian1962
Indian yards (1962)
@ FeetBritishSears1922Truncated
British feet (Sears 1922 truncated)
@ MetersGermanLegal
German legal meter.
@ LinksBritishBenoit1895B
British links (Benoit 1895 B)
@ ChainsInternational
International chains.
@ LinksInternational
International links.
@ ChainsBritishSears1922Truncated
British chains (Sears 1922 truncated)
@ FeetIndian
Indian (geodetic) feet.
@ NauticalMiles
Nautical miles.
@ ChainsClarkes
Clarke's chains.
@ LinksClarkes
Clarke's links.
@ ChainsBritishSears1922
British chains (Sears 1922)
@ Kilometers
Kilometers.
@ FeetIndian1975
Indian feet (1975)
@ FeetGoldCoast
Gold Coast feet.
@ FeetBritishBenoit1895A
British feet (Benoit 1895 A)
@ 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
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:177
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:397
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 render(QgsRenderContext &context)
Renders the plot.
Definition qgsplot.cpp:229
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.
Interface for classes which can generate elevation profiles.
virtual QgsAbstractTerrainProvider * clone() const =0
Creates a clone of the provider and returns the new object.
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:35
QgsRange which stores a range of double values.
Definition qgsrange.h:233
A canvas for elevation profiles.
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 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.
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 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).
void render(QgsRenderContext &context, double width, double height, const Qgs2DPlot &plotSettings)
Renders a portion of the profile using the specified render context.
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.
const Qgs2DPlot & plot() const
Returns a reference to the 2D plot used by the widget.
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.
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 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:139
A line symbol type, for rendering LineString and MultiLineString geometries.
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...
Base class for all map layer types.
Definition qgsmaplayer.h:77
QString id
Definition qgsmaplayer.h:80
Qgis::LayerType type
Definition qgsmaplayer.h:87
void dataChanged()
Data of layer changed.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Perform transforms between map coordinates and device coordinates.
A context for numeric formats.
void setLabelSuffixPlacement(Qgis::PlotAxisSuffixPlacement placement)
Sets the placement for the axis label suffixes.
Definition qgsplot.cpp:129
void setLabelSuffix(const QString &suffix)
Sets the axis label suffix.
Definition qgsplot.cpp:119
An abstract class for items that can be placed on a QgsPlotCanvas.
virtual void paint(QPainter *painter)=0
Paints the item.
Plot canvas is a class for displaying interactive 2d charts and plots.
bool event(QEvent *e) override
void plotAreaChanged()
Emitted whenever the visible area of the plot is changed.
void mouseMoveEvent(QMouseEvent *e) override
void resizeEvent(QResizeEvent *e) override
Represents a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
bool isEmpty() const
Returns true if the geometry is empty.
Definition qgspointxy.h:242
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double z
Definition qgspoint.h:54
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 setSubsectionsSymbol(QgsLineSymbol *symbol)
Sets the symbol used to draw the subsections.
QgsProfileSnapResult snapPoint(const QgsProfilePoint &point, const QgsProfileSnapContext &context)
Snap a point to the results.
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.
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context)
Identify results visible at the specified profile point.
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 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.
QList< QgsAbstractProfileSource * > profileSources() const
Returns a list of registered profile sources.
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
const QgsProjectElevationProperties * elevationProperties() const
Returns the project's elevation properties, which contains the project's elevation related settings.
QgsCoordinateTransformContext transformContext
Definition qgsproject.h:113
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
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.
double screenDpi() const
Returns the current screen DPI for the screen that the parent widget appears on.
Stores settings for use within QGIS.
Definition qgssettings.h:66
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Container for all settings relating to text rendering.
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.
Represents a vector layer which manages a vector based dataset.
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:75
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:6896
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6286
const QgsCoordinateReferenceSystem & crs