QGIS API Documentation 3.31.0-Master (9f23a2c1dc)
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 double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
142
143 const QStringList sourceIds = mRenderer->sourceIds();
144 for ( const QString &source : sourceIds )
145 {
146 QImage plot;
147 auto it = mCachedImages.constFind( source );
148 if ( it != mCachedImages.constEnd() )
149 {
150 plot = it.value();
151 }
152 else
153 {
154 plot = mRenderer->renderToImage( plotArea.width() * pixelRatio,
155 plotArea.height() * pixelRatio, xMinimum(), xMaximum(), yMinimum(), yMaximum(), source, pixelRatio );
156 plot.setDevicePixelRatio( pixelRatio );
157 mCachedImages.insert( source, plot );
158 }
159 rc.painter()->drawImage( QPointF( plotArea.left(),
160 plotArea.top() ), plot );
161 }
162 }
163
164 void paint( QPainter *painter ) override
165 {
166 // cache rendering to an image, so we don't need to redraw the plot
167 if ( !mImage.isNull() )
168 {
169 painter->drawImage( QPointF( 0, 0 ), mImage );
170 }
171 else
172 {
173 const double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
174 mImage = QImage( mRect.width() * pixelRatio, mRect.height() * pixelRatio, QImage::Format_ARGB32_Premultiplied );
175 mImage.setDevicePixelRatio( pixelRatio );
176 mImage.fill( Qt::transparent );
177
178 QPainter imagePainter( &mImage );
179 imagePainter.setRenderHint( QPainter::Antialiasing, true );
181 rc.setDevicePixelRatio( pixelRatio );
182
183 const double mapUnitsPerPixel = ( xMaximum() - xMinimum() ) / plotArea().width();
184 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
185
188
190 render( rc );
191 imagePainter.end();
192
193 painter->drawImage( QPointF( 0, 0 ), mImage );
194 }
195 }
196
197 QgsProject *mProject = nullptr;
198
199 private:
200
201 QImage mImage;
202
203 QMap< QString, QImage > mCachedImages;
204
205 QRectF mRect;
206 QRectF mPlotArea;
207 QgsProfilePlotRenderer *mRenderer = nullptr;
208};
209
210class QgsElevationProfileCrossHairsItem : public QgsPlotCanvasItem
211{
212 public:
213
214 QgsElevationProfileCrossHairsItem( QgsElevationProfileCanvas *canvas, QgsElevationProfilePlotItem *plotItem )
215 : QgsPlotCanvasItem( canvas )
216 , mPlotItem( plotItem )
217 {
218 }
219
220 void updateRect()
221 {
222 mRect = mCanvas->rect();
223
224 prepareGeometryChange();
225 setPos( mRect.topLeft() );
226 update();
227 }
228
229 void setPoint( const QgsProfilePoint &point )
230 {
231 mPoint = point;
232 update();
233 }
234
235 QRectF boundingRect() const override
236 {
237 return mRect;
238 }
239
240 void paint( QPainter *painter ) override
241 {
242 const QgsPointXY crossHairPlotPoint = mPlotItem->plotPointToCanvasPoint( mPoint );
243 if ( crossHairPlotPoint.isEmpty() )
244 return;
245
246 painter->save();
247 painter->setBrush( Qt::NoBrush );
248 QPen crossHairPen;
249 crossHairPen.setCosmetic( true );
250 crossHairPen.setWidthF( 1 );
251 crossHairPen.setStyle( Qt::DashLine );
252 crossHairPen.setCapStyle( Qt::FlatCap );
253 crossHairPen.setColor( QColor( 0, 0, 0, 150 ) );
254 painter->setPen( crossHairPen );
255 painter->drawLine( QPointF( mPlotItem->plotArea().left(), crossHairPlotPoint.y() ), QPointF( mPlotItem->plotArea().right(), crossHairPlotPoint.y() ) );
256 painter->drawLine( QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().top() ), QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().bottom() ) );
257
258 // also render current point text
259 QgsNumericFormatContext numericContext;
260
261 const QString xCoordinateText = mPlotItem->xAxis().numericFormat()->formatDouble( mPoint.distance(), numericContext );
262 const QString yCoordinateText = mPlotItem->yAxis().numericFormat()->formatDouble( mPoint.elevation(), numericContext );
263
264 QFont font;
265 const QFontMetrics fm( font );
266 const double height = fm.capHeight();
267 const double xWidth = fm.horizontalAdvance( xCoordinateText );
268 const double yWidth = fm.horizontalAdvance( yCoordinateText );
269 const double textAxisMargin = fm.horizontalAdvance( ' ' );
270
271 QPointF xCoordOrigin;
272 QPointF yCoordOrigin;
273
274 if ( mPoint.distance() < ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5 )
275 {
276 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
277 {
278 // render x coordinate on right top (left top align)
279 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
280 // render y coordinate on right top (right bottom align)
281 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
282 }
283 else
284 {
285 // render x coordinate on right bottom (left bottom align)
286 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
287 // render y coordinate on right bottom (right top align)
288 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
289 }
290 }
291 else
292 {
293 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
294 {
295 // render x coordinate on left top (right top align)
296 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
297 // render y coordinate on left top (left bottom align)
298 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
299 }
300 else
301 {
302 // render x coordinate on left bottom (right bottom align)
303 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
304 // render y coordinate on left bottom (left top align)
305 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
306 }
307 }
308
309 // semi opaque white background
310 painter->setBrush( QBrush( QColor( 255, 255, 255, 220 ) ) );
311 painter->setPen( Qt::NoPen );
312 painter->drawRect( QRectF( xCoordOrigin.x() - textAxisMargin + 1, xCoordOrigin.y() - textAxisMargin - height + 1, xWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
313 painter->drawRect( QRectF( yCoordOrigin.x() - textAxisMargin + 1, yCoordOrigin.y() - textAxisMargin - height + 1, yWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
314
315 painter->setBrush( Qt::NoBrush );
316 painter->setPen( Qt::black );
317
318 painter->drawText( xCoordOrigin, xCoordinateText );
319 painter->drawText( yCoordOrigin, yCoordinateText );
320 painter->restore();
321 }
322
323 private:
324
325 QRectF mRect;
326 QgsProfilePoint mPoint;
327 QgsElevationProfilePlotItem *mPlotItem = nullptr;
328};
330
331
333 : QgsPlotCanvas( parent )
334{
335 mScreenHelper = new QgsScreenHelper( this );
336
337 mPlotItem = new QgsElevationProfilePlotItem( this );
338 mCrossHairsItem = new QgsElevationProfileCrossHairsItem( this, mPlotItem );
339 mCrossHairsItem->setZValue( 100 );
340 mCrossHairsItem->hide();
341
342 // updating the profile plot is deferred on a timer, so that we don't trigger it too often
343 mDeferredRegenerationTimer = new QTimer( this );
344 mDeferredRegenerationTimer->setSingleShot( true );
345 mDeferredRegenerationTimer->stop();
346 connect( mDeferredRegenerationTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRegeneration );
347
348 mDeferredRedrawTimer = new QTimer( this );
349 mDeferredRedrawTimer->setSingleShot( true );
350 mDeferredRedrawTimer->stop();
351 connect( mDeferredRedrawTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRedraw );
352
353}
354
356{
357 if ( mCurrentJob )
358 {
359 mPlotItem->setRenderer( nullptr );
360 mCurrentJob->deleteLater();
361 mCurrentJob = nullptr;
362 }
363}
364
366{
367 if ( mCurrentJob )
368 {
369 mPlotItem->setRenderer( nullptr );
370 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
371 mCurrentJob->cancelGeneration();
372 mCurrentJob->deleteLater();
373 mCurrentJob = nullptr;
374 }
375}
376
378{
379 const double dxPercent = dx / mPlotItem->plotArea().width();
380 const double dyPercent = dy / mPlotItem->plotArea().height();
381
382 // these look backwards, but we are dragging the paper, not the view!
383 const double dxPlot = - dxPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
384 const double dyPlot = dyPercent * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
385
386 // no need to handle axis scale lock here, we aren't changing scales
387 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
388 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
389 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
390 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
391
392 refineResults();
393
394 mPlotItem->updatePlot();
395 emit plotAreaChanged();
396}
397
399{
400 if ( !mPlotItem->plotArea().contains( x, y ) )
401 return;
402
403 const double newCenterX = mPlotItem->xMinimum() + ( x - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
404 const double newCenterY = mPlotItem->yMinimum() + ( mPlotItem->plotArea().bottom() - y ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
405
406 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
407 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
408
409 // no need to handle axis scale lock here, we aren't changing scales
410 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
411 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
412 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
413 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
414
415 refineResults();
416
417 mPlotItem->updatePlot();
418 emit plotAreaChanged();
419}
420
422{
423 scalePlot( factor, factor );
424 emit plotAreaChanged();
425}
426
427QgsProfileSnapContext QgsElevationProfileCanvas::snapContext() const
428{
429 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
430 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
431 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
432
433 QgsProfileSnapContext context;
434 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
435 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
436 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
437 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
438 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
439 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) / ( mPlotItem->plotArea().width() ) );
440
441 return context;
442}
443
444QgsProfileIdentifyContext QgsElevationProfileCanvas::identifyContext() const
445{
446 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
447 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
448 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
449
451 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
452 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
453 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
454 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
455 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
456 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) / ( mPlotItem->plotArea().width() ) );
457
458 context.project = mProject;
459
460 return context;
461}
462
463void QgsElevationProfileCanvas::setupLayerConnections( QgsMapLayer *layer, bool isDisconnect )
464{
465 if ( isDisconnect )
466 {
467 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
468 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
469 disconnect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
470 }
471 else
472 {
473 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
474 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
475 connect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
476 }
477
478 switch ( layer->type() )
479 {
480 case Qgis::LayerType::Vector:
481 {
482 QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
483 if ( isDisconnect )
484 {
485 disconnect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
486 disconnect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
487 disconnect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
488 disconnect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
489 }
490 else
491 {
492 connect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
493 connect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
494 connect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
495 connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
496 }
497 break;
498 }
499 case Qgis::LayerType::Raster:
500 case Qgis::LayerType::Plugin:
501 case Qgis::LayerType::Mesh:
502 case Qgis::LayerType::VectorTile:
503 case Qgis::LayerType::Annotation:
504 case Qgis::LayerType::PointCloud:
505 case Qgis::LayerType::Group:
506 break;
507 }
508}
509
510void QgsElevationProfileCanvas::adjustRangeForAxisScaleLock( double &xMinimum, double &xMaximum, double &yMinimum, double &yMaximum ) const
511{
512 // ensures that we always "zoom out" to match horizontal/vertical scales
513 const double horizontalScale = ( xMaximum - xMinimum ) / mPlotItem->plotArea().width();
514 const double verticalScale = ( yMaximum - yMinimum ) / mPlotItem->plotArea().height();
515 if ( horizontalScale > verticalScale )
516 {
517 const double height = horizontalScale * mPlotItem->plotArea().height();
518 const double deltaHeight = ( yMaximum - yMinimum ) - height;
519 yMinimum += deltaHeight / 2;
520 yMaximum -= deltaHeight / 2;
521 }
522 else
523 {
524 const double width = verticalScale * mPlotItem->plotArea().width();
525 const double deltaWidth = ( xMaximum - xMinimum ) - width;
526 xMinimum += deltaWidth / 2;
527 xMaximum -= deltaWidth / 2;
528 }
529}
530
532{
533 return mLockAxisScales;
534}
535
537{
538 mLockAxisScales = lock;
539 if ( mLockAxisScales )
540 {
541 double xMinimum = mPlotItem->xMinimum();
542 double xMaximum = mPlotItem->xMaximum();
543 double yMinimum = mPlotItem->yMinimum();
544 double yMaximum = mPlotItem->yMaximum();
545 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
546 mPlotItem->setXMinimum( xMinimum );
547 mPlotItem->setXMaximum( xMaximum );
548 mPlotItem->setYMinimum( yMinimum );
549 mPlotItem->setYMaximum( yMaximum );
550
551 refineResults();
552 mPlotItem->updatePlot();
553 emit plotAreaChanged();
554 }
555}
556
558{
559 if ( !mCurrentJob || !mSnappingEnabled )
560 return QgsPointXY();
561
562 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
563
564 const QgsProfileSnapResult snappedPoint = mCurrentJob->snapPoint( plotPoint, snapContext() );
565 if ( !snappedPoint.isValid() )
566 return QgsPointXY();
567
568 return plotPointToCanvasPoint( snappedPoint.snappedPoint );
569}
570
571void QgsElevationProfileCanvas::scalePlot( double xFactor, double yFactor )
572{
573 if ( mLockAxisScales )
574 yFactor = xFactor;
575
576 const double currentWidth = mPlotItem->xMaximum() - mPlotItem->xMinimum();
577 const double currentHeight = mPlotItem->yMaximum() - mPlotItem->yMinimum();
578
579 const double newWidth = currentWidth / xFactor;
580 const double newHeight = currentHeight / yFactor;
581
582 const double currentCenterX = ( mPlotItem->xMinimum() + mPlotItem->xMaximum() ) * 0.5;
583 const double currentCenterY = ( mPlotItem->yMinimum() + mPlotItem->yMaximum() ) * 0.5;
584
585 double xMinimum = currentCenterX - newWidth * 0.5;
586 double xMaximum = currentCenterX + newWidth * 0.5;
587 double yMinimum = currentCenterY - newHeight * 0.5;
588 double yMaximum = currentCenterY + newHeight * 0.5;
589 if ( mLockAxisScales )
590 {
591 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
592 }
593
594 mPlotItem->setXMinimum( xMinimum );
595 mPlotItem->setXMaximum( xMaximum );
596 mPlotItem->setYMinimum( yMinimum );
597 mPlotItem->setYMaximum( yMaximum );
598
599 refineResults();
600 mPlotItem->updatePlot();
601 emit plotAreaChanged();
602}
603
605{
606 const QRectF intersected = rect.intersected( mPlotItem->plotArea() );
607
608 double minX = ( intersected.left() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
609 double maxX = ( intersected.right() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
610 double minY = ( mPlotItem->plotArea().bottom() - intersected.bottom() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
611 double maxY = ( mPlotItem->plotArea().bottom() - intersected.top() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
612
613 if ( mLockAxisScales )
614 {
615 adjustRangeForAxisScaleLock( minX, maxX, minY, maxY );
616 }
617
618 mPlotItem->setXMinimum( minX );
619 mPlotItem->setXMaximum( maxX );
620 mPlotItem->setYMinimum( minY );
621 mPlotItem->setYMaximum( maxY );
622
623 refineResults();
624 mPlotItem->updatePlot();
625 emit plotAreaChanged();
626}
627
628void QgsElevationProfileCanvas::wheelZoom( QWheelEvent *event )
629{
630 //get mouse wheel zoom behavior settings
631 QgsSettings settings;
632 double zoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
633 bool reverseZoom = settings.value( QStringLiteral( "qgis/reverse_wheel_zoom" ), false ).toBool();
634 bool zoomIn = reverseZoom ? event->angleDelta().y() < 0 : event->angleDelta().y() > 0;
635
636 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
637 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
638
639 if ( event->modifiers() & Qt::ControlModifier )
640 {
641 //holding ctrl while wheel zooming results in a finer zoom
642 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
643 }
644
645 //calculate zoom scale factor
646 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
647
648 QRectF viewportRect = mPlotItem->plotArea();
649
650 if ( viewportRect.contains( event->position() ) )
651 {
652 //adjust view center
653 const double oldCenterX = 0.5 * ( mPlotItem->xMaximum() + mPlotItem->xMinimum() );
654 const double oldCenterY = 0.5 * ( mPlotItem->yMaximum() + mPlotItem->yMinimum() );
655
656 const double eventPosX = ( event->position().x() - viewportRect.left() ) / viewportRect.width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
657 const double eventPosY = ( viewportRect.bottom() - event->position().y() ) / viewportRect.height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
658
659 const double newCenterX = eventPosX + ( ( oldCenterX - eventPosX ) * scaleFactor );
660 const double newCenterY = eventPosY + ( ( oldCenterY - eventPosY ) * scaleFactor );
661
662 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
663 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
664
665 // don't need to handle axis scale lock here, we are always changing axis by the same scale
666 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
667 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
668 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
669 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
670 }
671
672 //zoom plot
673 if ( zoomIn )
674 {
675 scalePlot( zoomFactor );
676 }
677 else
678 {
679 scalePlot( 1 / zoomFactor );
680 }
681 emit plotAreaChanged();
682}
683
685{
687 if ( e->isAccepted() )
688 {
689 mCrossHairsItem->hide();
690 return;
691 }
692
693 QgsProfilePoint plotPoint = canvasPointToPlotPoint( e->pos() );
694 if ( mCurrentJob && mSnappingEnabled && !plotPoint.isEmpty() )
695 {
696 const QgsProfileSnapResult snapResult = mCurrentJob->snapPoint( plotPoint, snapContext() );
697 if ( snapResult.isValid() )
698 plotPoint = snapResult.snappedPoint;
699 }
700
701 if ( plotPoint.isEmpty() )
702 {
703 mCrossHairsItem->hide();
704 }
705 else
706 {
707 mCrossHairsItem->setPoint( plotPoint );
708 mCrossHairsItem->show();
709 }
710 emit canvasPointHovered( e->pos(), plotPoint );
711}
712
714{
715 return mPlotItem->plotArea();
716}
717
719{
720 if ( !mProject || !profileCurve() )
721 return;
722
723 if ( mCurrentJob )
724 {
725 mPlotItem->setRenderer( nullptr );
726 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
727 mCurrentJob->deleteLater();
728 mCurrentJob = nullptr;
729 }
730
731 QgsProfileRequest request( profileCurve()->clone() );
732 request.setCrs( mCrs );
733 request.setTolerance( mTolerance );
734 request.setTransformContext( mProject->transformContext() );
735 request.setTerrainProvider( mProject->elevationProperties()->terrainProvider() ? mProject->elevationProperties()->terrainProvider()->clone() : nullptr );
736 QgsExpressionContext context;
739 request.setExpressionContext( context );
740
741 const QList< QgsMapLayer * > layersToGenerate = layers();
742 QList< QgsAbstractProfileSource * > sources;
743 sources.reserve( layersToGenerate .size() );
744 for ( QgsMapLayer *layer : layersToGenerate )
745 {
746 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer ) )
747 sources.append( source );
748 }
749
750 mCurrentJob = new QgsProfilePlotRenderer( sources, request );
751 connect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
752
753 QgsProfileGenerationContext generationContext;
754 generationContext.setDpi( mScreenHelper->screenDpi() );
755 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( mProfileCurve->length() ) / mPlotItem->plotArea().width() );
756 generationContext.setMapUnitsPerDistancePixel( mProfileCurve->length() / mPlotItem->plotArea().width() );
757 mCurrentJob->setContext( generationContext );
758
759 mCurrentJob->startGeneration();
760 mPlotItem->setRenderer( mCurrentJob );
761
762 emit activeJobCountChanged( 1 );
763}
764
766{
767 mZoomFullWhenJobFinished = true;
768}
769
770void QgsElevationProfileCanvas::generationFinished()
771{
772 if ( !mCurrentJob )
773 return;
774
775 emit activeJobCountChanged( 0 );
776
777 if ( mZoomFullWhenJobFinished )
778 {
779 // we only zoom full for the initial generation
780 mZoomFullWhenJobFinished = false;
781 zoomFull();
782 }
783 else
784 {
785 // here we should invalidate cached results only for the layers which have been refined
786
787 // and if no layers are being refeined, don't invalidate anything
788
789 mPlotItem->updatePlot();
790 }
791
792 if ( mForceRegenerationAfterCurrentJobCompletes )
793 {
794 mForceRegenerationAfterCurrentJobCompletes = false;
795 mCurrentJob->invalidateAllRefinableSources();
796 scheduleDeferredRegeneration();
797 }
798}
799
800void QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged()
801{
802 // TODO -- handle nicely when existing job is in progress
803 if ( !mCurrentJob || mCurrentJob->isActive() )
804 return;
805
806 QgsMapLayerElevationProperties *properties = qobject_cast< QgsMapLayerElevationProperties * >( sender() );
807 if ( !properties )
808 return;
809
810 if ( QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( properties->parent() ) )
811 {
812 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer ) )
813 {
814 if ( mCurrentJob->invalidateResults( source ) )
815 scheduleDeferredRegeneration();
816 }
817 }
818}
819
820void QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged()
821{
822 // TODO -- handle nicely when existing job is in progress
823 if ( !mCurrentJob || mCurrentJob->isActive() )
824 return;
825
826 QgsMapLayerElevationProperties *properties = qobject_cast< QgsMapLayerElevationProperties * >( sender() );
827 if ( !properties )
828 return;
829
830 if ( QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( properties->parent() ) )
831 {
832 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer ) )
833 {
834 mCurrentJob->replaceSource( source );
835 }
836 if ( mPlotItem->redrawResults( layer->id() ) )
837 scheduleDeferredRedraw();
838 }
839}
840
841void QgsElevationProfileCanvas::regenerateResultsForLayer()
842{
843 if ( QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( sender() ) )
844 {
845 if ( QgsAbstractProfileSource *source = dynamic_cast< QgsAbstractProfileSource * >( layer ) )
846 {
847 if ( mCurrentJob->invalidateResults( source ) )
848 scheduleDeferredRegeneration();
849 }
850 }
851}
852
853void QgsElevationProfileCanvas::scheduleDeferredRegeneration()
854{
855 if ( !mDeferredRegenerationScheduled )
856 {
857 mDeferredRegenerationTimer->start( 1 );
858 mDeferredRegenerationScheduled = true;
859 }
860}
861
862void QgsElevationProfileCanvas::scheduleDeferredRedraw()
863{
864 if ( !mDeferredRedrawScheduled )
865 {
866 mDeferredRedrawTimer->start( 1 );
867 mDeferredRedrawScheduled = true;
868 }
869}
870
871void QgsElevationProfileCanvas::startDeferredRegeneration()
872{
873 if ( mCurrentJob && !mCurrentJob->isActive() )
874 {
875 emit activeJobCountChanged( 1 );
876 mCurrentJob->regenerateInvalidatedResults();
877 }
878 else if ( mCurrentJob )
879 {
880 mForceRegenerationAfterCurrentJobCompletes = true;
881 }
882
883 mDeferredRegenerationScheduled = false;
884}
885
886void QgsElevationProfileCanvas::startDeferredRedraw()
887{
888 mPlotItem->update();
889 mDeferredRedrawScheduled = false;
890}
891
892void QgsElevationProfileCanvas::refineResults()
893{
894 if ( mCurrentJob )
895 {
897 context.setDpi( mScreenHelper->screenDpi() );
898 const double plotDistanceRange = mPlotItem->xMaximum() - mPlotItem->xMinimum();
899 const double plotElevationRange = mPlotItem->yMaximum() - mPlotItem->yMinimum();
900 const double plotDistanceUnitsPerPixel = plotDistanceRange / mPlotItem->plotArea().width();
901
902 // we round the actual desired map error down to just one significant figure, to avoid tiny differences
903 // as the plot is panned
904 const double targetMaxErrorInMapUnits = MAX_ERROR_PIXELS * plotDistanceUnitsPerPixel;
905 const double factor = std::pow( 10.0, 1 - std::ceil( std::log10( std::fabs( targetMaxErrorInMapUnits ) ) ) );
906 const double roundedErrorInMapUnits = std::floor( targetMaxErrorInMapUnits * factor ) / factor;
907 context.setMaximumErrorMapUnits( roundedErrorInMapUnits );
908
909 context.setMapUnitsPerDistancePixel( plotDistanceUnitsPerPixel );
910
911 // for similar reasons we round the minimum distance off to multiples of the maximum error in map units
912 const double distanceMin = std::floor( ( mPlotItem->xMinimum() - plotDistanceRange * 0.05 ) / context.maximumErrorMapUnits() ) * context.maximumErrorMapUnits();
913 context.setDistanceRange( QgsDoubleRange( std::max( 0.0, distanceMin ),
914 mPlotItem->xMaximum() + plotDistanceRange * 0.05 ) );
915
916 context.setElevationRange( QgsDoubleRange( mPlotItem->yMinimum() - plotElevationRange * 0.05,
917 mPlotItem->yMaximum() + plotElevationRange * 0.05 ) );
918 mCurrentJob->setContext( context );
919 }
920 scheduleDeferredRegeneration();
921}
922
924{
925 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
926 return QgsProfilePoint();
927
928 return mPlotItem->canvasPointToPlotPoint( point );
929}
930
932{
933 return mPlotItem->plotPointToCanvasPoint( point );
934}
935
937{
938 mProject = project;
939 mPlotItem->mProject = project;
940}
941
943{
944 mCrs = crs;
945}
946
948{
949 mProfileCurve.reset( curve );
950}
951
953{
954 return mProfileCurve.get();
955}
956
958{
959 mTolerance = tolerance;
960}
961
963{
964 return mCrs;
965}
966
967void QgsElevationProfileCanvas::setLayers( const QList<QgsMapLayer *> &layers )
968{
969 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
970 {
971 setupLayerConnections( layer, true );
972 }
973
974 // filter list, removing null layers and invalid layers
975 auto filteredList = layers;
976 filteredList.erase( std::remove_if( filteredList.begin(), filteredList.end(),
977 []( QgsMapLayer * layer )
978 {
979 return !layer || !layer->isValid();
980 } ), filteredList.end() );
981
982 mLayers = _qgis_listRawToQPointer( filteredList );
983 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
984 {
985 setupLayerConnections( layer, false );
986 }
987}
988
989QList<QgsMapLayer *> QgsElevationProfileCanvas::layers() const
990{
991 return _qgis_listQPointerToRaw( mLayers );
992}
993
994void QgsElevationProfileCanvas::resizeEvent( QResizeEvent *event )
995{
997 mPlotItem->updateRect();
998 mCrossHairsItem->updateRect();
999}
1000
1002{
1003 QgsPlotCanvas::paintEvent( event );
1004
1005 if ( !mFirstDrawOccurred )
1006 {
1007 // 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).
1008 mFirstDrawOccurred = true;
1009 mPlotItem->updateRect();
1010 mCrossHairsItem->updateRect();
1011 }
1012}
1013
1015{
1016 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1017 return QgsPoint();
1018
1019 if ( !mProfileCurve )
1020 return QgsPoint();
1021
1022 const double dx = point.x() - mPlotItem->plotArea().left();
1023
1024 const double distanceAlongPlotPercent = dx / mPlotItem->plotArea().width();
1025 double distanceAlongCurveLength = distanceAlongPlotPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
1026
1027 std::unique_ptr< QgsPoint > mapXyPoint( mProfileCurve->interpolatePoint( distanceAlongCurveLength ) );
1028 if ( !mapXyPoint )
1029 return QgsPoint();
1030
1031 const double mapZ = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * ( mPlotItem->plotArea().bottom() - point.y() ) + mPlotItem->yMinimum();
1032
1033 return QgsPoint( mapXyPoint->x(), mapXyPoint->y(), mapZ );
1034}
1035
1037{
1038 if ( !mProfileCurve )
1039 return QgsPointXY();
1040
1041 QgsGeos geos( mProfileCurve.get() );
1042 QString error;
1043 const double distanceAlongCurve = geos.lineLocatePoint( point, &error );
1044
1045 const double distanceAlongCurveOnPlot = distanceAlongCurve - mPlotItem->xMinimum();
1046 const double distanceAlongCurvePercent = distanceAlongCurveOnPlot / ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
1047 const double distanceAlongPlotRect = distanceAlongCurvePercent * mPlotItem->plotArea().width();
1048
1049 const double canvasX = mPlotItem->plotArea().left() + distanceAlongPlotRect;
1050
1051 double canvasY = 0;
1052 if ( std::isnan( point.z() ) || point.z() < mPlotItem->yMinimum() )
1053 {
1054 canvasY = mPlotItem->plotArea().top();
1055 }
1056 else if ( point.z() > mPlotItem->yMaximum() )
1057 {
1058 canvasY = mPlotItem->plotArea().bottom();
1059 }
1060 else
1061 {
1062 const double yPercent = ( point.z() - mPlotItem->yMinimum() ) / ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
1063 canvasY = mPlotItem->plotArea().bottom() - mPlotItem->plotArea().height() * yPercent;
1064 }
1065
1066 return QgsPointXY( canvasX, canvasY );
1067}
1068
1070{
1071 if ( !mCurrentJob )
1072 return;
1073
1074 const QgsDoubleRange zRange = mCurrentJob->zRange();
1075
1076 double yMinimum = 0;
1077 double yMaximum = 0;
1078
1079 if ( zRange.upper() < zRange.lower() )
1080 {
1081 // invalid range, e.g. no features found in plot!
1082 yMinimum = 0;
1083 yMaximum = 10;
1084 }
1085 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
1086 {
1087 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
1088 yMinimum = zRange.lower() - 5;
1089 yMaximum = zRange.lower() + 5;
1090 }
1091 else
1092 {
1093 // add 5% margin to height range
1094 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
1095 yMinimum = zRange.lower() - margin;
1096 yMaximum = zRange.upper() + margin;
1097 }
1098
1099 const double profileLength = profileCurve()->length();
1100 double xMinimum = 0;
1101 // just 2% margin to max distance -- any more is overkill and wasted space
1102 double xMaximum = profileLength * 1.02;
1103
1104 if ( mLockAxisScales )
1105 {
1106 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1107 }
1108
1109 mPlotItem->setXMinimum( xMinimum );
1110 mPlotItem->setXMaximum( xMaximum );
1111 mPlotItem->setYMinimum( yMinimum );
1112 mPlotItem->setYMaximum( yMaximum );
1113
1114 refineResults();
1115 mPlotItem->updatePlot();
1116 emit plotAreaChanged();
1117}
1118
1119void QgsElevationProfileCanvas::setVisiblePlotRange( double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation )
1120{
1121 if ( mLockAxisScales )
1122 {
1123 adjustRangeForAxisScaleLock( minimumDistance, maximumDistance, minimumElevation, maximumElevation );
1124 }
1125
1126 mPlotItem->setYMinimum( minimumElevation );
1127 mPlotItem->setYMaximum( maximumElevation );
1128 mPlotItem->setXMinimum( minimumDistance );
1129 mPlotItem->setXMaximum( maximumDistance );
1130 refineResults();
1131 mPlotItem->updatePlot();
1132 emit plotAreaChanged();
1133}
1134
1136{
1137 return QgsDoubleRange( mPlotItem->xMinimum(), mPlotItem->xMaximum() );
1138}
1139
1141{
1142 return QgsDoubleRange( mPlotItem->yMinimum(), mPlotItem->yMaximum() );
1143}
1144
1146{
1147 return *mPlotItem;
1148}
1149
1151class QgsElevationProfilePlot : public Qgs2DPlot
1152{
1153 public:
1154
1155 QgsElevationProfilePlot( QgsProfilePlotRenderer *renderer )
1156 : mRenderer( renderer )
1157 {
1158 }
1159
1160 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
1161 {
1162 if ( !mRenderer )
1163 return;
1164
1165 rc.painter()->translate( plotArea.left(), plotArea.top() );
1166 mRenderer->render( rc, plotArea.width(), plotArea.height(), xMinimum(), xMaximum(), yMinimum(), yMaximum() );
1167 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
1168 }
1169
1170 private:
1171
1172 QgsProfilePlotRenderer *mRenderer = nullptr;
1173};
1175
1176void QgsElevationProfileCanvas::render( QgsRenderContext &context, double width, double height, const Qgs2DPlot &plotSettings )
1177{
1178 if ( !mCurrentJob )
1179 return;
1180
1183
1184 QgsElevationProfilePlot profilePlot( mCurrentJob );
1185
1186 // 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...
1187 QDomDocument doc;
1188 QDomElement elem = doc.createElement( QStringLiteral( "plot" ) );
1189 QgsReadWriteContext rwContext;
1190 plotSettings.writeXml( elem, doc, rwContext );
1191 profilePlot.readXml( elem, rwContext );
1192
1193 profilePlot.setSize( QSizeF( width, height ) );
1194 profilePlot.render( context );
1195}
1196
1197QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( QPointF point )
1198{
1199 if ( !mCurrentJob )
1200 return {};
1201
1202 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
1203
1204 return mCurrentJob->identify( plotPoint, identifyContext() );
1205}
1206
1207QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( const QRectF &rect )
1208{
1209 if ( !mCurrentJob )
1210 return {};
1211
1212 const QgsProfilePoint topLeftPlotPoint = canvasPointToPlotPoint( rect.topLeft() );
1213 const QgsProfilePoint bottomRightPlotPoint = canvasPointToPlotPoint( rect.bottomRight() );
1214
1215 double distance1 = topLeftPlotPoint.distance();
1216 double distance2 = bottomRightPlotPoint.distance();
1217 if ( distance2 < distance1 )
1218 std::swap( distance1, distance2 );
1219
1220 double elevation1 = topLeftPlotPoint.elevation();
1221 double elevation2 = bottomRightPlotPoint.elevation();
1222 if ( elevation2 < elevation1 )
1223 std::swap( elevation1, elevation2 );
1224
1225 return mCurrentJob->identify( QgsDoubleRange( distance1, distance2 ), QgsDoubleRange( elevation1, elevation2 ), identifyContext() );
1226}
1227
1229{
1230 setProfileCurve( nullptr );
1231 mPlotItem->setRenderer( nullptr );
1232 mPlotItem->updatePlot();
1233}
1234
1236{
1237 mSnappingEnabled = enabled;
1238}
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 setLockAxisScales(bool lock)
Sets whether the distance and elevation scales are locked to each other.
void setProfileCurve(QgsCurve *curve)
Sets the profile curve.
void zoomToRect(const QRectF &rect) override
Zooms the plot to the specified rect in canvas units.
void activeJobCountChanged(int count)
Emitted when the number of active background jobs changes.
QgsElevationProfileCanvas(QWidget *parent=nullptr)
Constructor for QgsElevationProfileCanvas, with the specified parent widget.
void scalePlot(double factor) override
Scales the plot by a specified scale factor.
void paintEvent(QPaintEvent *event) override
QgsDoubleRange visibleDistanceRange() const
Returns the distance range currently visible in the plot.
void cancelJobs() override
Cancel any rendering job, in a blocking way.
QgsCoordinateReferenceSystem crs() const override
Returns the coordinate reference system (CRS) for map coordinates used by the canvas.
void clear()
Clears the current profile.
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.
bool lockAxisScales() const
Returns true if the distance and elevation scales are locked to each other.
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:1533
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
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:107
const QgsProjectElevationProperties * elevationProperties() const
Returns the project's elevation properties, which contains the project's elevation related settings.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:113
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.
void setDevicePixelRatio(float ratio)
Sets the device pixel ratio.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
A utility class for dynamic handling of changes to screen properties.
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:3903
const QgsCoordinateReferenceSystem & crs