QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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
21#include "qgsplotcanvasitem.h"
22#include "qgsprofilerequest.h"
24#include "qgscurve.h"
26#include "qgsterrainprovider.h"
28#include "qgsprofilerenderer.h"
29#include "qgspoint.h"
30#include "qgsgeos.h"
31#include "qgsplot.h"
32#include "qgsguiutils.h"
33#include "qgsnumericformat.h"
35#include "qgsprofilesnapping.h"
37#include "qgsapplication.h"
38#include "qgsscreenhelper.h"
39
40#include <QWheelEvent>
41#include <QTimer>
42
44class QgsElevationProfilePlotItem : public Qgs2DPlot, public QgsPlotCanvasItem
45{
46 public:
47
48 QgsElevationProfilePlotItem( QgsElevationProfileCanvas *canvas )
49 : QgsPlotCanvasItem( canvas )
50 {
51 setYMinimum( 0 );
52 setYMaximum( 100 );
53 }
54
55 void setRenderer( QgsProfilePlotRenderer *renderer )
56 {
57 mRenderer = renderer;
58 }
59
60 void updateRect()
61 {
62 mRect = mCanvas->rect();
63 setSize( mRect.size() );
64
65 prepareGeometryChange();
66 setPos( mRect.topLeft() );
67
68 mImage = QImage();
69 mCachedImages.clear();
70 mPlotArea = QRectF();
71 update();
72 }
73
74 void updatePlot()
75 {
76 mImage = QImage();
77 mCachedImages.clear();
78 mPlotArea = QRectF();
79 update();
80 }
81
82 bool redrawResults( const QString &sourceId )
83 {
84 auto it = mCachedImages.find( sourceId );
85 if ( it == mCachedImages.end() )
86 return false;
87
88 mCachedImages.erase( it );
89 mImage = QImage();
90 return true;
91 }
92
93 QRectF boundingRect() const override
94 {
95 return mRect;
96 }
97
98 QRectF plotArea()
99 {
100 if ( !mPlotArea.isNull() )
101 return mPlotArea;
102
103 // force immediate recalculation of plot area
104 QgsRenderContext context;
105 if ( !scene()->views().isEmpty() )
106 context.setScaleFactor( scene()->views().at( 0 )->logicalDpiX() / 25.4 );
107
109 mPlotArea = interiorPlotArea( context );
110 return mPlotArea;
111 }
112
113 QgsProfilePoint canvasPointToPlotPoint( QPointF point )
114 {
115 const QRectF area = plotArea();
116 if ( !area.contains( point.x(), point.y() ) )
117 return QgsProfilePoint();
118
119 const double distance = ( point.x() - area.left() ) / area.width() * ( xMaximum() - xMinimum() ) + xMinimum();
120 const double elevation = ( area.bottom() - point.y() ) / area.height() * ( yMaximum() - yMinimum() ) + yMinimum();
121 return QgsProfilePoint( distance, elevation );
122 }
123
124 QgsPointXY plotPointToCanvasPoint( const QgsProfilePoint &point )
125 {
126 if ( point.distance() < xMinimum() || point.distance() > xMaximum() || point.elevation() < yMinimum() || point.elevation() > yMaximum() )
127 return QgsPointXY();
128
129 const QRectF area = plotArea();
130
131 const double x = ( point.distance() - xMinimum() ) / ( xMaximum() - xMinimum() ) * ( area.width() ) + area.left();
132 const double y = area.bottom() - ( point.elevation() - yMinimum() ) / ( yMaximum() - yMinimum() ) * ( area.height() );
133 return QgsPointXY( x, y );
134 }
135
136 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
137 {
138 mPlotArea = plotArea;
139
140 if ( !mRenderer )
141 return;
142
143 const QStringList sourceIds = mRenderer->sourceIds();
144 for ( const QString &source : sourceIds )
145 {
146 QImage plot;
147 auto it = mCachedImages.constFind( source );
148 if ( it != mCachedImages.constEnd() )
149 {
150 plot = it.value();
151 }
152 else
153 {
154 plot = mRenderer->renderToImage( plotArea.width(), plotArea.height(), xMinimum(), xMaximum(), yMinimum(), yMaximum(), source );
155 mCachedImages.insert( source, plot );
156 }
157 rc.painter()->drawImage( plotArea.left(), plotArea.top(), plot );
158 }
159 }
160
161 void paint( QPainter *painter ) override
162 {
163 // cache rendering to an image, so we don't need to redraw the plot
164 if ( !mImage.isNull() )
165 {
166 painter->drawImage( 0, 0, mImage );
167 }
168 else
169 {
170 mImage = QImage( mRect.width(), mRect.height(), QImage::Format_ARGB32_Premultiplied );
171 mImage.fill( Qt::transparent );
172
173 QPainter imagePainter( &mImage );
174 imagePainter.setRenderHint( QPainter::Antialiasing, true );
176
179
181 render( rc );
182 imagePainter.end();
183
184 painter->drawImage( 0, 0, mImage );
185 }
186 }
187
188 QgsProject *mProject = nullptr;
189
190 private:
191
192 QImage mImage;
193
194 QMap< QString, QImage > mCachedImages;
195
196 QRectF mRect;
197 QRectF mPlotArea;
198 QgsProfilePlotRenderer *mRenderer = nullptr;
199};
200
201class QgsElevationProfileCrossHairsItem : public QgsPlotCanvasItem
202{
203 public:
204
205 QgsElevationProfileCrossHairsItem( QgsElevationProfileCanvas *canvas, QgsElevationProfilePlotItem *plotItem )
206 : QgsPlotCanvasItem( canvas )
207 , mPlotItem( plotItem )
208 {
209 }
210
211 void updateRect()
212 {
213 mRect = mCanvas->rect();
214
215 prepareGeometryChange();
216 setPos( mRect.topLeft() );
217 update();
218 }
219
220 void setPoint( const QgsProfilePoint &point )
221 {
222 mPoint = point;
223 update();
224 }
225
226 QRectF boundingRect() const override
227 {
228 return mRect;
229 }
230
231 void paint( QPainter *painter ) override
232 {
233 const QgsPointXY crossHairPlotPoint = mPlotItem->plotPointToCanvasPoint( mPoint );
234 if ( crossHairPlotPoint.isEmpty() )
235 return;
236
237 painter->save();
238 painter->setBrush( Qt::NoBrush );
239 QPen crossHairPen;
240 crossHairPen.setCosmetic( true );
241 crossHairPen.setWidthF( 1 );
242 crossHairPen.setStyle( Qt::DashLine );
243 crossHairPen.setCapStyle( Qt::FlatCap );
244 crossHairPen.setColor( QColor( 0, 0, 0, 150 ) );
245 painter->setPen( crossHairPen );
246 painter->drawLine( QPointF( mPlotItem->plotArea().left(), crossHairPlotPoint.y() ), QPointF( mPlotItem->plotArea().right(), crossHairPlotPoint.y() ) );
247 painter->drawLine( QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().top() ), QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().bottom() ) );
248
249 // also render current point text
250 QgsNumericFormatContext numericContext;
251
252 const QString xCoordinateText = mPlotItem->xAxis().numericFormat()->formatDouble( mPoint.distance(), numericContext );
253 const QString yCoordinateText = mPlotItem->yAxis().numericFormat()->formatDouble( mPoint.elevation(), numericContext );
254
255 QFont font;
256 const QFontMetrics fm( font );
257 const double height = fm.capHeight();
258 const double xWidth = fm.horizontalAdvance( xCoordinateText );
259 const double yWidth = fm.horizontalAdvance( yCoordinateText );
260 const double textAxisMargin = fm.horizontalAdvance( ' ' );
261
262 QPointF xCoordOrigin;
263 QPointF yCoordOrigin;
264
265 if ( mPoint.distance() < ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5 )
266 {
267 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
268 {
269 // render x coordinate on right top (left top align)
270 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
271 // render y coordinate on right top (right bottom align)
272 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
273 }
274 else
275 {
276 // render x coordinate on right bottom (left bottom align)
277 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
278 // render y coordinate on right bottom (right top align)
279 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
280 }
281 }
282 else
283 {
284 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
285 {
286 // render x coordinate on left top (right top align)
287 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
288 // render y coordinate on left top (left bottom align)
289 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
290 }
291 else
292 {
293 // render x coordinate on left bottom (right bottom align)
294 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
295 // render y coordinate on left bottom (left top align)
296 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
297 }
298 }
299
300 // semi opaque white background
301 painter->setBrush( QBrush( QColor( 255, 255, 255, 220 ) ) );
302 painter->setPen( Qt::NoPen );
303 painter->drawRect( QRectF( xCoordOrigin.x() - textAxisMargin + 1, xCoordOrigin.y() - textAxisMargin - height + 1, xWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
304 painter->drawRect( QRectF( yCoordOrigin.x() - textAxisMargin + 1, yCoordOrigin.y() - textAxisMargin - height + 1, yWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
305
306 painter->setBrush( Qt::NoBrush );
307 painter->setPen( Qt::black );
308
309 painter->drawText( xCoordOrigin, xCoordinateText );
310 painter->drawText( yCoordOrigin, yCoordinateText );
311 painter->restore();
312 }
313
314 private:
315
316 QRectF mRect;
317 QgsProfilePoint mPoint;
318 QgsElevationProfilePlotItem *mPlotItem = nullptr;
319};
321
322
324 : QgsPlotCanvas( parent )
325{
326 mScreenHelper = new QgsScreenHelper( this );
327
328 mPlotItem = new QgsElevationProfilePlotItem( this );
329 mCrossHairsItem = new QgsElevationProfileCrossHairsItem( this, mPlotItem );
330 mCrossHairsItem->setZValue( 100 );
331 mCrossHairsItem->hide();
332
333 // updating the profile plot is deferred on a timer, so that we don't trigger it too often
334 mDeferredRegenerationTimer = new QTimer( this );
335 mDeferredRegenerationTimer->setSingleShot( true );
336 mDeferredRegenerationTimer->stop();
337 connect( mDeferredRegenerationTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRegeneration );
338
339 mDeferredRedrawTimer = new QTimer( this );
340 mDeferredRedrawTimer->setSingleShot( true );
341 mDeferredRedrawTimer->stop();
342 connect( mDeferredRedrawTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRedraw );
343
344}
345
347{
348 if ( mCurrentJob )
349 {
350 mPlotItem->setRenderer( nullptr );
351 mCurrentJob->deleteLater();
352 mCurrentJob = nullptr;
353 }
354}
355
357{
358 if ( mCurrentJob )
359 {
360 mPlotItem->setRenderer( nullptr );
361 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
362 mCurrentJob->cancelGeneration();
363 mCurrentJob->deleteLater();
364 mCurrentJob = nullptr;
365 }
366}
367
369{
370 const double dxPercent = dx / mPlotItem->plotArea().width();
371 const double dyPercent = dy / mPlotItem->plotArea().height();
372
373 // these look backwards, but we are dragging the paper, not the view!
374 const double dxPlot = - dxPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
375 const double dyPlot = dyPercent * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
376
377 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
378 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
379 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
380 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
381
382 refineResults();
383
384 mPlotItem->updatePlot();
385 emit plotAreaChanged();
386}
387
389{
390 if ( !mPlotItem->plotArea().contains( x, y ) )
391 return;
392
393 const double newCenterX = mPlotItem->xMinimum() + ( x - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
394 const double newCenterY = mPlotItem->yMinimum() + ( mPlotItem->plotArea().bottom() - y ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
395
396 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
397 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
398
399 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
400 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
401 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
402 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
403
404 refineResults();
405
406 mPlotItem->updatePlot();
407 emit plotAreaChanged();
408}
409
411{
412 scalePlot( factor, factor );
413 emit plotAreaChanged();
414}
415
416QgsProfileSnapContext QgsElevationProfileCanvas::snapContext() const
417{
418 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
419 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
420 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
421
422 QgsProfileSnapContext context;
423 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
424 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
425 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
426 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
427 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
428 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) / ( mPlotItem->plotArea().width() ) );
429
430 return context;
431}
432
433QgsProfileIdentifyContext QgsElevationProfileCanvas::identifyContext() const
434{
435 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
436 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
437 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
438
440 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
441 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
442 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
443 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
444 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
445 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) / ( mPlotItem->plotArea().width() ) );
446
447 context.project = mProject;
448
449 return context;
450}
451
452void QgsElevationProfileCanvas::setupLayerConnections( QgsMapLayer *layer, bool isDisconnect )
453{
454 if ( isDisconnect )
455 {
456 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
457 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
458 disconnect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
459 }
460 else
461 {
462 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
463 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
464 connect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
465 }
466
467 switch ( layer->type() )
468 {
470 {
471 QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
472 if ( isDisconnect )
473 {
474 disconnect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
475 disconnect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
476 disconnect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
477 disconnect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
478 }
479 else
480 {
481 connect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
482 connect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
483 connect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
484 connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
485 }
486 break;
487 }
495 break;
496 }
497}
498
500{
501 if ( !mCurrentJob || !mSnappingEnabled )
502 return QgsPointXY();
503
504 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
505
506 const QgsProfileSnapResult snappedPoint = mCurrentJob->snapPoint( plotPoint, snapContext() );
507 if ( !snappedPoint.isValid() )
508 return QgsPointXY();
509
510 return plotPointToCanvasPoint( snappedPoint.snappedPoint );
511}
512
513void QgsElevationProfileCanvas::scalePlot( double xFactor, double yFactor )
514{
515 const double currentWidth = mPlotItem->xMaximum() - mPlotItem->xMinimum();
516 const double currentHeight = mPlotItem->yMaximum() - mPlotItem->yMinimum();
517
518 const double newWidth = currentWidth / xFactor;
519 const double newHeight = currentHeight / yFactor;
520
521 const double currentCenterX = ( mPlotItem->xMinimum() + mPlotItem->xMaximum() ) * 0.5;
522 const double currentCenterY = ( mPlotItem->yMinimum() + mPlotItem->yMaximum() ) * 0.5;
523
524 mPlotItem->setXMinimum( currentCenterX - newWidth * 0.5 );
525 mPlotItem->setXMaximum( currentCenterX + newWidth * 0.5 );
526 mPlotItem->setYMinimum( currentCenterY - newHeight * 0.5 );
527 mPlotItem->setYMaximum( currentCenterY + newHeight * 0.5 );
528
529 refineResults();
530 mPlotItem->updatePlot();
531 emit plotAreaChanged();
532}
533
535{
536 const QRectF intersected = rect.intersected( mPlotItem->plotArea() );
537
538 const double minX = ( intersected.left() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
539 const double maxX = ( intersected.right() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
540 const double minY = ( mPlotItem->plotArea().bottom() - intersected.bottom() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
541 const double maxY = ( mPlotItem->plotArea().bottom() - intersected.top() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
542
543 mPlotItem->setXMinimum( minX );
544 mPlotItem->setXMaximum( maxX );
545 mPlotItem->setYMinimum( minY );
546 mPlotItem->setYMaximum( maxY );
547
548 refineResults();
549 mPlotItem->updatePlot();
550 emit plotAreaChanged();
551}
552
553void QgsElevationProfileCanvas::wheelZoom( QWheelEvent *event )
554{
555 //get mouse wheel zoom behavior settings
556 QgsSettings settings;
557 double zoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
558
559 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
560 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
561
562 if ( event->modifiers() & Qt::ControlModifier )
563 {
564 //holding ctrl while wheel zooming results in a finer zoom
565 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
566 }
567
568 //calculate zoom scale factor
569 bool zoomIn = event->angleDelta().y() > 0;
570 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
571
572 QRectF viewportRect = mPlotItem->plotArea();
573
574 if ( viewportRect.contains( event->position() ) )
575 {
576 //adjust view center
577 const double oldCenterX = 0.5 * ( mPlotItem->xMaximum() + mPlotItem->xMinimum() );
578 const double oldCenterY = 0.5 * ( mPlotItem->yMaximum() + mPlotItem->yMinimum() );
579
580 const double eventPosX = ( event->position().x() - viewportRect.left() ) / viewportRect.width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
581 const double eventPosY = ( viewportRect.bottom() - event->position().y() ) / viewportRect.height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
582
583 const double newCenterX = eventPosX + ( ( oldCenterX - eventPosX ) * scaleFactor );
584 const double newCenterY = eventPosY + ( ( oldCenterY - eventPosY ) * scaleFactor );
585
586 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
587 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
588
589 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
590 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
591 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
592 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
593 }
594
595 //zoom plot
596 if ( zoomIn )
597 {
598 scalePlot( zoomFactor );
599 }
600 else
601 {
602 scalePlot( 1 / zoomFactor );
603 }
604 emit plotAreaChanged();
605}
606
608{
610 if ( e->isAccepted() )
611 {
612 mCrossHairsItem->hide();
613 return;
614 }
615
616 QgsProfilePoint plotPoint = canvasPointToPlotPoint( e->pos() );
617 if ( mCurrentJob && mSnappingEnabled && !plotPoint.isEmpty() )
618 {
619 const QgsProfileSnapResult snapResult = mCurrentJob->snapPoint( plotPoint, snapContext() );
620 if ( snapResult.isValid() )
621 plotPoint = snapResult.snappedPoint;
622 }
623
624 if ( plotPoint.isEmpty() )
625 {
626 mCrossHairsItem->hide();
627 }
628 else
629 {
630 mCrossHairsItem->setPoint( plotPoint );
631 mCrossHairsItem->show();
632 }
633 emit canvasPointHovered( e->pos(), plotPoint );
634}
635
637{
638 return mPlotItem->plotArea();
639}
640
642{
643 if ( !mProject || !profileCurve() )
644 return;
645
646 if ( mCurrentJob )
647 {
648 mPlotItem->setRenderer( nullptr );
649 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
650 mCurrentJob->deleteLater();
651 mCurrentJob = nullptr;
652 }
653
654 QgsProfileRequest request( profileCurve()->clone() );
655 request.setCrs( mCrs );
656 request.setTolerance( mTolerance );
657 request.setTransformContext( mProject->transformContext() );
658 request.setTerrainProvider( mProject->elevationProperties()->terrainProvider() ? mProject->elevationProperties()->terrainProvider()->clone() : nullptr );
659 QgsExpressionContext context;
662 request.setExpressionContext( context );
663
664 const QList< QgsMapLayer * > layersToGenerate = layers();
665 QList< QgsAbstractProfileSource * > sources;
666 sources.reserve( layersToGenerate .size() );
667 for ( QgsMapLayer *layer : layersToGenerate )
668 {
669 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer ) )
670 sources.append( source );
671 }
672
673 mCurrentJob = new QgsProfilePlotRenderer( sources, request );
674 connect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
675
676 QgsProfileGenerationContext generationContext;
677 generationContext.setDpi( mScreenHelper->screenDpi() );
678 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( mProfileCurve->length() ) / mPlotItem->plotArea().width() );
679 generationContext.setMapUnitsPerDistancePixel( mProfileCurve->length() / mPlotItem->plotArea().width() );
680 mCurrentJob->setContext( generationContext );
681
682 mCurrentJob->startGeneration();
683 mPlotItem->setRenderer( mCurrentJob );
684
685 emit activeJobCountChanged( 1 );
686}
687
689{
690 mZoomFullWhenJobFinished = true;
691}
692
693void QgsElevationProfileCanvas::generationFinished()
694{
695 if ( !mCurrentJob )
696 return;
697
698 emit activeJobCountChanged( 0 );
699
700 if ( mZoomFullWhenJobFinished )
701 {
702 // we only zoom full for the initial generation
703 mZoomFullWhenJobFinished = false;
704 zoomFull();
705 }
706 else
707 {
708 // here we should invalidate cached results only for the layers which have been refined
709
710 // and if no layers are being refeined, don't invalidate anything
711
712 mPlotItem->updatePlot();
713 }
714
715 if ( mForceRegenerationAfterCurrentJobCompletes )
716 {
717 mForceRegenerationAfterCurrentJobCompletes = false;
718 mCurrentJob->invalidateAllRefinableSources();
719 scheduleDeferredRegeneration();
720 }
721}
722
723void QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged()
724{
725 // TODO -- handle nicely when existing job is in progress
726 if ( !mCurrentJob || mCurrentJob->isActive() )
727 return;
728
729 QgsMapLayerElevationProperties *properties = qobject_cast< QgsMapLayerElevationProperties * >( sender() );
730 if ( !properties )
731 return;
732
733 if ( QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( properties->parent() ) )
734 {
735 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer ) )
736 {
737 if ( mCurrentJob->invalidateResults( source ) )
738 scheduleDeferredRegeneration();
739 }
740 }
741}
742
743void QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged()
744{
745 // TODO -- handle nicely when existing job is in progress
746 if ( !mCurrentJob || mCurrentJob->isActive() )
747 return;
748
749 QgsMapLayerElevationProperties *properties = qobject_cast< QgsMapLayerElevationProperties * >( sender() );
750 if ( !properties )
751 return;
752
753 if ( QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( properties->parent() ) )
754 {
755 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer ) )
756 {
757 mCurrentJob->replaceSource( source );
758 }
759 if ( mPlotItem->redrawResults( layer->id() ) )
760 scheduleDeferredRedraw();
761 }
762}
763
764void QgsElevationProfileCanvas::regenerateResultsForLayer()
765{
766 if ( QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( sender() ) )
767 {
768 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer ) )
769 {
770 if ( mCurrentJob->invalidateResults( source ) )
771 scheduleDeferredRegeneration();
772 }
773 }
774}
775
776void QgsElevationProfileCanvas::scheduleDeferredRegeneration()
777{
778 if ( !mDeferredRegenerationScheduled )
779 {
780 mDeferredRegenerationTimer->start( 1 );
781 mDeferredRegenerationScheduled = true;
782 }
783}
784
785void QgsElevationProfileCanvas::scheduleDeferredRedraw()
786{
787 if ( !mDeferredRedrawScheduled )
788 {
789 mDeferredRedrawTimer->start( 1 );
790 mDeferredRedrawScheduled = true;
791 }
792}
793
794void QgsElevationProfileCanvas::startDeferredRegeneration()
795{
796 if ( mCurrentJob && !mCurrentJob->isActive() )
797 {
798 emit activeJobCountChanged( 1 );
799 mCurrentJob->regenerateInvalidatedResults();
800 }
801 else if ( mCurrentJob )
802 {
803 mForceRegenerationAfterCurrentJobCompletes = true;
804 }
805
806 mDeferredRegenerationScheduled = false;
807}
808
809void QgsElevationProfileCanvas::startDeferredRedraw()
810{
811 mPlotItem->update();
812 mDeferredRedrawScheduled = false;
813}
814
815void QgsElevationProfileCanvas::refineResults()
816{
817 if ( mCurrentJob )
818 {
820 context.setDpi( mScreenHelper->screenDpi() );
821 const double plotDistanceRange = mPlotItem->xMaximum() - mPlotItem->xMinimum();
822 const double plotElevationRange = mPlotItem->yMaximum() - mPlotItem->yMinimum();
823 const double plotDistanceUnitsPerPixel = plotDistanceRange / mPlotItem->plotArea().width();
824
825 // we round the actual desired map error down to just one significant figure, to avoid tiny differences
826 // as the plot is panned
827 const double targetMaxErrorInMapUnits = MAX_ERROR_PIXELS * plotDistanceUnitsPerPixel;
828 const double factor = std::pow( 10.0, 1 - std::ceil( std::log10( std::fabs( targetMaxErrorInMapUnits ) ) ) );
829 const double roundedErrorInMapUnits = std::floor( targetMaxErrorInMapUnits * factor ) / factor;
830 context.setMaximumErrorMapUnits( roundedErrorInMapUnits );
831
832 context.setMapUnitsPerDistancePixel( plotDistanceUnitsPerPixel );
833
834 // for similar reasons we round the minimum distance off to multiples of the maximum error in map units
835 const double distanceMin = std::floor( ( mPlotItem->xMinimum() - plotDistanceRange * 0.05 ) / context.maximumErrorMapUnits() ) * context.maximumErrorMapUnits();
836 context.setDistanceRange( QgsDoubleRange( std::max( 0.0, distanceMin ),
837 mPlotItem->xMaximum() + plotDistanceRange * 0.05 ) );
838
839 context.setElevationRange( QgsDoubleRange( mPlotItem->yMinimum() - plotElevationRange * 0.05,
840 mPlotItem->yMaximum() + plotElevationRange * 0.05 ) );
841 mCurrentJob->setContext( context );
842 }
843 scheduleDeferredRegeneration();
844}
845
847{
848 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
849 return QgsProfilePoint();
850
851 return mPlotItem->canvasPointToPlotPoint( point );
852}
853
855{
856 return mPlotItem->plotPointToCanvasPoint( point );
857}
858
860{
861 mProject = project;
862 mPlotItem->mProject = project;
863}
864
866{
867 mCrs = crs;
868}
869
871{
872 mProfileCurve.reset( curve );
873}
874
876{
877 return mProfileCurve.get();
878}
879
881{
882 mTolerance = tolerance;
883}
884
886{
887 return mCrs;
888}
889
890void QgsElevationProfileCanvas::setLayers( const QList<QgsMapLayer *> &layers )
891{
892 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
893 {
894 setupLayerConnections( layer, true );
895 }
896
897 // filter list, removing null layers and invalid layers
898 auto filteredList = layers;
899 filteredList.erase( std::remove_if( filteredList.begin(), filteredList.end(),
900 []( QgsMapLayer * layer )
901 {
902 return !layer || !layer->isValid();
903 } ), filteredList.end() );
904
905 mLayers = _qgis_listRawToQPointer( filteredList );
906 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
907 {
908 setupLayerConnections( layer, false );
909 }
910}
911
912QList<QgsMapLayer *> QgsElevationProfileCanvas::layers() const
913{
914 return _qgis_listQPointerToRaw( mLayers );
915}
916
917void QgsElevationProfileCanvas::resizeEvent( QResizeEvent *event )
918{
920 mPlotItem->updateRect();
921 mCrossHairsItem->updateRect();
922}
923
925{
926 QgsPlotCanvas::paintEvent( event );
927
928 if ( !mFirstDrawOccurred )
929 {
930 // 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).
931 mFirstDrawOccurred = true;
932 mPlotItem->updateRect();
933 mCrossHairsItem->updateRect();
934 }
935}
936
938{
939 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
940 return QgsPoint();
941
942 if ( !mProfileCurve )
943 return QgsPoint();
944
945 const double dx = point.x() - mPlotItem->plotArea().left();
946
947 const double distanceAlongPlotPercent = dx / mPlotItem->plotArea().width();
948 double distanceAlongCurveLength = distanceAlongPlotPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
949
950 std::unique_ptr< QgsPoint > mapXyPoint( mProfileCurve->interpolatePoint( distanceAlongCurveLength ) );
951 if ( !mapXyPoint )
952 return QgsPoint();
953
954 const double mapZ = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * ( mPlotItem->plotArea().bottom() - point.y() ) + mPlotItem->yMinimum();
955
956 return QgsPoint( mapXyPoint->x(), mapXyPoint->y(), mapZ );
957}
958
960{
961 if ( !mProfileCurve )
962 return QgsPointXY();
963
964 QgsGeos geos( mProfileCurve.get() );
965 QString error;
966 const double distanceAlongCurve = geos.lineLocatePoint( point, &error );
967
968 const double distanceAlongCurveOnPlot = distanceAlongCurve - mPlotItem->xMinimum();
969 const double distanceAlongCurvePercent = distanceAlongCurveOnPlot / ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
970 const double distanceAlongPlotRect = distanceAlongCurvePercent * mPlotItem->plotArea().width();
971
972 const double canvasX = mPlotItem->plotArea().left() + distanceAlongPlotRect;
973
974 double canvasY = 0;
975 if ( std::isnan( point.z() ) || point.z() < mPlotItem->yMinimum() )
976 {
977 canvasY = mPlotItem->plotArea().top();
978 }
979 else if ( point.z() > mPlotItem->yMaximum() )
980 {
981 canvasY = mPlotItem->plotArea().bottom();
982 }
983 else
984 {
985 const double yPercent = ( point.z() - mPlotItem->yMinimum() ) / ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
986 canvasY = mPlotItem->plotArea().bottom() - mPlotItem->plotArea().height() * yPercent;
987 }
988
989 return QgsPointXY( canvasX, canvasY );
990}
991
993{
994 if ( !mCurrentJob )
995 return;
996
997 const QgsDoubleRange zRange = mCurrentJob->zRange();
998
999 if ( zRange.upper() < zRange.lower() )
1000 {
1001 // invalid range, e.g. no features found in plot!
1002 mPlotItem->setYMinimum( 0 );
1003 mPlotItem->setYMaximum( 10 );
1004 }
1005 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
1006 {
1007 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
1008 mPlotItem->setYMinimum( zRange.lower() - 5 );
1009 mPlotItem->setYMaximum( zRange.lower() + 5 );
1010 }
1011 else
1012 {
1013 // add 5% margin to height range
1014 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
1015 mPlotItem->setYMinimum( zRange.lower() - margin );
1016 mPlotItem->setYMaximum( zRange.upper() + margin );
1017 }
1018
1019 const double profileLength = profileCurve()->length();
1020 mPlotItem->setXMinimum( 0 );
1021 // just 2% margin to max distance -- any more is overkill and wasted space
1022 mPlotItem->setXMaximum( profileLength * 1.02 );
1023
1024 refineResults();
1025 mPlotItem->updatePlot();
1026 emit plotAreaChanged();
1027}
1028
1029void QgsElevationProfileCanvas::setVisiblePlotRange( double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation )
1030{
1031 mPlotItem->setYMinimum( minimumElevation );
1032 mPlotItem->setYMaximum( maximumElevation );
1033 mPlotItem->setXMinimum( minimumDistance );
1034 mPlotItem->setXMaximum( maximumDistance );
1035 refineResults();
1036 mPlotItem->updatePlot();
1037 emit plotAreaChanged();
1038}
1039
1041{
1042 return QgsDoubleRange( mPlotItem->xMinimum(), mPlotItem->xMaximum() );
1043}
1044
1046{
1047 return QgsDoubleRange( mPlotItem->yMinimum(), mPlotItem->yMaximum() );
1048}
1049
1051{
1052 return *mPlotItem;
1053}
1054
1056class QgsElevationProfilePlot : public Qgs2DPlot
1057{
1058 public:
1059
1060 QgsElevationProfilePlot( QgsProfilePlotRenderer *renderer )
1061 : mRenderer( renderer )
1062 {
1063 }
1064
1065 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
1066 {
1067 if ( !mRenderer )
1068 return;
1069
1070 rc.painter()->translate( plotArea.left(), plotArea.top() );
1071 mRenderer->render( rc, plotArea.width(), plotArea.height(), xMinimum(), xMaximum(), yMinimum(), yMaximum() );
1072 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
1073 }
1074
1075 private:
1076
1077 QgsProfilePlotRenderer *mRenderer = nullptr;
1078};
1080
1081void QgsElevationProfileCanvas::render( QgsRenderContext &context, double width, double height, const Qgs2DPlot &plotSettings )
1082{
1083 if ( !mCurrentJob )
1084 return;
1085
1088
1089 QgsElevationProfilePlot profilePlot( mCurrentJob );
1090
1091 // 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...
1092 QDomDocument doc;
1093 QDomElement elem = doc.createElement( QStringLiteral( "plot" ) );
1094 QgsReadWriteContext rwContext;
1095 plotSettings.writeXml( elem, doc, rwContext );
1096 profilePlot.readXml( elem, rwContext );
1097
1098 profilePlot.setSize( QSizeF( width, height ) );
1099 profilePlot.render( context );
1100}
1101
1102QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( QPointF point )
1103{
1104 if ( !mCurrentJob )
1105 return {};
1106
1107 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
1108
1109 return mCurrentJob->identify( plotPoint, identifyContext() );
1110}
1111
1112QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( const QRectF &rect )
1113{
1114 if ( !mCurrentJob )
1115 return {};
1116
1117 const QgsProfilePoint topLeftPlotPoint = canvasPointToPlotPoint( rect.topLeft() );
1118 const QgsProfilePoint bottomRightPlotPoint = canvasPointToPlotPoint( rect.bottomRight() );
1119
1120 double distance1 = topLeftPlotPoint.distance();
1121 double distance2 = bottomRightPlotPoint.distance();
1122 if ( distance2 < distance1 )
1123 std::swap( distance1, distance2 );
1124
1125 double elevation1 = topLeftPlotPoint.elevation();
1126 double elevation2 = bottomRightPlotPoint.elevation();
1127 if ( elevation2 < elevation1 )
1128 std::swap( elevation1, elevation2 );
1129
1130 return mCurrentJob->identify( QgsDoubleRange( distance1, distance2 ), QgsDoubleRange( elevation1, elevation2 ), identifyContext() );
1131}
1132
1134{
1135 setProfileCurve( nullptr );
1136 mPlotItem->setRenderer( nullptr );
1137 mPlotItem->updatePlot();
1138}
1139
1141{
1142 mSnappingEnabled = enabled;
1143}
Base class for 2-dimensional plot/chart/graphs.
Definition: qgsplot.h:235
void calculateOptimisedIntervals(QgsRenderContext &context)
Automatically sets the grid and label intervals to optimal values for display in the given render con...
Definition: qgsplot.cpp:434
double yMaximum() const
Returns the maximum value of the y axis.
Definition: qgsplot.h:347
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:380
double xMaximum() const
Returns the maximum value of the x axis.
Definition: qgsplot.h:333
void render(QgsRenderContext &context)
Renders the plot.
Definition: qgsplot.cpp:214
void setYMaximum(double maximum)
Sets the maximum value of the y axis.
Definition: qgsplot.h:354
double xMinimum() const
Returns the minimum value of the x axis.
Definition: qgsplot.h:305
double yMinimum() const
Returns the minimum value of the y axis.
Definition: qgsplot.h:319
QRectF interiorPlotArea(QgsRenderContext &context) const
Returns the area of the plot which corresponds to the actual plot content (excluding all titles and o...
Definition: qgsplot.cpp:385
void setYMinimum(double minimum)
Sets the minimum value of the y axis.
Definition: qgsplot.h:326
virtual void renderContent(QgsRenderContext &context, const QRectF &plotArea)
Renders the plot content.
Definition: qgsplot.cpp:368
bool writeXml(QDomElement &element, QDomDocument &document, QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition: qgsplot.cpp:162
virtual double length() const
Returns the planar, 2-dimensional length of the geometry.
Interface for classes which can generate elevation profiles.
virtual QgsAbstractTerrainProvider * clone() const =0
Creates a clone of the provider and returns the new object.
This class 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:203
A canvas for elevation profiles.
QgsDoubleRange visibleElevationRange() const
Returns the elevation range currently visible in the plot.
QgsCurve * profileCurve() const
Returns the profile curve.
void setTolerance(double tolerance)
Sets the profile tolerance (in crs() units).
void 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.
QgsProfilePoint canvasPointToPlotPoint(QPointF point) const
Converts a canvas point to the equivalent plot point.
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.
void setVisiblePlotRange(double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation)
Sets the visible area of the plot.
void canvasPointHovered(const QgsPointXY &point, const QgsProfilePoint &profilePoint)
Emitted when the mouse hovers over the specified point (in canvas coordinates).
void render(QgsRenderContext &context, double width, double height, const Qgs2DPlot &plotSettings)
Renders a portion of the profile using the specified render context.
QgsPointXY snapToPlot(QPoint point) override
Snap a canvas point to the plot.
void setProject(QgsProject *project)
Sets the project associated with the profile.
QList< QgsMapLayer * > layers() const
Returns the list of layers included in the profile.
void resizeEvent(QResizeEvent *event) override
void centerPlotOn(double x, double y) override
Centers the plot on the plot point corresponding to x, y in canvas units.
const Qgs2DPlot & plot() const
Returns a reference to the 2D plot used by the widget.
void wheelZoom(QWheelEvent *event) override
Zoom plot from a mouse wheel event.
void refresh() override
Triggers a complete regeneration of the profile, causing the profile extraction to perform in the bac...
double tolerance() const
Returns the tolerance of the profile (in crs() units).
void mouseMoveEvent(QMouseEvent *e) override
void panContentsBy(double dx, double dy) override
Pans the plot contents by dx, dy in canvas units.
void invalidateCurrentPlotExtent()
Invalidates the current plot extent, which means that the visible plot area will be recalculated and ...
QgsPointXY toCanvasCoordinates(const QgsPoint &point) const override
Converts a point in map coordinates to the associated canvas point.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the crs associated with the canvas' map coordinates.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to include in the profile.
void zoomFull()
Zooms to the full extent of the profile.
void setSnappingEnabled(bool enabled)
Sets whether snapping of cursor points is enabled.
QVector< QgsProfileIdentifyResults > identify(QPointF point)
Identify results visible at the specified plot point.
QRectF plotArea() const
Returns the interior rectangle representing the surface of the plot, in canvas coordinates.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition: qgsgeos.h:99
Base class for storage of map layer elevation properties.
void profileGenerationPropertyChanged()
Emitted when any of the elevation properties which relate solely to generation of elevation profiles ...
void profileRenderingPropertyChanged()
Emitted when any of the elevation properties which relate solely to presentation of elevation results...
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QgsMapLayerType type
Definition: qgsmaplayer.h:80
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
void dataChanged()
Data of layer changed.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Definition: qgsmaplayer.h:1510
A context for numeric formats.
An abstract class for items that can be placed on a QgsPlotCanvas.
virtual void paint(QPainter *painter)=0
Paints the item.
Plot canvas is a class for displaying interactive 2d charts and plots.
Definition: qgsplotcanvas.h:54
bool event(QEvent *e) override
void plotAreaChanged()
Emitted whenever the visible area of the plot is changed.
void mouseMoveEvent(QMouseEvent *e) override
void resizeEvent(QResizeEvent *e) override
A class to represent a 2D point.
Definition: qgspointxy.h:59
bool isEmpty() const SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspointxy.h:249
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
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.
QgsProfileSnapResult snapPoint(const QgsProfilePoint &point, const QgsProfileSnapContext &context)
Snap a point to the results.
void regenerateInvalidatedResults()
Starts a background regeneration of any invalidated results and immediately returns.
void invalidateAllRefinableSources()
Invalidates previous results from all refinable sources.
void cancelGeneration()
Stop the generation job - does not return until the job has terminated.
void startGeneration()
Start the generation job and immediately return.
QgsDoubleRange zRange() const
Returns the limits of the retrieved elevation values.
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context)
Identify results visible at the specified profile point.
bool isActive() const
Returns true if the generation job is currently running in background.
bool invalidateResults(QgsAbstractProfileSource *source)
Invalidates the profile results from the source with matching ID.
void replaceSource(QgsAbstractProfileSource *source)
Replaces the existing source with matching ID.
void setContext(const QgsProfileGenerationContext &context)
Sets the context in which the profile generation will occur.
void generationFinished()
Emitted when the profile generation is finished (or canceled).
Encapsulates a point on a distance-elevation profile.
double elevation() const SIP_HOLDGIL
Returns the elevation of the point.
bool isEmpty() const SIP_HOLDGIL
Returns true if the point is empty.
double distance() const SIP_HOLDGIL
Returns the distance of the point.
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.
QgsAbstractTerrainProvider * terrainProvider()
Returns the project's terrain provider.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:104
const QgsProjectElevationProperties * elevationProperties() const
Returns the project's elevation properties, which contains the project's elevation related settings.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:110
T lower() const
Returns the lower bound of the range.
Definition: qgsrange.h:66
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:73
The class is used as a container of context for various read/write operations on other 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.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
A utility class for dynamic handling of changes to screen properties.
double screenDpi() const
Returns the current screen DPI for the screen that the parent widget appears on.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Represents a vector layer which manages a vector based data sets.
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.
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
Contains geos related utilities and functions.
Definition: qgsgeos.h:37
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2527
const QgsCoordinateReferenceSystem & crs