QGIS API Documentation 4.1.0-Master (376402f9aeb)
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"
41#include "qgsterrainprovider.h"
42
43#include <QPalette>
44#include <QString>
45#include <QTimer>
46#include <QWheelEvent>
47
48#include "moc_qgselevationprofilecanvas.cpp"
49
50using namespace Qt::StringLiterals;
51
53class QgsElevationProfilePlotItem : public Qgs2DXyPlot, public QgsPlotCanvasItem
54{
55 public:
56 QgsElevationProfilePlotItem( QgsElevationProfileCanvas *canvas )
57 : QgsPlotCanvasItem( canvas )
58 {
59 setYMinimum( 0 );
60 setYMaximum( 100 );
61
63 }
64
65 void setRenderer( QgsProfilePlotRenderer *renderer ) { mRenderer = renderer; }
66
67 void updateRect()
68 {
69 mRect = mCanvas->rect();
70 setSize( mRect.size() );
71
72 prepareGeometryChange();
73 setPos( mRect.topLeft() );
74
75 mImage = QImage();
76 mCachedImages.clear();
77 mPlotArea = QRectF();
78 update();
79 }
80
81 void updatePlot()
82 {
83 mImage = QImage();
84 mCachedImages.clear();
85 mPlotArea = QRectF();
86 update();
87 }
88
89 bool redrawResults( const QString &sourceId )
90 {
91 auto it = mCachedImages.find( sourceId );
92 if ( it == mCachedImages.end() )
93 return false;
94
95 mCachedImages.erase( it );
96 mImage = QImage();
97 return true;
98 }
99
100 QRectF boundingRect() const override { return mRect; }
101
102 QString distanceSuffix() const
103 {
104 switch ( mDistanceUnit )
105 {
154 return u" %1"_s.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
181 QgsPlotRenderContext plotContext;
182 calculateOptimisedIntervals( context, plotContext );
183 mPlotArea = interiorPlotArea( context, plotContext );
184 return mPlotArea;
185 }
186
187 QgsProfilePoint canvasPointToPlotPoint( QPointF point )
188 {
189 const QRectF area = plotArea();
190 if ( !area.contains( point.x(), point.y() ) )
191 return QgsProfilePoint();
192
193 const double distance = ( point.x() - area.left() ) / area.width() * ( xMaximum() - xMinimum() ) * mXScaleFactor + xMinimum() * mXScaleFactor;
194 const double elevation = ( area.bottom() - point.y() ) / area.height() * ( yMaximum() - yMinimum() ) + yMinimum();
195 return QgsProfilePoint( distance, elevation );
196 }
197
198 QgsPointXY plotPointToCanvasPoint( const QgsProfilePoint &point )
199 {
200 if ( point.distance() < xMinimum() * mXScaleFactor || point.distance() > xMaximum() * mXScaleFactor || point.elevation() < yMinimum() || point.elevation() > yMaximum() )
201 return QgsPointXY();
202
203 const QRectF area = plotArea();
204
205 const double x = ( point.distance() - xMinimum() * mXScaleFactor ) / ( ( xMaximum() - xMinimum() ) * mXScaleFactor ) * ( area.width() ) + area.left();
206 const double y = area.bottom() - ( point.elevation() - yMinimum() ) / ( yMaximum() - yMinimum() ) * ( area.height() );
207 return QgsPointXY( x, y );
208 }
209
210 void renderContent( QgsRenderContext &rc, QgsPlotRenderContext &, const QRectF &plotArea, const QgsPlotData & ) override
211 {
212 mPlotArea = plotArea;
213
214 if ( !mRenderer )
215 return;
216
217 const double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
218
219 const QStringList sourceIds = mRenderer->sourceIds();
220 for ( const QString &source : sourceIds )
221 {
222 QImage plot;
223 auto it = mCachedImages.constFind( source );
224 if ( it != mCachedImages.constEnd() )
225 {
226 plot = it.value();
227 }
228 else
229 {
230 plot = mRenderer->renderToImage( plotArea.width() * pixelRatio, plotArea.height() * pixelRatio, xMinimum() * mXScaleFactor, xMaximum() * mXScaleFactor, yMinimum(), yMaximum(), source, pixelRatio );
231 plot.setDevicePixelRatio( pixelRatio );
232 mCachedImages.insert( source, plot );
233 }
234 rc.painter()->drawImage( QPointF( plotArea.left(), plotArea.top() ), plot );
235 }
236 }
237
239 void paint( QPainter *painter ) override
240 {
241 // cache rendering to an image, so we don't need to redraw the plot
242 if ( !mImage.isNull() )
243 {
244 painter->drawImage( QPointF( 0, 0 ), mImage );
245 }
246 else
247 {
248 const double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
249 mImage = QImage( mRect.width() * pixelRatio, mRect.height() * pixelRatio, QImage::Format_ARGB32_Premultiplied );
250 mImage.setDevicePixelRatio( pixelRatio );
251 mImage.fill( Qt::transparent );
252
253 QPainter imagePainter( &mImage );
254 imagePainter.setRenderHint( QPainter::Antialiasing, true );
255 QgsRenderContext rc = QgsRenderContext::fromQPainter( &imagePainter );
256 rc.setDevicePixelRatio( pixelRatio );
257
258 const double mapUnitsPerPixel = ( xMaximum() - xMinimum() ) * mXScaleFactor / plotArea().width();
259 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
260
263
264 QgsPlotRenderContext plotContext;
265 calculateOptimisedIntervals( rc, plotContext );
266 render( rc, plotContext );
267 imagePainter.end();
268
269 painter->drawImage( QPointF( 0, 0 ), mImage );
270 }
271 }
272
273 void setSubsectionsSymbol( QgsLineSymbol *symbol )
274 {
275 if ( mRenderer )
276 {
277 mRenderer->setSubsectionsSymbol( symbol );
278 updatePlot();
279 }
280 }
281
282 QgsProject *mProject = nullptr;
283 double mXScaleFactor = 1.0;
284
286
287 private:
288 QImage mImage;
289
290 QMap<QString, QImage> mCachedImages;
291
292 QRectF mRect;
293 QRectF mPlotArea;
294 QgsProfilePlotRenderer *mRenderer = nullptr;
295};
296
297class QgsElevationProfileCrossHairsItem : public QgsPlotCanvasItem
298{
299 public:
300 QgsElevationProfileCrossHairsItem( QgsElevationProfileCanvas *canvas, QgsElevationProfilePlotItem *plotItem )
301 : QgsPlotCanvasItem( canvas )
302 , mPlotItem( plotItem )
303 {}
304
305 void updateRect()
306 {
307 mRect = mCanvas->rect();
308
309 prepareGeometryChange();
310 setPos( mRect.topLeft() );
311 update();
312 }
313
314 void setPoint( const QgsProfilePoint &point )
315 {
316 mPoint = point;
317 update();
318 }
319
320 QRectF boundingRect() const override { return mRect; }
321
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 ) + mPlotItem->distanceSuffix();
348
349 const QString yCoordinateText = mPlotItem->yAxis().numericFormat()->formatDouble( mPoint.elevation(), numericContext );
350
351 QFont font;
352 const QFontMetrics fm( font );
353 const double height = fm.capHeight();
354 const double xWidth = fm.horizontalAdvance( xCoordinateText );
355 const double yWidth = fm.horizontalAdvance( yCoordinateText );
356 const double textAxisMargin = fm.horizontalAdvance( ' ' );
357
358 QPointF xCoordOrigin;
359 QPointF yCoordOrigin;
360
361 if ( mPoint.distance() < ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5 * mPlotItem->mXScaleFactor )
362 {
363 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
364 {
365 // render x coordinate on right top (left top align)
366 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
367 // render y coordinate on right top (right bottom align)
368 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
369 }
370 else
371 {
372 // render x coordinate on right bottom (left bottom align)
373 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
374 // render y coordinate on right bottom (right top align)
375 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
376 }
377 }
378 else
379 {
380 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
381 {
382 // render x coordinate on left top (right top align)
383 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
384 // render y coordinate on left top (left bottom align)
385 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
386 }
387 else
388 {
389 // render x coordinate on left bottom (right bottom align)
390 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
391 // render y coordinate on left bottom (left top align)
392 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
393 }
394 }
395
396 // semi opaque background color brush
397 QColor backgroundColor = mPlotItem->chartBackgroundSymbol()->color();
398 backgroundColor.setAlpha( 220 );
399 painter->setBrush( QBrush( backgroundColor ) );
400 painter->setPen( Qt::NoPen );
401 painter->drawRect( QRectF( xCoordOrigin.x() - textAxisMargin + 1, xCoordOrigin.y() - textAxisMargin - height + 1, xWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
402 painter->drawRect( QRectF( yCoordOrigin.x() - textAxisMargin + 1, yCoordOrigin.y() - textAxisMargin - height + 1, yWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
403
404 painter->setBrush( Qt::NoBrush );
405 painter->setPen( scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
406
407 painter->drawText( xCoordOrigin, xCoordinateText );
408 painter->drawText( yCoordOrigin, yCoordinateText );
409 painter->restore();
410 }
411
412 private:
413 QRectF mRect;
414 QgsProfilePoint mPoint;
415 QgsElevationProfilePlotItem *mPlotItem = nullptr;
416};
418
419
421 : QgsPlotCanvas( parent )
422{
423 mScreenHelper = new QgsScreenHelper( this );
424
425 mPlotItem = new QgsElevationProfilePlotItem( this );
426
427 // follow system color scheme by default
428 setBackgroundColor( QColor() );
429
430 mCrossHairsItem = new QgsElevationProfileCrossHairsItem( this, mPlotItem );
431 mCrossHairsItem->setZValue( 100 );
432 mCrossHairsItem->hide();
433
434 // updating the profile plot is deferred on a timer, so that we don't trigger it too often
435 mDeferredRegenerationTimer = new QTimer( this );
436 mDeferredRegenerationTimer->setSingleShot( true );
437 mDeferredRegenerationTimer->stop();
438 connect( mDeferredRegenerationTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRegeneration );
439
440 mDeferredRedrawTimer = new QTimer( this );
441 mDeferredRedrawTimer->setSingleShot( true );
442 mDeferredRedrawTimer->stop();
443 connect( mDeferredRedrawTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRedraw );
444
445 connect( QgsApplication::profileSourceRegistry(), &QgsProfileSourceRegistry::profileSourceRegistered, this, &QgsElevationProfileCanvas::setSourcesPrivate );
446 connect( QgsApplication::profileSourceRegistry(), &QgsProfileSourceRegistry::profileSourceUnregistered, this, &QgsElevationProfileCanvas::setSourcesPrivate );
447
448 setSourcesPrivate(); // Initialize with registered sources
449}
450
452{
453 if ( mCurrentJob )
454 {
455 mPlotItem->setRenderer( nullptr );
456 mCurrentJob->deleteLater();
457 mCurrentJob = nullptr;
458 }
459}
460
462{
463 if ( mCurrentJob )
464 {
465 mPlotItem->setRenderer( nullptr );
466 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
467 mCurrentJob->cancelGeneration();
468 mCurrentJob->deleteLater();
469 mCurrentJob = nullptr;
470 }
471}
472
474{
475 const double dxPercent = dx / mPlotItem->plotArea().width();
476 const double dyPercent = dy / mPlotItem->plotArea().height();
477
478 // these look backwards, but we are dragging the paper, not the view!
479 const double dxPlot = -dxPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
480 const double dyPlot = dyPercent * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
481
482 // no need to handle axis scale lock here, we aren't changing scales
483 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
484 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
485 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
486 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
487
488 refineResults();
489
490 mPlotItem->updatePlot();
491 emit plotAreaChanged();
492}
493
495{
496 if ( !mPlotItem->plotArea().contains( x, y ) )
497 return;
498
499 const double newCenterX = mPlotItem->xMinimum() + ( x - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
500 const double newCenterY = mPlotItem->yMinimum() + ( mPlotItem->plotArea().bottom() - y ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
501
502 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
503 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
504
505 // no need to handle axis scale lock here, we aren't changing scales
506 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
507 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
508 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
509 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
510
511 refineResults();
512
513 mPlotItem->updatePlot();
514 emit plotAreaChanged();
515}
516
518{
519 scalePlot( factor, factor );
520 emit plotAreaChanged();
521}
522
523QgsProfileSnapContext QgsElevationProfileCanvas::snapContext() const
524{
525 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
526 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
527 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
528
529 QgsProfileSnapContext context;
530 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
531 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
532 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
533 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
534 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
535 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) );
536
537 return context;
538}
539
540QgsProfileIdentifyContext QgsElevationProfileCanvas::identifyContext() const
541{
542 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
543 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
544 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
545
546 QgsProfileIdentifyContext context;
547 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
548 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
549 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
550 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
551 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
552 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) );
553
554 context.project = mProject;
555
556 return context;
557}
558
559void QgsElevationProfileCanvas::setupLayerConnections( QgsMapLayer *layer, bool isDisconnect )
560{
561 if ( isDisconnect )
562 {
563 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
564 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
565 disconnect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
566 }
567 else
568 {
569 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
570 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
571 connect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
572 }
573
574 switch ( layer->type() )
575 {
577 {
578 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
579 if ( isDisconnect )
580 {
581 disconnect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
582 disconnect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
583 disconnect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
584 disconnect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
585 }
586 else
587 {
588 connect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
589 connect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
590 connect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
591 connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
592 }
593 break;
594 }
603 break;
604 }
605}
606
607void QgsElevationProfileCanvas::adjustRangeForAxisScaleLock( double &xMinimum, double &xMaximum, double &yMinimum, double &yMaximum ) const
608{
609 // ensures that we always "zoom out" to match horizontal/vertical scales
610 const double horizontalScale = ( xMaximum - xMinimum ) / mPlotItem->plotArea().width();
611 const double verticalScale = ( yMaximum - yMinimum ) / mPlotItem->plotArea().height();
612
613 const double currentRatio = horizontalScale / verticalScale;
614
615 if ( currentRatio <= mLockedAxisScale )
616 {
617 const double height = horizontalScale * mPlotItem->plotArea().height() / mLockedAxisScale;
618 const double deltaHeight = ( yMaximum - yMinimum ) - height;
619 yMinimum += deltaHeight / 2;
620 yMaximum -= deltaHeight / 2;
621 }
622 else
623 {
624 const double width = verticalScale * mPlotItem->plotArea().width() * mLockedAxisScale;
625 const double deltaWidth = ( ( xMaximum - xMinimum ) - width );
626 xMinimum += deltaWidth / 2;
627 xMaximum -= deltaWidth / 2;
628 }
629}
630
632{
633 return mDistanceUnit;
634}
635
637{
638 mDistanceUnit = unit;
639 const double oldMin = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
640 const double oldMax = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
641 mPlotItem->mXScaleFactor = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
642 mPlotItem->setXAxisUnits( mDistanceUnit );
643 mPlotItem->setXMinimum( oldMin / mPlotItem->mXScaleFactor );
644 mPlotItem->setXMaximum( oldMax / mPlotItem->mXScaleFactor );
645 mPlotItem->updatePlot();
646 emit scaleChanged();
647}
648
650{
651 if ( !color.isValid() )
652 {
653 QPalette customPalette = qApp->palette();
654 const QColor baseColor = qApp->palette().color( QPalette::ColorRole::Base );
655 const QColor windowColor = qApp->palette().color( QPalette::ColorRole::Window );
656 customPalette.setColor( QPalette::ColorRole::Base, windowColor );
657 customPalette.setColor( QPalette::ColorRole::Window, baseColor );
658 setPalette( customPalette );
659 scene()->setPalette( customPalette );
660 }
661 else
662 {
663 // build custom palette
664 const bool isDarkTheme = color.lightnessF() < 0.5;
665 QPalette customPalette = qApp->palette();
666 customPalette.setColor( QPalette::ColorRole::Window, color );
667 if ( isDarkTheme )
668 {
669 customPalette.setColor( QPalette::ColorRole::Text, QColor( 255, 255, 255 ) );
670 customPalette.setColor( QPalette::ColorRole::Base, color.lighter( 120 ) );
671 }
672 else
673 {
674 customPalette.setColor( QPalette::ColorRole::Text, QColor( 0, 0, 0 ) );
675 customPalette.setColor( QPalette::ColorRole::Base, color.darker( 120 ) );
676 }
677
678 setPalette( customPalette );
679 scene()->setPalette( customPalette );
680 }
681
682 updateChartFromPalette();
683}
684
686{
687 return mLockAxisScales;
688}
689
691{
692 mLockAxisScales = lock;
693 if ( mLockAxisScales )
694 {
695 double xMinimum = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
696 double xMaximum = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
697 double yMinimum = mPlotItem->yMinimum();
698 double yMaximum = mPlotItem->yMaximum();
699 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
700 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
701 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
702 mPlotItem->setYMinimum( yMinimum );
703 mPlotItem->setYMaximum( yMaximum );
704
705 refineResults();
706 mPlotItem->updatePlot();
707 emit plotAreaChanged();
708 emit scaleChanged();
709 }
710}
711
713{
714 const double horizontalScale = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / mPlotItem->plotArea().width();
715 const double verticalScale = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / mPlotItem->plotArea().height();
716 return horizontalScale / verticalScale;
717}
718
720{
721 mLockedAxisScale = scale;
722
723 double xMinimum = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
724 double xMaximum = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
725 double yMinimum = mPlotItem->yMinimum();
726 double yMaximum = mPlotItem->yMaximum();
727 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
728 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
729 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
730 mPlotItem->setYMinimum( yMinimum );
731 mPlotItem->setYMaximum( yMaximum );
732
733 refineResults();
734 mPlotItem->updatePlot();
735 emit plotAreaChanged();
736 emit scaleChanged();
737}
738
740{
741 if ( !mCurrentJob || !mSnappingEnabled )
742 return QgsPointXY();
743
744 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
745
746 const QgsProfileSnapResult snappedPoint = mCurrentJob->snapPoint( plotPoint, snapContext() );
747 if ( !snappedPoint.isValid() )
748 return QgsPointXY();
749
750 return plotPointToCanvasPoint( snappedPoint.snappedPoint );
751}
752
753void QgsElevationProfileCanvas::scalePlot( double xFactor, double yFactor )
754{
755 if ( mLockAxisScales )
756 yFactor = xFactor;
757
758 const double currentWidth = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor;
759 const double currentHeight = mPlotItem->yMaximum() - mPlotItem->yMinimum();
760
761 const double newWidth = currentWidth / xFactor;
762 const double newHeight = currentHeight / yFactor;
763
764 const double currentCenterX = ( mPlotItem->xMinimum() + mPlotItem->xMaximum() ) * 0.5 * mPlotItem->mXScaleFactor;
765 const double currentCenterY = ( mPlotItem->yMinimum() + mPlotItem->yMaximum() ) * 0.5;
766
767 double xMinimum = currentCenterX - newWidth * 0.5;
768 double xMaximum = currentCenterX + newWidth * 0.5;
769 double yMinimum = currentCenterY - newHeight * 0.5;
770 double yMaximum = currentCenterY + newHeight * 0.5;
771 if ( mLockAxisScales )
772 {
773 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
774 }
775
776 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
777 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
778 mPlotItem->setYMinimum( yMinimum );
779 mPlotItem->setYMaximum( yMaximum );
780
781 refineResults();
782 mPlotItem->updatePlot();
783 emit plotAreaChanged();
784 emit scaleChanged();
785}
786
788{
789 const QRectF intersected = rect.intersected( mPlotItem->plotArea() );
790
791 double minX = ( intersected.left() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor
792 + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
793 double maxX = ( intersected.right() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor
794 + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
795 double minY = ( mPlotItem->plotArea().bottom() - intersected.bottom() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
796 double maxY = ( mPlotItem->plotArea().bottom() - intersected.top() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
797
798 if ( mLockAxisScales )
799 {
800 adjustRangeForAxisScaleLock( minX, maxX, minY, maxY );
801 }
802
803 mPlotItem->setXMinimum( minX / mPlotItem->mXScaleFactor );
804 mPlotItem->setXMaximum( maxX / mPlotItem->mXScaleFactor );
805 mPlotItem->setYMinimum( minY );
806 mPlotItem->setYMaximum( maxY );
807
808 refineResults();
809 mPlotItem->updatePlot();
810 emit plotAreaChanged();
811 emit scaleChanged();
812}
813
815{
816 //get mouse wheel zoom behavior settings
817 double zoomFactor = QgsSettingsRegistryGui::settingsZoomFactor->value();
818 bool reverseZoom = QgsSettingsRegistryGui::settingsReverseWheelZoom->value();
819 bool zoomIn = reverseZoom ? event->angleDelta().y() < 0 : event->angleDelta().y() > 0;
820
821 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
822 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
823
824 if ( event->modifiers() & Qt::ControlModifier )
825 {
826 //holding ctrl while wheel zooming results in a finer zoom
827 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
828 }
829
830 //calculate zoom scale factor
831 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
832
833 QRectF viewportRect = mPlotItem->plotArea();
834
835 if ( viewportRect.contains( event->position() ) )
836 {
837 //adjust view center
838 const double oldCenterX = 0.5 * ( mPlotItem->xMaximum() + mPlotItem->xMinimum() );
839 const double oldCenterY = 0.5 * ( mPlotItem->yMaximum() + mPlotItem->yMinimum() );
840
841 const double eventPosX = ( event->position().x() - viewportRect.left() ) / viewportRect.width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
842 const double eventPosY = ( viewportRect.bottom() - event->position().y() ) / viewportRect.height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
843
844 const double newCenterX = eventPosX + ( ( oldCenterX - eventPosX ) * scaleFactor );
845 const double newCenterY = eventPosY + ( ( oldCenterY - eventPosY ) * scaleFactor );
846
847 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
848 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
849
850 // don't need to handle axis scale lock here, we are always changing axis by the same scale
851 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
852 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
853 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
854 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
855 }
856
857 //zoom plot
858 if ( zoomIn )
859 {
860 scalePlot( zoomFactor );
861 }
862 else
863 {
864 scalePlot( 1 / zoomFactor );
865 }
866 emit plotAreaChanged();
867 emit scaleChanged();
868}
869
871{
873 if ( e->isAccepted() )
874 {
875 mCrossHairsItem->hide();
876 return;
877 }
878
879 QgsProfilePoint plotPoint = canvasPointToPlotPoint( e->pos() );
880 if ( mCurrentJob && mSnappingEnabled && !plotPoint.isEmpty() )
881 {
882 const QgsProfileSnapResult snapResult = mCurrentJob->snapPoint( plotPoint, snapContext() );
883 if ( snapResult.isValid() )
884 plotPoint = snapResult.snappedPoint;
885 }
886
887 if ( plotPoint.isEmpty() )
888 {
889 mCrossHairsItem->hide();
890 }
891 else
892 {
893 mCrossHairsItem->setPoint( plotPoint );
894 mCrossHairsItem->show();
895 }
896 emit canvasPointHovered( e->pos(), plotPoint );
897}
898
900{
901 return mPlotItem->plotArea();
902}
903
905{
906 if ( !mProject || !profileCurve() )
907 return;
908
909 cancelJobs();
910
911 QgsProfileRequest request( profileCurve()->clone() );
912 request.setCrs( mCrs );
913 request.setTolerance( mTolerance );
914 request.setTransformContext( mProject->transformContext() );
915 request.setTerrainProvider( mProject->elevationProperties()->terrainProvider() ? mProject->elevationProperties()->terrainProvider()->clone() : nullptr );
916 QgsExpressionContext context;
919 request.setExpressionContext( context );
920
921 QList< QgsAbstractProfileSource *> sourcesToRender = sources();
922 std::reverse( sourcesToRender.begin(), sourcesToRender.end() ); // sources are rendered from bottom to top
923
924 mCurrentJob = new QgsProfilePlotRenderer( sourcesToRender, request );
925 connect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
926
927 QgsProfileGenerationContext generationContext;
928 generationContext.setDpi( mScreenHelper->screenDpi() );
929 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( mProfileCurve->length() ) / mPlotItem->plotArea().width() );
930 generationContext.setMapUnitsPerDistancePixel( mProfileCurve->length() / mPlotItem->plotArea().width() );
931 mCurrentJob->setContext( generationContext );
932
933 if ( mSubsectionsSymbol )
934 {
935 mCurrentJob->setSubsectionsSymbol( mSubsectionsSymbol->clone() );
936 }
937
938 mCurrentJob->startGeneration();
939 mPlotItem->setRenderer( mCurrentJob );
940
941 emit activeJobCountChanged( 1 );
942}
943
945{
946 mZoomFullWhenJobFinished = true;
947}
948
949void QgsElevationProfileCanvas::generationFinished()
950{
951 if ( !mCurrentJob )
952 return;
953
954 emit activeJobCountChanged( 0 );
955
956 if ( mZoomFullWhenJobFinished )
957 {
958 // we only zoom full for the initial generation
959 mZoomFullWhenJobFinished = false;
960 zoomFull();
961 }
962 else
963 {
964 // here we should invalidate cached results only for the layers which have been refined
965
966 // and if no layers are being refeined, don't invalidate anything
967
968 mPlotItem->updatePlot();
969 }
970
971 if ( mForceRegenerationAfterCurrentJobCompletes )
972 {
973 mForceRegenerationAfterCurrentJobCompletes = false;
974 mCurrentJob->invalidateAllRefinableSources();
975 scheduleDeferredRegeneration();
976 }
977}
978
979void QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged()
980{
981 // TODO -- handle nicely when existing job is in progress
982 if ( !mCurrentJob || mCurrentJob->isActive() )
983 return;
984
985 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
986 if ( !properties )
987 return;
988
989 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
990 {
991 if ( QgsAbstractProfileSource *source = layer->profileSource() )
992 {
993 if ( mCurrentJob->invalidateResults( source ) )
994 scheduleDeferredRegeneration();
995 }
996 }
997}
998
999void QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged()
1000{
1001 // TODO -- handle nicely when existing job is in progress
1002 if ( !mCurrentJob || mCurrentJob->isActive() )
1003 return;
1004
1005 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
1006 if ( !properties )
1007 return;
1008
1009 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
1010 {
1011 if ( QgsAbstractProfileSource *source = layer->profileSource() )
1012 {
1013 mCurrentJob->replaceSource( source );
1014 }
1015 if ( mPlotItem->redrawResults( layer->id() ) )
1016 scheduleDeferredRedraw();
1017 }
1018}
1019
1020void QgsElevationProfileCanvas::regenerateResultsForLayer()
1021{
1022 if ( !mCurrentJob )
1023 return;
1024
1025 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() ) )
1026 {
1027 if ( QgsAbstractProfileSource *source = layer->profileSource() )
1028 {
1029 if ( mCurrentJob->invalidateResults( source ) )
1030 scheduleDeferredRegeneration();
1031 }
1032 }
1033}
1034
1035void QgsElevationProfileCanvas::scheduleDeferredRegeneration()
1036{
1037 if ( !mDeferredRegenerationScheduled )
1038 {
1039 mDeferredRegenerationTimer->start( 1 );
1040 mDeferredRegenerationScheduled = true;
1041 }
1042}
1043
1044void QgsElevationProfileCanvas::scheduleDeferredRedraw()
1045{
1046 if ( !mDeferredRedrawScheduled )
1047 {
1048 mDeferredRedrawTimer->start( 1 );
1049 mDeferredRedrawScheduled = true;
1050 }
1051}
1052
1053void QgsElevationProfileCanvas::startDeferredRegeneration()
1054{
1055 if ( mCurrentJob && !mCurrentJob->isActive() )
1056 {
1057 emit activeJobCountChanged( 1 );
1058 mCurrentJob->regenerateInvalidatedResults();
1059 }
1060 else if ( mCurrentJob )
1061 {
1062 mForceRegenerationAfterCurrentJobCompletes = true;
1063 }
1064
1065 mDeferredRegenerationScheduled = false;
1066}
1067
1068void QgsElevationProfileCanvas::startDeferredRedraw()
1069{
1070 mPlotItem->update();
1071 mDeferredRedrawScheduled = false;
1072}
1073
1074void QgsElevationProfileCanvas::refineResults()
1075{
1076 if ( mCurrentJob )
1077 {
1078 QgsProfileGenerationContext context;
1079 context.setDpi( mScreenHelper->screenDpi() );
1080 const double plotDistanceRange = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor;
1081 const double plotElevationRange = mPlotItem->yMaximum() - mPlotItem->yMinimum();
1082 const double plotDistanceUnitsPerPixel = plotDistanceRange / mPlotItem->plotArea().width();
1083
1084 // we round the actual desired map error down to just one significant figure, to avoid tiny differences
1085 // as the plot is panned
1086 const double targetMaxErrorInMapUnits = MAX_ERROR_PIXELS * plotDistanceUnitsPerPixel;
1087 const double factor = std::pow( 10.0, 1 - std::ceil( std::log10( std::fabs( targetMaxErrorInMapUnits ) ) ) );
1088 const double roundedErrorInMapUnits = std::floor( targetMaxErrorInMapUnits * factor ) / factor;
1089 context.setMaximumErrorMapUnits( roundedErrorInMapUnits );
1090
1091 context.setMapUnitsPerDistancePixel( plotDistanceUnitsPerPixel );
1092
1093 // for similar reasons we round the minimum distance off to multiples of the maximum error in map units
1094 const double distanceMin = std::floor( ( mPlotItem->xMinimum() * mPlotItem->mXScaleFactor - plotDistanceRange * 0.05 ) / context.maximumErrorMapUnits() ) * context.maximumErrorMapUnits();
1095 context.setDistanceRange( QgsDoubleRange( std::max( 0.0, distanceMin ), mPlotItem->xMaximum() * mPlotItem->mXScaleFactor + plotDistanceRange * 0.05 ) );
1096
1097 context.setElevationRange( QgsDoubleRange( mPlotItem->yMinimum() - plotElevationRange * 0.05, mPlotItem->yMaximum() + plotElevationRange * 0.05 ) );
1098 mCurrentJob->setContext( context );
1099 }
1100 scheduleDeferredRegeneration();
1101}
1102
1103void QgsElevationProfileCanvas::updateChartFromPalette()
1104{
1105 const QPalette chartPalette = palette();
1106 setBackgroundBrush( QBrush( chartPalette.color( QPalette::ColorRole::Base ) ) );
1107 {
1108 QgsTextFormat textFormat = mPlotItem->xAxis().textFormat();
1109 textFormat.setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
1110 mPlotItem->xAxis().setTextFormat( textFormat );
1111 mPlotItem->yAxis().setTextFormat( textFormat );
1112 }
1113 {
1114 std::unique_ptr<QgsFillSymbol> chartFill( mPlotItem->chartBackgroundSymbol()->clone() );
1115 chartFill->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Window ) );
1116 mPlotItem->setChartBackgroundSymbol( chartFill.release() );
1117 }
1118 {
1119 std::unique_ptr<QgsFillSymbol> chartBorder( mPlotItem->chartBorderSymbol()->clone() );
1120 chartBorder->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text ) );
1121 mPlotItem->setChartBorderSymbol( chartBorder.release() );
1122 }
1123 {
1124 std::unique_ptr<QgsLineSymbol> chartMajorSymbol( mPlotItem->xAxis().gridMajorSymbol()->clone() );
1125 QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
1126 c.setAlpha( 150 );
1127 chartMajorSymbol->setColor( c );
1128 mPlotItem->xAxis().setGridMajorSymbol( chartMajorSymbol->clone() );
1129 mPlotItem->yAxis().setGridMajorSymbol( chartMajorSymbol.release() );
1130 }
1131 {
1132 std::unique_ptr<QgsLineSymbol> chartMinorSymbol( mPlotItem->xAxis().gridMinorSymbol()->clone() );
1133 QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
1134 c.setAlpha( 50 );
1135 chartMinorSymbol->setColor( c );
1136 mPlotItem->xAxis().setGridMinorSymbol( chartMinorSymbol->clone() );
1137 mPlotItem->yAxis().setGridMinorSymbol( chartMinorSymbol.release() );
1138 }
1139 mPlotItem->updatePlot();
1140}
1141
1143{
1144 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1145 return QgsProfilePoint();
1146
1147 return mPlotItem->canvasPointToPlotPoint( point );
1148}
1149
1151{
1152 return mPlotItem->plotPointToCanvasPoint( point );
1153}
1154
1156{
1157 mProject = project;
1158 mPlotItem->mProject = project;
1159}
1160
1165
1167{
1168 mProfileCurve.reset( curve );
1169}
1170
1172{
1173 return mProfileCurve.get();
1174}
1175
1177{
1178 mTolerance = tolerance;
1179}
1180
1185
1186void QgsElevationProfileCanvas::setLayers( const QList<QgsMapLayer *> &layers )
1187{
1188 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
1189 {
1190 setupLayerConnections( layer, true );
1191 }
1192
1193 // filter list, removing null layers and invalid layers
1194 auto filteredList = layers;
1195 filteredList.erase( std::remove_if( filteredList.begin(), filteredList.end(), []( QgsMapLayer *layer ) { return !layer || !layer->isValid(); } ), filteredList.end() );
1196
1197 mLayers = _qgis_listRawToQPointer( filteredList );
1198 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
1199 {
1200 setupLayerConnections( layer, false );
1201 }
1202}
1203
1204QList<QgsMapLayer *> QgsElevationProfileCanvas::layers() const
1205{
1206 return _qgis_listQPointerToRaw( mLayers );
1207}
1208
1209void QgsElevationProfileCanvas::setSources( const QList<QgsAbstractProfileSource *> &sources )
1210{
1211 mSources.clear();
1212 mSources.reserve( sources.count() );
1213 for ( QgsAbstractProfileSource *profileSource : sources )
1214 {
1215 if ( auto layer = dynamic_cast<QgsMapLayer *>( profileSource ) )
1216 {
1217 mSources << QgsWeakMapLayerPointer( layer );
1218 }
1219 else if ( QgsApplication::profileSourceRegistry()->findSourceById( profileSource->profileSourceId() ) )
1220 {
1221 mSources << profileSource;
1222 }
1223 }
1224}
1225
1226QList<QgsAbstractProfileSource *> QgsElevationProfileCanvas::sources() const
1227{
1228 if ( mSources.isEmpty() && !mLayers.isEmpty() )
1229 {
1230 // Legacy: If we have layers, extract their sources and return them.
1231 // We don't set mSources here, because we want the previous check to
1232 // continue failing if only layers are set.
1233 // TODO: Remove in QGIS 5.0.
1234 QList< QgsAbstractProfileSource * > sources;
1235 const QList<QgsMapLayer *> layersToGenerate = layers();
1236 sources.reserve( layersToGenerate.size() );
1237
1238 for ( QgsMapLayer *layer : layersToGenerate )
1239 {
1240 if ( QgsAbstractProfileSource *source = layer->profileSource() )
1241 sources << source;
1242 }
1243
1244 // More legacy: canvas layers are in the same direction as the renderer
1245 // expects, but since we reverse sources() (i.e., layers) before passing
1246 // them to the renderer, then we need to reverse them here first.
1247 std::reverse( sources.begin(), sources.end() );
1248 return sources;
1249 }
1250
1251 QList< QgsAbstractProfileSource * > sources;
1252 sources.reserve( mSources.count() );
1253 for ( const auto &source : mSources )
1254 {
1255 if ( const QgsWeakMapLayerPointer *weakLayerPointer = std::get_if< QgsWeakMapLayerPointer >( &source ) )
1256 {
1257 if ( QgsMapLayer *layer = weakLayerPointer->data() )
1258 {
1259 sources << layer->profileSource();
1260 }
1261 }
1262 else if ( auto profileSource = std::get_if< QgsAbstractProfileSource * >( &source ) )
1263 {
1264 if ( QgsApplication::profileSourceRegistry()->findSourceById( ( *profileSource )->profileSourceId() ) )
1265 {
1266 sources << *profileSource;
1267 }
1268 }
1269 }
1270
1271 return sources;
1272}
1273
1275{
1277
1278 if ( mLockAxisScales )
1279 {
1280 double xMinimum = mPlotItem->xMinimum();
1281 double xMaximum = mPlotItem->xMaximum();
1282 double yMinimum = mPlotItem->yMinimum();
1283 double yMaximum = mPlotItem->yMaximum();
1284 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1285 mPlotItem->setXMinimum( xMinimum );
1286 mPlotItem->setXMaximum( xMaximum );
1287 mPlotItem->setYMinimum( yMinimum );
1288 mPlotItem->setYMaximum( yMaximum );
1289 }
1290
1291 mPlotItem->updateRect();
1292 mCrossHairsItem->updateRect();
1293
1294 emit scaleChanged();
1295}
1296
1298{
1299 QgsPlotCanvas::paintEvent( event );
1300
1301 if ( !mFirstDrawOccurred )
1302 {
1303 // 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).
1304 mFirstDrawOccurred = true;
1305 mPlotItem->updateRect();
1306 mCrossHairsItem->updateRect();
1307 }
1308}
1309
1311{
1312 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1313 return QgsPoint();
1314
1315 if ( !mProfileCurve )
1316 return QgsPoint();
1317
1318 const double dx = point.x() - mPlotItem->plotArea().left();
1319
1320 const double distanceAlongPlotPercent = dx / mPlotItem->plotArea().width();
1321 double distanceAlongCurveLength = distanceAlongPlotPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
1322
1323 std::unique_ptr<QgsPoint> mapXyPoint( mProfileCurve->interpolatePoint( distanceAlongCurveLength ) );
1324 if ( !mapXyPoint )
1325 return QgsPoint();
1326
1327 const double mapZ = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * ( mPlotItem->plotArea().bottom() - point.y() ) + mPlotItem->yMinimum();
1328
1329 return QgsPoint( mapXyPoint->x(), mapXyPoint->y(), mapZ );
1330}
1331
1333{
1334 if ( !mProfileCurve )
1335 return QgsPointXY();
1336
1337 QgsGeos geos( mProfileCurve.get() );
1338 QString error;
1339 const double distanceAlongCurve = geos.lineLocatePoint( point, &error );
1340
1341 const double distanceAlongCurveOnPlot = distanceAlongCurve - mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
1342 const double distanceAlongCurvePercent = distanceAlongCurveOnPlot / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor );
1343 const double distanceAlongPlotRect = distanceAlongCurvePercent * mPlotItem->plotArea().width();
1344
1345 const double canvasX = mPlotItem->plotArea().left() + distanceAlongPlotRect;
1346
1347 double canvasY = 0;
1348 if ( std::isnan( point.z() ) || point.z() < mPlotItem->yMinimum() )
1349 {
1350 canvasY = mPlotItem->plotArea().top();
1351 }
1352 else if ( point.z() > mPlotItem->yMaximum() )
1353 {
1354 canvasY = mPlotItem->plotArea().bottom();
1355 }
1356 else
1357 {
1358 const double yPercent = ( point.z() - mPlotItem->yMinimum() ) / ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
1359 canvasY = mPlotItem->plotArea().bottom() - mPlotItem->plotArea().height() * yPercent;
1360 }
1361
1362 return QgsPointXY( canvasX, canvasY );
1363}
1364
1366{
1367 if ( !mCurrentJob )
1368 return;
1369
1370 const QgsDoubleRange zRange = mCurrentJob->zRange();
1371
1372 double yMinimum = 0;
1373 double yMaximum = 0;
1374
1375 if ( zRange.upper() < zRange.lower() )
1376 {
1377 // invalid range, e.g. no features found in plot!
1378 yMinimum = 0;
1379 yMaximum = 10;
1380 }
1381 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
1382 {
1383 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
1384 yMinimum = zRange.lower() - 5;
1385 yMaximum = zRange.lower() + 5;
1386 }
1387 else
1388 {
1389 // add 5% margin to height range
1390 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
1391 yMinimum = zRange.lower() - margin;
1392 yMaximum = zRange.upper() + margin;
1393 }
1394
1395 const double profileLength = profileCurve()->length();
1396 double xMinimum = 0;
1397 // just 2% margin to max distance -- any more is overkill and wasted space
1398 double xMaximum = profileLength * 1.02;
1399
1400 if ( mLockAxisScales )
1401 {
1402 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1403 }
1404
1405 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
1406 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
1407 mPlotItem->setYMinimum( yMinimum );
1408 mPlotItem->setYMaximum( yMaximum );
1409
1410 refineResults();
1411 mPlotItem->updatePlot();
1412 emit plotAreaChanged();
1413 emit scaleChanged();
1414}
1415
1416void QgsElevationProfileCanvas::setVisiblePlotRange( double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation )
1417{
1418 if ( mLockAxisScales )
1419 {
1420 adjustRangeForAxisScaleLock( minimumDistance, maximumDistance, minimumElevation, maximumElevation );
1421 }
1422
1423 mPlotItem->setYMinimum( minimumElevation );
1424 mPlotItem->setYMaximum( maximumElevation );
1425 mPlotItem->setXMinimum( minimumDistance / mPlotItem->mXScaleFactor );
1426 mPlotItem->setXMaximum( maximumDistance / mPlotItem->mXScaleFactor );
1427 refineResults();
1428 mPlotItem->updatePlot();
1429 emit plotAreaChanged();
1430 emit scaleChanged();
1431}
1432
1434{
1435 return QgsDoubleRange( mPlotItem->xMinimum() * mPlotItem->mXScaleFactor, mPlotItem->xMaximum() * mPlotItem->mXScaleFactor );
1436}
1437
1439{
1440 return QgsDoubleRange( mPlotItem->yMinimum(), mPlotItem->yMaximum() );
1441}
1442
1444{
1445 if ( mCurrentJob )
1446 return mCurrentJob->zRange();
1447 return QgsDoubleRange();
1448}
1449
1451{
1452 return *mPlotItem;
1453}
1454
1456class QgsElevationProfilePlot : public Qgs2DXyPlot
1457{
1458 public:
1459 QgsElevationProfilePlot( QgsProfilePlotRenderer *renderer )
1460 : mRenderer( renderer )
1461 {}
1462
1463 void renderContent( QgsRenderContext &rc, QgsPlotRenderContext &, const QRectF &plotArea, const QgsPlotData & ) override
1464 {
1465 if ( !mRenderer )
1466 return;
1467
1468 rc.painter()->translate( plotArea.left(), plotArea.top() );
1469 mRenderer->render( rc, plotArea.width(), plotArea.height(), xMinimum() * mXScale, xMaximum() * mXScale, yMinimum(), yMaximum() );
1470 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
1471 }
1472
1473 double mXScale = 1;
1474
1475 private:
1476 QgsProfilePlotRenderer *mRenderer = nullptr;
1477};
1479
1480void QgsElevationProfileCanvas::render( QgsRenderContext &context, double width, double height, const Qgs2DXyPlot &plotSettings )
1481{
1482 if ( !mCurrentJob )
1483 return;
1484
1487
1488 QgsElevationProfilePlot profilePlot( mCurrentJob );
1489
1490 // 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...
1491 QDomDocument doc;
1492 QDomElement elem = doc.createElement( u"plot"_s );
1493 QgsReadWriteContext rwContext;
1494 plotSettings.writeXml( elem, doc, rwContext );
1495 profilePlot.readXml( elem, rwContext );
1496
1497 profilePlot.mXScale = mPlotItem->mXScaleFactor;
1498 profilePlot.xAxis().setLabelSuffix( mPlotItem->xAxis().labelSuffix() );
1499 profilePlot.xAxis().setLabelSuffixPlacement( mPlotItem->xAxis().labelSuffixPlacement() );
1500
1501 profilePlot.setSize( QSizeF( width, height ) );
1502 QgsPlotRenderContext plotContext;
1503 profilePlot.render( context, plotContext );
1504}
1505
1506QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( QPointF point )
1507{
1508 if ( !mCurrentJob )
1509 return {};
1510
1511 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
1512
1513 return mCurrentJob->identify( plotPoint, identifyContext() );
1514}
1515
1516QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( const QRectF &rect )
1517{
1518 if ( !mCurrentJob )
1519 return {};
1520
1521 const QgsProfilePoint topLeftPlotPoint = canvasPointToPlotPoint( rect.topLeft() );
1522 const QgsProfilePoint bottomRightPlotPoint = canvasPointToPlotPoint( rect.bottomRight() );
1523
1524 double distance1 = topLeftPlotPoint.distance();
1525 double distance2 = bottomRightPlotPoint.distance();
1526 if ( distance2 < distance1 )
1527 std::swap( distance1, distance2 );
1528
1529 double elevation1 = topLeftPlotPoint.elevation();
1530 double elevation2 = bottomRightPlotPoint.elevation();
1531 if ( elevation2 < elevation1 )
1532 std::swap( elevation1, elevation2 );
1533
1534 return mCurrentJob->identify( QgsDoubleRange( distance1, distance2 ), QgsDoubleRange( elevation1, elevation2 ), identifyContext() );
1535}
1536
1538{
1539 setProfileCurve( nullptr );
1540 cancelJobs();
1541 mPlotItem->updatePlot();
1542}
1543
1545{
1546 mSnappingEnabled = enabled;
1547}
1548
1550{
1551 mSubsectionsSymbol.reset( symbol );
1552 std::unique_ptr<QgsLineSymbol> plotItemSymbol( mSubsectionsSymbol ? mSubsectionsSymbol->clone() : nullptr );
1553 mPlotItem->setSubsectionsSymbol( plotItemSymbol.release() );
1554}
1555
1556void QgsElevationProfileCanvas::setSourcesPrivate()
1557{
1558 mSources.clear();
1559 mSources.reserve( QgsApplication::profileSourceRegistry()->profileSources().count() );
1560 for ( QgsAbstractProfileSource *source : QgsApplication::profileSourceRegistry()->profileSources() )
1561 mSources << source;
1562}
@ FirstAndLastLabels
Place suffix after the first and last label values only.
Definition qgis.h:3504
DistanceUnit
Units of distance.
Definition qgis.h:5326
@ YardsBritishSears1922Truncated
British yards (Sears 1922 truncated).
Definition qgis.h:5366
@ Feet
Imperial feet.
Definition qgis.h:5329
@ MilesUSSurvey
US Survey miles.
Definition qgis.h:5373
@ LinksBritishSears1922
British links (Sears 1922).
Definition qgis.h:5361
@ YardsBritishBenoit1895A
British yards (Benoit 1895 A).
Definition qgis.h:5364
@ LinksBritishBenoit1895A
British links (Benoit 1895 A).
Definition qgis.h:5358
@ Centimeters
Centimeters.
Definition qgis.h:5334
@ YardsIndian1975
Indian yards (1975).
Definition qgis.h:5372
@ FeetUSSurvey
US Survey feet.
Definition qgis.h:5356
@ Millimeters
Millimeters.
Definition qgis.h:5335
@ FeetBritishSears1922
British feet (Sears 1922).
Definition qgis.h:5349
@ YardsClarkes
Clarke's yards.
Definition qgis.h:5368
@ YardsIndian
Indian yards.
Definition qgis.h:5369
@ FeetBritishBenoit1895B
British feet (Benoit 1895 B).
Definition qgis.h:5347
@ Miles
Terrestrial miles.
Definition qgis.h:5332
@ LinksUSSurvey
US Survey links.
Definition qgis.h:5363
@ Meters
Meters.
Definition qgis.h:5327
@ ChainsUSSurvey
US Survey chains.
Definition qgis.h:5343
@ FeetClarkes
Clarke's feet.
Definition qgis.h:5350
@ Unknown
Unknown distance unit.
Definition qgis.h:5376
@ Yards
Imperial yards.
Definition qgis.h:5331
@ FeetBritish1936
British feet (1936).
Definition qgis.h:5345
@ FeetIndian1962
Indian feet (1962).
Definition qgis.h:5354
@ YardsBritishSears1922
British yards (Sears 1922).
Definition qgis.h:5367
@ FeetIndian1937
Indian feet (1937).
Definition qgis.h:5353
@ YardsIndian1937
Indian yards (1937).
Definition qgis.h:5370
@ Degrees
Degrees, for planar geographic CRS distance measurements.
Definition qgis.h:5333
@ ChainsBritishBenoit1895B
British chains (Benoit 1895 B).
Definition qgis.h:5339
@ LinksBritishSears1922Truncated
British links (Sears 1922 truncated).
Definition qgis.h:5360
@ ChainsBritishBenoit1895A
British chains (Benoit 1895 A).
Definition qgis.h:5338
@ YardsBritishBenoit1895B
British yards (Benoit 1895 B).
Definition qgis.h:5365
@ FeetBritish1865
British feet (1865).
Definition qgis.h:5344
@ YardsIndian1962
Indian yards (1962).
Definition qgis.h:5371
@ FeetBritishSears1922Truncated
British feet (Sears 1922 truncated).
Definition qgis.h:5348
@ MetersGermanLegal
German legal meter.
Definition qgis.h:5375
@ LinksBritishBenoit1895B
British links (Benoit 1895 B).
Definition qgis.h:5359
@ ChainsInternational
International chains.
Definition qgis.h:5337
@ Inches
Inches.
Definition qgis.h:5336
@ Fathoms
Fathoms.
Definition qgis.h:5374
@ LinksInternational
International links.
Definition qgis.h:5357
@ ChainsBritishSears1922Truncated
British chains (Sears 1922 truncated).
Definition qgis.h:5340
@ FeetIndian
Indian (geodetic) feet.
Definition qgis.h:5352
@ NauticalMiles
Nautical miles.
Definition qgis.h:5330
@ ChainsClarkes
Clarke's chains.
Definition qgis.h:5342
@ LinksClarkes
Clarke's links.
Definition qgis.h:5362
@ ChainsBritishSears1922
British chains (Sears 1922).
Definition qgis.h:5341
@ Kilometers
Kilometers.
Definition qgis.h:5328
@ FeetIndian1975
Indian feet (1975).
Definition qgis.h:5355
@ FeetGoldCoast
Gold Coast feet.
Definition qgis.h:5351
@ FeetBritishBenoit1895A
British feet (Benoit 1895 A).
Definition qgis.h:5346
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:214
@ Plugin
Plugin based layer.
Definition qgis.h:209
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:215
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:212
@ Vector
Vector layer.
Definition qgis.h:207
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:210
@ Raster
Raster layer.
Definition qgis.h:208
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:213
virtual void renderContent(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QRectF &plotArea, const QgsPlotData &plotData=QgsPlotData())
Renders the plot content.
Definition qgsplot.cpp:310
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:320
Base class for 2-dimensional plot/chart/graphs with an X and Y axes.
Definition qgsplot.h:687
double yMaximum() const
Returns the maximum value of the y axis.
Definition qgsplot.h:769
void setYMinimum(double minimum)
Sets the minimum value of the y axis.
Definition qgsplot.h:748
double xMinimum() const
Returns the minimum value of the x axis.
Definition qgsplot.h:727
void render(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData=QgsPlotData()) override
Renders the plot.
Definition qgsplot.cpp:474
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:1042
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:783
double yMinimum() const
Returns the minimum value of the y axis.
Definition qgsplot.h:741
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:871
void setYMaximum(double maximum)
Sets the maximum value of the y axis.
Definition qgsplot.h:776
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:422
double xMaximum() const
Returns the maximum value of the x axis.
Definition qgsplot.h:755
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:217
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.
QgsDoubleRange dataElevationRange() const
Returns elevation range of the computed profile results.
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:175
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:210
void setLabelSuffix(const QString &suffix)
Sets the axis label suffix.
Definition qgsplot.cpp:200
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:191
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:245
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:114
T lower() const
Returns the lower bound of the range.
Definition qgsrange.h:79
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:86
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.
static const QgsSettingsEntryDouble * settingsZoomFactor
Settings entry zoom factor.
static const QgsSettingsEntryBool * settingsReverseWheelZoom
Settings entry reverse wheel zoom.
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:112
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:7714
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7134
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.