QGIS API Documentation 3.34.0-Prizren (ffbdd678812)
Loading...
Searching...
No Matches
qgsvectorlayerprofilegenerator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectorlayerprofilegenerator.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 "qgsprofilerequest.h"
19#include "qgscurve.h"
20#include "qgsvectorlayer.h"
23#include "qgsgeos.h"
25#include "qgsterrainprovider.h"
26#include "qgspolygon.h"
27#include "qgstessellator.h"
28#include "qgsmultipolygon.h"
29#include "qgsmeshlayerutils.h"
30#include "qgsmultipoint.h"
31#include "qgsmultilinestring.h"
32#include "qgslinesymbol.h"
33#include "qgsfillsymbol.h"
34#include "qgsmarkersymbol.h"
35#include "qgsprofilepoint.h"
36#include "qgsprofilesnapping.h"
38#include <QPolygonF>
39
40//
41// QgsVectorLayerProfileResults
42//
43
45{
46 return QStringLiteral( "vector" );
47}
48
50{
51 QVector<QgsGeometry> res;
52 res.reserve( features.size() );
53 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
54 {
55 for ( const Feature &feature : it.value() )
56 {
57 res.append( feature.geometry );
58 }
59 }
60 return res;
61}
62
63QVector<QgsAbstractProfileResults::Feature> QgsVectorLayerProfileResults::asFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback ) const
64{
65 switch ( profileType )
66 {
69 return asIndividualFeatures( type, feedback );
70 // distance vs elevation table results are always handled like a continuous surface
71 [[fallthrough]];
72
75 }
77}
78
80{
81 switch ( profileType )
82 {
84 return snapPointToIndividualFeatures( point, context );
86 return QgsAbstractProfileSurfaceResults::snapPoint( point, context );
87 }
89}
90
91
92QVector<QgsProfileIdentifyResults> QgsVectorLayerProfileResults::identify( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const QgsProfileIdentifyContext & )
93{
94 QgsFeatureIds ids;
95 auto visitFeature = [&ids]( QgsFeatureId featureId )
96 {
97 ids << featureId;
98 };
99
100 visitFeaturesInRange( distanceRange, elevationRange, visitFeature );
101 if ( ids.empty() )
102 return {};
103
104 QVector< QVariantMap> idsList;
105 for ( auto it = ids.constBegin(); it != ids.constEnd(); ++it )
106 idsList.append( QVariantMap( {{QStringLiteral( "id" ), *it}} ) );
107
108 return { QgsProfileIdentifyResults( mLayer, idsList ) };
109}
110
111QVector<QgsProfileIdentifyResults> QgsVectorLayerProfileResults::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
112{
113 QHash< QgsFeatureId, QVariantMap > features;
114 auto visitFeature = [&features]( QgsFeatureId featureId, double delta, double distance, double elevation )
115 {
116 auto it = features.find( featureId );
117 if ( it == features.end() )
118 {
119 features[ featureId ] = QVariantMap( {{QStringLiteral( "id" ), featureId },
120 {QStringLiteral( "delta" ), delta },
121 {QStringLiteral( "distance" ), distance },
122 {QStringLiteral( "elevation" ), elevation }
123 } );
124 }
125 else
126 {
127 const double currentDelta = it.value().value( QStringLiteral( "delta" ) ).toDouble();
128 if ( delta < currentDelta )
129 {
130 *it = QVariantMap( {{QStringLiteral( "id" ), featureId },
131 {QStringLiteral( "delta" ), delta },
132 {QStringLiteral( "distance" ), distance },
133 {QStringLiteral( "elevation" ), elevation }
134 } );
135 }
136 }
137 };
138
139 visitFeaturesAtPoint( point, context.maximumPointDistanceDelta, context.maximumPointElevationDelta, context.maximumSurfaceElevationDelta, visitFeature, true );
140
141 QVector< QVariantMap> attributes;
142 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
143 attributes.append( *it );
144
145 QVector<QgsProfileIdentifyResults> res;
146
147 if ( !attributes.empty() )
148 res.append( QgsProfileIdentifyResults( mLayer, attributes ) );
149
151 {
152 const QVector<QgsProfileIdentifyResults> surfaceResults = QgsAbstractProfileSurfaceResults::identify( point, context );
153 res.reserve( surfaceResults.size() );
154 for ( const QgsProfileIdentifyResults &surfaceResult : surfaceResults )
155 {
156 res.append( QgsProfileIdentifyResults( mLayer, surfaceResult.results() ) );
157 }
158 }
159
160 return res;
161}
162
163QgsProfileSnapResult QgsVectorLayerProfileResults::snapPointToIndividualFeatures( const QgsProfilePoint &point, const QgsProfileSnapContext &context )
164{
166 double bestSnapDistance = std::numeric_limits< double >::max();
167
168 auto visitFeature = [&bestSnapDistance, &res]( QgsFeatureId, double delta, double distance, double elevation )
169 {
170 if ( distance < bestSnapDistance )
171 {
172 bestSnapDistance = delta;
173 res.snappedPoint = QgsProfilePoint( distance, elevation );
174 }
175 };
176
177 visitFeaturesAtPoint( point, context.maximumPointDistanceDelta, context.maximumPointElevationDelta, context.maximumSurfaceElevationDelta, visitFeature, false );
178
179 return res;
180}
181
182void QgsVectorLayerProfileResults::visitFeaturesAtPoint( const QgsProfilePoint &point, double maximumPointDistanceDelta, double maximumPointElevationDelta, double maximumSurfaceElevationDelta, const std::function< void( QgsFeatureId, double delta, double distance, double elevation ) > &visitor, bool visitWithin )
183{
184 // TODO -- add spatial index if performance is an issue
185
186 const QgsPoint targetPoint( point.distance(), point.elevation() );
187
188 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
189 {
190 for ( const Feature &feature : it.value() )
191 {
192 const QgsRectangle featureBounds = feature.crossSectionGeometry.boundingBox();
193 if ( ( featureBounds.xMinimum() - maximumPointDistanceDelta <= point.distance() ) && ( featureBounds.xMaximum() + maximumPointDistanceDelta >= point.distance() ) )
194 {
195 switch ( feature.crossSectionGeometry.type() )
196 {
198 {
199 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
200 {
201 if ( const QgsPoint *candidatePoint = qgsgeometry_cast< const QgsPoint * >( *partIt ) )
202 {
203 const double snapDistanceDelta = std::fabs( point.distance() - candidatePoint->x() );
204 if ( snapDistanceDelta > maximumPointDistanceDelta )
205 continue;
206
207 const double snapHeightDelta = std::fabs( point.elevation() - candidatePoint->y() );
208 if ( snapHeightDelta > maximumPointElevationDelta )
209 continue;
210
211 const double snapDistance = candidatePoint->distance( targetPoint );
212 visitor( feature.featureId, snapDistance, candidatePoint->x(), candidatePoint->y() );
213 }
214 }
215 break;
216 }
217
219 {
220 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
221 {
222 if ( const QgsCurve *line = qgsgeometry_cast< const QgsCurve * >( *partIt ) )
223 {
224 // might be a vertical line
225 if ( const QgsLineString *lineString = qgsgeometry_cast< const QgsLineString * >( line ) )
226 {
227 if ( lineString->numPoints() == 2 && qgsDoubleNear( lineString->pointN( 0 ).x(), lineString->pointN( 1 ).x() ) )
228 {
229 const double snapDistanceDelta = std::fabs( point.distance() - lineString->pointN( 0 ).x() );
230 if ( snapDistanceDelta > maximumPointDistanceDelta )
231 continue;
232
233 const double snapHeightDelta = std::fabs( point.elevation() - lineString->pointN( 0 ).y() );
234 if ( snapHeightDelta <= maximumPointElevationDelta )
235 {
236 const double snapDistanceP1 = lineString->pointN( 0 ).distance( targetPoint );
237 visitor( feature.featureId, snapDistanceP1, lineString->pointN( 0 ).x(), lineString->pointN( 0 ).y() );
238 }
239
240 const double snapHeightDelta2 = std::fabs( point.elevation() - lineString->pointN( 1 ).y() );
241 if ( snapHeightDelta2 <= maximumPointElevationDelta )
242 {
243 const double snapDistanceP2 = lineString->pointN( 1 ).distance( targetPoint );
244 visitor( feature.featureId, snapDistanceP2, lineString->pointN( 1 ).x(), lineString->pointN( 1 ).y() );
245 }
246
247 if ( visitWithin )
248 {
249 double elevation1 = lineString->pointN( 0 ).y();
250 double elevation2 = lineString->pointN( 1 ).y();
251 if ( elevation1 > elevation2 )
252 std::swap( elevation1, elevation2 );
253
254 if ( point.elevation() > elevation1 && point.elevation() < elevation2 )
255 {
256 const double snapDistance = std::fabs( lineString->pointN( 0 ).x() - point.distance() );
257 visitor( feature.featureId, snapDistance, lineString->pointN( 0 ).x(), point.elevation() );
258 }
259 }
260 continue;
261 }
262 }
263
264 const QgsRectangle partBounds = ( *partIt )->boundingBox();
265 if ( point.distance() < partBounds.xMinimum() - maximumPointDistanceDelta || point.distance() > partBounds.xMaximum() + maximumPointDistanceDelta )
266 continue;
267
268 const double snappedDistance = point.distance() < partBounds.xMinimum() ? partBounds.xMinimum()
269 : point.distance() > partBounds.xMaximum() ? partBounds.xMaximum() : point.distance();
270
271 const QgsGeometry cutLine( new QgsLineString( QgsPoint( snappedDistance, minZ ), QgsPoint( snappedDistance, maxZ ) ) );
272 QgsGeos cutLineGeos( cutLine.constGet() );
273
274 const QgsGeometry points( cutLineGeos.intersection( line ) );
275
276 for ( auto vertexIt = points.vertices_begin(); vertexIt != points.vertices_end(); ++vertexIt )
277 {
278 const double snapHeightDelta = std::fabs( point.elevation() - ( *vertexIt ).y() );
279 if ( snapHeightDelta > maximumSurfaceElevationDelta )
280 continue;
281
282 const double snapDistance = ( *vertexIt ).distance( targetPoint );
283 visitor( feature.featureId, snapDistance, ( *vertexIt ).x(), ( *vertexIt ).y() );
284 }
285 }
286 }
287 break;
288 }
289
291 {
292 if ( visitWithin )
293 {
294 if ( feature.crossSectionGeometry.intersects( QgsGeometry::fromPointXY( QgsPointXY( point.distance(), point.elevation() ) ) ) )
295 {
296 visitor( feature.featureId, 0, point.distance(), point.elevation() );
297 break;
298 }
299 }
300 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
301 {
302 if ( const QgsCurve *exterior = qgsgeometry_cast< const QgsPolygon * >( *partIt )->exteriorRing() )
303 {
304 const QgsRectangle partBounds = ( *partIt )->boundingBox();
305 if ( point.distance() < partBounds.xMinimum() - maximumPointDistanceDelta || point.distance() > partBounds.xMaximum() + maximumPointDistanceDelta )
306 continue;
307
308 const double snappedDistance = point.distance() < partBounds.xMinimum() ? partBounds.xMinimum()
309 : point.distance() > partBounds.xMaximum() ? partBounds.xMaximum() : point.distance();
310
311 const QgsGeometry cutLine( new QgsLineString( QgsPoint( snappedDistance, minZ ), QgsPoint( snappedDistance, maxZ ) ) );
312 QgsGeos cutLineGeos( cutLine.constGet() );
313
314 const QgsGeometry points( cutLineGeos.intersection( exterior ) );
315 for ( auto vertexIt = points.vertices_begin(); vertexIt != points.vertices_end(); ++vertexIt )
316 {
317 const double snapHeightDelta = std::fabs( point.elevation() - ( *vertexIt ).y() );
318 if ( snapHeightDelta > maximumSurfaceElevationDelta )
319 continue;
320
321 const double snapDistance = ( *vertexIt ).distance( targetPoint );
322 visitor( feature.featureId, snapDistance, ( *vertexIt ).x(), ( *vertexIt ).y() );
323 }
324 }
325 }
326 break;
327 }
330 break;
331 }
332 }
333 }
334 }
335}
336
337void QgsVectorLayerProfileResults::visitFeaturesInRange( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const std::function<void ( QgsFeatureId )> &visitor )
338{
339 // TODO -- add spatial index if performance is an issue
340 const QgsRectangle profileRange( distanceRange.lower(), elevationRange.lower(), distanceRange.upper(), elevationRange.upper() );
341 const QgsGeometry profileRangeGeometry = QgsGeometry::fromRect( profileRange );
342 QgsGeos profileRangeGeos( profileRangeGeometry.constGet() );
343 profileRangeGeos.prepareGeometry();
344
345 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
346 {
347 for ( const Feature &feature : it.value() )
348 {
349 if ( feature.crossSectionGeometry.boundingBoxIntersects( profileRange ) )
350 {
351 switch ( feature.crossSectionGeometry.type() )
352 {
354 {
355 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
356 {
357 if ( const QgsPoint *candidatePoint = qgsgeometry_cast< const QgsPoint * >( *partIt ) )
358 {
359 if ( profileRange.contains( candidatePoint->x(), candidatePoint->y() ) )
360 {
361 visitor( feature.featureId );
362 }
363 }
364 }
365 break;
366 }
367
370 {
371 if ( profileRangeGeos.intersects( feature.crossSectionGeometry.constGet() ) )
372 {
373 visitor( feature.featureId );
374 }
375 break;
376 }
377
380 break;
381 }
382 }
383 }
384 }
385}
386
388{
389 const QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), mLayer ? mLayer->createExpressionContextScope() : nullptr );
390 switch ( profileType )
391 {
393 renderResultsAsIndividualFeatures( context );
394 break;
398 renderMarkersOverContinuousSurfacePlot( context );
399 break;
400 }
401}
402
403void QgsVectorLayerProfileResults::renderResultsAsIndividualFeatures( QgsProfileRenderContext &context )
404{
405 QPainter *painter = context.renderContext().painter();
406 if ( !painter )
407 return;
408
409 const QgsScopedQPainterState painterState( painter );
410
411 painter->setBrush( Qt::NoBrush );
412 painter->setPen( Qt::NoPen );
413
414 const double minDistance = context.distanceRange().lower();
415 const double maxDistance = context.distanceRange().upper();
416 const double minZ = context.elevationRange().lower();
417 const double maxZ = context.elevationRange().upper();
418
419 const QRectF visibleRegion( minDistance, minZ, maxDistance - minDistance, maxZ - minZ );
420 QPainterPath clipPath;
421 clipPath.addPolygon( context.worldTransform().map( visibleRegion ) );
422 painter->setClipPath( clipPath, Qt::ClipOperation::IntersectClip );
423
424 const QgsRectangle clipPathRect( clipPath.boundingRect() );
425
426 auto renderResult = [&context, &clipPathRect]( const Feature & profileFeature, QgsMarkerSymbol * markerSymbol, QgsLineSymbol * lineSymbol, QgsFillSymbol * fillSymbol )
427 {
428 if ( profileFeature.crossSectionGeometry.isEmpty() )
429 return;
430
431 QgsGeometry transformed = profileFeature.crossSectionGeometry;
432 transformed.transform( context.worldTransform() );
433
434 if ( !transformed.boundingBoxIntersects( clipPathRect ) )
435 return;
436
437 // we can take some shortcuts here, because we know that the geometry will already be segmentized and can't be a curved type
438 switch ( transformed.type() )
439 {
441 {
442 if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( transformed.constGet() ) )
443 {
444 markerSymbol->renderPoint( QPointF( point->x(), point->y() ), nullptr, context.renderContext() );
445 }
446 else if ( const QgsMultiPoint *multipoint = qgsgeometry_cast< const QgsMultiPoint * >( transformed.constGet() ) )
447 {
448 const int numGeometries = multipoint->numGeometries();
449 for ( int i = 0; i < numGeometries; ++i )
450 {
451 markerSymbol->renderPoint( QPointF( multipoint->pointN( i )->x(), multipoint->pointN( i )->y() ), nullptr, context.renderContext() );
452 }
453 }
454 break;
455 }
456
458 {
459 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( transformed.constGet() ) )
460 {
461 lineSymbol->renderPolyline( line->asQPolygonF(), nullptr, context.renderContext() );
462 }
463 else if ( const QgsMultiLineString *multiLinestring = qgsgeometry_cast< const QgsMultiLineString * >( transformed.constGet() ) )
464 {
465 const int numGeometries = multiLinestring->numGeometries();
466 for ( int i = 0; i < numGeometries; ++i )
467 {
468 lineSymbol->renderPolyline( multiLinestring->lineStringN( i )->asQPolygonF(), nullptr, context.renderContext() );
469 }
470 }
471 break;
472 }
473
475 {
476 if ( const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( transformed.constGet() ) )
477 {
478 if ( const QgsCurve *exterior = polygon->exteriorRing() )
479 fillSymbol->renderPolygon( exterior->asQPolygonF(), nullptr, nullptr, context.renderContext() );
480 }
481 else if ( const QgsMultiPolygon *multiPolygon = qgsgeometry_cast< const QgsMultiPolygon * >( transformed.constGet() ) )
482 {
483 const int numGeometries = multiPolygon->numGeometries();
484 for ( int i = 0; i < numGeometries; ++i )
485 {
486 fillSymbol->renderPolygon( multiPolygon->polygonN( i )->exteriorRing()->asQPolygonF(), nullptr, nullptr, context.renderContext() );
487 }
488 }
489 break;
490 }
491
494 return;
495 }
496 };
497
499 req.setFilterFids( qgis::listToSet( features.keys() ) );
500
501 if ( respectLayerSymbology && mLayer && mLayer->renderer() )
502 {
503 std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer()->clone() );
504 renderer->startRender( context.renderContext(), mLayer->fields() );
505
506 // if we are respecting the layer's symbology then we'll fire off a feature request and iterate through
507 // features from the profile, rendering each in turn
508 QSet<QString> attributes = renderer->usedAttributes( context.renderContext() );
509
510 std::unique_ptr< QgsMarkerSymbol > marker( mMarkerSymbol->clone() );
511 std::unique_ptr< QgsLineSymbol > line( mLineSymbol->clone() );
512 std::unique_ptr< QgsFillSymbol > fill( mFillSymbol->clone() );
513 attributes.unite( marker->usedAttributes( context.renderContext() ) );
514 attributes.unite( line->usedAttributes( context.renderContext() ) );
515 attributes.unite( fill->usedAttributes( context.renderContext() ) );
516
517 req.setSubsetOfAttributes( attributes, mLayer->fields() );
518
519 QgsFeature feature;
520 QgsFeatureIterator it = mLayer->getFeatures( req );
521 while ( it.nextFeature( feature ) )
522 {
523 context.renderContext().expressionContext().setFeature( feature );
524 QgsSymbol *rendererSymbol = renderer->symbolForFeature( feature, context.renderContext() );
525 if ( !rendererSymbol )
526 continue;
527
528 marker->setColor( rendererSymbol->color() );
529 marker->setOpacity( rendererSymbol->opacity() );
530 line->setColor( rendererSymbol->color() );
531 line->setOpacity( rendererSymbol->opacity() );
532 fill->setColor( rendererSymbol->color() );
533 fill->setOpacity( rendererSymbol->opacity() );
534
535 marker->startRender( context.renderContext() );
536 line->startRender( context.renderContext() );
537 fill->startRender( context.renderContext() );
538
539 const QVector< Feature > profileFeatures = features.value( feature.id() );
540 for ( const Feature &profileFeature : profileFeatures )
541 {
542 renderResult( profileFeature,
543 rendererSymbol->type() == Qgis::SymbolType::Marker ? qgis::down_cast< QgsMarkerSymbol * >( rendererSymbol ) : marker.get(),
544 rendererSymbol->type() == Qgis::SymbolType::Line ? qgis::down_cast< QgsLineSymbol * >( rendererSymbol ) : line.get(),
545 rendererSymbol->type() == Qgis::SymbolType::Fill ? qgis::down_cast< QgsFillSymbol * >( rendererSymbol ) : fill.get() );
546 }
547
548 marker->stopRender( context.renderContext() );
549 line->stopRender( context.renderContext() );
550 fill->stopRender( context.renderContext() );
551 }
552
553 renderer->stopRender( context.renderContext() );
554 }
555 else if ( mLayer )
556 {
557 QSet<QString> attributes;
558 attributes.unite( mMarkerSymbol->usedAttributes( context.renderContext() ) );
559 attributes.unite( mFillSymbol->usedAttributes( context.renderContext() ) );
560 attributes.unite( mLineSymbol->usedAttributes( context.renderContext() ) );
561
562 mMarkerSymbol->startRender( context.renderContext() );
563 mFillSymbol->startRender( context.renderContext() );
564 mLineSymbol->startRender( context.renderContext() );
565 req.setSubsetOfAttributes( attributes, mLayer->fields() );
566
567 QgsFeature feature;
568 QgsFeatureIterator it = mLayer->getFeatures( req );
569 while ( it.nextFeature( feature ) )
570 {
571 context.renderContext().expressionContext().setFeature( feature );
572 const QVector< Feature > profileFeatures = features.value( feature.id() );
573 for ( const Feature &profileFeature : profileFeatures )
574 {
575 renderResult( profileFeature, mMarkerSymbol.get(), mLineSymbol.get(), mFillSymbol.get() );
576 }
577 }
578 mMarkerSymbol->stopRender( context.renderContext() );
579 mFillSymbol->stopRender( context.renderContext() );
580 mLineSymbol->stopRender( context.renderContext() );
581 }
582}
583
584void QgsVectorLayerProfileResults::renderMarkersOverContinuousSurfacePlot( QgsProfileRenderContext &context )
585{
586 QPainter *painter = context.renderContext().painter();
587 if ( !painter )
588 return;
589
590 const QgsScopedQPainterState painterState( painter );
591
592 painter->setBrush( Qt::NoBrush );
593 painter->setPen( Qt::NoPen );
594
595 const double minDistance = context.distanceRange().lower();
596 const double maxDistance = context.distanceRange().upper();
597 const double minZ = context.elevationRange().lower();
598 const double maxZ = context.elevationRange().upper();
599
600 const QRectF visibleRegion( minDistance, minZ, maxDistance - minDistance, maxZ - minZ );
601 QPainterPath clipPath;
602 clipPath.addPolygon( context.worldTransform().map( visibleRegion ) );
603 painter->setClipPath( clipPath, Qt::ClipOperation::IntersectClip );
604
605 mMarkerSymbol->startRender( context.renderContext() );
606
607 for ( auto pointIt = mDistanceToHeightMap.constBegin(); pointIt != mDistanceToHeightMap.constEnd(); ++pointIt )
608 {
609 if ( std::isnan( pointIt.value() ) )
610 continue;
611
612 mMarkerSymbol->renderPoint( context.worldTransform().map( QPointF( pointIt.key(), pointIt.value() ) ), nullptr, context.renderContext() );
613 }
614 mMarkerSymbol->stopRender( context.renderContext() );
615}
616
617QVector<QgsAbstractProfileResults::Feature> QgsVectorLayerProfileResults::asIndividualFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback ) const
618{
619 QVector<QgsAbstractProfileResults::Feature> res;
620 res.reserve( features.size() );
621 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
622 {
623 if ( feedback && feedback->isCanceled() )
624 break;
625
626 for ( const Feature &feature : it.value() )
627 {
628 if ( feedback && feedback->isCanceled() )
629 break;
630
632 outFeature.layerIdentifier = mId;
633 outFeature.attributes = {{QStringLiteral( "id" ), feature.featureId }};
634 switch ( type )
635 {
637 outFeature.geometry = feature.geometry;
638 break;
639
641 outFeature.geometry = feature.crossSectionGeometry;
642 break;
643
645 break; // unreachable
646 }
647 res << outFeature;
648 }
649 }
650 return res;
651}
652
654{
656 const QgsVectorLayerProfileGenerator *vlGenerator = qgis::down_cast< const QgsVectorLayerProfileGenerator * >( generator );
657
658 mId = vlGenerator->mId;
659 profileType = vlGenerator->mType;
660 respectLayerSymbology = vlGenerator->mRespectLayerSymbology;
661 mMarkerSymbol.reset( vlGenerator->mProfileMarkerSymbol->clone() );
662 mShowMarkerSymbolInSurfacePlots = vlGenerator->mShowMarkerSymbolInSurfacePlots;
663}
664
665//
666// QgsVectorLayerProfileGenerator
667//
668
671 , mId( layer->id() )
672 , mFeedback( std::make_unique< QgsFeedback >() )
673 , mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
674 , mTerrainProvider( request.terrainProvider() ? request.terrainProvider()->clone() : nullptr )
675 , mTolerance( request.tolerance() )
676 , mSourceCrs( layer->crs() )
677 , mTargetCrs( request.crs() )
678 , mTransformContext( request.transformContext() )
679 , mExtent( layer->extent() )
680 , mSource( std::make_unique< QgsVectorLayerFeatureSource >( layer ) )
681 , mOffset( layer->elevationProperties()->zOffset() )
682 , mScale( layer->elevationProperties()->zScale() )
683 , mType( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->type() )
684 , mClamping( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->clamping() )
685 , mBinding( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->binding() )
686 , mExtrusionEnabled( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->extrusionEnabled() )
687 , mExtrusionHeight( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->extrusionHeight() )
688 , mExpressionContext( request.expressionContext() )
689 , mFields( layer->fields() )
690 , mDataDefinedProperties( layer->elevationProperties()->dataDefinedProperties() )
691 , mWkbType( layer->wkbType() )
692 , mRespectLayerSymbology( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->respectLayerSymbology() )
693 , mProfileMarkerSymbol( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileMarkerSymbol()->clone() )
694 , mShowMarkerSymbolInSurfacePlots( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->showMarkerSymbolInSurfacePlots() )
695 , mLayer( layer )
696{
697 if ( mTerrainProvider )
698 mTerrainProvider->prepare(); // must be done on main thread
699
700 // make sure profile curve is always 2d, or we may get unwanted z value averaging for intersections from GEOS
701 if ( mProfileCurve )
702 mProfileCurve->dropZValue();
703
704 mSymbology = qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileSymbology();
705 mElevationLimit = qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->elevationLimit();
706
707 mLineSymbol.reset( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileLineSymbol()->clone() );
708 mFillSymbol.reset( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileFillSymbol()->clone() );
709}
710
712{
713 return mId;
714}
715
717
719{
720 if ( !mProfileCurve || mFeedback->isCanceled() )
721 return false;
722
723 // we need to transform the profile curve to the vector's CRS
724 mTransformedCurve.reset( mProfileCurve->clone() );
725 mLayerToTargetTransform = QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext );
726 if ( mTerrainProvider )
727 mTargetToTerrainProviderTransform = QgsCoordinateTransform( mTargetCrs, mTerrainProvider->crs(), mTransformContext );
728
729 try
730 {
731 mTransformedCurve->transform( mLayerToTargetTransform, Qgis::TransformDirection::Reverse );
732 }
733 catch ( QgsCsException & )
734 {
735 QgsDebugError( QStringLiteral( "Error transforming profile line to vector CRS" ) );
736 return false;
737 }
738
739 const QgsRectangle profileCurveBoundingBox = mTransformedCurve->boundingBox();
740 if ( !profileCurveBoundingBox.intersects( mExtent ) )
741 return false;
742
743 if ( mFeedback->isCanceled() )
744 return false;
745
746 mResults = std::make_unique< QgsVectorLayerProfileResults >();
747 mResults->mLayer = mLayer;
748 mResults->copyPropertiesFromGenerator( this );
749
750 mProfileCurveEngine.reset( new QgsGeos( mProfileCurve.get() ) );
751 mProfileCurveEngine->prepareGeometry();
752
753 mDataDefinedProperties.prepare( mExpressionContext );
754
755 if ( mFeedback->isCanceled() )
756 return false;
757
758 switch ( QgsWkbTypes::geometryType( mWkbType ) )
759 {
761 if ( !generateProfileForPoints() )
762 return false;
763 break;
764
766 if ( !generateProfileForLines() )
767 return false;
768 break;
769
771 if ( !generateProfileForPolygons() )
772 return false;
773 break;
774
777 return false;
778 }
779
780 return true;
781}
782
787
789{
790 return mFeedback.get();
791}
792
793bool QgsVectorLayerProfileGenerator::generateProfileForPoints()
794{
795 // get features from layer
796 QgsFeatureRequest request;
797 request.setDestinationCrs( mTargetCrs, mTransformContext );
798 request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance );
799 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
800 request.setFeedback( mFeedback.get() );
801
802 // our feature request is using the optimised distance within check (allowing use of spatial index)
803 // BUT this will also include points which are within the tolerance distance before/after the end of line.
804 // So we also need to double check that they fall within the flat buffered curve too.
805 std::unique_ptr< QgsAbstractGeometry > bufferedCurve( mProfileCurveEngine->buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) );
806 QgsGeos bufferedCurveEngine( bufferedCurve.get() );
807 bufferedCurveEngine.prepareGeometry();
808
809 auto processPoint = [this, &bufferedCurveEngine]( const QgsFeature & feature, const QgsPoint * point )
810 {
811 if ( !bufferedCurveEngine.intersects( point ) )
812 return;
813
814 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ZOffset, mExpressionContext, mOffset );
815
816 const double height = featureZToHeight( point->x(), point->y(), point->z(), offset );
817 mResults->mRawPoints.append( QgsPoint( point->x(), point->y(), height ) );
818 mResults->minZ = std::min( mResults->minZ, height );
819 mResults->maxZ = std::max( mResults->maxZ, height );
820
821 QString lastError;
822 const double distance = mProfileCurveEngine->lineLocatePoint( *point, &lastError );
823 mResults->mDistanceToHeightMap.insert( distance, height );
824
826 resultFeature.featureId = feature.id();
827 if ( mExtrusionEnabled )
828 {
829 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
830
831 resultFeature.geometry = QgsGeometry( new QgsLineString( QgsPoint( point->x(), point->y(), height ),
832 QgsPoint( point->x(), point->y(), height + extrusion ) ) );
833 resultFeature.crossSectionGeometry = QgsGeometry( new QgsLineString( QgsPoint( distance, height ),
834 QgsPoint( distance, height + extrusion ) ) );
835 mResults->minZ = std::min( mResults->minZ, height + extrusion );
836 mResults->maxZ = std::max( mResults->maxZ, height + extrusion );
837 }
838 else
839 {
840 resultFeature.geometry = QgsGeometry( new QgsPoint( point->x(), point->y(), height ) );
841 resultFeature.crossSectionGeometry = QgsGeometry( new QgsPoint( distance, height ) );
842 }
843 mResults->features[resultFeature.featureId].append( resultFeature );
844 };
845
846 QgsFeature feature;
847 QgsFeatureIterator it = mSource->getFeatures( request );
848 while ( it.nextFeature( feature ) )
849 {
850 if ( mFeedback->isCanceled() )
851 return false;
852
853 mExpressionContext.setFeature( feature );
854
855 const QgsGeometry g = feature.geometry();
856 if ( g.isMultipart() )
857 {
858 for ( auto it = g.const_parts_begin(); it != g.const_parts_end(); ++it )
859 {
860 processPoint( feature, qgsgeometry_cast< const QgsPoint * >( *it ) );
861 }
862 }
863 else
864 {
865 processPoint( feature, qgsgeometry_cast< const QgsPoint * >( g.constGet() ) );
866 }
867 }
868 return true;
869}
870
871bool QgsVectorLayerProfileGenerator::generateProfileForLines()
872{
873 // get features from layer
874 QgsFeatureRequest request;
875 request.setDestinationCrs( mTargetCrs, mTransformContext );
876 request.setFilterRect( mProfileCurve->boundingBox() );
877 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
878 request.setFeedback( mFeedback.get() );
879
880 auto processCurve = [this]( const QgsFeature & feature, const QgsCurve * curve )
881 {
882 QString error;
883 std::unique_ptr< QgsAbstractGeometry > intersection( mProfileCurveEngine->intersection( curve, &error ) );
884 if ( !intersection )
885 return;
886
887 if ( mFeedback->isCanceled() )
888 return;
889
890 QgsGeos curveGeos( curve );
891 curveGeos.prepareGeometry();
892
893 if ( mFeedback->isCanceled() )
894 return;
895
896 for ( auto it = intersection->const_parts_begin(); it != intersection->const_parts_end(); ++it )
897 {
898 if ( mFeedback->isCanceled() )
899 return;
900
901 if ( const QgsPoint *intersectionPoint = qgsgeometry_cast< const QgsPoint * >( *it ) )
902 {
903 // unfortunately we need to do some work to interpolate the z value for the line -- GEOS doesn't give us this
904 const double distance = curveGeos.lineLocatePoint( *intersectionPoint, &error );
905 std::unique_ptr< QgsPoint > interpolatedPoint( curve->interpolatePoint( distance ) );
906
907 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ZOffset, mExpressionContext, mOffset );
908
909 const double height = featureZToHeight( interpolatedPoint->x(), interpolatedPoint->y(), interpolatedPoint->z(), offset );
910 mResults->mRawPoints.append( QgsPoint( interpolatedPoint->x(), interpolatedPoint->y(), height ) );
911 mResults->minZ = std::min( mResults->minZ, height );
912 mResults->maxZ = std::max( mResults->maxZ, height );
913
914 const double distanceAlongProfileCurve = mProfileCurveEngine->lineLocatePoint( *interpolatedPoint, &error );
915 mResults->mDistanceToHeightMap.insert( distanceAlongProfileCurve, height );
916
918 resultFeature.featureId = feature.id();
919 if ( mExtrusionEnabled )
920 {
921 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
922
923 resultFeature.geometry = QgsGeometry( new QgsLineString( QgsPoint( interpolatedPoint->x(), interpolatedPoint->y(), height ),
924 QgsPoint( interpolatedPoint->x(), interpolatedPoint->y(), height + extrusion ) ) );
925 resultFeature.crossSectionGeometry = QgsGeometry( new QgsLineString( QgsPoint( distanceAlongProfileCurve, height ),
926 QgsPoint( distanceAlongProfileCurve, height + extrusion ) ) );
927 mResults->minZ = std::min( mResults->minZ, height + extrusion );
928 mResults->maxZ = std::max( mResults->maxZ, height + extrusion );
929 }
930 else
931 {
932 resultFeature.geometry = QgsGeometry( new QgsPoint( interpolatedPoint->x(), interpolatedPoint->y(), height ) );
933 resultFeature.crossSectionGeometry = QgsGeometry( new QgsPoint( distanceAlongProfileCurve, height ) );
934 }
935 mResults->features[resultFeature.featureId].append( resultFeature );
936 }
937 else if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( *it ) )
938 {
939 const int numPoints = ls->numPoints();
940 QVector< double > newX;
941 newX.resize( numPoints );
942 QVector< double > newY;
943 newY.resize( numPoints );
944 QVector< double > newZ;
945 newZ.resize( numPoints );
946 QVector< double > newDistance;
947 newDistance.resize( numPoints );
948
949 const double *inX = ls->xData();
950 const double *inY = ls->yData();
951 const double *inZ = ls->zData();
952 double *outX = newX.data();
953 double *outY = newY.data();
954 double *outZ = newZ.data();
955 double *outDistance = newDistance.data();
956
957 QVector< double > extrudedZ;
958 double *extZOut = nullptr;
959 double extrusion = 0;
960 if ( mExtrusionEnabled )
961 {
962 extrudedZ.resize( numPoints );
963 extZOut = extrudedZ.data();
964
965 extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
966 }
967
968 QString lastError;
969 for ( int i = 0 ; i < numPoints; ++i )
970 {
971 double x = *inX++;
972 double y = *inY++;
973
974 // find z value from original curve by interpolating to this point
975 const double distanceAlongOriginalGeometry = curveGeos.lineLocatePoint( QgsPoint( x, y ) );
976 std::unique_ptr< QgsPoint > closestOriginalPoint( curve->interpolatePoint( distanceAlongOriginalGeometry ) );
977
978 double z = *inZ++;
979
980 *outX++ = x;
981 *outY++ = y;
982 *outZ++ = closestOriginalPoint->z();
983 if ( extZOut )
984 *extZOut++ = z + extrusion;
985
986 mResults->mRawPoints.append( QgsPoint( x, y, z ) );
987 mResults->minZ = std::min( mResults->minZ, z );
988 mResults->maxZ = std::max( mResults->maxZ, z );
989 if ( mExtrusionEnabled )
990 {
991 mResults->minZ = std::min( mResults->minZ, z + extrusion );
992 mResults->maxZ = std::max( mResults->maxZ, z + extrusion );
993 }
994
995 const double distance = mProfileCurveEngine->lineLocatePoint( QgsPoint( x, y ), &lastError );
996 *outDistance++ = distance;
997
998 mResults->mDistanceToHeightMap.insert( distance, z );
999 }
1000
1002 resultFeature.featureId = feature.id();
1003
1004 if ( mExtrusionEnabled )
1005 {
1006 std::unique_ptr< QgsLineString > ring = std::make_unique< QgsLineString >( newX, newY, newZ );
1007 std::unique_ptr< QgsLineString > extrudedRing = std::make_unique< QgsLineString >( newX, newY, extrudedZ );
1008 std::unique_ptr< QgsLineString > reversedExtrusion( extrudedRing->reversed() );
1009 ring->append( reversedExtrusion.get() );
1010 ring->close();
1011 resultFeature.geometry = QgsGeometry( new QgsPolygon( ring.release() ) );
1012
1013
1014 std::unique_ptr< QgsLineString > distanceVHeightRing = std::make_unique< QgsLineString >( newDistance, newZ );
1015 std::unique_ptr< QgsLineString > extrudedDistanceVHeightRing = std::make_unique< QgsLineString >( newDistance, extrudedZ );
1016 std::unique_ptr< QgsLineString > reversedDistanceVHeightExtrusion( extrudedDistanceVHeightRing->reversed() );
1017 distanceVHeightRing->append( reversedDistanceVHeightExtrusion.get() );
1018 distanceVHeightRing->close();
1019 resultFeature.crossSectionGeometry = QgsGeometry( new QgsPolygon( distanceVHeightRing.release() ) );
1020 }
1021 else
1022 {
1023 resultFeature.geometry = QgsGeometry( new QgsLineString( newX, newY, newZ ) );
1024 resultFeature.crossSectionGeometry = QgsGeometry( new QgsLineString( newDistance, newZ ) );
1025 }
1026 mResults->features[resultFeature.featureId].append( resultFeature );
1027 }
1028 }
1029 };
1030
1031 QgsFeature feature;
1032 QgsFeatureIterator it = mSource->getFeatures( request );
1033 while ( it.nextFeature( feature ) )
1034 {
1035 if ( mFeedback->isCanceled() )
1036 return false;
1037
1038 if ( !mProfileCurveEngine->intersects( feature.geometry().constGet() ) )
1039 continue;
1040
1041 mExpressionContext.setFeature( feature );
1042
1043 const QgsGeometry g = feature.geometry();
1044 if ( g.isMultipart() )
1045 {
1046 for ( auto it = g.const_parts_begin(); it != g.const_parts_end(); ++it )
1047 {
1048 if ( !mProfileCurveEngine->intersects( *it ) )
1049 continue;
1050
1051 processCurve( feature, qgsgeometry_cast< const QgsCurve * >( *it ) );
1052 }
1053 }
1054 else
1055 {
1056 processCurve( feature, qgsgeometry_cast< const QgsCurve * >( g.constGet() ) );
1057 }
1058 }
1059 return true;
1060}
1061
1062bool QgsVectorLayerProfileGenerator::generateProfileForPolygons()
1063{
1064 // get features from layer
1065 QgsFeatureRequest request;
1066 request.setDestinationCrs( mTargetCrs, mTransformContext );
1067 request.setFilterRect( mProfileCurve->boundingBox() );
1068 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
1069 request.setFeedback( mFeedback.get() );
1070
1071 auto interpolatePointOnTriangle = []( const QgsPolygon * triangle, double x, double y ) -> QgsPoint
1072 {
1073 QgsPoint p1, p2, p3;
1075 triangle->exteriorRing()->pointAt( 0, p1, vt );
1076 triangle->exteriorRing()->pointAt( 1, p2, vt );
1077 triangle->exteriorRing()->pointAt( 2, p3, vt );
1078 const double z = QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, p1.z(), p2.z(), p3.z(), QgsPointXY( x, y ) );
1079 return QgsPoint( x, y, z );
1080 };
1081
1082 std::function< void( const QgsPolygon *triangle, const QgsAbstractGeometry *intersect, QVector< QgsGeometry > &, QVector< QgsGeometry > & ) > processTriangleLineIntersect;
1083 processTriangleLineIntersect = [this, &interpolatePointOnTriangle, &processTriangleLineIntersect]( const QgsPolygon * triangle, const QgsAbstractGeometry * intersect, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts )
1084 {
1085 // intersect may be a (multi)point or (multi)linestring
1086 switch ( QgsWkbTypes::geometryType( intersect->wkbType() ) )
1087 {
1089 if ( const QgsMultiPoint *mp = qgsgeometry_cast< const QgsMultiPoint * >( intersect ) )
1090 {
1091 const int numPoint = mp->numGeometries();
1092 for ( int i = 0; i < numPoint; ++i )
1093 {
1094 processTriangleLineIntersect( triangle, mp->geometryN( i ), transformedParts, crossSectionParts );
1095 }
1096 }
1097 else if ( const QgsPoint *p = qgsgeometry_cast< const QgsPoint * >( intersect ) )
1098 {
1099 const QgsPoint interpolatedPoint = interpolatePointOnTriangle( triangle, p->x(), p->y() );
1100 mResults->mRawPoints.append( interpolatedPoint );
1101 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() );
1102 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() );
1103
1104 QString lastError;
1105 const double distance = mProfileCurveEngine->lineLocatePoint( *p, &lastError );
1106 mResults->mDistanceToHeightMap.insert( distance, interpolatedPoint.z() );
1107
1108 if ( mExtrusionEnabled )
1109 {
1110 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
1111
1112 transformedParts.append( QgsGeometry( new QgsLineString( interpolatedPoint,
1113 QgsPoint( interpolatedPoint.x(), interpolatedPoint.y(), interpolatedPoint.z() + extrusion ) ) ) );
1114 crossSectionParts.append( QgsGeometry( new QgsLineString( QgsPoint( distance, interpolatedPoint.z() ),
1115 QgsPoint( distance, interpolatedPoint.z() + extrusion ) ) ) );
1116 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() + extrusion );
1117 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() + extrusion );
1118 }
1119 else
1120 {
1121 transformedParts.append( QgsGeometry( new QgsPoint( interpolatedPoint ) ) );
1122 crossSectionParts.append( QgsGeometry( new QgsPoint( distance, interpolatedPoint.z() ) ) );
1123 }
1124 }
1125 break;
1127 if ( const QgsMultiLineString *ml = qgsgeometry_cast< const QgsMultiLineString * >( intersect ) )
1128 {
1129 const int numLines = ml->numGeometries();
1130 for ( int i = 0; i < numLines; ++i )
1131 {
1132 processTriangleLineIntersect( triangle, ml->geometryN( i ), transformedParts, crossSectionParts );
1133 }
1134 }
1135 else if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( intersect ) )
1136 {
1137 const int numPoints = ls->numPoints();
1138 QVector< double > newX;
1139 newX.resize( numPoints );
1140 QVector< double > newY;
1141 newY.resize( numPoints );
1142 QVector< double > newZ;
1143 newZ.resize( numPoints );
1144 QVector< double > newDistance;
1145 newDistance.resize( numPoints );
1146
1147 const double *inX = ls->xData();
1148 const double *inY = ls->yData();
1149 double *outX = newX.data();
1150 double *outY = newY.data();
1151 double *outZ = newZ.data();
1152 double *outDistance = newDistance.data();
1153
1154 QVector< double > extrudedZ;
1155 double *extZOut = nullptr;
1156 double extrusion = 0;
1157 if ( mExtrusionEnabled )
1158 {
1159 extrudedZ.resize( numPoints );
1160 extZOut = extrudedZ.data();
1161
1162 extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
1163 }
1164
1165 QString lastError;
1166 for ( int i = 0 ; i < numPoints; ++i )
1167 {
1168 double x = *inX++;
1169 double y = *inY++;
1170
1171 QgsPoint interpolatedPoint = interpolatePointOnTriangle( triangle, x, y );
1172 *outX++ = x;
1173 *outY++ = y;
1174 *outZ++ = interpolatedPoint.z();
1175 if ( extZOut )
1176 *extZOut++ = interpolatedPoint.z() + extrusion;
1177
1178 mResults->mRawPoints.append( interpolatedPoint );
1179 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() );
1180 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() );
1181 if ( mExtrusionEnabled )
1182 {
1183 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() + extrusion );
1184 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() + extrusion );
1185 }
1186
1187 const double distance = mProfileCurveEngine->lineLocatePoint( interpolatedPoint, &lastError );
1188 *outDistance++ = distance;
1189
1190 mResults->mDistanceToHeightMap.insert( distance, interpolatedPoint.z() );
1191 }
1192
1193 if ( mExtrusionEnabled )
1194 {
1195 std::unique_ptr< QgsLineString > ring = std::make_unique< QgsLineString >( newX, newY, newZ );
1196 std::unique_ptr< QgsLineString > extrudedRing = std::make_unique< QgsLineString >( newX, newY, extrudedZ );
1197 std::unique_ptr< QgsLineString > reversedExtrusion( extrudedRing->reversed() );
1198 ring->append( reversedExtrusion.get() );
1199 ring->close();
1200 transformedParts.append( QgsGeometry( new QgsPolygon( ring.release() ) ) );
1201
1202
1203 std::unique_ptr< QgsLineString > distanceVHeightRing = std::make_unique< QgsLineString >( newDistance, newZ );
1204 std::unique_ptr< QgsLineString > extrudedDistanceVHeightRing = std::make_unique< QgsLineString >( newDistance, extrudedZ );
1205 std::unique_ptr< QgsLineString > reversedDistanceVHeightExtrusion( extrudedDistanceVHeightRing->reversed() );
1206 distanceVHeightRing->append( reversedDistanceVHeightExtrusion.get() );
1207 distanceVHeightRing->close();
1208 crossSectionParts.append( QgsGeometry( new QgsPolygon( distanceVHeightRing.release() ) ) );
1209 }
1210 else
1211 {
1212 transformedParts.append( QgsGeometry( new QgsLineString( newX, newY, newZ ) ) );
1213 crossSectionParts.append( QgsGeometry( new QgsLineString( newDistance, newZ ) ) );
1214 }
1215 }
1216 break;
1217
1221 return;
1222 }
1223 };
1224
1225 auto triangleIsCollinearInXYPlane = []( const QgsPolygon * polygon )-> bool
1226 {
1227 const QgsLineString *ring = qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() );
1228 return QgsGeometryUtils::pointsAreCollinear( ring->xAt( 0 ), ring->yAt( 0 ),
1229 ring->xAt( 1 ), ring->yAt( 1 ),
1230 ring->xAt( 2 ), ring->yAt( 2 ), 0.005 );
1231 };
1232
1233 auto processPolygon = [this, &triangleIsCollinearInXYPlane, &processTriangleLineIntersect]( const QgsCurvePolygon * polygon, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts, double offset, bool & wasCollinear )
1234 {
1235 std::unique_ptr< QgsPolygon > clampedPolygon;
1236 if ( const QgsPolygon *p = qgsgeometry_cast< const QgsPolygon * >( polygon ) )
1237 {
1238 clampedPolygon.reset( p->clone() );
1239 }
1240 else
1241 {
1242 clampedPolygon.reset( qgsgeometry_cast< QgsPolygon * >( polygon->segmentize() ) );
1243 }
1244 clampAltitudes( clampedPolygon.get(), offset );
1245
1246 if ( mFeedback->isCanceled() )
1247 return;
1248
1249 QgsGeometry tessellation;
1250 if ( clampedPolygon->numInteriorRings() == 0 && clampedPolygon->exteriorRing() && clampedPolygon->exteriorRing()->numPoints() == 4 && clampedPolygon->exteriorRing()->isClosed() )
1251 {
1252 // special case -- polygon is already a triangle, so no need to tessellate
1253 std::unique_ptr< QgsMultiPolygon > multiPolygon = std::make_unique< QgsMultiPolygon >();
1254 multiPolygon->addGeometry( clampedPolygon.release() );
1255 tessellation = QgsGeometry( std::move( multiPolygon ) );
1256 }
1257 else
1258 {
1259 const QgsRectangle bounds = clampedPolygon->boundingBox();
1260 QgsTessellator t( bounds, false, false, false, false );
1261 t.addPolygon( *clampedPolygon, 0 );
1262
1263 tessellation = QgsGeometry( t.asMultiPolygon() );
1264 if ( mFeedback->isCanceled() )
1265 return;
1266
1267 tessellation.translate( bounds.xMinimum(), bounds.yMinimum() );
1268 }
1269
1270 // iterate through the tessellation, finding triangles which intersect the line
1271 const int numTriangles = qgsgeometry_cast< const QgsMultiPolygon * >( tessellation.constGet() )->numGeometries();
1272 for ( int i = 0; i < numTriangles; ++i )
1273 {
1274 if ( mFeedback->isCanceled() )
1275 return;
1276
1277 const QgsPolygon *triangle = qgsgeometry_cast< const QgsPolygon * >( qgsgeometry_cast< const QgsMultiPolygon * >( tessellation.constGet() )->geometryN( i ) );
1278
1279 if ( triangleIsCollinearInXYPlane( triangle ) )
1280 {
1281 wasCollinear = true;
1282 const QgsLineString *ring = qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() );
1283
1284 QString lastError;
1285 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( mProfileCurve.get() ) )
1286 {
1287 for ( int curveSegmentIndex = 0; curveSegmentIndex < mProfileCurve->numPoints() - 1; ++curveSegmentIndex )
1288 {
1289 const QgsPoint p1 = ls->pointN( curveSegmentIndex );
1290 const QgsPoint p2 = ls->pointN( curveSegmentIndex + 1 );
1291
1292 QgsPoint intersectionPoint;
1293 double minZ = std::numeric_limits< double >::max();
1294 double maxZ = std::numeric_limits< double >::lowest();
1295
1296 for ( auto vertexPair : std::array<std::pair<int, int>, 3> {{ { 0, 1}, {1, 2}, {2, 0} }} )
1297 {
1298 bool isIntersection = false;
1299 if ( QgsGeometryUtils::segmentIntersection( ring->pointN( vertexPair.first ), ring->pointN( vertexPair.second ), p1, p2, intersectionPoint, isIntersection ) )
1300 {
1301 const double fraction = QgsGeometryUtils::pointFractionAlongLine( ring->xAt( vertexPair.first ), ring->yAt( vertexPair.first ), ring->xAt( vertexPair.second ), ring->yAt( vertexPair.second ), intersectionPoint.x(), intersectionPoint.y() );
1302 const double intersectionZ = ring->zAt( vertexPair.first ) + ( ring->zAt( vertexPair.second ) - ring->zAt( vertexPair.first ) ) * fraction;
1303 minZ = std::min( minZ, intersectionZ );
1304 maxZ = std::max( maxZ, intersectionZ );
1305 }
1306 }
1307
1308 if ( !intersectionPoint.isEmpty() )
1309 {
1310 // need z?
1311 mResults->mRawPoints.append( intersectionPoint );
1312 mResults->minZ = std::min( mResults->minZ, minZ );
1313 mResults->maxZ = std::max( mResults->maxZ, maxZ );
1314
1315 const double distance = mProfileCurveEngine->lineLocatePoint( intersectionPoint, &lastError );
1316
1317 crossSectionParts.append( QgsGeometry( new QgsLineString( QVector< double > {distance, distance}, QVector< double > {minZ, maxZ} ) ) );
1318
1319 mResults->mDistanceToHeightMap.insert( distance, minZ );
1320 mResults->mDistanceToHeightMap.insert( distance, maxZ );
1321 }
1322 }
1323 }
1324 else
1325 {
1326 // curved geometries, not supported yet, but not possible through the GUI anyway
1327 QgsDebugError( QStringLiteral( "Collinear triangles with curved profile lines are not supported yet" ) );
1328 }
1329 }
1330 else
1331 {
1332 if ( mProfileCurveEngine->intersects( triangle ) )
1333 {
1334 QString error;
1335 std::unique_ptr< QgsAbstractGeometry > intersection( mProfileCurveEngine->intersection( triangle, &error ) );
1336 if ( intersection && !intersection->isEmpty() )
1337 {
1338 processTriangleLineIntersect( triangle, intersection.get(), transformedParts, crossSectionParts );
1339 }
1340 }
1341 }
1342 }
1343 };
1344
1345 QgsFeature feature;
1346 QgsFeatureIterator it = mSource->getFeatures( request );
1347 while ( it.nextFeature( feature ) )
1348 {
1349 if ( mFeedback->isCanceled() )
1350 return false;
1351
1352 if ( !mProfileCurveEngine->intersects( feature.geometry().constGet() ) )
1353 continue;
1354
1355 mExpressionContext.setFeature( feature );
1356
1357 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ZOffset, mExpressionContext, mOffset );
1358
1359 const QgsGeometry g = feature.geometry();
1360 QVector< QgsGeometry > transformedParts;
1361 QVector< QgsGeometry > crossSectionParts;
1362 bool wasCollinear = false;
1363 if ( g.isMultipart() )
1364 {
1365 for ( auto it = g.const_parts_begin(); it != g.const_parts_end(); ++it )
1366 {
1367 if ( mFeedback->isCanceled() )
1368 break;
1369
1370 if ( !mProfileCurveEngine->intersects( *it ) )
1371 continue;
1372
1373 processPolygon( qgsgeometry_cast< const QgsCurvePolygon * >( *it ), transformedParts, crossSectionParts, offset, wasCollinear );
1374 }
1375 }
1376 else
1377 {
1378 processPolygon( qgsgeometry_cast< const QgsCurvePolygon * >( g.constGet() ), transformedParts, crossSectionParts, offset, wasCollinear );
1379 }
1380
1381 if ( mFeedback->isCanceled() )
1382 return false;
1383
1385 resultFeature.featureId = feature.id();
1386 resultFeature.geometry = transformedParts.size() > 1 ? QgsGeometry::collectGeometry( transformedParts ) : transformedParts.value( 0 );
1387 if ( !crossSectionParts.empty() )
1388 {
1389 if ( !wasCollinear )
1390 {
1391 QgsGeometry unioned = QgsGeometry::unaryUnion( crossSectionParts );
1392 if ( unioned.type() == Qgis::GeometryType::Line )
1393 unioned = unioned.mergeLines();
1394 resultFeature.crossSectionGeometry = unioned;
1395 }
1396 else
1397 {
1398 resultFeature.crossSectionGeometry = QgsGeometry::collectGeometry( crossSectionParts );
1399 }
1400 }
1401 mResults->features[resultFeature.featureId].append( resultFeature );
1402 }
1403 return true;
1404}
1405
1406double QgsVectorLayerProfileGenerator::terrainHeight( double x, double y )
1407{
1408 if ( !mTerrainProvider )
1409 return std::numeric_limits<double>::quiet_NaN();
1410
1411 // transform feature point to terrain provider crs
1412 try
1413 {
1414 double dummyZ = 0;
1415 mTargetToTerrainProviderTransform.transformInPlace( x, y, dummyZ );
1416 }
1417 catch ( QgsCsException & )
1418 {
1419 return std::numeric_limits<double>::quiet_NaN();
1420 }
1421
1422 return mTerrainProvider->heightAt( x, y );
1423}
1424
1425double QgsVectorLayerProfileGenerator::featureZToHeight( double x, double y, double z, double offset )
1426{
1427 switch ( mClamping )
1428 {
1430 break;
1431
1434 {
1435 const double terrainZ = terrainHeight( x, y );
1436 if ( !std::isnan( terrainZ ) )
1437 {
1438 switch ( mClamping )
1439 {
1441 if ( std::isnan( z ) )
1442 z = terrainZ;
1443 else
1444 z += terrainZ;
1445 break;
1446
1448 z = terrainZ;
1449 break;
1450
1452 break;
1453 }
1454 }
1455 break;
1456 }
1457 }
1458
1459 return ( std::isnan( z ) ? 0 : z ) * mScale + offset;
1460}
1461
1462void QgsVectorLayerProfileGenerator::clampAltitudes( QgsLineString *lineString, const QgsPoint &centroid, double offset )
1463{
1464 for ( int i = 0; i < lineString->nCoordinates(); ++i )
1465 {
1466 if ( mFeedback->isCanceled() )
1467 break;
1468
1469 double terrainZ = 0;
1470 switch ( mClamping )
1471 {
1474 {
1475 QgsPointXY pt;
1476 switch ( mBinding )
1477 {
1479 pt.setX( lineString->xAt( i ) );
1480 pt.setY( lineString->yAt( i ) );
1481 break;
1482
1484 pt.set( centroid.x(), centroid.y() );
1485 break;
1486 }
1487
1488 terrainZ = terrainHeight( pt.x(), pt.y() );
1489 break;
1490 }
1491
1493 break;
1494 }
1495
1496 double geomZ = 0;
1497
1498 switch ( mClamping )
1499 {
1502 geomZ = lineString->zAt( i );
1503 break;
1504
1506 break;
1507 }
1508
1509 const double z = ( terrainZ + ( std::isnan( geomZ ) ? 0 : geomZ ) ) * mScale + offset;
1510 lineString->setZAt( i, z );
1511 }
1512}
1513
1514bool QgsVectorLayerProfileGenerator::clampAltitudes( QgsPolygon *polygon, double offset )
1515{
1516 if ( !polygon->is3D() )
1517 polygon->addZValue( 0 );
1518
1519 QgsPoint centroid;
1520 switch ( mBinding )
1521 {
1523 break;
1524
1526 centroid = polygon->centroid();
1527 break;
1528 }
1529
1530 QgsCurve *curve = const_cast<QgsCurve *>( polygon->exteriorRing() );
1531 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( curve );
1532 if ( !lineString )
1533 return false;
1534
1535 clampAltitudes( lineString, centroid, offset );
1536
1537 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
1538 {
1539 if ( mFeedback->isCanceled() )
1540 break;
1541
1542 QgsCurve *curve = const_cast<QgsCurve *>( polygon->interiorRing( i ) );
1543 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( curve );
1544 if ( !lineString )
1545 return false;
1546
1547 clampAltitudes( lineString, centroid, offset );
1548 }
1549 return true;
1550}
1551
The Qgis class provides global constants for use throughout the application.
Definition qgis.h:54
@ Relative
Elevation is relative to terrain height (final elevation = terrain elevation + feature elevation)
@ Terrain
Elevation is clamped to terrain (final elevation = terrain elevation)
@ Absolute
Elevation is taken directly from feature and is independent of terrain height (final elevation = feat...
VertexType
Types of vertex.
Definition qgis.h:2223
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
@ Round
Use rounded joins.
@ Centroid
Clamp just centroid of feature.
@ Vertex
Clamp every vertex of feature.
@ Flat
Flat cap (in line with start/end of line)
@ ContinuousSurface
The features should be treated as representing values on a continuous surface (eg contour lines)
@ IndividualFeatures
Treat each feature as an individual object (eg buildings)
ProfileExportType
Types of export for elevation profiles.
Definition qgis.h:2863
@ 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.
@ Marker
Marker symbol.
@ Reverse
Reverse/inverse transform (from destination to source)
Abstract base class for all geometries.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual QgsPoint centroid() const
Returns the centroid of the geometry.
Abstract base class for objects which generate elevation profiles.
Abstract base class for storage of elevation profiles.
Abstract base class for objects which generate elevation profiles which represent a continuous surfac...
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< 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.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
Class for doing transforms between two map coordinate systems.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
Custom exception class for Coordinate Reference System related exceptions.
Curve polygon geometry type.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
virtual bool pointAt(int node, QgsPoint &point, Qgis::VertexType &type) const =0
Returns the point and vertex id of a point within the curve.
QgsRange which stores a range of double values.
Definition qgsrange.h:203
RAII class to pop scope from an expression context on destruction.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
void setFeedback(QgsFeedback *feedback)
Attach a feedback object that can be queried regularly by the iterator to check if it should be cance...
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
QgsFeatureRequest & setDistanceWithin(const QgsGeometry &geometry, double distance)
Sets a reference geometry and a maximum distance from this geometry to retrieve features within.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:56
QgsFeatureId id
Definition qgsfeature.h:64
QgsGeometry geometry
Definition qgsfeature.h:67
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:45
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:54
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
static bool segmentIntersection(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &q1, const QgsPoint &q2, QgsPoint &intersectionPoint, bool &isIntersection, double tolerance=1e-8, bool acceptImproperIntersection=false)
Compute the intersection between two segments.
static double pointFractionAlongLine(double x1, double y1, double x2, double y2, double px, double py)
Given the line (x1, y1) to (x2, y2) and a point (px, py) returns the fraction of the line length at w...
static bool pointsAreCollinear(double x1, double y1, double x2, double y2, double x3, double y3, double epsilon)
Given the points (x1, y1), (x2, y2) and (x3, y3) returns true if these points can be considered colli...
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
bool boundingBoxIntersects(const QgsRectangle &rectangle) const
Returns true if the bounding box of this geometry intersects with a rectangle.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
Qgis::GeometryType type
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
QgsGeometry mergeLines() const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
Qgis::GeometryOperationResult translate(double dx, double dy, double dz=0.0, double dm=0.0)
Translates this geometry by dx, dy, dz and dm.
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition qgsgeos.h:99
Line string geometry type, with support for z-dimension and m-values.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
int nCoordinates() const override
Returns the number of nodes contained in the geometry.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
void setZAt(int index, double z)
Sets the z-coordinate of the specified node in the line string.
double zAt(int index) const override
Returns the z-coordinate of the specified node in the line string.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
A line symbol type, for rendering LineString and MultiLineString geometries.
A marker symbol type, for rendering Point and MultiPoint geometries.
Multi line string geometry collection.
Multi point geometry collection.
Multi polygon geometry collection.
A class to represent a 2D point.
Definition qgspointxy.h:59
void setY(double y)
Sets the y value of the point.
Definition qgspointxy.h:132
void set(double x, double y)
Sets the x and y value of the point.
Definition qgspointxy.h:139
double y
Definition qgspointxy.h:63
double x
Definition qgspointxy.h:62
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:122
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double z
Definition qgspoint.h:54
double x
Definition qgspoint.h:52
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:773
double y
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:34
Encapsulates the context in which an elevation profile is to be generated.
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.
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 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.
Encapsulates results of snapping a profile point.
QgsProfilePoint snappedPoint
Snapped point.
QSet< QString > referencedFields(const QgsExpressionContext &context=QgsExpressionContext(), bool ignoreContext=false) const override
Returns the set of any fields referenced by the active properties from the collection.
bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const override
Prepares the collection against a specified expression context.
T lower() const
Returns the lower bound of the range.
Definition qgsrange.h:66
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:73
A rectangle specified with double values.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double xMaximum() const
Returns the x maximum value (right side of rectangle).
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
Scoped object for saving and restoring a QPainter object's state.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:94
void setColor(const QColor &color) const
Sets the color for the symbol.
qreal opacity() const
Returns the opacity for the symbol.
Definition qgssymbol.h:497
QColor color() const
Returns the symbol's color.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:153
Class that takes care of tessellation of polygons into triangles.
Vector layer specific subclass of QgsMapLayerElevationProperties.
Partial snapshot of vector layer's state (only the members necessary for access to features)
Implementation of QgsAbstractProfileGenerator for vector layers.
QgsAbstractProfileResults * takeResults() override
Takes results from the generator.
bool generateProfile(const QgsProfileGenerationContext &context=QgsProfileGenerationContext()) override
Generate the profile (based on data stored in the class).
QString sourceId() const override
Returns a unique identifier representing the source of the profile.
~QgsVectorLayerProfileGenerator() override
QgsFeedback * feedback() const override
Access to feedback object of the generator (may be nullptr)
QgsVectorLayerProfileGenerator(QgsVectorLayer *layer, const QgsProfileRequest &request)
Constructor for QgsVectorLayerProfileGenerator.
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context) override
Identify results visible at the specified profile point.
std::unique_ptr< QgsMarkerSymbol > mMarkerSymbol
QVector< QgsGeometry > asGeometries() const override
Returns a list of geometries representing the calculated elevation results.
void renderResults(QgsProfileRenderContext &context) override
Renders the results to the specified context.
QVector< QgsAbstractProfileResults::Feature > asFeatures(Qgis::ProfileExportType type, QgsFeedback *feedback=nullptr) const override
Returns a list of features representing the calculated elevation results.
QgsProfileSnapResult snapPoint(const QgsProfilePoint &point, const QgsProfileSnapContext &context) override
Snaps a point to the generated elevation profile.
QString type() const override
Returns the unique string identifier for the results type.
QHash< QgsFeatureId, QVector< Feature > > features
void copyPropertiesFromGenerator(const QgsAbstractProfileGenerator *generator) override
Copies properties from specified generator to the results object.
Represents a vector layer which manages a vector based data sets.
QgsMapLayerElevationProperties * elevationProperties() override
Returns the layer's elevation properties.
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
#define BUILTIN_UNREACHABLE
Definition qgis.h:4993
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:4332
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugError(str)
Definition qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
Encapsulates information about a feature exported from the profile results.
QString layerIdentifier
Identifier for grouping output features.
QVariantMap attributes
Exported attributes.
QgsGeometry crossSectionGeometry
Cross section distance vs height geometry for feature.
QgsGeometry geometry
Feature's geometry with any terrain height adjustment and extrusion applied.