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