QGIS API Documentation 3.41.0-Master (cea29feecf2)
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 explicit QgsAlgorithmElevationProfilePlotItem( int width, int height, int dpi )
38 : mDpi( dpi )
39 {
40 setYMinimum( 0 );
41 setYMaximum( 10 );
42 setSize( QSizeF( width, height ) );
43 }
44
45 void setRenderer( QgsProfilePlotRenderer *renderer )
46 {
47 mRenderer = renderer;
48 }
49
50 QRectF plotArea()
51 {
52 if ( !mPlotArea.isNull() )
53 {
54 return mPlotArea;
55 }
56
57 // calculate plot area
58 QgsRenderContext context;
59 context.setScaleFactor( mDpi / 25.4 );
60
62 mPlotArea = interiorPlotArea( context );
63 return mPlotArea;
64 }
65
66 void renderContent( QgsRenderContext &rc, const QRectF &plotArea ) override
67 {
68 mPlotArea = plotArea;
69
70 if ( !mRenderer )
71 return;
72
73 rc.painter()->translate( mPlotArea.left(), mPlotArea.top() );
74 const QStringList sourceIds = mRenderer->sourceIds();
75 for ( const QString &source : sourceIds )
76 {
77 mRenderer->render( rc, mPlotArea.width(), mPlotArea.height(), xMinimum(), xMaximum(), yMinimum(), yMaximum(), source );
78 }
79 rc.painter()->translate( -mPlotArea.left(), -mPlotArea.top() );
80 }
81
82 private:
83 int mDpi = 96;
84 QRectF mPlotArea;
85 QgsProfilePlotRenderer *mRenderer = nullptr;
86};
87
88void QgsGenerateElevationProfileAlgorithm::initAlgorithm( const QVariantMap & )
89{
90 addParameter( new QgsProcessingParameterGeometry( QStringLiteral( "CURVE" ), QObject::tr( "Profile curve" ), QVariant(), false, QList<int>() << static_cast<int>( Qgis::GeometryType::Line ) ) );
91 addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "MAP_LAYERS" ), QObject::tr( "Map layers" ), Qgis::ProcessingSourceType::MapLayer, QVariant(), false ) );
92 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "WIDTH" ), QObject::tr( "Chart width (in pixels)" ), Qgis::ProcessingNumberParameterType::Integer, 400, false, 0 ) );
93 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "HEIGHT" ), QObject::tr( "Chart height (in pixels)" ), Qgis::ProcessingNumberParameterType::Integer, 300, false, 0 ) );
94 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 ) ) );
95
96 auto minimumDistanceParam = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "MINIMUM_DISTANCE" ), QObject::tr( "Chart minimum distance (X axis)" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true );
97 minimumDistanceParam->setFlags( minimumDistanceParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
98 addParameter( minimumDistanceParam.release() );
99 auto maximumDistanceParam = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "MAXIMUM_DISTANCE" ), QObject::tr( "Chart maximum distance (X axis)" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true );
100 maximumDistanceParam->setFlags( maximumDistanceParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
101 addParameter( maximumDistanceParam.release() );
102 auto minimumElevationParam = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "MINIMUM_ELEVATION" ), QObject::tr( "Chart minimum elevation (Y axis)" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true );
103 minimumElevationParam->setFlags( minimumElevationParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
104 addParameter( minimumElevationParam.release() );
105 auto maximumElevationParam = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "MAXIMUM_ELEVATION" ), QObject::tr( "Chart maximum elevation (Y axis)" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true );
106 maximumElevationParam->setFlags( maximumElevationParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
107 addParameter( maximumElevationParam.release() );
108
109 auto textColorParam = std::make_unique<QgsProcessingParameterColor>( QStringLiteral( "TEXT_COLOR" ), QObject::tr( "Chart text color" ), QColor( 0, 0, 0 ), true, true );
110 textColorParam->setFlags( textColorParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
111 addParameter( textColorParam.release() );
112 auto backgroundColorParam = std::make_unique<QgsProcessingParameterColor>( QStringLiteral( "BACKGROUND_COLOR" ), QObject::tr( "Chart background color" ), QColor( 255, 255, 255 ), true, true );
113 backgroundColorParam->setFlags( backgroundColorParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
114 addParameter( backgroundColorParam.release() );
115 auto borderColorParam = std::make_unique<QgsProcessingParameterColor>( QStringLiteral( "BORDER_COLOR" ), QObject::tr( "Chart border color" ), QColor( 99, 99, 99 ), true, true );
116 borderColorParam->setFlags( borderColorParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
117 addParameter( borderColorParam.release() );
118
119 auto toleranceParam = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "TOLERANCE" ), QObject::tr( "Profile tolerance" ), Qgis::ProcessingNumberParameterType::Double, 5.0, false, 0 );
120 toleranceParam->setFlags( toleranceParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
121 addParameter( toleranceParam.release() );
122
123 auto dpiParam = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "DPI" ), QObject::tr( "Chart DPI" ), Qgis::ProcessingNumberParameterType::Integer, 96, false, 0 );
124 dpiParam->setFlags( dpiParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
125 addParameter( dpiParam.release() );
126
127 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output image" ) ) );
128}
129
130QString QgsGenerateElevationProfileAlgorithm::name() const
131{
132 return QStringLiteral( "generateelevationprofileimage" );
133}
134
135QString QgsGenerateElevationProfileAlgorithm::displayName() const
136{
137 return QObject::tr( "Generate elevation profile image" );
138}
139
140QStringList QgsGenerateElevationProfileAlgorithm::tags() const
141{
142 return QObject::tr( "altitude,elevation,terrain,dem" ).split( ',' );
143}
144
145QString QgsGenerateElevationProfileAlgorithm::group() const
146{
147 return QObject::tr( "Plots" );
148}
149
150QString QgsGenerateElevationProfileAlgorithm::groupId() const
151{
152 return QStringLiteral( "plots" );
153}
154
155QString QgsGenerateElevationProfileAlgorithm::shortHelpString() const
156{
157 return QObject::tr( "This algorithm creates an elevation profile image from a list of map layer and an optional terrain." );
158}
159
160QgsGenerateElevationProfileAlgorithm *QgsGenerateElevationProfileAlgorithm::createInstance() const
161{
162 return new QgsGenerateElevationProfileAlgorithm();
163}
164
165bool QgsGenerateElevationProfileAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
166{
167 const QgsGeometry curveGeom = parameterAsGeometry( parameters, QStringLiteral( "CURVE" ), context );
168 const QgsCoordinateReferenceSystem curveCrs = parameterAsGeometryCrs( parameters, QStringLiteral( "CURVE" ), context );
169
170 QList<QgsMapLayer *> layers = parameterAsLayerList( parameters, QStringLiteral( "MAP_LAYERS" ), context );
171 QgsMapLayer *terrainLayer = parameterAsLayer( parameters, QStringLiteral( "TERRAIN_LAYER" ), context );
172
173 const double tolerance = parameterAsDouble( parameters, QStringLiteral( "TOLERANCE" ), context );
174
175 QList<QgsAbstractProfileSource *> sources;
176 for ( QgsMapLayer *layer : layers )
177 {
178 if ( QgsAbstractProfileSource *source = dynamic_cast<QgsAbstractProfileSource *>( layer ) )
179 sources.append( source );
180 }
181
182 QgsProfileRequest request( static_cast<QgsCurve *>( curveGeom.constGet()->clone() ) );
183 request.setCrs( curveCrs );
184 request.setTolerance( tolerance );
185 request.setTransformContext( context.transformContext() );
186 request.setExpressionContext( context.expressionContext() );
187
188 if ( terrainLayer )
189 {
190 if ( QgsRasterLayer *rasterLayer = dynamic_cast<QgsRasterLayer *>( terrainLayer ) )
191 {
192 std::unique_ptr<QgsRasterDemTerrainProvider> terrainProvider = std::make_unique<QgsRasterDemTerrainProvider>();
193 terrainProvider->setLayer( rasterLayer );
194 request.setTerrainProvider( terrainProvider.release() );
195 }
196 else if ( QgsMeshLayer *meshLayer = dynamic_cast<QgsMeshLayer *>( terrainLayer ) )
197 {
198 std::unique_ptr<QgsMeshTerrainProvider> terrainProvider = std::make_unique<QgsMeshTerrainProvider>();
199 terrainProvider->setLayer( meshLayer );
200 request.setTerrainProvider( terrainProvider.release() );
201 }
202 }
203
204
205 mRenderer = std::make_unique<QgsProfilePlotRenderer>( sources, request );
206
207 return true;
208}
209
210QVariantMap QgsGenerateElevationProfileAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
211{
212 const QgsGeometry curveGeom = parameterAsGeometry( parameters, QStringLiteral( "CURVE" ), context );
213
214 const bool hasMinimumDistance = parameters.value( QStringLiteral( "MINIMUM_DISTANCE" ) ).isValid();
215 const double minimumDistance = parameterAsDouble( parameters, QStringLiteral( "MINIMUM_DISTANCE" ), context );
216 const bool hasMaximumDistance = parameters.value( QStringLiteral( "MAXIMUM_DISTANCE" ) ).isValid();
217 const double maximumDistance = parameterAsDouble( parameters, QStringLiteral( "MAXIMUM_DISTANCE" ), context );
218 const bool hasMinimumElevation = parameters.value( QStringLiteral( "MINIMUM_ELEVATION" ) ).isValid();
219 const double minimumElevation = parameterAsDouble( parameters, QStringLiteral( "MINIMUM_ELEVATION" ), context );
220 const bool hasMaximumElevation = parameters.value( QStringLiteral( "MAXIMUM_ELEVATION" ) ).isValid();
221 const double maximumElevation = parameterAsDouble( parameters, QStringLiteral( "MAXIMUM_ELEVATION" ), context );
222
223 const int width = parameterAsInt( parameters, QStringLiteral( "WIDTH" ), context );
224 const int height = parameterAsInt( parameters, QStringLiteral( "HEIGHT" ), context );
225 const int dpi = parameterAsInt( parameters, QStringLiteral( "DPI" ), context );
226
227 const QString outputImage = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
228
229 const QColor textColor = parameterAsColor( parameters, QStringLiteral( "TEXT_COLOR" ), context );
230 const QColor backgroundColor = parameterAsColor( parameters, QStringLiteral( "BACKGROUND_COLOR" ), context );
231 const QColor borderColor = parameterAsColor( parameters, QStringLiteral( "BORDER_COLOR" ), context );
232
233 QgsAlgorithmElevationProfilePlotItem plotItem( width, height, dpi );
234
235 if ( textColor.isValid() )
236 {
237 QgsTextFormat textFormat = plotItem.xAxis().textFormat();
238 textFormat.setColor( textColor );
239 plotItem.xAxis().setTextFormat( textFormat );
240 textFormat = plotItem.yAxis().textFormat();
241 textFormat.setColor( textColor );
242 plotItem.yAxis().setTextFormat( textFormat );
243 }
244
245 if ( borderColor.isValid() )
246 {
247 std::unique_ptr<QgsSimpleLineSymbolLayer> lineSymbolLayer = std::make_unique<QgsSimpleLineSymbolLayer>( borderColor, 0.1 );
248 lineSymbolLayer->setPenCapStyle( Qt::FlatCap );
249 plotItem.xAxis().setGridMinorSymbol( new QgsLineSymbol( QgsSymbolLayerList( { lineSymbolLayer->clone() } ) ) );
250 plotItem.yAxis().setGridMinorSymbol( new QgsLineSymbol( QgsSymbolLayerList( { lineSymbolLayer->clone() } ) ) );
251 plotItem.xAxis().setGridMajorSymbol( new QgsLineSymbol( QgsSymbolLayerList( { lineSymbolLayer->clone() } ) ) );
252 plotItem.yAxis().setGridMajorSymbol( new QgsLineSymbol( QgsSymbolLayerList( { lineSymbolLayer->clone() } ) ) );
253 plotItem.setChartBorderSymbol( new QgsFillSymbol( QgsSymbolLayerList( { lineSymbolLayer.release() } ) ) );
254 }
255
256 if ( backgroundColor.isValid() )
257 {
258 std::unique_ptr<QgsSimpleFillSymbolLayer> fillSymbolLayer = std::make_unique<QgsSimpleFillSymbolLayer>( backgroundColor, Qt::SolidPattern, backgroundColor );
259 plotItem.setChartBackgroundSymbol( new QgsFillSymbol( QgsSymbolLayerList( { fillSymbolLayer.release() } ) ) );
260 }
261
262 QgsProfileGenerationContext generationContext;
263 generationContext.setDpi( dpi );
264 generationContext.setMaximumErrorMapUnits( MAX_ERROR_PIXELS * ( curveGeom.constGet()->length() ) / plotItem.plotArea().width() );
265 generationContext.setMapUnitsPerDistancePixel( curveGeom.constGet()->length() / plotItem.plotArea().width() );
266
267 mRenderer->setContext( generationContext );
268
269 mRenderer->startGeneration();
270 mRenderer->waitForFinished();
271
272 const QgsDoubleRange zRange = mRenderer->zRange();
273 double zMinimum = 0;
274 double zMaximum = 0;
275 if ( zRange.upper() < zRange.lower() )
276 {
277 // invalid range, e.g. no features found in plot!
278 zMinimum = 0;
279 zMaximum = 10;
280 }
281 else if ( qgsDoubleNear( zRange.lower(), zRange.upper(), 0.0000001 ) )
282 {
283 // corner case ... a zero height plot! Just pick an arbitrary +/- 5 height range.
284 zMinimum = zRange.lower() - 5;
285 zMaximum = zRange.lower() + 5;
286 }
287 else
288 {
289 // add 5% margin to height range
290 const double margin = ( zRange.upper() - zRange.lower() ) * 0.05;
291 zMinimum = zRange.lower() - margin;
292 zMaximum = zRange.upper() + margin;
293 }
294
295 plotItem.setYMinimum( hasMinimumElevation ? minimumElevation : zMinimum );
296 plotItem.setYMaximum( hasMaximumElevation ? maximumElevation : zMaximum );
297 plotItem.setXMinimum( hasMinimumDistance ? minimumDistance : 0 );
298 plotItem.setXMaximum( hasMaximumDistance ? maximumDistance : curveGeom.constGet()->length() );
299
300 plotItem.setRenderer( mRenderer.get() );
301
302 QImage image( static_cast<int>( plotItem.size().width() ), static_cast<int>( plotItem.size().height() ), QImage::Format_ARGB32_Premultiplied );
303 image.fill( Qt::transparent );
304
305 QPainter painter( &image );
306 painter.setRenderHint( QPainter::Antialiasing, true );
307 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
308 renderContext.setScaleFactor( dpi / 25.4 );
309 renderContext.setExpressionContext( context.expressionContext() );
310 plotItem.calculateOptimisedIntervals( renderContext );
311 plotItem.render( renderContext );
312 painter.end();
313 image.save( outputImage );
314
315 QVariantMap outputs;
316 outputs.insert( QStringLiteral( "OUTPUT" ), outputImage );
317 return outputs;
318}
319
@ 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:6024
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30