QGIS API Documentation 3.41.0-Master (af5edcb665c)
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"
21#include "moc_qgselevationprofilecanvas.cpp"
23#include "qgsplotcanvasitem.h"
24#include "qgsprofilerequest.h"
26#include "qgscurve.h"
28#include "qgsterrainprovider.h"
30#include "qgsprofilerenderer.h"
31#include "qgspoint.h"
32#include "qgsgeos.h"
33#include "qgsplot.h"
34#include "qgsnumericformat.h"
36#include "qgsprofilesnapping.h"
38#include "qgsscreenhelper.h"
39#include "qgsfillsymbol.h"
40#include "qgslinesymbol.h"
42
43#include <QWheelEvent>
44#include <QTimer>
45#include <QPalette>
46
48class QgsElevationProfilePlotItem : public Qgs2DPlot, public QgsPlotCanvasItem
49{
50 public:
51 QgsElevationProfilePlotItem( QgsElevationProfileCanvas *canvas )
52 : QgsPlotCanvasItem( canvas )
53 {
54 setYMinimum( 0 );
55 setYMaximum( 100 );
56
58 }
59
60 void setRenderer( QgsProfilePlotRenderer *renderer )
61 {
62 mRenderer = renderer;
63 }
64
65 void updateRect()
66 {
67 mRect = mCanvas->rect();
68 setSize( mRect.size() );
69
70 prepareGeometryChange();
71 setPos( mRect.topLeft() );
72
73 mImage = QImage();
74 mCachedImages.clear();
75 mPlotArea = QRectF();
76 update();
77 }
78
79 void updatePlot()
80 {
81 mImage = QImage();
82 mCachedImages.clear();
83 mPlotArea = QRectF();
84 update();
85 }
86
87 bool redrawResults( const QString &sourceId )
88 {
89 auto it = mCachedImages.find( sourceId );
90 if ( it == mCachedImages.end() )
91 return false;
92
93 mCachedImages.erase( it );
94 mImage = QImage();
95 return true;
96 }
97
98 QRectF boundingRect() const override
99 {
100 return mRect;
101 }
102
103 QString distanceSuffix() const
104 {
105 switch ( mDistanceUnit )
106 {
155 return QStringLiteral( " %1" ).arg( QgsUnitTypes::toAbbreviatedString( mDistanceUnit ) );
156
158 return QObject::tr( "°" );
160 return QString();
161 }
163 }
164
165 void setXAxisUnits( Qgis::DistanceUnit unit )
166 {
167 mDistanceUnit = unit;
168 xAxis().setLabelSuffix( distanceSuffix() );
169 update();
170 }
171
172 QRectF plotArea()
173 {
174 if ( !mPlotArea.isNull() )
175 return mPlotArea;
176
177 // force immediate recalculation of plot area
178 QgsRenderContext context;
179 if ( !scene()->views().isEmpty() )
180 context.setScaleFactor( scene()->views().at( 0 )->logicalDpiX() / 25.4 );
181
183 mPlotArea = interiorPlotArea( context );
184 return mPlotArea;
185 }
186
187 QgsProfilePoint canvasPointToPlotPoint( QPointF point )
188 {
189 const QRectF area = plotArea();
190 if ( !area.contains( point.x(), point.y() ) )
191 return QgsProfilePoint();
192
193 const double distance = ( point.x() - area.left() ) / area.width() * ( xMaximum() - xMinimum() ) * mXScaleFactor + xMinimum() * mXScaleFactor;
194 const double elevation = ( area.bottom() - point.y() ) / area.height() * ( yMaximum() - yMinimum() ) + yMinimum();
195 return QgsProfilePoint( distance, elevation );
196 }
197
198 QgsPointXY plotPointToCanvasPoint( const QgsProfilePoint &point )
199 {
200 if ( point.distance() < xMinimum() * mXScaleFactor || point.distance() > xMaximum() * mXScaleFactor || point.elevation() < yMinimum() || point.elevation() > yMaximum() )
201 return QgsPointXY();
202
203 const QRectF area = plotArea();
204
205 const double x = ( point.distance() - xMinimum() * mXScaleFactor ) / ( ( xMaximum() - xMinimum() ) * mXScaleFactor ) * ( area.width() ) + area.left();
206 const double y = area.bottom() - ( point.elevation() - yMinimum() ) / ( yMaximum() - yMinimum() ) * ( area.height() );
207 return QgsPointXY( x, y );
208 }
209
210 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
211 {
212 mPlotArea = plotArea;
213
214 if ( !mRenderer )
215 return;
216
217 const double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
218
219 const QStringList sourceIds = mRenderer->sourceIds();
220 for ( const QString &source : sourceIds )
221 {
222 QImage plot;
223 auto it = mCachedImages.constFind( source );
224 if ( it != mCachedImages.constEnd() )
225 {
226 plot = it.value();
227 }
228 else
229 {
230 plot = mRenderer->renderToImage( plotArea.width() * pixelRatio, plotArea.height() * pixelRatio, xMinimum() * mXScaleFactor, xMaximum() * mXScaleFactor, yMinimum(), yMaximum(), source, pixelRatio );
231 plot.setDevicePixelRatio( pixelRatio );
232 mCachedImages.insert( source, plot );
233 }
234 rc.painter()->drawImage( QPointF( plotArea.left(), plotArea.top() ), plot );
235 }
236 }
237
238 void paint( QPainter *painter ) override
239 {
240 // cache rendering to an image, so we don't need to redraw the plot
241 if ( !mImage.isNull() )
242 {
243 painter->drawImage( QPointF( 0, 0 ), mImage );
244 }
245 else
246 {
247 const double pixelRatio = !scene()->views().empty() ? scene()->views().at( 0 )->devicePixelRatioF() : 1;
248 mImage = QImage( mRect.width() * pixelRatio, mRect.height() * pixelRatio, QImage::Format_ARGB32_Premultiplied );
249 mImage.setDevicePixelRatio( pixelRatio );
250 mImage.fill( Qt::transparent );
251
252 QPainter imagePainter( &mImage );
253 imagePainter.setRenderHint( QPainter::Antialiasing, true );
255 rc.setDevicePixelRatio( pixelRatio );
256
257 const double mapUnitsPerPixel = ( xMaximum() - xMinimum() ) * mXScaleFactor / plotArea().width();
258 rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
259
262
264 render( rc );
265 imagePainter.end();
266
267 painter->drawImage( QPointF( 0, 0 ), mImage );
268 }
269 }
270
271 QgsProject *mProject = nullptr;
272 double mXScaleFactor = 1.0;
273
275
276 private:
277 QImage mImage;
278
279 QMap<QString, QImage> mCachedImages;
280
281 QRectF mRect;
282 QRectF mPlotArea;
283 QgsProfilePlotRenderer *mRenderer = nullptr;
284};
285
286class QgsElevationProfileCrossHairsItem : public QgsPlotCanvasItem
287{
288 public:
289 QgsElevationProfileCrossHairsItem( QgsElevationProfileCanvas *canvas, QgsElevationProfilePlotItem *plotItem )
290 : QgsPlotCanvasItem( canvas )
291 , mPlotItem( plotItem )
292 {
293 }
294
295 void updateRect()
296 {
297 mRect = mCanvas->rect();
298
299 prepareGeometryChange();
300 setPos( mRect.topLeft() );
301 update();
302 }
303
304 void setPoint( const QgsProfilePoint &point )
305 {
306 mPoint = point;
307 update();
308 }
309
310 QRectF boundingRect() const override
311 {
312 return mRect;
313 }
314
315 void paint( QPainter *painter ) override
316 {
317 const QgsPointXY crossHairPlotPoint = mPlotItem->plotPointToCanvasPoint( mPoint );
318 if ( crossHairPlotPoint.isEmpty() )
319 return;
320
321 painter->save();
322 painter->setBrush( Qt::NoBrush );
323 QPen crossHairPen;
324 crossHairPen.setCosmetic( true );
325 crossHairPen.setWidthF( 1 );
326 crossHairPen.setStyle( Qt::DashLine );
327 crossHairPen.setCapStyle( Qt::FlatCap );
328 const QPalette scenePalette = mPlotItem->scene()->palette();
329 QColor penColor = scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text );
330 penColor.setAlpha( 150 );
331 crossHairPen.setColor( penColor );
332 painter->setPen( crossHairPen );
333 painter->drawLine( QPointF( mPlotItem->plotArea().left(), crossHairPlotPoint.y() ), QPointF( mPlotItem->plotArea().right(), crossHairPlotPoint.y() ) );
334 painter->drawLine( QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().top() ), QPointF( crossHairPlotPoint.x(), mPlotItem->plotArea().bottom() ) );
335
336 // also render current point text
337 QgsNumericFormatContext numericContext;
338
339 const QString xCoordinateText = mPlotItem->xAxis().numericFormat()->formatDouble( mPoint.distance() / mPlotItem->mXScaleFactor, numericContext )
340 + mPlotItem->distanceSuffix();
341
342 const QString yCoordinateText = mPlotItem->yAxis().numericFormat()->formatDouble( mPoint.elevation(), numericContext );
343
344 QFont font;
345 const QFontMetrics fm( font );
346 const double height = fm.capHeight();
347 const double xWidth = fm.horizontalAdvance( xCoordinateText );
348 const double yWidth = fm.horizontalAdvance( yCoordinateText );
349 const double textAxisMargin = fm.horizontalAdvance( ' ' );
350
351 QPointF xCoordOrigin;
352 QPointF yCoordOrigin;
353
354 if ( mPoint.distance() < ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5 * mPlotItem->mXScaleFactor )
355 {
356 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
357 {
358 // render x coordinate on right top (left top align)
359 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
360 // render y coordinate on right top (right bottom align)
361 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
362 }
363 else
364 {
365 // render x coordinate on right bottom (left bottom align)
366 xCoordOrigin = QPointF( crossHairPlotPoint.x() + textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
367 // render y coordinate on right bottom (right top align)
368 yCoordOrigin = QPointF( mPlotItem->plotArea().right() - yWidth - textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
369 }
370 }
371 else
372 {
373 if ( mPoint.elevation() < ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5 )
374 {
375 // render x coordinate on left top (right top align)
376 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().top() + height + textAxisMargin );
377 // render y coordinate on left top (left bottom align)
378 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() - textAxisMargin );
379 }
380 else
381 {
382 // render x coordinate on left bottom (right bottom align)
383 xCoordOrigin = QPointF( crossHairPlotPoint.x() - xWidth - textAxisMargin, mPlotItem->plotArea().bottom() - textAxisMargin );
384 // render y coordinate on left bottom (left top align)
385 yCoordOrigin = QPointF( mPlotItem->plotArea().left() + textAxisMargin, crossHairPlotPoint.y() + height + textAxisMargin );
386 }
387 }
388
389 // semi opaque background color brush
390 QColor backgroundColor = mPlotItem->chartBackgroundSymbol()->color();
391 backgroundColor.setAlpha( 220 );
392 painter->setBrush( QBrush( backgroundColor ) );
393 painter->setPen( Qt::NoPen );
394 painter->drawRect( QRectF( xCoordOrigin.x() - textAxisMargin + 1, xCoordOrigin.y() - textAxisMargin - height + 1, xWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
395 painter->drawRect( QRectF( yCoordOrigin.x() - textAxisMargin + 1, yCoordOrigin.y() - textAxisMargin - height + 1, yWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
396
397 painter->setBrush( Qt::NoBrush );
398 painter->setPen( scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
399
400 painter->drawText( xCoordOrigin, xCoordinateText );
401 painter->drawText( yCoordOrigin, yCoordinateText );
402 painter->restore();
403 }
404
405 private:
406 QRectF mRect;
407 QgsProfilePoint mPoint;
408 QgsElevationProfilePlotItem *mPlotItem = nullptr;
409};
411
412
414 : QgsPlotCanvas( parent )
415{
416 mScreenHelper = new QgsScreenHelper( this );
417
418 mPlotItem = new QgsElevationProfilePlotItem( this );
419
420 // follow system color scheme by default
421 setBackgroundColor( QColor() );
422
423 mCrossHairsItem = new QgsElevationProfileCrossHairsItem( this, mPlotItem );
424 mCrossHairsItem->setZValue( 100 );
425 mCrossHairsItem->hide();
426
427 // updating the profile plot is deferred on a timer, so that we don't trigger it too often
428 mDeferredRegenerationTimer = new QTimer( this );
429 mDeferredRegenerationTimer->setSingleShot( true );
430 mDeferredRegenerationTimer->stop();
431 connect( mDeferredRegenerationTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRegeneration );
432
433 mDeferredRedrawTimer = new QTimer( this );
434 mDeferredRedrawTimer->setSingleShot( true );
435 mDeferredRedrawTimer->stop();
436 connect( mDeferredRedrawTimer, &QTimer::timeout, this, &QgsElevationProfileCanvas::startDeferredRedraw );
437}
438
440{
441 if ( mCurrentJob )
442 {
443 mPlotItem->setRenderer( nullptr );
444 mCurrentJob->deleteLater();
445 mCurrentJob = nullptr;
446 }
447}
448
450{
451 if ( mCurrentJob )
452 {
453 mPlotItem->setRenderer( nullptr );
454 disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
455 mCurrentJob->cancelGeneration();
456 mCurrentJob->deleteLater();
457 mCurrentJob = nullptr;
458 }
459}
460
462{
463 const double dxPercent = dx / mPlotItem->plotArea().width();
464 const double dyPercent = dy / mPlotItem->plotArea().height();
465
466 // these look backwards, but we are dragging the paper, not the view!
467 const double dxPlot = -dxPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
468 const double dyPlot = dyPercent * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
469
470 // no need to handle axis scale lock here, we aren't changing scales
471 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
472 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
473 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
474 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
475
476 refineResults();
477
478 mPlotItem->updatePlot();
479 emit plotAreaChanged();
480}
481
483{
484 if ( !mPlotItem->plotArea().contains( x, y ) )
485 return;
486
487 const double newCenterX = mPlotItem->xMinimum() + ( x - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() );
488 const double newCenterY = mPlotItem->yMinimum() + ( mPlotItem->plotArea().bottom() - y ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
489
490 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
491 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
492
493 // no need to handle axis scale lock here, we aren't changing scales
494 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
495 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
496 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
497 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
498
499 refineResults();
500
501 mPlotItem->updatePlot();
502 emit plotAreaChanged();
503}
504
506{
507 scalePlot( factor, factor );
508 emit plotAreaChanged();
509}
510
511QgsProfileSnapContext QgsElevationProfileCanvas::snapContext() const
512{
513 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
514 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
515 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
516
517 QgsProfileSnapContext context;
518 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
519 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
520 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
521 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
522 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
523 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) );
524
525 return context;
526}
527
528QgsProfileIdentifyContext QgsElevationProfileCanvas::identifyContext() const
529{
530 const double toleranceInPixels = QFontMetrics( font() ).horizontalAdvance( ' ' );
531 const double xToleranceInPlotUnits = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) * toleranceInPixels;
532 const double yToleranceInPlotUnits = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * toleranceInPixels;
533
535 context.maximumSurfaceDistanceDelta = 2 * xToleranceInPlotUnits;
536 context.maximumSurfaceElevationDelta = 10 * yToleranceInPlotUnits;
537 context.maximumPointDistanceDelta = 4 * xToleranceInPlotUnits;
538 context.maximumPointElevationDelta = 4 * yToleranceInPlotUnits;
539 context.displayRatioElevationVsDistance = ( ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) )
540 / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor / ( mPlotItem->plotArea().width() ) );
541
542 context.project = mProject;
543
544 return context;
545}
546
547void QgsElevationProfileCanvas::setupLayerConnections( QgsMapLayer *layer, bool isDisconnect )
548{
549 if ( isDisconnect )
550 {
551 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
552 disconnect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
553 disconnect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
554 }
555 else
556 {
557 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileGenerationPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged );
558 connect( layer->elevationProperties(), &QgsMapLayerElevationProperties::profileRenderingPropertyChanged, this, &QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged );
559 connect( layer, &QgsMapLayer::dataChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
560 }
561
562 switch ( layer->type() )
563 {
565 {
566 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
567 if ( isDisconnect )
568 {
569 disconnect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
570 disconnect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
571 disconnect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
572 disconnect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
573 }
574 else
575 {
576 connect( vl, &QgsVectorLayer::featureAdded, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
577 connect( vl, &QgsVectorLayer::featureDeleted, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
578 connect( vl, &QgsVectorLayer::geometryChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
579 connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsElevationProfileCanvas::regenerateResultsForLayer );
580 }
581 break;
582 }
591 break;
592 }
593}
594
595void QgsElevationProfileCanvas::adjustRangeForAxisScaleLock( double &xMinimum, double &xMaximum, double &yMinimum, double &yMaximum ) const
596{
597 // ensures that we always "zoom out" to match horizontal/vertical scales
598 const double horizontalScale = ( xMaximum - xMinimum ) / mPlotItem->plotArea().width();
599 const double verticalScale = ( yMaximum - yMinimum ) / mPlotItem->plotArea().height();
600 if ( horizontalScale > verticalScale )
601 {
602 const double height = horizontalScale * mPlotItem->plotArea().height();
603 const double deltaHeight = ( yMaximum - yMinimum ) - height;
604 yMinimum += deltaHeight / 2;
605 yMaximum -= deltaHeight / 2;
606 }
607 else
608 {
609 const double width = verticalScale * mPlotItem->plotArea().width();
610 const double deltaWidth = ( ( xMaximum - xMinimum ) - width );
611 xMinimum += deltaWidth / 2;
612 xMaximum -= deltaWidth / 2;
613 }
614}
615
617{
618 return mDistanceUnit;
619}
620
622{
623 mDistanceUnit = unit;
624 const double oldMin = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
625 const double oldMax = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
626 mPlotItem->mXScaleFactor = QgsUnitTypes::fromUnitToUnitFactor( mDistanceUnit, mCrs.mapUnits() );
627 mPlotItem->setXAxisUnits( mDistanceUnit );
628 mPlotItem->setXMinimum( oldMin / mPlotItem->mXScaleFactor );
629 mPlotItem->setXMaximum( oldMax / mPlotItem->mXScaleFactor );
630 mPlotItem->updatePlot();
631}
632
634{
635 if ( !color.isValid() )
636 {
637 QPalette customPalette = qApp->palette();
638 const QColor baseColor = qApp->palette().color( QPalette::ColorRole::Base );
639 const QColor windowColor = qApp->palette().color( QPalette::ColorRole::Window );
640 customPalette.setColor( QPalette::ColorRole::Base, windowColor );
641 customPalette.setColor( QPalette::ColorRole::Window, baseColor );
642 setPalette( customPalette );
643 scene()->setPalette( customPalette );
644 }
645 else
646 {
647 // build custom palette
648 const bool isDarkTheme = color.lightnessF() < 0.5;
649 QPalette customPalette = qApp->palette();
650 customPalette.setColor( QPalette::ColorRole::Window, color );
651 if ( isDarkTheme )
652 {
653 customPalette.setColor( QPalette::ColorRole::Text, QColor( 255, 255, 255 ) );
654 customPalette.setColor( QPalette::ColorRole::Base, color.lighter( 120 ) );
655 }
656 else
657 {
658 customPalette.setColor( QPalette::ColorRole::Text, QColor( 0, 0, 0 ) );
659 customPalette.setColor( QPalette::ColorRole::Base, color.darker( 120 ) );
660 }
661
662 setPalette( customPalette );
663 scene()->setPalette( customPalette );
664 }
665
666 updateChartFromPalette();
667}
668
670{
671 return mLockAxisScales;
672}
673
675{
676 mLockAxisScales = lock;
677 if ( mLockAxisScales )
678 {
679 double xMinimum = mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
680 double xMaximum = mPlotItem->xMaximum() * mPlotItem->mXScaleFactor;
681 double yMinimum = mPlotItem->yMinimum();
682 double yMaximum = mPlotItem->yMaximum();
683 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
684 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
685 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
686 mPlotItem->setYMinimum( yMinimum );
687 mPlotItem->setYMaximum( yMaximum );
688
689 refineResults();
690 mPlotItem->updatePlot();
691 emit plotAreaChanged();
692 }
693}
694
696{
697 if ( !mCurrentJob || !mSnappingEnabled )
698 return QgsPointXY();
699
700 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
701
702 const QgsProfileSnapResult snappedPoint = mCurrentJob->snapPoint( plotPoint, snapContext() );
703 if ( !snappedPoint.isValid() )
704 return QgsPointXY();
705
706 return plotPointToCanvasPoint( snappedPoint.snappedPoint );
707}
708
709void QgsElevationProfileCanvas::scalePlot( double xFactor, double yFactor )
710{
711 if ( mLockAxisScales )
712 yFactor = xFactor;
713
714 const double currentWidth = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor;
715 const double currentHeight = mPlotItem->yMaximum() - mPlotItem->yMinimum();
716
717 const double newWidth = currentWidth / xFactor;
718 const double newHeight = currentHeight / yFactor;
719
720 const double currentCenterX = ( mPlotItem->xMinimum() + mPlotItem->xMaximum() ) * 0.5 * mPlotItem->mXScaleFactor;
721 const double currentCenterY = ( mPlotItem->yMinimum() + mPlotItem->yMaximum() ) * 0.5;
722
723 double xMinimum = currentCenterX - newWidth * 0.5;
724 double xMaximum = currentCenterX + newWidth * 0.5;
725 double yMinimum = currentCenterY - newHeight * 0.5;
726 double yMaximum = currentCenterY + newHeight * 0.5;
727 if ( mLockAxisScales )
728 {
729 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
730 }
731
732 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
733 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
734 mPlotItem->setYMinimum( yMinimum );
735 mPlotItem->setYMaximum( yMaximum );
736
737 refineResults();
738 mPlotItem->updatePlot();
739 emit plotAreaChanged();
740}
741
743{
744 const QRectF intersected = rect.intersected( mPlotItem->plotArea() );
745
746 double minX = ( intersected.left() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
747 double maxX = ( intersected.right() - mPlotItem->plotArea().left() ) / mPlotItem->plotArea().width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
748 double minY = ( mPlotItem->plotArea().bottom() - intersected.bottom() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
749 double maxY = ( mPlotItem->plotArea().bottom() - intersected.top() ) / mPlotItem->plotArea().height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
750
751 if ( mLockAxisScales )
752 {
753 adjustRangeForAxisScaleLock( minX, maxX, minY, maxY );
754 }
755
756 mPlotItem->setXMinimum( minX / mPlotItem->mXScaleFactor );
757 mPlotItem->setXMaximum( maxX / mPlotItem->mXScaleFactor );
758 mPlotItem->setYMinimum( minY );
759 mPlotItem->setYMaximum( maxY );
760
761 refineResults();
762 mPlotItem->updatePlot();
763 emit plotAreaChanged();
764}
765
766void QgsElevationProfileCanvas::wheelZoom( QWheelEvent *event )
767{
768 //get mouse wheel zoom behavior settings
769 QgsSettings settings;
770 double zoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
771 bool reverseZoom = settings.value( QStringLiteral( "qgis/reverse_wheel_zoom" ), false ).toBool();
772 bool zoomIn = reverseZoom ? event->angleDelta().y() < 0 : event->angleDelta().y() > 0;
773
774 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
775 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
776
777 if ( event->modifiers() & Qt::ControlModifier )
778 {
779 //holding ctrl while wheel zooming results in a finer zoom
780 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
781 }
782
783 //calculate zoom scale factor
784 double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
785
786 QRectF viewportRect = mPlotItem->plotArea();
787
788 if ( viewportRect.contains( event->position() ) )
789 {
790 //adjust view center
791 const double oldCenterX = 0.5 * ( mPlotItem->xMaximum() + mPlotItem->xMinimum() );
792 const double oldCenterY = 0.5 * ( mPlotItem->yMaximum() + mPlotItem->yMinimum() );
793
794 const double eventPosX = ( event->position().x() - viewportRect.left() ) / viewportRect.width() * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) + mPlotItem->xMinimum();
795 const double eventPosY = ( viewportRect.bottom() - event->position().y() ) / viewportRect.height() * ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) + mPlotItem->yMinimum();
796
797 const double newCenterX = eventPosX + ( ( oldCenterX - eventPosX ) * scaleFactor );
798 const double newCenterY = eventPosY + ( ( oldCenterY - eventPosY ) * scaleFactor );
799
800 const double dxPlot = newCenterX - ( mPlotItem->xMaximum() + mPlotItem->xMinimum() ) * 0.5;
801 const double dyPlot = newCenterY - ( mPlotItem->yMaximum() + mPlotItem->yMinimum() ) * 0.5;
802
803 // don't need to handle axis scale lock here, we are always changing axis by the same scale
804 mPlotItem->setXMinimum( mPlotItem->xMinimum() + dxPlot );
805 mPlotItem->setXMaximum( mPlotItem->xMaximum() + dxPlot );
806 mPlotItem->setYMinimum( mPlotItem->yMinimum() + dyPlot );
807 mPlotItem->setYMaximum( mPlotItem->yMaximum() + dyPlot );
808 }
809
810 //zoom plot
811 if ( zoomIn )
812 {
813 scalePlot( zoomFactor );
814 }
815 else
816 {
817 scalePlot( 1 / zoomFactor );
818 }
819 emit plotAreaChanged();
820}
821
823{
825 if ( e->isAccepted() )
826 {
827 mCrossHairsItem->hide();
828 return;
829 }
830
831 QgsProfilePoint plotPoint = canvasPointToPlotPoint( e->pos() );
832 if ( mCurrentJob && mSnappingEnabled && !plotPoint.isEmpty() )
833 {
834 const QgsProfileSnapResult snapResult = mCurrentJob->snapPoint( plotPoint, snapContext() );
835 if ( snapResult.isValid() )
836 plotPoint = snapResult.snappedPoint;
837 }
838
839 if ( plotPoint.isEmpty() )
840 {
841 mCrossHairsItem->hide();
842 }
843 else
844 {
845 mCrossHairsItem->setPoint( plotPoint );
846 mCrossHairsItem->show();
847 }
848 emit canvasPointHovered( e->pos(), plotPoint );
849}
850
852{
853 return mPlotItem->plotArea();
854}
855
857{
858 if ( !mProject || !profileCurve() )
859 return;
860
861 cancelJobs();
862
863 QgsProfileRequest request( profileCurve()->clone() );
864 request.setCrs( mCrs );
865 request.setTolerance( mTolerance );
866 request.setTransformContext( mProject->transformContext() );
867 request.setTerrainProvider( mProject->elevationProperties()->terrainProvider() ? mProject->elevationProperties()->terrainProvider()->clone() : nullptr );
868 QgsExpressionContext context;
871 request.setExpressionContext( context );
872
873 const QList<QgsMapLayer *> layersToGenerate = layers();
874 QList<QgsAbstractProfileSource *> sources;
875 const QList<QgsAbstractProfileSource *> registrySources = QgsApplication::profileSourceRegistry()->profileSources();
876 sources.reserve( layersToGenerate.size() + registrySources.size() );
877
878 sources << registrySources;
879 for ( QgsMapLayer *layer : layersToGenerate )
880 {
881 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
882 sources.append( source );
883 }
884
885 mCurrentJob = new QgsProfilePlotRenderer( sources, request );
886 connect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished );
887
888 QgsProfileGenerationContext generationContext;
889 generationContext.setDpi( mScreenHelper->screenDpi() );
890 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( mProfileCurve->length() ) / mPlotItem->plotArea().width() );
891 generationContext.setMapUnitsPerDistancePixel( mProfileCurve->length() / mPlotItem->plotArea().width() );
892 mCurrentJob->setContext( generationContext );
893
894 mCurrentJob->startGeneration();
895 mPlotItem->setRenderer( mCurrentJob );
896
897 emit activeJobCountChanged( 1 );
898}
899
901{
902 mZoomFullWhenJobFinished = true;
903}
904
905void QgsElevationProfileCanvas::generationFinished()
906{
907 if ( !mCurrentJob )
908 return;
909
910 emit activeJobCountChanged( 0 );
911
912 if ( mZoomFullWhenJobFinished )
913 {
914 // we only zoom full for the initial generation
915 mZoomFullWhenJobFinished = false;
916 zoomFull();
917 }
918 else
919 {
920 // here we should invalidate cached results only for the layers which have been refined
921
922 // and if no layers are being refeined, don't invalidate anything
923
924 mPlotItem->updatePlot();
925 }
926
927 if ( mForceRegenerationAfterCurrentJobCompletes )
928 {
929 mForceRegenerationAfterCurrentJobCompletes = false;
930 mCurrentJob->invalidateAllRefinableSources();
931 scheduleDeferredRegeneration();
932 }
933}
934
935void QgsElevationProfileCanvas::onLayerProfileGenerationPropertyChanged()
936{
937 // TODO -- handle nicely when existing job is in progress
938 if ( !mCurrentJob || mCurrentJob->isActive() )
939 return;
940
941 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
942 if ( !properties )
943 return;
944
945 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
946 {
947 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
948 {
949 if ( mCurrentJob->invalidateResults( source ) )
950 scheduleDeferredRegeneration();
951 }
952 }
953}
954
955void QgsElevationProfileCanvas::onLayerProfileRendererPropertyChanged()
956{
957 // TODO -- handle nicely when existing job is in progress
958 if ( !mCurrentJob || mCurrentJob->isActive() )
959 return;
960
961 QgsMapLayerElevationProperties *properties = qobject_cast<QgsMapLayerElevationProperties *>( sender() );
962 if ( !properties )
963 return;
964
965 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( properties->parent() ) )
966 {
967 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
968 {
969 mCurrentJob->replaceSource( source );
970 }
971 if ( mPlotItem->redrawResults( layer->id() ) )
972 scheduleDeferredRedraw();
973 }
974}
975
976void QgsElevationProfileCanvas::regenerateResultsForLayer()
977{
978 if ( !mCurrentJob )
979 return;
980
981 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() ) )
982 {
983 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
984 {
985 if ( mCurrentJob->invalidateResults( source ) )
986 scheduleDeferredRegeneration();
987 }
988 }
989}
990
991void QgsElevationProfileCanvas::scheduleDeferredRegeneration()
992{
993 if ( !mDeferredRegenerationScheduled )
994 {
995 mDeferredRegenerationTimer->start( 1 );
996 mDeferredRegenerationScheduled = true;
997 }
998}
999
1000void QgsElevationProfileCanvas::scheduleDeferredRedraw()
1001{
1002 if ( !mDeferredRedrawScheduled )
1003 {
1004 mDeferredRedrawTimer->start( 1 );
1005 mDeferredRedrawScheduled = true;
1006 }
1007}
1008
1009void QgsElevationProfileCanvas::startDeferredRegeneration()
1010{
1011 if ( mCurrentJob && !mCurrentJob->isActive() )
1012 {
1013 emit activeJobCountChanged( 1 );
1014 mCurrentJob->regenerateInvalidatedResults();
1015 }
1016 else if ( mCurrentJob )
1017 {
1018 mForceRegenerationAfterCurrentJobCompletes = true;
1019 }
1020
1021 mDeferredRegenerationScheduled = false;
1022}
1023
1024void QgsElevationProfileCanvas::startDeferredRedraw()
1025{
1026 mPlotItem->update();
1027 mDeferredRedrawScheduled = false;
1028}
1029
1030void QgsElevationProfileCanvas::refineResults()
1031{
1032 if ( mCurrentJob )
1033 {
1035 context.setDpi( mScreenHelper->screenDpi() );
1036 const double plotDistanceRange = ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor;
1037 const double plotElevationRange = mPlotItem->yMaximum() - mPlotItem->yMinimum();
1038 const double plotDistanceUnitsPerPixel = plotDistanceRange / mPlotItem->plotArea().width();
1039
1040 // we round the actual desired map error down to just one significant figure, to avoid tiny differences
1041 // as the plot is panned
1042 const double targetMaxErrorInMapUnits = MAX_ERROR_PIXELS * plotDistanceUnitsPerPixel;
1043 const double factor = std::pow( 10.0, 1 - std::ceil( std::log10( std::fabs( targetMaxErrorInMapUnits ) ) ) );
1044 const double roundedErrorInMapUnits = std::floor( targetMaxErrorInMapUnits * factor ) / factor;
1045 context.setMaximumErrorMapUnits( roundedErrorInMapUnits );
1046
1047 context.setMapUnitsPerDistancePixel( plotDistanceUnitsPerPixel );
1048
1049 // for similar reasons we round the minimum distance off to multiples of the maximum error in map units
1050 const double distanceMin = std::floor( ( mPlotItem->xMinimum() * mPlotItem->mXScaleFactor - plotDistanceRange * 0.05 ) / context.maximumErrorMapUnits() ) * context.maximumErrorMapUnits();
1051 context.setDistanceRange( QgsDoubleRange( std::max( 0.0, distanceMin ), mPlotItem->xMaximum() * mPlotItem->mXScaleFactor + plotDistanceRange * 0.05 ) );
1052
1053 context.setElevationRange( QgsDoubleRange( mPlotItem->yMinimum() - plotElevationRange * 0.05, mPlotItem->yMaximum() + plotElevationRange * 0.05 ) );
1054 mCurrentJob->setContext( context );
1055 }
1056 scheduleDeferredRegeneration();
1057}
1058
1059void QgsElevationProfileCanvas::updateChartFromPalette()
1060{
1061 const QPalette chartPalette = palette();
1062 setBackgroundBrush( QBrush( chartPalette.color( QPalette::ColorRole::Base ) ) );
1063 {
1064 QgsTextFormat textFormat = mPlotItem->xAxis().textFormat();
1065 textFormat.setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
1066 mPlotItem->xAxis().setTextFormat( textFormat );
1067 mPlotItem->yAxis().setTextFormat( textFormat );
1068 }
1069 {
1070 std::unique_ptr<QgsFillSymbol> chartFill( mPlotItem->chartBackgroundSymbol()->clone() );
1071 chartFill->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Window ) );
1072 mPlotItem->setChartBackgroundSymbol( chartFill.release() );
1073 }
1074 {
1075 std::unique_ptr<QgsFillSymbol> chartBorder( mPlotItem->chartBorderSymbol()->clone() );
1076 chartBorder->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text ) );
1077 mPlotItem->setChartBorderSymbol( chartBorder.release() );
1078 }
1079 {
1080 std::unique_ptr<QgsLineSymbol> chartMajorSymbol( mPlotItem->xAxis().gridMajorSymbol()->clone() );
1081 QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
1082 c.setAlpha( 150 );
1083 chartMajorSymbol->setColor( c );
1084 mPlotItem->xAxis().setGridMajorSymbol( chartMajorSymbol->clone() );
1085 mPlotItem->yAxis().setGridMajorSymbol( chartMajorSymbol.release() );
1086 }
1087 {
1088 std::unique_ptr<QgsLineSymbol> chartMinorSymbol( mPlotItem->xAxis().gridMinorSymbol()->clone() );
1089 QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
1090 c.setAlpha( 50 );
1091 chartMinorSymbol->setColor( c );
1092 mPlotItem->xAxis().setGridMinorSymbol( chartMinorSymbol->clone() );
1093 mPlotItem->yAxis().setGridMinorSymbol( chartMinorSymbol.release() );
1094 }
1095 mPlotItem->updatePlot();
1096}
1097
1099{
1100 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1101 return QgsProfilePoint();
1102
1103 return mPlotItem->canvasPointToPlotPoint( point );
1104}
1105
1107{
1108 return mPlotItem->plotPointToCanvasPoint( point );
1109}
1110
1112{
1113 mProject = project;
1114 mPlotItem->mProject = project;
1115}
1116
1121
1123{
1124 mProfileCurve.reset( curve );
1125}
1126
1128{
1129 return mProfileCurve.get();
1130}
1131
1133{
1134 mTolerance = tolerance;
1135}
1136
1141
1142void QgsElevationProfileCanvas::setLayers( const QList<QgsMapLayer *> &layers )
1143{
1144 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
1145 {
1146 setupLayerConnections( layer, true );
1147 }
1148
1149 // filter list, removing null layers and invalid layers
1150 auto filteredList = layers;
1151 filteredList.erase( std::remove_if( filteredList.begin(), filteredList.end(), []( QgsMapLayer *layer ) {
1152 return !layer || !layer->isValid();
1153 } ),
1154 filteredList.end() );
1155
1156 mLayers = _qgis_listRawToQPointer( filteredList );
1157 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
1158 {
1159 setupLayerConnections( layer, false );
1160 }
1161}
1162
1163QList<QgsMapLayer *> QgsElevationProfileCanvas::layers() const
1164{
1165 return _qgis_listQPointerToRaw( mLayers );
1166}
1167
1169{
1171
1172 if ( mLockAxisScales )
1173 {
1174 double xMinimum = mPlotItem->xMinimum();
1175 double xMaximum = mPlotItem->xMaximum();
1176 double yMinimum = mPlotItem->yMinimum();
1177 double yMaximum = mPlotItem->yMaximum();
1178 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1179 mPlotItem->setXMinimum( xMinimum );
1180 mPlotItem->setXMaximum( xMaximum );
1181 mPlotItem->setYMinimum( yMinimum );
1182 mPlotItem->setYMaximum( yMaximum );
1183 }
1184
1185 mPlotItem->updateRect();
1186 mCrossHairsItem->updateRect();
1187}
1188
1190{
1191 QgsPlotCanvas::paintEvent( event );
1192
1193 if ( !mFirstDrawOccurred )
1194 {
1195 // 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).
1196 mFirstDrawOccurred = true;
1197 mPlotItem->updateRect();
1198 mCrossHairsItem->updateRect();
1199 }
1200}
1201
1203{
1204 if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )
1205 return QgsPoint();
1206
1207 if ( !mProfileCurve )
1208 return QgsPoint();
1209
1210 const double dx = point.x() - mPlotItem->plotArea().left();
1211
1212 const double distanceAlongPlotPercent = dx / mPlotItem->plotArea().width();
1213 double distanceAlongCurveLength = distanceAlongPlotPercent * ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor + mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
1214
1215 std::unique_ptr<QgsPoint> mapXyPoint( mProfileCurve->interpolatePoint( distanceAlongCurveLength ) );
1216 if ( !mapXyPoint )
1217 return QgsPoint();
1218
1219 const double mapZ = ( mPlotItem->yMaximum() - mPlotItem->yMinimum() ) / ( mPlotItem->plotArea().height() ) * ( mPlotItem->plotArea().bottom() - point.y() ) + mPlotItem->yMinimum();
1220
1221 return QgsPoint( mapXyPoint->x(), mapXyPoint->y(), mapZ );
1222}
1223
1225{
1226 if ( !mProfileCurve )
1227 return QgsPointXY();
1228
1229 QgsGeos geos( mProfileCurve.get() );
1230 QString error;
1231 const double distanceAlongCurve = geos.lineLocatePoint( point, &error );
1232
1233 const double distanceAlongCurveOnPlot = distanceAlongCurve - mPlotItem->xMinimum() * mPlotItem->mXScaleFactor;
1234 const double distanceAlongCurvePercent = distanceAlongCurveOnPlot / ( ( mPlotItem->xMaximum() - mPlotItem->xMinimum() ) * mPlotItem->mXScaleFactor );
1235 const double distanceAlongPlotRect = distanceAlongCurvePercent * mPlotItem->plotArea().width();
1236
1237 const double canvasX = mPlotItem->plotArea().left() + distanceAlongPlotRect;
1238
1239 double canvasY = 0;
1240 if ( std::isnan( point.z() ) || point.z() < mPlotItem->yMinimum() )
1241 {
1242 canvasY = mPlotItem->plotArea().top();
1243 }
1244 else if ( point.z() > mPlotItem->yMaximum() )
1245 {
1246 canvasY = mPlotItem->plotArea().bottom();
1247 }
1248 else
1249 {
1250 const double yPercent = ( point.z() - mPlotItem->yMinimum() ) / ( mPlotItem->yMaximum() - mPlotItem->yMinimum() );
1251 canvasY = mPlotItem->plotArea().bottom() - mPlotItem->plotArea().height() * yPercent;
1252 }
1253
1254 return QgsPointXY( canvasX, canvasY );
1255}
1256
1258{
1259 if ( !mCurrentJob )
1260 return;
1261
1262 const QgsDoubleRange zRange = mCurrentJob->zRange();
1263
1264 double yMinimum = 0;
1265 double yMaximum = 0;
1266
1267 if ( zRange.upper() < zRange.lower() )
1268 {
1269 // invalid range, e.g. no features found in plot!
1270 yMinimum = 0;
1271 yMaximum = 10;
1272 }
1273 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
1274 {
1275 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
1276 yMinimum = zRange.lower() - 5;
1277 yMaximum = zRange.lower() + 5;
1278 }
1279 else
1280 {
1281 // add 5% margin to height range
1282 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
1283 yMinimum = zRange.lower() - margin;
1284 yMaximum = zRange.upper() + margin;
1285 }
1286
1287 const double profileLength = profileCurve()->length();
1288 double xMinimum = 0;
1289 // just 2% margin to max distance -- any more is overkill and wasted space
1290 double xMaximum = profileLength * 1.02;
1291
1292 if ( mLockAxisScales )
1293 {
1294 adjustRangeForAxisScaleLock( xMinimum, xMaximum, yMinimum, yMaximum );
1295 }
1296
1297 mPlotItem->setXMinimum( xMinimum / mPlotItem->mXScaleFactor );
1298 mPlotItem->setXMaximum( xMaximum / mPlotItem->mXScaleFactor );
1299 mPlotItem->setYMinimum( yMinimum );
1300 mPlotItem->setYMaximum( yMaximum );
1301
1302 refineResults();
1303 mPlotItem->updatePlot();
1304 emit plotAreaChanged();
1305}
1306
1307void QgsElevationProfileCanvas::setVisiblePlotRange( double minimumDistance, double maximumDistance, double minimumElevation, double maximumElevation )
1308{
1309 if ( mLockAxisScales )
1310 {
1311 adjustRangeForAxisScaleLock( minimumDistance, maximumDistance, minimumElevation, maximumElevation );
1312 }
1313
1314 mPlotItem->setYMinimum( minimumElevation );
1315 mPlotItem->setYMaximum( maximumElevation );
1316 mPlotItem->setXMinimum( minimumDistance / mPlotItem->mXScaleFactor );
1317 mPlotItem->setXMaximum( maximumDistance / mPlotItem->mXScaleFactor );
1318 refineResults();
1319 mPlotItem->updatePlot();
1320 emit plotAreaChanged();
1321}
1322
1324{
1325 return QgsDoubleRange( mPlotItem->xMinimum() * mPlotItem->mXScaleFactor, mPlotItem->xMaximum() * mPlotItem->mXScaleFactor );
1326}
1327
1329{
1330 return QgsDoubleRange( mPlotItem->yMinimum(), mPlotItem->yMaximum() );
1331}
1332
1334{
1335 return *mPlotItem;
1336}
1337
1339class QgsElevationProfilePlot : public Qgs2DPlot
1340{
1341 public:
1342 QgsElevationProfilePlot( QgsProfilePlotRenderer *renderer )
1343 : mRenderer( renderer )
1344 {
1345 }
1346
1347 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
1348 {
1349 if ( !mRenderer )
1350 return;
1351
1352 rc.painter()->translate( plotArea.left(), plotArea.top() );
1353 mRenderer->render( rc, plotArea.width(), plotArea.height(), xMinimum() * mXScale, xMaximum() * mXScale, yMinimum(), yMaximum() );
1354 rc.painter()->translate( -plotArea.left(), -plotArea.top() );
1355 }
1356
1357 double mXScale = 1;
1358
1359 private:
1360 QgsProfilePlotRenderer *mRenderer = nullptr;
1361};
1363
1364void QgsElevationProfileCanvas::render( QgsRenderContext &context, double width, double height, const Qgs2DPlot &plotSettings )
1365{
1366 if ( !mCurrentJob )
1367 return;
1368
1371
1372 QgsElevationProfilePlot profilePlot( mCurrentJob );
1373
1374 // 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...
1375 QDomDocument doc;
1376 QDomElement elem = doc.createElement( QStringLiteral( "plot" ) );
1377 QgsReadWriteContext rwContext;
1378 plotSettings.writeXml( elem, doc, rwContext );
1379 profilePlot.readXml( elem, rwContext );
1380
1381 profilePlot.mXScale = mPlotItem->mXScaleFactor;
1382 profilePlot.xAxis().setLabelSuffix( mPlotItem->xAxis().labelSuffix() );
1383 profilePlot.xAxis().setLabelSuffixPlacement( mPlotItem->xAxis().labelSuffixPlacement() );
1384
1385 profilePlot.setSize( QSizeF( width, height ) );
1386 profilePlot.render( context );
1387}
1388
1389QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( QPointF point )
1390{
1391 if ( !mCurrentJob )
1392 return {};
1393
1394 const QgsProfilePoint plotPoint = canvasPointToPlotPoint( point );
1395
1396 return mCurrentJob->identify( plotPoint, identifyContext() );
1397}
1398
1399QVector<QgsProfileIdentifyResults> QgsElevationProfileCanvas::identify( const QRectF &rect )
1400{
1401 if ( !mCurrentJob )
1402 return {};
1403
1404 const QgsProfilePoint topLeftPlotPoint = canvasPointToPlotPoint( rect.topLeft() );
1405 const QgsProfilePoint bottomRightPlotPoint = canvasPointToPlotPoint( rect.bottomRight() );
1406
1407 double distance1 = topLeftPlotPoint.distance();
1408 double distance2 = bottomRightPlotPoint.distance();
1409 if ( distance2 < distance1 )
1410 std::swap( distance1, distance2 );
1411
1412 double elevation1 = topLeftPlotPoint.elevation();
1413 double elevation2 = bottomRightPlotPoint.elevation();
1414 if ( elevation2 < elevation1 )
1415 std::swap( elevation1, elevation2 );
1416
1417 return mCurrentJob->identify( QgsDoubleRange( distance1, distance2 ), QgsDoubleRange( elevation1, elevation2 ), identifyContext() );
1418}
1419
1421{
1422 setProfileCurve( nullptr );
1423 cancelJobs();
1424 mPlotItem->updatePlot();
1425}
1426
1428{
1429 mSnappingEnabled = enabled;
1430}
@ FirstAndLastLabels
Place suffix after the first and last label values only.
DistanceUnit
Units of distance.
Definition qgis.h:4740
@ YardsBritishSears1922Truncated
British yards (Sears 1922 truncated)
@ Feet
Imperial feet.
@ MilesUSSurvey
US Survey miles.
@ LinksBritishSears1922
British links (Sears 1922)
@ YardsBritishBenoit1895A
British yards (Benoit 1895 A)
@ LinksBritishBenoit1895A
British links (Benoit 1895 A)
@ Centimeters
Centimeters.
@ YardsIndian1975
Indian yards (1975)
@ FeetUSSurvey
US Survey feet.
@ Millimeters
Millimeters.
@ FeetBritishSears1922
British feet (Sears 1922)
@ YardsClarkes
Clarke's yards.
@ YardsIndian
Indian yards.
@ FeetBritishBenoit1895B
British feet (Benoit 1895 B)
@ Miles
Terrestrial miles.
@ LinksUSSurvey
US Survey links.
@ ChainsUSSurvey
US Survey chains.
@ FeetClarkes
Clarke's feet.
@ Unknown
Unknown distance unit.
@ Yards
Imperial yards.
@ FeetBritish1936
British feet (1936)
@ FeetIndian1962
Indian feet (1962)
@ YardsBritishSears1922
British yards (Sears 1922)
@ FeetIndian1937
Indian feet (1937)
@ YardsIndian1937
Indian yards (1937)
@ Degrees
Degrees, for planar geographic CRS distance measurements.
@ ChainsBritishBenoit1895B
British chains (Benoit 1895 B)
@ LinksBritishSears1922Truncated
British links (Sears 1922 truncated)
@ ChainsBritishBenoit1895A
British chains (Benoit 1895 A)
@ YardsBritishBenoit1895B
British yards (Benoit 1895 B)
@ FeetBritish1865
British feet (1865)
@ YardsIndian1962
Indian yards (1962)
@ FeetBritishSears1922Truncated
British feet (Sears 1922 truncated)
@ MetersGermanLegal
German legal meter.
@ LinksBritishBenoit1895B
British links (Benoit 1895 B)
@ ChainsInternational
International chains.
@ LinksInternational
International links.
@ ChainsBritishSears1922Truncated
British chains (Sears 1922 truncated)
@ FeetIndian
Indian (geodetic) feet.
@ NauticalMiles
Nautical miles.
@ ChainsClarkes
Clarke's chains.
@ LinksClarkes
Clarke's links.
@ ChainsBritishSears1922
British chains (Sears 1922)
@ Kilometers
Kilometers.
@ FeetIndian1975
Indian feet (1975)
@ FeetGoldCoast
Gold Coast feet.
@ FeetBritishBenoit1895A
British feet (Benoit 1895 A)
@ Group
Composite group layer. Added in QGIS 3.24.
@ Plugin
Plugin based layer.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ Vector
Vector layer.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ Mesh
Mesh layer. Added in QGIS 3.2.
@ Raster
Raster layer.
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Base class for 2-dimensional plot/chart/graphs.
Definition qgsplot.h:273
void calculateOptimisedIntervals(QgsRenderContext &context)
Automatically sets the grid and label intervals to optimal values for display in the given render con...
Definition qgsplot.cpp:611
double yMaximum() const
Returns the maximum value of the y axis.
Definition qgsplot.h:383
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:177
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:397
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:491
double xMaximum() const
Returns the maximum value of the x axis.
Definition qgsplot.h:369
void render(QgsRenderContext &context)
Renders the plot.
Definition qgsplot.cpp:229
void setYMaximum(double maximum)
Sets the maximum value of the y axis.
Definition qgsplot.h:390
double xMinimum() const
Returns the minimum value of the x axis.
Definition qgsplot.h:341
double yMinimum() const
Returns the minimum value of the y axis.
Definition qgsplot.h:355
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:496
void setYMinimum(double minimum)
Sets the minimum value of the y axis.
Definition qgsplot.h:362
virtual void renderContent(QgsRenderContext &context, const QRectF &plotArea)
Renders the plot content.
Definition qgsplot.cpp:479
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.
static QgsProfileSourceRegistry * profileSourceRegistry()
Returns registry of available profile source implementations.
This class represents a coordinate reference system (CRS).
Abstract base class for curved geometry type.
Definition qgscurve.h:35
QgsRange which stores a range of double values.
Definition qgsrange.h:231
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.
void setDistanceUnit(Qgis::DistanceUnit unit)
Sets the distance unit used by the canvas.
QgsProfilePoint canvasPointToPlotPoint(QPointF point) const
Converts a canvas point to the equivalent plot point.
void setBackgroundColor(const QColor &color)
Sets the background color to use for the profile canvas.
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...
Qgis::DistanceUnit distanceUnit() const
Returns the distance unit used by the canvas.
double tolerance() const
Returns the tolerance of the profile (in crs() units).
void mouseMoveEvent(QMouseEvent *e) override
void panContentsBy(double dx, double dy) override
Pans the plot contents by dx, dy in canvas units.
void invalidateCurrentPlotExtent()
Invalidates the current plot extent, which means that the visible plot area will be recalculated and ...
QgsPointXY toCanvasCoordinates(const QgsPoint &point) const override
Converts a point in map coordinates to the associated canvas point.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the crs associated with the canvas' map coordinates.
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, and exception handling.
Definition qgsgeos.h:139
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:76
QString id
Definition qgsmaplayer.h:79
Qgis::LayerType type
Definition qgsmaplayer.h:86
void dataChanged()
Data of layer changed.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Perform transforms between map coordinates and device coordinates.
A context for numeric formats.
void setLabelSuffixPlacement(Qgis::PlotAxisSuffixPlacement placement)
Sets the placement for the axis label suffixes.
Definition qgsplot.cpp:129
void setLabelSuffix(const QString &suffix)
Sets the axis label suffix.
Definition qgsplot.cpp:119
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:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
bool isEmpty() const
Returns true if the geometry is empty.
Definition qgspointxy.h:242
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double z
Definition qgspoint.h:54
Encapsulates the context in which an elevation profile is to be generated.
double maximumErrorMapUnits() const
Returns the maximum allowed error in the generated result, in profile curve map units.
void setDpi(double dpi)
Sets the dpi (dots per inch) for the profie, to be used in size conversions.
void setMaximumErrorMapUnits(double error)
Sets the maximum allowed error in the generated result, in profile curve map units.
void setDistanceRange(const QgsDoubleRange &range)
Sets the range of distances to include in the generation.
void setElevationRange(const QgsDoubleRange &range)
Sets the range of elevations to include in the generation.
void setMapUnitsPerDistancePixel(double units)
Sets the number of map units per pixel in the distance dimension.
Encapsulates the context of identifying profile results.
double maximumPointElevationDelta
Maximum allowed snapping delta for the elevation values when identifying a point.
double maximumPointDistanceDelta
Maximum allowed snapping delta for the distance values when identifying a point.
QgsProject * project
Associated project.
double displayRatioElevationVsDistance
Display ratio of elevation vs distance units.
double maximumSurfaceDistanceDelta
Maximum allowed snapping delta for the distance values when identifying a continuous elevation surfac...
double maximumSurfaceElevationDelta
Maximum allowed snapping delta for the elevation values when identifying a continuous elevation surfa...
Generates and renders elevation profile plots.
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.
QList< QgsAbstractProfileSource * > profileSources() const
Returns a list of registered profile sources.
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:78
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:85
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:64
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.
static Q_INVOKABLE double fromUnitToUnitFactor(Qgis::DistanceUnit fromUnit, Qgis::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
static Q_INVOKABLE QString toAbbreviatedString(Qgis::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
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:75
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define BUILTIN_UNREACHABLE
Definition qgis.h:6720
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6066
const QgsCoordinateReferenceSystem & crs