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