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