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