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