QGIS API Documentation 3.99.0-Master (752b475928d)
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
241 void paint( QPainter *painter ) override
242 {
243 // cache rendering to an image, so we don't need to redraw the plot
244 if ( !mImage.isNull() )
245 {
246 painter->drawImage( QPointF( 0, 0 ), mImage );
247 }
248 else
249 {
250 const double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
251 mImage = QImage( mRect.width() * pixelRatio, mRect.height() * pixelRatio, QImage::Format_ARGB32_Premultiplied );
252 mImage.setDevicePixelRatio( pixelRatio );
253 mImage.fill( Qt::transparent );
254
255 QPainter imagePainter( &mImage );
256 imagePainter.setRenderHint( QPainter::Antialiasing, true );
257 QgsRenderContext rc = QgsRenderContext::fromQPainter( &imagePainter );
258 rc.setDevicePixelRatio( pixelRatio );
259
260 const double mapUnitsPerPixel = ( xMaximum() - xMinimum() ) * mXScaleFactor / plotArea().width();
261 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
262
265
266 QgsPlotRenderContext plotContext;
267 calculateOptimisedIntervals( rc, plotContext );
268 render( rc, plotContext );
269 imagePainter.end();
270
271 painter->drawImage( QPointF( 0, 0 ), mImage );
272 }
273 }
274
275 void setSubsectionsSymbol( QgsLineSymbol *symbol )
276 {
277 if ( mRenderer )
278 {
279 mRenderer->setSubsectionsSymbol( symbol );
280 updatePlot();
281 }
282 }
283
284 QgsProject *mProject = nullptr;
285 double mXScaleFactor = 1.0;
286
288
289 private:
290 QImage mImage;
291
292 QMap<QString, QImage> mCachedImages;
293
294 QRectF mRect;
295 QRectF mPlotArea;
296 QgsProfilePlotRenderer *mRenderer = nullptr;
297};
298
299class QgsElevationProfileCrossHairsItem : public QgsPlotCanvasItem
300{
301 public:
302 QgsElevationProfileCrossHairsItem( QgsElevationProfileCanvas *canvas, QgsElevationProfilePlotItem *plotItem )
303 : QgsPlotCanvasItem( canvas )
304 , mPlotItem( plotItem )
305 {
306 }
307
308 void updateRect()
309 {
310 mRect = mCanvas->rect();
311
312 prepareGeometryChange();
313 setPos( mRect.topLeft() );
314 update();
315 }
316
317 void setPoint( const QgsProfilePoint &point )
318 {
319 mPoint = point;
320 update();
321 }
322
323 QRectF boundingRect() const override
324 {
325 return mRect;
326 }
327
329 void paint( QPainter *painter ) override
330 {
331 const QgsPointXY crossHairPlotPoint = mPlotItem->plotPointToCanvasPoint( mPoint );
332 if ( crossHairPlotPoint.isEmpty() )
333 return;
334
335 painter->save();
336 painter->setBrush( Qt::NoBrush );
337 QPen crossHairPen;
338 crossHairPen.setCosmetic( true );
339 crossHairPen.setWidthF( 1 );
340 crossHairPen.setStyle( Qt::DashLine );
341 crossHairPen.setCapStyle( Qt::FlatCap );
342 const QPalette scenePalette = mPlotItem->scene()->palette();
343 QColor penColor = scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text );
344 penColor.setAlpha( 150 );
345 crossHairPen.setColor( penColor );
346 painter->setPen( crossHairPen );
347 painter->drawLine( QPointF( mPlotItem->plotArea().left(), crossHairPlotPoint.y() ), QPointF( mPlotItem->plotArea().right(), crossHairPlotPoint.y() ) );
348 painter->drawLine( QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().top() ), QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().bottom() ) );
349
350 // also render current point text
351 QgsNumericFormatContext numericContext;
352
353 const QString xCoordinateText = mPlotItem->xAxis().numericFormat()->formatDouble( mPoint.distance() / mPlotItem->mXScaleFactor, numericContext )
354 + mPlotItem->distanceSuffix();
355
356 const QString yCoordinateText = mPlotItem->yAxis().numericFormat()->formatDouble( mPoint.elevation(), numericContext );
357
358 QFont font;
359 const QFontMetrics fm( font );
360 const double height = fm.capHeight();
361 const double xWidth = fm.horizontalAdvance( xCoordinateText );
362 const double yWidth = fm.horizontalAdvance( yCoordinateText );
363 const double textAxisMargin = fm.horizontalAdvance( ' ' );
364
365 QPointF xCoordOrigin;
366 QPointF yCoordOrigin;
367
368 if ( mPoint.distance() < ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5 * mPlotItem->mXScaleFactor )
369 {
370 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
371 {
372 // render x coordinate on right top (left top align)
373 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
374 // render y coordinate on right top (right bottom align)
375 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
376 }
377 else
378 {
379 // render x coordinate on right bottom (left bottom align)
380 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
381 // render y coordinate on right bottom (right top align)
382 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
383 }
384 }
385 else
386 {
387 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
388 {
389 // render x coordinate on left top (right top align)
390 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
391 // render y coordinate on left top (left bottom align)
392 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
393 }
394 else
395 {
396 // render x coordinate on left bottom (right bottom align)
397 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
398 // render y coordinate on left bottom (left top align)
399 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
400 }
401 }
402
403 // semi opaque background color brush
404 QColor backgroundColor = mPlotItem->chartBackgroundSymbol()->color();
405 backgroundColor.setAlpha( 220 );
406 painter->setBrush( QBrush( backgroundColor ) );
407 painter->setPen( Qt::NoPen );
408 painter->drawRect( QRectF( xCoordOrigin.x() - textAxisMargin + 1, xCoordOrigin.y() - textAxisMargin - height + 1, xWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
409 painter->drawRect( QRectF( yCoordOrigin.x() - textAxisMargin + 1, yCoordOrigin.y() - textAxisMargin - height + 1, yWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
410
411 painter->setBrush( Qt::NoBrush );
412 painter->setPen( scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
413
414 painter->drawText( xCoordOrigin, xCoordinateText );
415 painter->drawText( yCoordOrigin, yCoordinateText );
416 painter->restore();
417 }
418
419 private:
420 QRectF mRect;
421 QgsProfilePoint mPoint;
422 QgsElevationProfilePlotItem *mPlotItem = nullptr;
423};
425
426
428 : QgsPlotCanvas( parent )
429{
430 mScreenHelper = new QgsScreenHelper( this );
431
432 mPlotItem = new QgsElevationProfilePlotItem( this );
433
434 // follow system color scheme by default
435 setBackgroundColor( QColor() );
436
437 mCrossHairsItem = new QgsElevationProfileCrossHairsItem( this, mPlotItem );
438 mCrossHairsItem->setZValue( 100 );
439 mCrossHairsItem->hide();
440
441 // updating the profile plot is deferred on a timer, so that we don't trigger it too often
442 mDeferredRegenerationTimer = new QTimer( this );
443 mDeferredRegenerationTimer->setSingleShot( true );
444 mDeferredRegenerationTimer->stop();
445 connect( mDeferredRegenerationTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRegeneration );
446
447 mDeferredRedrawTimer = new QTimer( this );
448 mDeferredRedrawTimer->setSingleShot( true );
449 mDeferredRedrawTimer->stop();
450 connect( mDeferredRedrawTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRedraw );
451
452 connect( QgsApplication::profileSourceRegistry(), &QgsProfileSourceRegistry::profileSourceRegistered, this, &QgsElevationProfileCanvas::setSourcesPrivate );
453 connect( QgsApplication::profileSourceRegistry(), &QgsProfileSourceRegistry::profileSourceUnregistered, this, &QgsElevationProfileCanvas::setSourcesPrivate );
454
455 setSourcesPrivate(); // Initialize with registered sources
456}
457
459{
460 if ( mCurrentJob )
461 {
462 mPlotItem->setRenderer( nullptr );
463 mCurrentJob->deleteLater();
464 mCurrentJob = nullptr;
465 }
466}
467
469{
470 if ( mCurrentJob )
471 {
472 mPlotItem->setRenderer( nullptr );
473 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
474 mCurrentJob->cancelGeneration();
475 mCurrentJob->deleteLater();
476 mCurrentJob = nullptr;
477 }
478}
479
481{
482 const double dxPercent = dx / mPlotItem->plotArea().width();
483 const double dyPercent = dy / mPlotItem->plotArea().height();
484
485 // these look backwards, but we are dragging the paper, not the view!
486 const double dxPlot = -dxPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
487 const double dyPlot = dyPercent * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
488
489 // no need to handle axis scale lock here, we aren't changing scales
490 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
491 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
492 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
493 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
494
495 refineResults();
496
497 mPlotItem->updatePlot();
498 emit plotAreaChanged();
499}
500
502{
503 if ( !mPlotItem->plotArea().contains( x, y ) )
504 return;
505
506 const double newCenterX = mPlotItem->xMinimum() + ( x - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
507 const double newCenterY = mPlotItem->yMinimum() + ( mPlotItem->plotArea().bottom() - y ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
508
509 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
510 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
511
512 // no need to handle axis scale lock here, we aren't changing scales
513 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
514 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
515 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
516 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
517
518 refineResults();
519
520 mPlotItem->updatePlot();
521 emit plotAreaChanged();
522}
523
525{
526 scalePlot( factor, factor );
527 emit plotAreaChanged();
528}
529
530QgsProfileSnapContext QgsElevationProfileCanvas::snapContext() const
531{
532 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
533 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
534 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
535
536 QgsProfileSnapContext context;
537 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
538 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
539 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
540 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
541 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
542 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) );
543
544 return context;
545}
546
547QgsProfileIdentifyContext QgsElevationProfileCanvas::identifyContext() const
548{
549 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
550 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
551 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
552
553 QgsProfileIdentifyContext context;
554 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
555 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
556 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
557 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
558 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
559 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) );
560
561 context.project = mProject;
562
563 return context;
564}
565
566void QgsElevationProfileCanvas::setupLayerConnections( QgsMapLayer *layer, bool isDisconnect )
567{
568 if ( isDisconnect )
569 {
570 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
571 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
572 disconnect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
573 }
574 else
575 {
576 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
577 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
578 connect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
579 }
580
581 switch ( layer->type() )
582 {
584 {
585 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
586 if ( isDisconnect )
587 {
588 disconnect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
589 disconnect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
590 disconnect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
591 disconnect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
592 }
593 else
594 {
595 connect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
596 connect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
597 connect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
598 connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
599 }
600 break;
601 }
610 break;
611 }
612}
613
614void QgsElevationProfileCanvas::adjustRangeForAxisScaleLock( double &xMinimum, double &xMaximum, double &yMinimum, double &yMaximum ) const
615{
616 // ensures that we always "zoom out" to match horizontal/vertical scales
617 const double horizontalScale = ( xMaximum - xMinimum ) / mPlotItem->plotArea().width();
618 const double verticalScale = ( yMaximum - yMinimum ) / mPlotItem->plotArea().height();
619
620 const double currentRatio = horizontalScale / verticalScale;
621
622 if ( currentRatio <= mLockedAxisScale )
623 {
624 const double height = horizontalScale * mPlotItem->plotArea().height() / mLockedAxisScale;
625 const double deltaHeight = ( yMaximum - yMinimum ) - height;
626 yMinimum += deltaHeight / 2;
627 yMaximum -= deltaHeight / 2;
628 }
629 else
630 {
631 const double width = verticalScale * mPlotItem->plotArea().width() * mLockedAxisScale;
632 const double deltaWidth = ( ( xMaximum - xMinimum ) - width );
633 xMinimum += deltaWidth / 2;
634 xMaximum -= deltaWidth / 2;
635 }
636}
637
639{
640 return mDistanceUnit;
641}
642
644{
645 mDistanceUnit = unit;
646 const double oldMin = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
647 const double oldMax = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
648 mPlotItem->mXScaleFactor = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
649 mPlotItem->setXAxisUnits( mDistanceUnit );
650 mPlotItem->setXMinimum( oldMin / mPlotItem->mXScaleFactor );
651 mPlotItem->setXMaximum( oldMax / mPlotItem->mXScaleFactor );
652 mPlotItem->updatePlot();
653 emit scaleChanged();
654}
655
657{
658 if ( !color.isValid() )
659 {
660 QPalette customPalette = qApp->palette();
661 const QColor baseColor = qApp->palette().color( QPalette::ColorRole::Base );
662 const QColor windowColor = qApp->palette().color( QPalette::ColorRole::Window );
663 customPalette.setColor( QPalette::ColorRole::Base, windowColor );
664 customPalette.setColor( QPalette::ColorRole::Window, baseColor );
665 setPalette( customPalette );
666 scene()->setPalette( customPalette );
667 }
668 else
669 {
670 // build custom palette
671 const bool isDarkTheme = color.lightnessF() < 0.5;
672 QPalette customPalette = qApp->palette();
673 customPalette.setColor( QPalette::ColorRole::Window, color );
674 if ( isDarkTheme )
675 {
676 customPalette.setColor( QPalette::ColorRole::Text, QColor( 255, 255, 255 ) );
677 customPalette.setColor( QPalette::ColorRole::Base, color.lighter( 120 ) );
678 }
679 else
680 {
681 customPalette.setColor( QPalette::ColorRole::Text, QColor( 0, 0, 0 ) );
682 customPalette.setColor( QPalette::ColorRole::Base, color.darker( 120 ) );
683 }
684
685 setPalette( customPalette );
686 scene()->setPalette( customPalette );
687 }
688
689 updateChartFromPalette();
690}
691
693{
694 return mLockAxisScales;
695}
696
698{
699 mLockAxisScales = lock;
700 if ( mLockAxisScales )
701 {
702 double xMinimum = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
703 double xMaximum = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
704 double yMinimum = mPlotItem->yMinimum();
705 double yMaximum = mPlotItem->yMaximum();
706 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
707 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
708 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
709 mPlotItem->setYMinimum( yMinimum );
710 mPlotItem->setYMaximum( yMaximum );
711
712 refineResults();
713 mPlotItem->updatePlot();
714 emit plotAreaChanged();
715 emit scaleChanged();
716 }
717}
718
720{
721 const double horizontalScale = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / mPlotItem->plotArea().width();
722 const double verticalScale = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / mPlotItem->plotArea().height();
723 return horizontalScale / verticalScale;
724}
725
727{
728 mLockedAxisScale = scale;
729
730 double xMinimum = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
731 double xMaximum = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
732 double yMinimum = mPlotItem->yMinimum();
733 double yMaximum = mPlotItem->yMaximum();
734 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
735 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
736 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
737 mPlotItem->setYMinimum( yMinimum );
738 mPlotItem->setYMaximum( yMaximum );
739
740 refineResults();
741 mPlotItem->updatePlot();
742 emit plotAreaChanged();
743 emit scaleChanged();
744}
745
747{
748 if ( !mCurrentJob || !mSnappingEnabled )
749 return QgsPointXY();
750
751 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
752
753 const QgsProfileSnapResult snappedPoint = mCurrentJob->snapPoint( plotPoint, snapContext() );
754 if ( !snappedPoint.isValid() )
755 return QgsPointXY();
756
757 return plotPointToCanvasPoint( snappedPoint.snappedPoint );
758}
759
760void QgsElevationProfileCanvas::scalePlot( double xFactor, double yFactor )
761{
762 if ( mLockAxisScales )
763 yFactor = xFactor;
764
765 const double currentWidth = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor;
766 const double currentHeight = mPlotItem->yMaximum() - mPlotItem->yMinimum();
767
768 const double newWidth = currentWidth / xFactor;
769 const double newHeight = currentHeight / yFactor;
770
771 const double currentCenterX = ( mPlotItem->xMinimum() + mPlotItem->xMaximum() ) * 0.5 * mPlotItem->mXScaleFactor;
772 const double currentCenterY = ( mPlotItem->yMinimum() + mPlotItem->yMaximum() ) * 0.5;
773
774 double xMinimum = currentCenterX - newWidth * 0.5;
775 double xMaximum = currentCenterX + newWidth * 0.5;
776 double yMinimum = currentCenterY - newHeight * 0.5;
777 double yMaximum = currentCenterY + newHeight * 0.5;
778 if ( mLockAxisScales )
779 {
780 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
781 }
782
783 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
784 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
785 mPlotItem->setYMinimum( yMinimum );
786 mPlotItem->setYMaximum( yMaximum );
787
788 refineResults();
789 mPlotItem->updatePlot();
790 emit plotAreaChanged();
791 emit scaleChanged();
792}
793
795{
796 const QRectF intersected = rect.intersected( mPlotItem->plotArea() );
797
798 double minX = ( intersected.left() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
799 double maxX = ( intersected.right() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
800 double minY = ( mPlotItem->plotArea().bottom() - intersected.bottom() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
801 double maxY = ( mPlotItem->plotArea().bottom() - intersected.top() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
802
803 if ( mLockAxisScales )
804 {
805 adjustRangeForAxisScaleLock( minX, maxX, minY, maxY );
806 }
807
808 mPlotItem->setXMinimum( minX / mPlotItem->mXScaleFactor );
809 mPlotItem->setXMaximum( maxX / mPlotItem->mXScaleFactor );
810 mPlotItem->setYMinimum( minY );
811 mPlotItem->setYMaximum( maxY );
812
813 refineResults();
814 mPlotItem->updatePlot();
815 emit plotAreaChanged();
816 emit scaleChanged();
817}
818
820{
821 //get mouse wheel zoom behavior settings
822 QgsSettings settings;
823 double zoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
824 bool reverseZoom = settings.value( QStringLiteral( "qgis/reverse_wheel_zoom" ), false ).toBool();
825 bool zoomIn = reverseZoom ? event->angleDelta().y() < 0 : event->angleDelta().y() > 0;
826
827 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
828 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
829
830 if ( event->modifiers() & Qt::ControlModifier )
831 {
832 //holding ctrl while wheel zooming results in a finer zoom
833 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
834 }
835
836 //calculate zoom scale factor
837 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
838
839 QRectF viewportRect = mPlotItem->plotArea();
840
841 if ( viewportRect.contains( event->position() ) )
842 {
843 //adjust view center
844 const double oldCenterX = 0.5 * ( mPlotItem->xMaximum() + mPlotItem->xMinimum() );
845 const double oldCenterY = 0.5 * ( mPlotItem->yMaximum() + mPlotItem->yMinimum() );
846
847 const double eventPosX = ( event->position().x() - viewportRect.left() ) / viewportRect.width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
848 const double eventPosY = ( viewportRect.bottom() - event->position().y() ) / viewportRect.height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
849
850 const double newCenterX = eventPosX + ( ( oldCenterX - eventPosX ) * scaleFactor );
851 const double newCenterY = eventPosY + ( ( oldCenterY - eventPosY ) * scaleFactor );
852
853 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
854 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
855
856 // don't need to handle axis scale lock here, we are always changing axis by the same scale
857 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
858 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
859 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
860 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
861 }
862
863 //zoom plot
864 if ( zoomIn )
865 {
866 scalePlot( zoomFactor );
867 }
868 else
869 {
870 scalePlot( 1 / zoomFactor );
871 }
872 emit plotAreaChanged();
873 emit scaleChanged();
874}
875
877{
879 if ( e->isAccepted() )
880 {
881 mCrossHairsItem->hide();
882 return;
883 }
884
885 QgsProfilePoint plotPoint = canvasPointToPlotPoint( e->pos() );
886 if ( mCurrentJob && mSnappingEnabled && !plotPoint.isEmpty() )
887 {
888 const QgsProfileSnapResult snapResult = mCurrentJob->snapPoint( plotPoint, snapContext() );
889 if ( snapResult.isValid() )
890 plotPoint = snapResult.snappedPoint;
891 }
892
893 if ( plotPoint.isEmpty() )
894 {
895 mCrossHairsItem->hide();
896 }
897 else
898 {
899 mCrossHairsItem->setPoint( plotPoint );
900 mCrossHairsItem->show();
901 }
902 emit canvasPointHovered( e->pos(), plotPoint );
903}
904
906{
907 return mPlotItem->plotArea();
908}
909
911{
912 if ( !mProject || !profileCurve() )
913 return;
914
915 cancelJobs();
916
917 QgsProfileRequest request( profileCurve()->clone() );
918 request.setCrs( mCrs );
919 request.setTolerance( mTolerance );
920 request.setTransformContext( mProject->transformContext() );
921 request.setTerrainProvider( mProject->elevationProperties()->terrainProvider() ? mProject->elevationProperties()->terrainProvider()->clone() : nullptr );
922 QgsExpressionContext context;
925 request.setExpressionContext( context );
926
927 QList< QgsAbstractProfileSource *> sourcesToRender = sources();
928 std::reverse( sourcesToRender.begin(), sourcesToRender.end() ); // sources are rendered from bottom to top
929
930 mCurrentJob = new QgsProfilePlotRenderer( sourcesToRender, request );
931 connect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
932
933 QgsProfileGenerationContext generationContext;
934 generationContext.setDpi( mScreenHelper->screenDpi() );
935 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( mProfileCurve->length() ) / mPlotItem->plotArea().width() );
936 generationContext.setMapUnitsPerDistancePixel( mProfileCurve->length() / mPlotItem->plotArea().width() );
937 mCurrentJob->setContext( generationContext );
938
939 if ( mSubsectionsSymbol )
940 {
941 mCurrentJob->setSubsectionsSymbol( mSubsectionsSymbol->clone() );
942 }
943
944 mCurrentJob->startGeneration();
945 mPlotItem->setRenderer( mCurrentJob );
946
947 emit activeJobCountChanged( 1 );
948}
949
951{
952 mZoomFullWhenJobFinished = true;
953}
954
955void QgsElevationProfileCanvas::generationFinished()
956{
957 if ( !mCurrentJob )
958 return;
959
960 emit activeJobCountChanged( 0 );
961
962 if ( mZoomFullWhenJobFinished )
963 {
964 // we only zoom full for the initial generation
965 mZoomFullWhenJobFinished = false;
966 zoomFull();
967 }
968 else
969 {
970 // here we should invalidate cached results only for the layers which have been refined
971
972 // and if no layers are being refeined, don't invalidate anything
973
974 mPlotItem->updatePlot();
975 }
976
977 if ( mForceRegenerationAfterCurrentJobCompletes )
978 {
979 mForceRegenerationAfterCurrentJobCompletes = false;
980 mCurrentJob->invalidateAllRefinableSources();
981 scheduleDeferredRegeneration();
982 }
983}
984
985void QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged()
986{
987 // TODO -- handle nicely when existing job is in progress
988 if ( !mCurrentJob || mCurrentJob->isActive() )
989 return;
990
991 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
992 if ( !properties )
993 return;
994
995 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
996 {
997 if ( QgsAbstractProfileSource *source = layer->profileSource() )
998 {
999 if ( mCurrentJob->invalidateResults( source ) )
1000 scheduleDeferredRegeneration();
1001 }
1002 }
1003}
1004
1005void QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged()
1006{
1007 // TODO -- handle nicely when existing job is in progress
1008 if ( !mCurrentJob || mCurrentJob->isActive() )
1009 return;
1010
1011 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
1012 if ( !properties )
1013 return;
1014
1015 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
1016 {
1017 if ( QgsAbstractProfileSource *source = layer->profileSource() )
1018 {
1019 mCurrentJob->replaceSource( source );
1020 }
1021 if ( mPlotItem->redrawResults( layer->id() ) )
1022 scheduleDeferredRedraw();
1023 }
1024}
1025
1026void QgsElevationProfileCanvas::regenerateResultsForLayer()
1027{
1028 if ( !mCurrentJob )
1029 return;
1030
1031 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() ) )
1032 {
1033 if ( QgsAbstractProfileSource *source = layer->profileSource() )
1034 {
1035 if ( mCurrentJob->invalidateResults( source ) )
1036 scheduleDeferredRegeneration();
1037 }
1038 }
1039}
1040
1041void QgsElevationProfileCanvas::scheduleDeferredRegeneration()
1042{
1043 if ( !mDeferredRegenerationScheduled )
1044 {
1045 mDeferredRegenerationTimer->start( 1 );
1046 mDeferredRegenerationScheduled = true;
1047 }
1048}
1049
1050void QgsElevationProfileCanvas::scheduleDeferredRedraw()
1051{
1052 if ( !mDeferredRedrawScheduled )
1053 {
1054 mDeferredRedrawTimer->start( 1 );
1055 mDeferredRedrawScheduled = true;
1056 }
1057}
1058
1059void QgsElevationProfileCanvas::startDeferredRegeneration()
1060{
1061 if ( mCurrentJob && !mCurrentJob->isActive() )
1062 {
1063 emit activeJobCountChanged( 1 );
1064 mCurrentJob->regenerateInvalidatedResults();
1065 }
1066 else if ( mCurrentJob )
1067 {
1068 mForceRegenerationAfterCurrentJobCompletes = true;
1069 }
1070
1071 mDeferredRegenerationScheduled = false;
1072}
1073
1074void QgsElevationProfileCanvas::startDeferredRedraw()
1075{
1076 mPlotItem->update();
1077 mDeferredRedrawScheduled = false;
1078}
1079
1080void QgsElevationProfileCanvas::refineResults()
1081{
1082 if ( mCurrentJob )
1083 {
1084 QgsProfileGenerationContext context;
1085 context.setDpi( mScreenHelper->screenDpi() );
1086 const double plotDistanceRange = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor;
1087 const double plotElevationRange = mPlotItem->yMaximum() - mPlotItem->yMinimum();
1088 const double plotDistanceUnitsPerPixel = plotDistanceRange / mPlotItem->plotArea().width();
1089
1090 // we round the actual desired map error down to just one significant figure, to avoid tiny differences
1091 // as the plot is panned
1092 const double targetMaxErrorInMapUnits = MAX_ERROR_PIXELS * plotDistanceUnitsPerPixel;
1093 const double factor = std::pow( 10.0, 1 - std::ceil( std::log10( std::fabs( targetMaxErrorInMapUnits ) ) ) );
1094 const double roundedErrorInMapUnits = std::floor( targetMaxErrorInMapUnits * factor ) / factor;
1095 context.setMaximumErrorMapUnits( roundedErrorInMapUnits );
1096
1097 context.setMapUnitsPerDistancePixel( plotDistanceUnitsPerPixel );
1098
1099 // for similar reasons we round the minimum distance off to multiples of the maximum error in map units
1100 const double distanceMin = std::floor( ( mPlotItem->xMinimum() * mPlotItem->mXScaleFactor - plotDistanceRange * 0.05 ) / context.maximumErrorMapUnits() ) * context.maximumErrorMapUnits();
1101 context.setDistanceRange( QgsDoubleRange( std::max( 0.0, distanceMin ), mPlotItem->xMaximum() * mPlotItem->mXScaleFactor + plotDistanceRange * 0.05 ) );
1102
1103 context.setElevationRange( QgsDoubleRange( mPlotItem->yMinimum() - plotElevationRange * 0.05, mPlotItem->yMaximum() + plotElevationRange * 0.05 ) );
1104 mCurrentJob->setContext( context );
1105 }
1106 scheduleDeferredRegeneration();
1107}
1108
1109void QgsElevationProfileCanvas::updateChartFromPalette()
1110{
1111 const QPalette chartPalette = palette();
1112 setBackgroundBrush( QBrush( chartPalette.color( QPalette::ColorRole::Base ) ) );
1113 {
1114 QgsTextFormat textFormat = mPlotItem->xAxis().textFormat();
1115 textFormat.setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
1116 mPlotItem->xAxis().setTextFormat( textFormat );
1117 mPlotItem->yAxis().setTextFormat( textFormat );
1118 }
1119 {
1120 std::unique_ptr<QgsFillSymbol> chartFill( mPlotItem->chartBackgroundSymbol()->clone() );
1121 chartFill->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Window ) );
1122 mPlotItem->setChartBackgroundSymbol( chartFill.release() );
1123 }
1124 {
1125 std::unique_ptr<QgsFillSymbol> chartBorder( mPlotItem->chartBorderSymbol()->clone() );
1126 chartBorder->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text ) );
1127 mPlotItem->setChartBorderSymbol( chartBorder.release() );
1128 }
1129 {
1130 std::unique_ptr<QgsLineSymbol> chartMajorSymbol( mPlotItem->xAxis().gridMajorSymbol()->clone() );
1131 QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
1132 c.setAlpha( 150 );
1133 chartMajorSymbol->setColor( c );
1134 mPlotItem->xAxis().setGridMajorSymbol( chartMajorSymbol->clone() );
1135 mPlotItem->yAxis().setGridMajorSymbol( chartMajorSymbol.release() );
1136 }
1137 {
1138 std::unique_ptr<QgsLineSymbol> chartMinorSymbol( mPlotItem->xAxis().gridMinorSymbol()->clone() );
1139 QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
1140 c.setAlpha( 50 );
1141 chartMinorSymbol->setColor( c );
1142 mPlotItem->xAxis().setGridMinorSymbol( chartMinorSymbol->clone() );
1143 mPlotItem->yAxis().setGridMinorSymbol( chartMinorSymbol.release() );
1144 }
1145 mPlotItem->updatePlot();
1146}
1147
1149{
1150 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1151 return QgsProfilePoint();
1152
1153 return mPlotItem->canvasPointToPlotPoint( point );
1154}
1155
1157{
1158 return mPlotItem->plotPointToCanvasPoint( point );
1159}
1160
1162{
1163 mProject = project;
1164 mPlotItem->mProject = project;
1165}
1166
1171
1173{
1174 mProfileCurve.reset( curve );
1175}
1176
1178{
1179 return mProfileCurve.get();
1180}
1181
1183{
1184 mTolerance = tolerance;
1185}
1186
1191
1192void QgsElevationProfileCanvas::setLayers( const QList<QgsMapLayer *> &layers )
1193{
1194 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
1195 {
1196 setupLayerConnections( layer, true );
1197 }
1198
1199 // filter list, removing null layers and invalid layers
1200 auto filteredList = layers;
1201 filteredList.erase( std::remove_if( filteredList.begin(), filteredList.end(), []( QgsMapLayer *layer ) {
1202 return !layer || !layer->isValid();
1203 } ),
1204 filteredList.end() );
1205
1206 mLayers = _qgis_listRawToQPointer( filteredList );
1207 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
1208 {
1209 setupLayerConnections( layer, false );
1210 }
1211}
1212
1213QList<QgsMapLayer *> QgsElevationProfileCanvas::layers() const
1214{
1215 return _qgis_listQPointerToRaw( mLayers );
1216}
1217
1218void QgsElevationProfileCanvas::setSources( const QList<QgsAbstractProfileSource *> &sources )
1219{
1220 mSources = sources;
1221}
1222
1223QList<QgsAbstractProfileSource *> QgsElevationProfileCanvas::sources() const
1224{
1225 if ( mSources.isEmpty() && !mLayers.isEmpty() )
1226 {
1227 // Legacy: If we have layers, extract their sources and return them.
1228 // We don't set mSources here, because we want the previous check to
1229 // continue failing if only layers are set.
1230 // TODO: Remove in QGIS 5.0.
1231 QList< QgsAbstractProfileSource * > sources;
1232 const QList<QgsMapLayer *> layersToGenerate = layers();
1233 sources.reserve( layersToGenerate.size() );
1234
1235 for ( QgsMapLayer *layer : layersToGenerate )
1236 {
1237 if ( QgsAbstractProfileSource *source = layer->profileSource() )
1238 sources << source;
1239 }
1240
1241 // More legacy: canvas layers are in the same direction as the renderer
1242 // expects, but since we reverse sources() (i.e., layers) before passing
1243 // them to the renderer, then we need to reverse them here first.
1244 std::reverse( sources.begin(), sources.end() );
1245 return sources;
1246 }
1247
1248 return mSources;
1249}
1250
1252{
1254
1255 if ( mLockAxisScales )
1256 {
1257 double xMinimum = mPlotItem->xMinimum();
1258 double xMaximum = mPlotItem->xMaximum();
1259 double yMinimum = mPlotItem->yMinimum();
1260 double yMaximum = mPlotItem->yMaximum();
1261 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1262 mPlotItem->setXMinimum( xMinimum );
1263 mPlotItem->setXMaximum( xMaximum );
1264 mPlotItem->setYMinimum( yMinimum );
1265 mPlotItem->setYMaximum( yMaximum );
1266 }
1267
1268 mPlotItem->updateRect();
1269 mCrossHairsItem->updateRect();
1270
1271 emit scaleChanged();
1272}
1273
1275{
1276 QgsPlotCanvas::paintEvent( event );
1277
1278 if ( !mFirstDrawOccurred )
1279 {
1280 // 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).
1281 mFirstDrawOccurred = true;
1282 mPlotItem->updateRect();
1283 mCrossHairsItem->updateRect();
1284 }
1285}
1286
1288{
1289 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1290 return QgsPoint();
1291
1292 if ( !mProfileCurve )
1293 return QgsPoint();
1294
1295 const double dx = point.x() - mPlotItem->plotArea().left();
1296
1297 const double distanceAlongPlotPercent = dx / mPlotItem->plotArea().width();
1298 double distanceAlongCurveLength = distanceAlongPlotPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
1299
1300 std::unique_ptr<QgsPoint> mapXyPoint( mProfileCurve->interpolatePoint( distanceAlongCurveLength ) );
1301 if ( !mapXyPoint )
1302 return QgsPoint();
1303
1304 const double mapZ = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * ( mPlotItem->plotArea().bottom() - point.y() ) + mPlotItem->yMinimum();
1305
1306 return QgsPoint( mapXyPoint->x(), mapXyPoint->y(), mapZ );
1307}
1308
1310{
1311 if ( !mProfileCurve )
1312 return QgsPointXY();
1313
1314 QgsGeos geos( mProfileCurve.get() );
1315 QString error;
1316 const double distanceAlongCurve = geos.lineLocatePoint( point, &error );
1317
1318 const double distanceAlongCurveOnPlot = distanceAlongCurve - mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
1319 const double distanceAlongCurvePercent = distanceAlongCurveOnPlot / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor );
1320 const double distanceAlongPlotRect = distanceAlongCurvePercent * mPlotItem->plotArea().width();
1321
1322 const double canvasX = mPlotItem->plotArea().left() + distanceAlongPlotRect;
1323
1324 double canvasY = 0;
1325 if ( std::isnan( point.z() ) || point.z() < mPlotItem->yMinimum() )
1326 {
1327 canvasY = mPlotItem->plotArea().top();
1328 }
1329 else if ( point.z() > mPlotItem->yMaximum() )
1330 {
1331 canvasY = mPlotItem->plotArea().bottom();
1332 }
1333 else
1334 {
1335 const double yPercent = ( point.z() - mPlotItem->yMinimum() ) / ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
1336 canvasY = mPlotItem->plotArea().bottom() - mPlotItem->plotArea().height() * yPercent;
1337 }
1338
1339 return QgsPointXY( canvasX, canvasY );
1340}
1341
1343{
1344 if ( !mCurrentJob )
1345 return;
1346
1347 const QgsDoubleRange zRange = mCurrentJob->zRange();
1348
1349 double yMinimum = 0;
1350 double yMaximum = 0;
1351
1352 if ( zRange.upper() < zRange.lower() )
1353 {
1354 // invalid range, e.g. no features found in plot!
1355 yMinimum = 0;
1356 yMaximum = 10;
1357 }
1358 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
1359 {
1360 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
1361 yMinimum = zRange.lower() - 5;
1362 yMaximum = zRange.lower() + 5;
1363 }
1364 else
1365 {
1366 // add 5% margin to height range
1367 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
1368 yMinimum = zRange.lower() - margin;
1369 yMaximum = zRange.upper() + margin;
1370 }
1371
1372 const double profileLength = profileCurve()->length();
1373 double xMinimum = 0;
1374 // just 2% margin to max distance -- any more is overkill and wasted space
1375 double xMaximum = profileLength * 1.02;
1376
1377 if ( mLockAxisScales )
1378 {
1379 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1380 }
1381
1382 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
1383 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
1384 mPlotItem->setYMinimum( yMinimum );
1385 mPlotItem->setYMaximum( yMaximum );
1386
1387 refineResults();
1388 mPlotItem->updatePlot();
1389 emit plotAreaChanged();
1390 emit scaleChanged();
1391}
1392
1393void QgsElevationProfileCanvas::setVisiblePlotRange( double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation )
1394{
1395 if ( mLockAxisScales )
1396 {
1397 adjustRangeForAxisScaleLock( minimumDistance, maximumDistance, minimumElevation, maximumElevation );
1398 }
1399
1400 mPlotItem->setYMinimum( minimumElevation );
1401 mPlotItem->setYMaximum( maximumElevation );
1402 mPlotItem->setXMinimum( minimumDistance / mPlotItem->mXScaleFactor );
1403 mPlotItem->setXMaximum( maximumDistance / mPlotItem->mXScaleFactor );
1404 refineResults();
1405 mPlotItem->updatePlot();
1406 emit plotAreaChanged();
1407 emit scaleChanged();
1408}
1409
1411{
1412 return QgsDoubleRange( mPlotItem->xMinimum() * mPlotItem->mXScaleFactor, mPlotItem->xMaximum() * mPlotItem->mXScaleFactor );
1413}
1414
1416{
1417 return QgsDoubleRange( mPlotItem->yMinimum(), mPlotItem->yMaximum() );
1418}
1419
1421{
1422 return *mPlotItem;
1423}
1424
1426class QgsElevationProfilePlot : public Qgs2DXyPlot
1427{
1428 public:
1429 QgsElevationProfilePlot( QgsProfilePlotRenderer *renderer )
1430 : mRenderer( renderer )
1431 {
1432 }
1433
1434 void renderContent( QgsRenderContext &rc, QgsPlotRenderContext &, const QRectF &plotArea, const QgsPlotData & ) override
1435 {
1436 if ( !mRenderer )
1437 return;
1438
1439 rc.painter()->translate( plotArea.left(), plotArea.top() );
1440 mRenderer->render( rc, plotArea.width(), plotArea.height(), xMinimum() * mXScale, xMaximum() * mXScale, yMinimum(), yMaximum() );
1441 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
1442 }
1443
1444 double mXScale = 1;
1445
1446 private:
1447 QgsProfilePlotRenderer *mRenderer = nullptr;
1448};
1450
1451void QgsElevationProfileCanvas::render( QgsRenderContext &context, double width, double height, const Qgs2DXyPlot &plotSettings )
1452{
1453 if ( !mCurrentJob )
1454 return;
1455
1458
1459 QgsElevationProfilePlot profilePlot( mCurrentJob );
1460
1461 // 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...
1462 QDomDocument doc;
1463 QDomElement elem = doc.createElement( QStringLiteral( "plot" ) );
1464 QgsReadWriteContext rwContext;
1465 plotSettings.writeXml( elem, doc, rwContext );
1466 profilePlot.readXml( elem, rwContext );
1467
1468 profilePlot.mXScale = mPlotItem->mXScaleFactor;
1469 profilePlot.xAxis().setLabelSuffix( mPlotItem->xAxis().labelSuffix() );
1470 profilePlot.xAxis().setLabelSuffixPlacement( mPlotItem->xAxis().labelSuffixPlacement() );
1471
1472 profilePlot.setSize( QSizeF( width, height ) );
1473 QgsPlotRenderContext plotContext;
1474 profilePlot.render( context, plotContext );
1475}
1476
1477QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( QPointF point )
1478{
1479 if ( !mCurrentJob )
1480 return {};
1481
1482 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
1483
1484 return mCurrentJob->identify( plotPoint, identifyContext() );
1485}
1486
1487QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( const QRectF &rect )
1488{
1489 if ( !mCurrentJob )
1490 return {};
1491
1492 const QgsProfilePoint topLeftPlotPoint = canvasPointToPlotPoint( rect.topLeft() );
1493 const QgsProfilePoint bottomRightPlotPoint = canvasPointToPlotPoint( rect.bottomRight() );
1494
1495 double distance1 = topLeftPlotPoint.distance();
1496 double distance2 = bottomRightPlotPoint.distance();
1497 if ( distance2 < distance1 )
1498 std::swap( distance1, distance2 );
1499
1500 double elevation1 = topLeftPlotPoint.elevation();
1501 double elevation2 = bottomRightPlotPoint.elevation();
1502 if ( elevation2 < elevation1 )
1503 std::swap( elevation1, elevation2 );
1504
1505 return mCurrentJob->identify( QgsDoubleRange( distance1, distance2 ), QgsDoubleRange( elevation1, elevation2 ), identifyContext() );
1506}
1507
1509{
1510 setProfileCurve( nullptr );
1511 cancelJobs();
1512 mPlotItem->updatePlot();
1513}
1514
1516{
1517 mSnappingEnabled = enabled;
1518}
1519
1521{
1522 mSubsectionsSymbol.reset( symbol );
1523 std::unique_ptr<QgsLineSymbol> plotItemSymbol( mSubsectionsSymbol ? mSubsectionsSymbol->clone() : nullptr );
1524 mPlotItem->setSubsectionsSymbol( plotItemSymbol.release() );
1525}
1526
1527void QgsElevationProfileCanvas::setSourcesPrivate()
1528{
1530}
@ 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