QGIS API Documentation 3.41.0-Master (3440c17df1d)
Loading...
Searching...
No Matches
qgsalgorithmgenerateelevationprofile.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmgenerateelevationprofile.cpp
3 ---------------------
4 begin : October 2024
5 copyright : (C) 2024 by Mathieu Pellerin
6 email : mathieu at opengis dot ch
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include "qgis.h"
22#include "qgstextformat.h"
23#include "qgsfillsymbol.h"
24#include "qgsfillsymbollayer.h"
25#include "qgslinesymbol.h"
26#include "qgslinesymbollayer.h"
27#include "qgsplot.h"
28#include "qgsprofilerequest.h"
29#include "qgsterrainprovider.h"
30#include "qgscurve.h"
31
33
34class QgsAlgorithmElevationProfilePlotItem: public Qgs2DPlot
35{
36 public:
37
38 explicit QgsAlgorithmElevationProfilePlotItem( int width, int height, int dpi )
39 : mDpi( dpi )
40 {
41 setYMinimum( 0 );
42 setYMaximum( 10 );
43 setSize( QSizeF( width, height ) );
44 }
45
46 void setRenderer( QgsProfilePlotRenderer *renderer )
47 {
48 mRenderer = renderer;
49 }
50
51 QRectF plotArea()
52 {
53 if ( !mPlotArea.isNull() )
54 {
55 return mPlotArea;
56 }
57
58 // calculate plot area
59 QgsRenderContext context;
60 context.setScaleFactor( mDpi / 25.4 );
61
63 mPlotArea = interiorPlotArea( context );
64 return mPlotArea;
65 }
66
67 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
68 {
69 mPlotArea = plotArea;
70
71 if ( !mRenderer )
72 return;
73
74 rc.painter()->translate( mPlotArea.left(), mPlotArea.top() );
75 const QStringList sourceIds = mRenderer->sourceIds();
76 for ( const QString &source : sourceIds )
77 {
78 mRenderer->render( rc, mPlotArea.width(), mPlotArea.height(), xMinimum(), xMaximum(), yMinimum(), yMaximum(), source );
79 }
80 rc.painter()->translate( -mPlotArea.left(), -mPlotArea.top() );
81 }
82
83 private:
84
85 int mDpi = 96;
86 QRectF mPlotArea;
87 QgsProfilePlotRenderer *mRenderer = nullptr;
88};
89
90void QgsGenerateElevationProfileAlgorithm::initAlgorithm( const QVariantMap & )
91{
92 addParameter( new QgsProcessingParameterGeometry( QStringLiteral( "CURVE" ), QObject::tr( "Profile curve" ), QVariant(), false, QList<int>() << static_cast<int>( Qgis::GeometryType::Line ) ) );
93 addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "MAP_LAYERS" ), QObject::tr( "Map layers" ), Qgis::ProcessingSourceType::MapLayer, QVariant(), false ) );
94 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "WIDTH" ), QObject::tr( "Chart width (in pixels)" ), Qgis::ProcessingNumberParameterType::Integer, 400, false, 0 ) );
95 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "HEIGHT" ), QObject::tr( "Chart height (in pixels)" ), Qgis::ProcessingNumberParameterType::Integer, 300, false, 0 ) );
96 addParameter( new QgsProcessingParameterMapLayer( QStringLiteral( "TERRAIN_LAYER" ), QObject::tr( "Terrain layer" ), QVariant(), true, QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::Raster ) << static_cast<int>( Qgis::ProcessingSourceType::Mesh ) ) );
97
98 auto minimumDistanceParam = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "MINIMUM_DISTANCE" ), QObject::tr( "Chart minimum distance (X axis)" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true );
99 minimumDistanceParam->setFlags( minimumDistanceParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
100 addParameter( minimumDistanceParam.release() );
101 auto maximumDistanceParam = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "MAXIMUM_DISTANCE" ), QObject::tr( "Chart maximum distance (X axis)" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true );
102 maximumDistanceParam->setFlags( maximumDistanceParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
103 addParameter( maximumDistanceParam.release() );
104 auto minimumElevationParam = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "MINIMUM_ELEVATION" ), QObject::tr( "Chart minimum elevation (Y axis)" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true );
105 minimumElevationParam->setFlags( minimumElevationParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
106 addParameter( minimumElevationParam.release() );
107 auto maximumElevationParam = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "MAXIMUM_ELEVATION" ), QObject::tr( "Chart maximum elevation (Y axis)" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true );
108 maximumElevationParam->setFlags( maximumElevationParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
109 addParameter( maximumElevationParam.release() );
110
111 auto textColorParam = std::make_unique< QgsProcessingParameterColor >( QStringLiteral( "TEXT_COLOR" ), QObject::tr( "Chart text color" ), QColor( 0, 0, 0 ), true, true );
112 textColorParam->setFlags( textColorParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
113 addParameter( textColorParam.release() );
114 auto backgroundColorParam = std::make_unique< QgsProcessingParameterColor >( QStringLiteral( "BACKGROUND_COLOR" ), QObject::tr( "Chart background color" ), QColor( 255, 255, 255 ), true, true );
115 backgroundColorParam->setFlags( backgroundColorParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
116 addParameter( backgroundColorParam.release() );
117 auto borderColorParam = std::make_unique< QgsProcessingParameterColor >( QStringLiteral( "BORDER_COLOR" ), QObject::tr( "Chart border color" ), QColor( 99, 99, 99 ), true, true );
118 borderColorParam->setFlags( borderColorParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
119 addParameter( borderColorParam.release() );
120
121 auto toleranceParam = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "TOLERANCE" ), QObject::tr( "Profile tolerance" ), Qgis::ProcessingNumberParameterType::Double, 5.0, false, 0 );
122 toleranceParam->setFlags( toleranceParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
123 addParameter( toleranceParam.release() );
124
125 auto dpiParam = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "DPI" ), QObject::tr( "Chart DPI" ), Qgis::ProcessingNumberParameterType::Integer, 96, false, 0 );
126 dpiParam->setFlags( dpiParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
127 addParameter( dpiParam.release() );
128
129 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output image" ) ) );
130}
131
132QString QgsGenerateElevationProfileAlgorithm::name() const
133{
134 return QStringLiteral( "generateelevationprofileimage" );
135}
136
137QString QgsGenerateElevationProfileAlgorithm::displayName() const
138{
139 return QObject::tr( "Generate elevation profile image" );
140}
141
142QStringList QgsGenerateElevationProfileAlgorithm::tags() const
143{
144 return QObject::tr( "altitude,elevation,terrain,dem" ).split( ',' );
145}
146
147QString QgsGenerateElevationProfileAlgorithm::group() const
148{
149 return QObject::tr( "Plots" );
150}
151
152QString QgsGenerateElevationProfileAlgorithm::groupId() const
153{
154 return QStringLiteral( "plots" );
155}
156
157QString QgsGenerateElevationProfileAlgorithm::shortHelpString() const
158{
159 return QObject::tr( "This algorithm creates an elevation profile image from a list of map layer and an optional terrain." );
160}
161
162QgsGenerateElevationProfileAlgorithm *QgsGenerateElevationProfileAlgorithm::createInstance() const
163{
164 return new QgsGenerateElevationProfileAlgorithm();
165}
166
167bool QgsGenerateElevationProfileAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
168{
169 const QgsGeometry curveGeom = parameterAsGeometry( parameters, QStringLiteral( "CURVE" ), context );
170 const QgsCoordinateReferenceSystem curveCrs = parameterAsGeometryCrs( parameters, QStringLiteral( "CURVE" ), context );
171
172 QList<QgsMapLayer *> layers = parameterAsLayerList( parameters, QStringLiteral( "MAP_LAYERS" ), context );
173 QgsMapLayer *terrainLayer = parameterAsLayer( parameters, QStringLiteral( "TERRAIN_LAYER" ), context );
174
175 const double tolerance = parameterAsDouble( parameters, QStringLiteral( "TOLERANCE" ), context );
176
177 QList<QgsAbstractProfileSource *> sources;
178 for ( QgsMapLayer *layer : layers )
179 {
180 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
181 sources.append( source );
182 }
183
184 QgsProfileRequest request( static_cast<QgsCurve *>( curveGeom.constGet()->clone() ) );
185 request.setCrs( curveCrs );
186 request.setTolerance( tolerance );
187 request.setTransformContext( context.transformContext() );
188 request.setExpressionContext( context.expressionContext() );
189
190 if ( terrainLayer )
191 {
192 if ( QgsRasterLayer *rasterLayer = dynamic_cast<QgsRasterLayer *>( terrainLayer ) )
193 {
194 std::unique_ptr<QgsRasterDemTerrainProvider> terrainProvider = std::make_unique<QgsRasterDemTerrainProvider>();
195 terrainProvider->setLayer( rasterLayer );
196 request.setTerrainProvider( terrainProvider.release() );
197 }
198 else if ( QgsMeshLayer *meshLayer = dynamic_cast<QgsMeshLayer *>( terrainLayer ) )
199 {
200 std::unique_ptr<QgsMeshTerrainProvider> terrainProvider = std::make_unique<QgsMeshTerrainProvider>();
201 terrainProvider->setLayer( meshLayer );
202 request.setTerrainProvider( terrainProvider.release() );
203 }
204 }
205
206
207 mRenderer = std::make_unique<QgsProfilePlotRenderer>( sources, request );
208
209 return true;
210}
211
212QVariantMap QgsGenerateElevationProfileAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
213{
214 const QgsGeometry curveGeom = parameterAsGeometry( parameters, QStringLiteral( "CURVE" ), context );
215
216 const bool hasMinimumDistance = parameters.value( QStringLiteral( "MINIMUM_DISTANCE" ) ).isValid();
217 const double minimumDistance = parameterAsDouble( parameters, QStringLiteral( "MINIMUM_DISTANCE" ), context );
218 const bool hasMaximumDistance = parameters.value( QStringLiteral( "MAXIMUM_DISTANCE" ) ).isValid();
219 const double maximumDistance = parameterAsDouble( parameters, QStringLiteral( "MAXIMUM_DISTANCE" ), context );
220 const bool hasMinimumElevation = parameters.value( QStringLiteral( "MINIMUM_ELEVATION" ) ).isValid();
221 const double minimumElevation = parameterAsDouble( parameters, QStringLiteral( "MINIMUM_ELEVATION" ), context );
222 const bool hasMaximumElevation = parameters.value( QStringLiteral( "MAXIMUM_ELEVATION" ) ).isValid();
223 const double maximumElevation = parameterAsDouble( parameters, QStringLiteral( "MAXIMUM_ELEVATION" ), context );
224
225 const int width = parameterAsInt( parameters, QStringLiteral( "WIDTH" ), context );
226 const int height = parameterAsInt( parameters, QStringLiteral( "HEIGHT" ), context );
227 const int dpi = parameterAsInt( parameters, QStringLiteral( "DPI" ), context );
228
229 const QString outputImage = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
230
231 const QColor textColor = parameterAsColor( parameters, QStringLiteral( "TEXT_COLOR" ), context );
232 const QColor backgroundColor = parameterAsColor( parameters, QStringLiteral( "BACKGROUND_COLOR" ), context );
233 const QColor borderColor = parameterAsColor( parameters, QStringLiteral( "BORDER_COLOR" ), context );
234
235 QgsAlgorithmElevationProfilePlotItem plotItem( width, height, dpi );
236
237 if ( textColor.isValid() )
238 {
239 QgsTextFormat textFormat = plotItem.xAxis().textFormat();
240 textFormat.setColor( textColor );
241 plotItem.xAxis().setTextFormat( textFormat );
242 textFormat = plotItem.yAxis().textFormat();
243 textFormat.setColor( textColor );
244 plotItem.yAxis().setTextFormat( textFormat );
245 }
246
247 if ( borderColor.isValid() )
248 {
249 std::unique_ptr<QgsSimpleLineSymbolLayer> lineSymbolLayer = std::make_unique<QgsSimpleLineSymbolLayer>( borderColor, 0.1 );
250 lineSymbolLayer->setPenCapStyle( Qt::FlatCap );
251 plotItem.xAxis().setGridMinorSymbol( new QgsLineSymbol( QgsSymbolLayerList( { lineSymbolLayer->clone() } ) ) );
252 plotItem.yAxis().setGridMinorSymbol( new QgsLineSymbol( QgsSymbolLayerList( { lineSymbolLayer->clone() } ) ) );
253 plotItem.xAxis().setGridMajorSymbol( new QgsLineSymbol( QgsSymbolLayerList( { lineSymbolLayer->clone() } ) ) );
254 plotItem.yAxis().setGridMajorSymbol( new QgsLineSymbol( QgsSymbolLayerList( { lineSymbolLayer->clone() } ) ) );
255 plotItem.setChartBorderSymbol( new QgsFillSymbol( QgsSymbolLayerList( { lineSymbolLayer.release() } ) ) );
256 }
257
258 if ( backgroundColor.isValid() )
259 {
260 std::unique_ptr<QgsSimpleFillSymbolLayer> fillSymbolLayer = std::make_unique<QgsSimpleFillSymbolLayer>( backgroundColor, Qt::SolidPattern, backgroundColor );
261 plotItem.setChartBackgroundSymbol( new QgsFillSymbol( QgsSymbolLayerList( { fillSymbolLayer.release() } ) ) );
262 }
263
264 QgsProfileGenerationContext generationContext;
265 generationContext.setDpi( dpi );
266 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( curveGeom.constGet()->length() ) / plotItem.plotArea().width() );
267 generationContext.setMapUnitsPerDistancePixel( curveGeom.constGet()->length() / plotItem.plotArea().width() );
268
269 mRenderer->setContext( generationContext );
270
271 mRenderer->startGeneration();
272 mRenderer->waitForFinished();
273
274 const QgsDoubleRange zRange = mRenderer->zRange();
275 double zMinimum = 0;
276 double zMaximum = 0;
277 if ( zRange.upper() < zRange.lower() )
278 {
279 // invalid range, e.g. no features found in plot!
280 zMinimum = 0;
281 zMaximum = 10;
282 }
283 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
284 {
285 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
286 zMinimum = zRange.lower() - 5;
287 zMaximum = zRange.lower() + 5;
288 }
289 else
290 {
291 // add 5% margin to height range
292 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
293 zMinimum = zRange.lower() - margin;
294 zMaximum = zRange.upper() + margin;
295 }
296
297 plotItem.setYMinimum( hasMinimumElevation ? minimumElevation : zMinimum );
298 plotItem.setYMaximum( hasMaximumElevation ? maximumElevation : zMaximum );
299 plotItem.setXMinimum( hasMinimumDistance ? minimumDistance : 0 );
300 plotItem.setXMaximum( hasMaximumDistance ? maximumDistance : curveGeom.constGet()->length() );
301
302 plotItem.setRenderer( mRenderer.get() );
303
304 QImage image( static_cast<int>( plotItem.size().width() ), static_cast<int>( plotItem.size().height() ), QImage::Format_ARGB32_Premultiplied );
305 image.fill( Qt::transparent );
306
307 QPainter painter( &image );
308 painter.setRenderHint( QPainter::Antialiasing, true );
309 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
310 renderContext.setScaleFactor( dpi / 25.4 );
311 renderContext.setExpressionContext( context.expressionContext() );
312 plotItem.calculateOptimisedIntervals( renderContext );
313 plotItem.render( renderContext );
314 painter.end();
315 image.save( outputImage );
316
317 QVariantMap outputs;
318 outputs.insert( QStringLiteral( "OUTPUT" ), outputImage );
319 return outputs;
320}
321
@ MapLayer
Any map layer type (raster, vector, mesh, point cloud, annotation or plugin layer)
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
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
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 setYMaximum(double maximum)
Sets the maximum value of the y axis.
Definition qgsplot.h:390
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.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
Interface for classes which can generate elevation profiles.
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 fill symbol type, for rendering Polygon and MultiPolygon geometries.
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
A line symbol type, for rendering LineString and MultiLineString geometries.
Base class for all map layer types.
Definition qgsmaplayer.h:76
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Contains information about the context in which a processing algorithm is executed.
QgsExpressionContext & expressionContext()
Returns the expression context.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Base class for providing feedback from a processing algorithm.
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
A geometry parameter for processing algorithms.
A map layer parameter for processing algorithms.
A parameter for processing algorithms which accepts multiple map layers.
A numeric parameter for processing algorithms.
Encapsulates the context in which an elevation profile is to be generated.
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 setMapUnitsPerDistancePixel(double units)
Sets the number of map units per pixel in the distance dimension.
Generates and renders elevation profile plots.
Encapsulates properties and constraints relating to fetching elevation profiles from different source...
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
Represents a raster layer.
Contains information about the context of a rendering operation.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
QPainter * painter()
Returns the destination QPainter for the render operation.
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
Container for all settings relating to text rendering.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
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
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30