QGIS API Documentation 3.37.0-Master (fa0c88a90b0)
qgsabstractprofilesurfacegenerator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsabstractprofilegenerator.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 * 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 ***************************************************************************/
18#include "qgsprofilesnapping.h"
19#include "qgsfillsymbol.h"
20#include "qgslinesymbol.h"
21#include "qgslinestring.h"
22#include "qgsprofilerequest.h"
23
24#include <QPainterPath>
25#include <optional>
26
27//
28// QgsAbstractProfileSurfaceResults
29//
30
32
34{
36}
37
39{
40 return mRawPoints;
41}
42
44{
45 return QgsDoubleRange( minZ, maxZ );
46}
47
49{
50 QVector<QgsGeometry> res;
51 res.reserve( mRawPoints.size() );
52 for ( const QgsPoint &point : mRawPoints )
53 res.append( QgsGeometry( point.clone() ) );
54
55 return res;
56}
57
58QVector<QgsAbstractProfileResults::Feature> QgsAbstractProfileSurfaceResults::asFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback ) const
59{
60 QVector< QgsAbstractProfileResults::Feature > res;
61 res.reserve( 1 );
62
63 QVector< double > currentLineX;
64 QVector< double > currentLineY;
65 QVector< double > currentLineZ;
66
67 switch ( type )
68 {
70 {
71 for ( auto pointIt = mDistanceToHeightMap.constBegin(); pointIt != mDistanceToHeightMap.constEnd(); ++pointIt )
72 {
73 if ( feedback && feedback->isCanceled() )
74 break;
75
76
77 if ( std::isnan( pointIt.value() ) )
78 {
79 if ( currentLineX.length() > 1 )
80 {
83 f.geometry = QgsGeometry( std::make_unique< QgsLineString >( currentLineX, currentLineY, currentLineZ ) );
84 res << f;
85 }
86 currentLineX.clear();
87 currentLineY.clear();
88 currentLineZ.clear();
89 continue;
90 }
91
92 std::unique_ptr< QgsPoint > curvePoint( mProfileCurve->interpolatePoint( pointIt.key() ) );
93 currentLineX << curvePoint->x();
94 currentLineY << curvePoint->y();
95 currentLineZ << pointIt.value();
96 }
97
98 if ( currentLineX.length() > 1 )
99 {
102 f.geometry = QgsGeometry( std::make_unique< QgsLineString >( currentLineX, currentLineY, currentLineZ ) );
103 res << f;
104 }
105 break;
106 }
107
109 {
110 for ( auto pointIt = mDistanceToHeightMap.constBegin(); pointIt != mDistanceToHeightMap.constEnd(); ++pointIt )
111 {
112 if ( feedback && feedback->isCanceled() )
113 break;
114
115 if ( std::isnan( pointIt.value() ) )
116 {
117 if ( currentLineX.length() > 1 )
118 {
121 f.geometry = QgsGeometry( std::make_unique< QgsLineString >( currentLineX, currentLineY ) );
122 res << f;
123 }
124 currentLineX.clear();
125 currentLineY.clear();
126 continue;
127 }
128
129 currentLineX << pointIt.key();
130 currentLineY << pointIt.value();
131 }
132 if ( currentLineX.length() > 1 )
133 {
136 f.geometry = QgsGeometry( std::make_unique< QgsLineString >( currentLineX, currentLineY ) );
137 res << f;
138 }
139 break;
140 }
141
143 {
144 res.reserve( mDistanceToHeightMap.size() );
145 for ( auto pointIt = mDistanceToHeightMap.constBegin(); pointIt != mDistanceToHeightMap.constEnd(); ++pointIt )
146 {
147 if ( feedback && feedback->isCanceled() )
148 break;
149
152 f.attributes =
153 {
154 { QStringLiteral( "distance" ), pointIt.key() },
155 { QStringLiteral( "elevation" ), pointIt.value() }
156 };
157 std::unique_ptr< QgsPoint> point( mProfileCurve->interpolatePoint( pointIt.key() ) );
158 if ( point->is3D() )
159 point->setZ( pointIt.value() );
160 else
161 point->addZValue( pointIt.value() );
162 f.geometry = QgsGeometry( std::move( point ) );
163 res << f;
164 }
165 break;
166 }
167 }
168
169 return res;
170}
171
173{
174 // TODO -- consider an index if performance is an issue
176
177 double prevDistance = std::numeric_limits< double >::max();
178 double prevElevation = 0;
179 for ( auto it = mDistanceToHeightMap.constBegin(); it != mDistanceToHeightMap.constEnd(); ++it )
180 {
181 // find segment which corresponds to the given distance along curve
182 if ( it != mDistanceToHeightMap.constBegin() && prevDistance <= point.distance() && it.key() >= point.distance() )
183 {
184 const double dx = it.key() - prevDistance;
185 const double dy = it.value() - prevElevation;
186 const double snappedZ = ( dy / dx ) * ( point.distance() - prevDistance ) + prevElevation;
187
188 if ( std::fabs( point.elevation() - snappedZ ) > context.maximumSurfaceElevationDelta )
189 return QgsProfileSnapResult();
190
191 result.snappedPoint = QgsProfilePoint( point.distance(), snappedZ );
192 break;
193 }
194
195 prevDistance = it.key();
196 prevElevation = it.value();
197 }
198 return result;
199}
200
201QVector<QgsProfileIdentifyResults> QgsAbstractProfileSurfaceResults::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
202{
203 // TODO -- consider an index if performance is an issue
204 std::optional< QgsProfileIdentifyResults > result;
205
206 double prevDistance = std::numeric_limits< double >::max();
207 double prevElevation = 0;
208 for ( auto it = mDistanceToHeightMap.constBegin(); it != mDistanceToHeightMap.constEnd(); ++it )
209 {
210 // find segment which corresponds to the given distance along curve
211 if ( it != mDistanceToHeightMap.constBegin() && prevDistance <= point.distance() && it.key() >= point.distance() )
212 {
213 const double dx = it.key() - prevDistance;
214 const double dy = it.value() - prevElevation;
215 const double snappedZ = ( dy / dx ) * ( point.distance() - prevDistance ) + prevElevation;
216
217 if ( std::fabs( point.elevation() - snappedZ ) > context.maximumSurfaceElevationDelta )
218 return {};
219
220 result = QgsProfileIdentifyResults( nullptr,
221 {
222 QVariantMap(
223 {
224 {QStringLiteral( "distance" ), point.distance() },
225 {QStringLiteral( "elevation" ), snappedZ }
226 } )
227 } );
228 break;
229 }
230
231 prevDistance = it.key();
232 prevElevation = it.value();
233 }
234 if ( result.has_value() )
235 return {result.value()};
236 else
237 return {};
238}
239
241{
242 QPainter *painter = context.renderContext().painter();
243 if ( !painter )
244 return;
245
246 const QgsScopedQPainterState painterState( painter );
247
248 painter->setBrush( Qt::NoBrush );
249 painter->setPen( Qt::NoPen );
250
251 const double minDistance = context.distanceRange().lower();
252 const double maxDistance = context.distanceRange().upper();
253 double minZ = context.elevationRange().lower();
254 double maxZ = context.elevationRange().upper();
255
256 const QRectF visibleRegion( minDistance, minZ, maxDistance - minDistance, maxZ - minZ );
257 QPainterPath clipPath;
258 clipPath.addPolygon( context.worldTransform().map( visibleRegion ) );
259 painter->setClipPath( clipPath, Qt::ClipOperation::IntersectClip );
260
261 switch ( symbology )
262 {
264 mLineSymbol->startRender( context.renderContext() );
265 break;
267 mFillSymbol->startRender( context.renderContext() );
268 if ( !std::isnan( mElevationLimit ) )
269 {
270 double dataLimit = std::numeric_limits< double >::max();
271 for ( auto pointIt = mDistanceToHeightMap.constBegin(); pointIt != mDistanceToHeightMap.constEnd(); ++pointIt )
272 {
273 if ( !std::isnan( pointIt.value() ) )
274 {
275 dataLimit = std::min( pointIt.value(), dataLimit );
276 }
277 }
278 if ( dataLimit > mElevationLimit )
279 minZ = std::max( minZ, mElevationLimit );
280 }
281 break;
283 mFillSymbol->startRender( context.renderContext() );
284 if ( !std::isnan( mElevationLimit ) )
285 {
286 double dataLimit = std::numeric_limits< double >::lowest();
287 for ( auto pointIt = mDistanceToHeightMap.constBegin(); pointIt != mDistanceToHeightMap.constEnd(); ++pointIt )
288 {
289 if ( !std::isnan( pointIt.value() ) )
290 {
291 dataLimit = std::max( pointIt.value(), dataLimit );
292 }
293 }
294 if ( dataLimit < mElevationLimit )
295 maxZ = std::min( maxZ, mElevationLimit );
296 }
297 break;
298 }
299
300 QPolygonF currentLine;
301 double prevDistance = std::numeric_limits< double >::quiet_NaN();
302 double currentPartStartDistance = 0;
303 for ( auto pointIt = mDistanceToHeightMap.constBegin(); pointIt != mDistanceToHeightMap.constEnd(); ++pointIt )
304 {
305 if ( std::isnan( pointIt.value() ) )
306 {
307 if ( currentLine.length() > 1 )
308 {
309 switch ( symbology )
310 {
312 mLineSymbol->renderPolyline( currentLine, nullptr, context.renderContext() );
313 break;
315 currentLine.append( context.worldTransform().map( QPointF( prevDistance, minZ ) ) );
316 currentLine.append( context.worldTransform().map( QPointF( currentPartStartDistance, minZ ) ) );
317 currentLine.append( currentLine.at( 0 ) );
318 mFillSymbol->renderPolygon( currentLine, nullptr, nullptr, context.renderContext() );
319 break;
321 currentLine.append( context.worldTransform().map( QPointF( prevDistance, maxZ ) ) );
322 currentLine.append( context.worldTransform().map( QPointF( currentPartStartDistance, maxZ ) ) );
323 currentLine.append( currentLine.at( 0 ) );
324 mFillSymbol->renderPolygon( currentLine, nullptr, nullptr, context.renderContext() );
325 break;
326 }
327 }
328 prevDistance = pointIt.key();
329 currentLine.clear();
330 continue;
331 }
332 if ( currentLine.length() < 1 )
333 {
334 currentPartStartDistance = pointIt.key();
335 }
336 currentLine.append( context.worldTransform().map( QPointF( pointIt.key(), pointIt.value() ) ) );
337 prevDistance = pointIt.key();
338 }
339 if ( currentLine.length() > 1 )
340 {
341 switch ( symbology )
342 {
344 mLineSymbol->renderPolyline( currentLine, nullptr, context.renderContext() );
345 break;
347 currentLine.append( context.worldTransform().map( QPointF( prevDistance, minZ ) ) );
348 currentLine.append( context.worldTransform().map( QPointF( currentPartStartDistance, minZ ) ) );
349 currentLine.append( currentLine.at( 0 ) );
350 mFillSymbol->renderPolygon( currentLine, nullptr, nullptr, context.renderContext() );
351 break;
353 currentLine.append( context.worldTransform().map( QPointF( prevDistance, maxZ ) ) );
354 currentLine.append( context.worldTransform().map( QPointF( currentPartStartDistance, maxZ ) ) );
355 currentLine.append( currentLine.at( 0 ) );
356 mFillSymbol->renderPolygon( currentLine, nullptr, nullptr, context.renderContext() );
357 break;
358 }
359 }
360
361 switch ( symbology )
362 {
364 mLineSymbol->stopRender( context.renderContext() );
365 break;
368 mFillSymbol->stopRender( context.renderContext() );
369 break;
370 }
371}
372
373
375{
376 const QgsAbstractProfileSurfaceGenerator *surfaceGenerator = qgis::down_cast< const QgsAbstractProfileSurfaceGenerator * >( generator );
377
378 mLineSymbol.reset( surfaceGenerator->lineSymbol()->clone() );
379 mFillSymbol.reset( surfaceGenerator->fillSymbol()->clone() );
380 symbology = surfaceGenerator->symbology();
381 mElevationLimit = surfaceGenerator->elevationLimit();
382
383 mProfileCurve.reset( surfaceGenerator->mProfileCurve->clone() );
384}
385
386//
387// QgsAbstractProfileSurfaceGenerator
388//
389
391 : mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
392{
393
394}
395
397
399{
400 return mSymbology;
401}
402
404{
405 return mLineSymbol.get();
406}
407
409{
410 return mFillSymbol.get();
411}
412
414{
415 return mElevationLimit;
416}
417
419{
420 mElevationLimit = limit;
421}
ProfileExportType
Types of export for elevation profiles.
Definition: qgis.h:3415
@ Profile2D
Export profiles as 2D profile lines, with elevation stored in exported geometry Y dimension and dista...
@ Features3D
Export profiles as 3D features, with elevation values stored in exported geometry Z values.
@ DistanceVsElevationTable
Export profiles as a table of sampled distance vs elevation values.
ProfileSurfaceSymbology
Surface symbology type for elevation profile plots.
Definition: qgis.h:3375
@ Line
The elevation surface will be rendered using a line symbol.
@ FillBelow
The elevation surface will be rendered using a fill symbol below the surface level.
@ FillAbove
The elevation surface will be rendered using a fill symbol above the surface level (since QGIS 3....
Abstract base class for objects which generate elevation profiles.
virtual QString type() const =0
Returns the unique string identifier for the results type.
Abstract base class for objects which generate elevation profiles which represent a continuous surfac...
QgsLineSymbol * lineSymbol() const
Returns the line symbol to be used for rendering the results.
Qgis::ProfileSurfaceSymbology symbology() const
Returns the symbology type for rendering the results.
void setElevationLimit(double limit)
Sets the elevation limit, which is used when symbology() is Qgis::ProfileSurfaceSymbology::FillBelow ...
QgsAbstractProfileSurfaceGenerator(const QgsProfileRequest &request)
Constructor for QgsAbstractProfileSurfaceGenerator.
double elevationLimit() const
Returns the elevation limit, which is used when symbology() is Qgis::ProfileSurfaceSymbology::FillBel...
QgsFillSymbol * fillSymbol() const
Returns the fill symbol to be used for rendering the results.
std::unique_ptr< QgsFillSymbol > mFillSymbol
std::unique_ptr< QgsLineSymbol > mLineSymbol
QgsPointSequence sampledPoints() const override
Returns a list of sampled points, with their calculated elevation as the point z value.
void renderResults(QgsProfileRenderContext &context) override
Renders the results to the specified context.
void copyPropertiesFromGenerator(const QgsAbstractProfileGenerator *generator) override
Copies properties from specified generator to the results object.
QVector< QgsAbstractProfileResults::Feature > asFeatures(Qgis::ProfileExportType type, QgsFeedback *feedback=nullptr) const override
Returns a list of features representing the calculated elevation results.
QVector< QgsGeometry > asGeometries() const override
Returns a list of geometries representing the calculated elevation results.
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context) override
Identify results visible at the specified profile point.
QgsProfileSnapResult snapPoint(const QgsProfilePoint &point, const QgsProfileSnapContext &context) override
Snaps a point to the generated elevation profile.
QgsDoubleRange zRange() const override
Returns the range of the retrieved elevation values.
QMap< double, double > distanceToHeightMap() const override
Returns the map of distance (chainage) to height.
QgsRange which stores a range of double values.
Definition: qgsrange.h:201
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:53
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
Definition: qgsfillsymbol.h:30
QgsFillSymbol * clone() const override
Returns a deep copy of this symbol.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgslinesymbol.h:30
QgsLineSymbol * clone() const override
Returns a deep copy of this symbol.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Encapsulates the context of identifying profile results.
double maximumSurfaceElevationDelta
Maximum allowed snapping delta for the elevation values when identifying a continuous elevation surfa...
Stores identify results generated by a QgsAbstractProfileResults object.
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.
Abstract base class for storage of elevation profiles.
const QTransform & worldTransform() const
Returns the transform from world coordinates to painter coordinates.
QgsDoubleRange elevationRange() const
Returns the range of elevations to include in the render.
QgsDoubleRange distanceRange() const
Returns the range of distances to include in the render.
QgsRenderContext & renderContext()
Returns a reference to the component QgsRenderContext.
Encapsulates properties and constraints relating to fetching elevation profiles from different source...
Encapsulates the context of snapping a profile point.
double maximumSurfaceElevationDelta
Maximum allowed snapping delta for the elevation values when snapping to a continuous elevation surfa...
Encapsulates results of snapping a profile point.
QgsProfilePoint snappedPoint
Snapped point.
T lower() const
Returns the lower bound of the range.
Definition: qgsrange.h:65
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:72
QPainter * painter()
Returns the destination QPainter for the render operation.
Scoped object for saving and restoring a QPainter object's state.
QVector< QgsPoint > QgsPointSequence
Encapsulates information about a feature exported from the profile results.
QString layerIdentifier
Identifier for grouping output features.
QVariantMap attributes
Exported attributes.