QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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, qgsDoubleNear( minZ, maxZ ) ? minZ - 1 : 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, qgsDoubleNear( minZ, maxZ ) ? minZ - 1 : 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 if ( mTolerance == 0.0 ) // geos does not handle very well buffer with 0 size
754 {
755 mProfileBufferedCurve = std::unique_ptr<QgsAbstractGeometry>( mProfileCurve->clone() );
756 }
757 else
758 {
759 mProfileBufferedCurve = std::unique_ptr<QgsAbstractGeometry>( mProfileCurveEngine->buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) );
760 }
761
762 mProfileBufferedCurveEngine.reset( new QgsGeos( mProfileBufferedCurve.get() ) );
763 mProfileBufferedCurveEngine->prepareGeometry();
764
765 mDataDefinedProperties.prepare( mExpressionContext );
766
767 if ( mFeedback->isCanceled() )
768 return false;
769
770 switch ( QgsWkbTypes::geometryType( mWkbType ) )
771 {
773 if ( !generateProfileForPoints() )
774 return false;
775 break;
776
778 if ( !generateProfileForLines() )
779 return false;
780 break;
781
783 if ( !generateProfileForPolygons() )
784 return false;
785 break;
786
789 return false;
790 }
791
792 return true;
793}
794
796{
797 return mResults.release();
798}
799
801{
802 return mFeedback.get();
803}
804
805bool QgsVectorLayerProfileGenerator::generateProfileForPoints()
806{
807 // get features from layer
808 QgsFeatureRequest request;
809 request.setDestinationCrs( mTargetCrs, mTransformContext );
810 request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance );
811 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
812 request.setFeedback( mFeedback.get() );
813
814 // our feature request is using the optimised distance within check (allowing use of spatial index)
815 // BUT this will also include points which are within the tolerance distance before/after the end of line.
816 // So we also need to double check that they fall within the flat buffered curve too.
817
818 QgsFeature feature;
819 QgsFeatureIterator it = mSource->getFeatures( request );
820 while ( !mFeedback->isCanceled() && it.nextFeature( feature ) )
821 {
822 mExpressionContext.setFeature( feature );
823
824 const QgsGeometry g = feature.geometry();
825 for ( auto it = g.const_parts_begin(); !mFeedback->isCanceled() && it != g.const_parts_end(); ++it )
826 {
827 if ( mProfileBufferedCurveEngine->intersects( *it ) )
828 {
829 processIntersectionPoint( qgsgeometry_cast< const QgsPoint * >( *it ), feature );
830 }
831 }
832 }
833 return !mFeedback->isCanceled();
834}
835
836void QgsVectorLayerProfileGenerator::processIntersectionPoint( const QgsPoint *point, const QgsFeature &feature )
837{
838 QString error;
839 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ZOffset, mExpressionContext, mOffset );
840
841 const double height = featureZToHeight( point->x(), point->y(), point->z(), offset );
842 mResults->mRawPoints.append( QgsPoint( point->x(), point->y(), height ) );
843 mResults->minZ = std::min( mResults->minZ, height );
844 mResults->maxZ = std::max( mResults->maxZ, height );
845
846 const double distanceAlongProfileCurve = mProfileCurveEngine->lineLocatePoint( *point, &error );
847 mResults->mDistanceToHeightMap.insert( distanceAlongProfileCurve, height );
848
850 resultFeature.featureId = feature.id();
851 if ( mExtrusionEnabled )
852 {
853 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
854
855 resultFeature.geometry = QgsGeometry( new QgsLineString( QgsPoint( point->x(), point->y(), height ),
856 QgsPoint( point->x(), point->y(), height + extrusion ) ) );
857 resultFeature.crossSectionGeometry = QgsGeometry( new QgsLineString( QgsPoint( distanceAlongProfileCurve, height ),
858 QgsPoint( distanceAlongProfileCurve, height + extrusion ) ) );
859 mResults->minZ = std::min( mResults->minZ, height + extrusion );
860 mResults->maxZ = std::max( mResults->maxZ, height + extrusion );
861 }
862 else
863 {
864 resultFeature.geometry = QgsGeometry( new QgsPoint( point->x(), point->y(), height ) );
865 resultFeature.crossSectionGeometry = QgsGeometry( new QgsPoint( distanceAlongProfileCurve, height ) );
866 }
867
868 mResults->features[resultFeature.featureId].append( resultFeature );
869}
870
871void QgsVectorLayerProfileGenerator::processIntersectionCurve( const QgsLineString *intersectionCurve, const QgsFeature &feature )
872{
873 QString error;
874
876 resultFeature.featureId = feature.id();
877 double maxDistanceAlongProfileCurve = std::numeric_limits<double>::lowest();
878
879 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ZOffset, mExpressionContext, mOffset );
880 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
881
882 const int numPoints = intersectionCurve->numPoints();
883 QVector< double > newX( numPoints );
884 QVector< double > newY( numPoints );
885 QVector< double > newZ( numPoints );
886 QVector< double > newDistance( numPoints );
887
888 const double *inX = intersectionCurve->xData();
889 const double *inY = intersectionCurve->yData();
890 const double *inZ = intersectionCurve->is3D() ? intersectionCurve->zData() : nullptr;
891 double *outX = newX.data();
892 double *outY = newY.data();
893 double *outZ = newZ.data();
894 double *outDistance = newDistance.data();
895
896 QVector< double > extrudedZ;
897 double *extZOut = nullptr;
898 if ( mExtrusionEnabled )
899 {
900 extrudedZ.resize( numPoints );
901 extZOut = extrudedZ.data();
902 }
903
904 for ( int i = 0 ; ! mFeedback->isCanceled() && i < numPoints; ++i )
905 {
906 QgsPoint intersectionPoint( *inX, *inY, ( inZ ? *inZ : std::numeric_limits<double>::quiet_NaN() ) );
907
908 const double height = featureZToHeight( intersectionPoint.x(), intersectionPoint.y(), intersectionPoint.z(), offset );
909 const double distanceAlongProfileCurve = mProfileCurveEngine->lineLocatePoint( intersectionPoint, &error );
910
911 maxDistanceAlongProfileCurve = std::max( maxDistanceAlongProfileCurve, distanceAlongProfileCurve );
912
913 mResults->mRawPoints.append( QgsPoint( intersectionPoint.x(), intersectionPoint.y(), height ) );
914 mResults->minZ = std::min( mResults->minZ, height );
915 mResults->maxZ = std::max( mResults->maxZ, height );
916
917 mResults->mDistanceToHeightMap.insert( distanceAlongProfileCurve, height );
918 *outDistance++ = distanceAlongProfileCurve;
919
920 *outX++ = intersectionPoint.x();
921 *outY++ = intersectionPoint.y();
922 *outZ++ = height;
923 if ( extZOut )
924 *extZOut++ = height + extrusion;
925
926 if ( mExtrusionEnabled )
927 {
928 mResults->minZ = std::min( mResults->minZ, height + extrusion );
929 mResults->maxZ = std::max( mResults->maxZ, height + extrusion );
930 }
931 inX++;
932 inY++;
933 if ( inZ )
934 inZ++;
935 }
936
937 mResults->mDistanceToHeightMap.insert( maxDistanceAlongProfileCurve + 0.000001, std::numeric_limits<double>::quiet_NaN() );
938
939 if ( mFeedback->isCanceled() )
940 return;
941
942 // create geometries from vector data
943 if ( mExtrusionEnabled )
944 {
945 std::unique_ptr< QgsLineString > ring = std::make_unique< QgsLineString >( newX, newY, newZ );
946 std::unique_ptr< QgsLineString > extrudedRing = std::make_unique< QgsLineString >( newX, newY, extrudedZ );
947 std::unique_ptr< QgsLineString > reversedExtrusion( extrudedRing->reversed() );
948 ring->append( reversedExtrusion.get() );
949 ring->close();
950 resultFeature.geometry = QgsGeometry( new QgsPolygon( ring.release() ) );
951
952 std::unique_ptr< QgsLineString > distanceVHeightRing = std::make_unique< QgsLineString >( newDistance, newZ );
953 std::unique_ptr< QgsLineString > extrudedDistanceVHeightRing = std::make_unique< QgsLineString >( newDistance, extrudedZ );
954 std::unique_ptr< QgsLineString > reversedDistanceVHeightExtrusion( extrudedDistanceVHeightRing->reversed() );
955 distanceVHeightRing->append( reversedDistanceVHeightExtrusion.get() );
956 distanceVHeightRing->close();
957 resultFeature.crossSectionGeometry = QgsGeometry( new QgsPolygon( distanceVHeightRing.release() ) );
958 }
959 else
960 {
961 resultFeature.geometry = QgsGeometry( new QgsLineString( newX, newY, newZ ) ) ;
962 resultFeature.crossSectionGeometry = QgsGeometry( new QgsLineString( newDistance, newZ ) );
963 }
964
965 mResults->features[resultFeature.featureId].append( resultFeature );
966}
967
968bool QgsVectorLayerProfileGenerator::generateProfileForLines()
969{
970 // get features from layer
971 QgsFeatureRequest request;
972 request.setDestinationCrs( mTargetCrs, mTransformContext );
973 if ( mTolerance > 0 )
974 {
975 request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance );
976 }
977 else
978 {
979 request.setFilterRect( mProfileCurve->boundingBox() );
980 }
981 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
982 request.setFeedback( mFeedback.get() );
983
984 auto processCurve = [this]( const QgsFeature & feature, const QgsCurve * featGeomPart )
985 {
986 QString error;
987 std::unique_ptr< QgsAbstractGeometry > intersection( mProfileBufferedCurveEngine->intersection( featGeomPart, &error ) );
988 if ( !intersection )
989 return;
990
991 if ( mFeedback->isCanceled() )
992 return;
993
994
995 // Intersection is empty : GEOS issue for vertical intersection : use feature geometry as intersection
996 if ( intersection->isEmpty() )
997 {
998 intersection.reset( featGeomPart->clone() );
999 }
1000
1001 QgsGeos featGeomPartGeos( featGeomPart );
1002 featGeomPartGeos.prepareGeometry();
1003
1004 for ( auto it = intersection->const_parts_begin();
1005 !mFeedback->isCanceled() && it != intersection->const_parts_end();
1006 ++it )
1007 {
1008 if ( const QgsPoint *intersectionPoint = qgsgeometry_cast< const QgsPoint * >( *it ) )
1009 {
1010 // unfortunately we need to do some work to interpolate the z value for the line -- GEOS doesn't give us this
1011 QString error;
1012 const double distance = featGeomPartGeos.lineLocatePoint( *intersectionPoint, &error );
1013 std::unique_ptr< QgsPoint > interpolatedPoint( featGeomPart->interpolatePoint( distance ) );
1014
1015 processIntersectionPoint( interpolatedPoint.get(), feature );
1016 }
1017 else if ( const QgsLineString *intersectionCurve = qgsgeometry_cast< const QgsLineString * >( *it ) )
1018 {
1019 processIntersectionCurve( intersectionCurve, feature );
1020 }
1021 }
1022 };
1023
1024 QgsFeature feature;
1025 QgsFeatureIterator it = mSource->getFeatures( request );
1026 while ( !mFeedback->isCanceled() && it.nextFeature( feature ) )
1027 {
1028 mExpressionContext.setFeature( feature );
1029
1030 const QgsGeometry g = feature.geometry();
1031 for ( auto it = g.const_parts_begin(); !mFeedback->isCanceled() && it != g.const_parts_end(); ++it )
1032 {
1033 if ( mProfileBufferedCurveEngine->intersects( *it ) )
1034 {
1035 processCurve( feature, qgsgeometry_cast< const QgsCurve * >( *it ) );
1036 }
1037 }
1038 }
1039
1040 return !mFeedback->isCanceled();
1041}
1042
1043QgsPoint QgsVectorLayerProfileGenerator::interpolatePointOnTriangle( const QgsPolygon *triangle, double x, double y ) const
1044{
1045 QgsPoint p1, p2, p3;
1047 triangle->exteriorRing()->pointAt( 0, p1, vt );
1048 triangle->exteriorRing()->pointAt( 1, p2, vt );
1049 triangle->exteriorRing()->pointAt( 2, p3, vt );
1050 const double z = QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, p1.z(), p2.z(), p3.z(), QgsPointXY( x, y ) );
1051 return QgsPoint( x, y, z );
1052};
1053
1054void QgsVectorLayerProfileGenerator::processTriangleIntersectForPoint( const QgsPolygon *triangle, const QgsPoint *p, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts )
1055{
1056 const QgsPoint interpolatedPoint = interpolatePointOnTriangle( triangle, p->x(), p->y() );
1057 mResults->mRawPoints.append( interpolatedPoint );
1058 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() );
1059 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() );
1060
1061 QString lastError;
1062 const double distance = mProfileCurveEngine->lineLocatePoint( *p, &lastError );
1063 mResults->mDistanceToHeightMap.insert( distance, interpolatedPoint.z() );
1064
1065 if ( mExtrusionEnabled )
1066 {
1067 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
1068
1069 transformedParts.append( QgsGeometry( new QgsLineString( interpolatedPoint,
1070 QgsPoint( interpolatedPoint.x(), interpolatedPoint.y(), interpolatedPoint.z() + extrusion ) ) ) );
1071 crossSectionParts.append( QgsGeometry( new QgsLineString( QgsPoint( distance, interpolatedPoint.z() ),
1072 QgsPoint( distance, interpolatedPoint.z() + extrusion ) ) ) );
1073 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() + extrusion );
1074 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() + extrusion );
1075 }
1076 else
1077 {
1078 transformedParts.append( QgsGeometry( new QgsPoint( interpolatedPoint ) ) );
1079 crossSectionParts.append( QgsGeometry( new QgsPoint( distance, interpolatedPoint.z() ) ) );
1080 }
1081}
1082
1083void QgsVectorLayerProfileGenerator::processTriangleIntersectForLine( const QgsPolygon *triangle, const QgsLineString *intersectionLine, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts )
1084{
1085 if ( triangle->exteriorRing()->numPoints() < 4 ) // not a polygon
1086 return;
1087
1088 int numPoints = intersectionLine->numPoints();
1089 QVector< double > newX( numPoints );
1090 QVector< double > newY( numPoints );
1091 QVector< double > newZ( numPoints );
1092 QVector< double > newDistance( numPoints );
1093
1094 const double *inX = intersectionLine->xData();
1095 const double *inY = intersectionLine->yData();
1096 const double *inZ = intersectionLine->is3D() ? intersectionLine->zData() : nullptr;
1097 double *outX = newX.data();
1098 double *outY = newY.data();
1099 double *outZ = newZ.data();
1100 double *outDistance = newDistance.data();
1101
1102 double lastDistanceAlongProfileCurve = 0.0;
1103 QVector< double > extrudedZ;
1104 double *extZOut = nullptr;
1105 double extrusion = 0;
1106
1107 if ( mExtrusionEnabled )
1108 {
1109 extrudedZ.resize( numPoints );
1110 extZOut = extrudedZ.data();
1111
1112 extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
1113 }
1114
1115 QString lastError;
1116 for ( int i = 0 ; ! mFeedback->isCanceled() && i < numPoints; ++i )
1117 {
1118 double x = *inX++;
1119 double y = *inY++;
1120 double z = inZ ? *inZ++ : 0;
1121
1122 QgsPoint interpolatedPoint( x, y, z ); // general case (not a triangle)
1123
1124 *outX++ = x;
1125 *outY++ = y;
1126 if ( triangle->exteriorRing()->numPoints() == 4 ) // triangle case
1127 {
1128 interpolatedPoint = interpolatePointOnTriangle( triangle, x, y );
1129 }
1130 double tempOutZ = std::isnan( interpolatedPoint.z() ) ? 0.0 : interpolatedPoint.z();
1131 *outZ++ = tempOutZ;
1132
1133 if ( mExtrusionEnabled )
1134 *extZOut++ = tempOutZ + extrusion;
1135
1136 mResults->mRawPoints.append( interpolatedPoint );
1137 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() );
1138 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() );
1139 if ( mExtrusionEnabled )
1140 {
1141 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() + extrusion );
1142 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() + extrusion );
1143 }
1144
1145 const double distance = mProfileCurveEngine->lineLocatePoint( interpolatedPoint, &lastError );
1146 *outDistance++ = distance;
1147
1148 mResults->mDistanceToHeightMap.insert( distance, interpolatedPoint.z() );
1149 lastDistanceAlongProfileCurve = distance;
1150 }
1151
1152 // insert nan point to end the line
1153 mResults->mDistanceToHeightMap.insert( lastDistanceAlongProfileCurve + 0.000001, std::numeric_limits<double>::quiet_NaN() );
1154
1155 if ( mFeedback->isCanceled() )
1156 return;
1157
1158 if ( mExtrusionEnabled )
1159 {
1160 std::unique_ptr< QgsLineString > ring = std::make_unique< QgsLineString >( newX, newY, newZ );
1161 std::unique_ptr< QgsLineString > extrudedRing = std::make_unique< QgsLineString >( newX, newY, extrudedZ );
1162 std::unique_ptr< QgsLineString > reversedExtrusion( extrudedRing->reversed() );
1163 ring->append( reversedExtrusion.get() );
1164 ring->close();
1165 transformedParts.append( QgsGeometry( new QgsPolygon( ring.release() ) ) );
1166
1167 std::unique_ptr< QgsLineString > distanceVHeightRing = std::make_unique< QgsLineString >( newDistance, newZ );
1168 std::unique_ptr< QgsLineString > extrudedDistanceVHeightRing = std::make_unique< QgsLineString >( newDistance, extrudedZ );
1169 std::unique_ptr< QgsLineString > reversedDistanceVHeightExtrusion( extrudedDistanceVHeightRing->reversed() );
1170 distanceVHeightRing->append( reversedDistanceVHeightExtrusion.get() );
1171 distanceVHeightRing->close();
1172 crossSectionParts.append( QgsGeometry( new QgsPolygon( distanceVHeightRing.release() ) ) );
1173 }
1174 else
1175 {
1176 transformedParts.append( QgsGeometry( new QgsLineString( newX, newY, newZ ) ) );
1177 crossSectionParts.append( QgsGeometry( new QgsLineString( newDistance, newZ ) ) );
1178 }
1179};
1180
1181void QgsVectorLayerProfileGenerator::processTriangleIntersectForPolygon( const QgsPolygon *sourcePolygon, const QgsPolygon *intersectionPolygon, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts )
1182{
1183 bool oldExtrusion = mExtrusionEnabled;
1184
1185 /* Polyone extrusion produces I or C or inverted C shapes because the starting and ending points are the same.
1186 We observe the same case with linestrings if the starting and ending points are not at the ends.
1187 In the case below, the Z polygon projected onto the curve produces a shape that cannot be used to represent the extrusion ==> we would obtain a 3D volume.
1188 In order to avoid having strange shapes that cannot be understood by the end user, extrusion is deactivated in the case of polygons.
1189
1190 .^..
1191 ./ | \..
1192 ../ | \...
1193 ../ | \...
1194 ../ | \.. ....^..
1195 ../ | ........\.../ \... ^
1196 ../ ......|......./ \... \.... .../ \
1197 /,........../ | \.. \... / \
1198 v | \... ..../ \... \
1199 | \ ./ \... \
1200 | v \.. \
1201 | `v
1202 |
1203 .^..
1204 ./ \..
1205 ../ \...
1206 ../ \...
1207 ../ \.. ....^..
1208 ../ ........\.../ \... ^
1209 ../ ............../ \... \.... .../ \
1210 /,........../ \.. \... / \
1211 v \... ..../ \... \
1212 \ ./ \... \
1213 v \.. \
1214 `v
1215 */
1216 mExtrusionEnabled = false;
1217 if ( mProfileBufferedCurveEngine->contains( sourcePolygon ) ) // sourcePolygon is entirely inside curve buffer, we keep it as whole
1218 {
1219 if ( const QgsCurve *exterior = sourcePolygon->exteriorRing() )
1220 {
1221 QgsLineString *exteriorLine = qgsgeometry_cast<QgsLineString *>( exterior );
1222 processTriangleIntersectForLine( sourcePolygon, exteriorLine, transformedParts, crossSectionParts );
1223 }
1224 for ( int i = 0; i < sourcePolygon->numInteriorRings(); ++i )
1225 {
1226 QgsLineString *interiorLine = qgsgeometry_cast<QgsLineString *>( sourcePolygon->interiorRing( i ) );
1227 processTriangleIntersectForLine( sourcePolygon, interiorLine, transformedParts, crossSectionParts );
1228 }
1229 }
1230 else // sourcePolygon is partially inside curve buffer, the intersectionPolygon is closed due to the intersection operation then
1231 // it must be 'reopened'
1232 {
1233 if ( const QgsCurve *exterior = intersectionPolygon->exteriorRing() )
1234 {
1235 QgsLineString *exteriorLine = qgsgeometry_cast<QgsLineString *>( exterior )->clone();
1236 exteriorLine->deleteVertex( QgsVertexId( 0, 0, exteriorLine->numPoints() - 1 ) ); // open linestring
1237 processTriangleIntersectForLine( sourcePolygon, exteriorLine, transformedParts, crossSectionParts );
1238 delete exteriorLine;
1239 }
1240 for ( int i = 0; i < intersectionPolygon->numInteriorRings(); ++i )
1241 {
1242 QgsLineString *interiorLine = qgsgeometry_cast<QgsLineString *>( intersectionPolygon->interiorRing( i ) );
1243 if ( mProfileBufferedCurveEngine->contains( interiorLine ) ) // interiorLine is entirely inside curve buffer
1244 {
1245 processTriangleIntersectForLine( sourcePolygon, interiorLine, transformedParts, crossSectionParts );
1246 }
1247 else
1248 {
1249 interiorLine = qgsgeometry_cast<QgsLineString *>( intersectionPolygon->interiorRing( i ) )->clone();
1250 interiorLine->deleteVertex( QgsVertexId( 0, 0, interiorLine->numPoints() - 1 ) ); // open linestring
1251 processTriangleIntersectForLine( sourcePolygon, interiorLine, transformedParts, crossSectionParts );
1252 delete interiorLine;
1253 }
1254 }
1255 }
1256
1257 mExtrusionEnabled = oldExtrusion;
1258};
1259
1260bool QgsVectorLayerProfileGenerator::generateProfileForPolygons()
1261{
1262 // get features from layer
1263 QgsFeatureRequest request;
1264 request.setDestinationCrs( mTargetCrs, mTransformContext );
1265 if ( mTolerance > 0 )
1266 {
1267 request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance );
1268 }
1269 else
1270 {
1271 request.setFilterRect( mProfileCurve->boundingBox() );
1272 }
1273 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
1274 request.setFeedback( mFeedback.get() );
1275
1276 std::function< void( const QgsPolygon *triangle, const QgsAbstractGeometry *intersect, QVector< QgsGeometry > &, QVector< QgsGeometry > & ) > processTriangleLineIntersect;
1277 processTriangleLineIntersect = [this]( const QgsPolygon * triangle, const QgsAbstractGeometry * intersection, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts )
1278 {
1279 for ( auto it = intersection->const_parts_begin();
1280 ! mFeedback->isCanceled() && it != intersection->const_parts_end();
1281 ++it )
1282 {
1283 // intersect may be a (multi)point or (multi)linestring
1284 switch ( QgsWkbTypes::geometryType( ( *it )->wkbType() ) )
1285 {
1287 if ( const QgsPoint *p = qgsgeometry_cast< const QgsPoint * >( *it ) )
1288 {
1289 processTriangleIntersectForPoint( triangle, p, transformedParts, crossSectionParts );
1290 }
1291 break;
1292
1294 if ( const QgsLineString *intersectionLine = qgsgeometry_cast< const QgsLineString * >( *it ) )
1295 {
1296 processTriangleIntersectForLine( triangle, intersectionLine, transformedParts, crossSectionParts );
1297 }
1298 break;
1299
1301 if ( const QgsPolygon *poly = qgsgeometry_cast< const QgsPolygon * >( *it ) )
1302 {
1303 processTriangleIntersectForPolygon( triangle, poly, transformedParts, crossSectionParts );
1304 }
1305 break;
1306
1309 return;
1310 }
1311 }
1312 };
1313
1314 auto triangleIsCollinearInXYPlane = []( const QgsPolygon * polygon )-> bool
1315 {
1316 const QgsLineString *ring = qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() );
1317 return QgsGeometryUtilsBase::pointsAreCollinear( ring->xAt( 0 ), ring->yAt( 0 ),
1318 ring->xAt( 1 ), ring->yAt( 1 ),
1319 ring->xAt( 2 ), ring->yAt( 2 ), 0.005 );
1320 };
1321
1322 auto processPolygon = [this, &processTriangleLineIntersect, &triangleIsCollinearInXYPlane]( const QgsCurvePolygon * polygon, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts, double offset, bool & wasCollinear )
1323 {
1324 std::unique_ptr< QgsPolygon > clampedPolygon;
1325 if ( const QgsPolygon *p = qgsgeometry_cast< const QgsPolygon * >( polygon ) )
1326 {
1327 clampedPolygon.reset( p->clone() );
1328 }
1329 else
1330 {
1331 clampedPolygon.reset( qgsgeometry_cast< QgsPolygon * >( polygon->segmentize() ) );
1332 }
1333 clampAltitudes( clampedPolygon.get(), offset );
1334
1335 if ( mFeedback->isCanceled() )
1336 return;
1337
1338 if ( mTolerance > 0.0 ) // if the tolerance is not 0.0 we will have a polygon / polygon intersection, we do not need tessellation
1339 {
1340 QString error;
1341 if ( mProfileBufferedCurveEngine->intersects( clampedPolygon.get(), &error ) )
1342 {
1343 std::unique_ptr< QgsAbstractGeometry > intersection;
1344 intersection.reset( mProfileBufferedCurveEngine->intersection( clampedPolygon.get(), &error ) );
1345 if ( error.isEmpty() )
1346 {
1347 processTriangleLineIntersect( clampedPolygon.get(), intersection.get(), transformedParts, crossSectionParts );
1348 }
1349 else
1350 {
1351 // this case may occur with vertical object as geos does not handle very well 3D data.
1352 // Geos works in 2D from the 3D coordinates then re-add the Z values, but when 2D-from-3D objects are vertical, they are topologically incorrects!
1353 // This piece of code is just a fix to handle this case, a better and real 3D capable library is needed (like SFCGAL).
1354 QgsLineString *ring = qgsgeometry_cast< QgsLineString * >( clampedPolygon->exteriorRing() );
1355 int numPoints = ring->numPoints();
1356 QVector< double > newX( numPoints );
1357 QVector< double > newY( numPoints );
1358 QVector< double > newZ( numPoints );
1359 double *outX = newX.data();
1360 double *outY = newY.data();
1361 double *outZ = newZ.data();
1362
1363 const double *inX = ring->xData();
1364 const double *inY = ring->yData();
1365 const double *inZ = ring->zData();
1366 for ( int i = 0 ; ! mFeedback->isCanceled() && i < ring->numPoints() - 1; ++i )
1367 {
1368 *outX++ = inX[i] + i * 1.0e-9;
1369 *outY++ = inY[i] + i * 1.0e-9;
1370 *outZ++ = inZ[i];
1371 }
1372 std::unique_ptr< QgsPolygon > shiftedPoly;
1373 shiftedPoly.reset( new QgsPolygon( new QgsLineString( newX, newY, newZ ) ) );
1374
1375 intersection.reset( mProfileBufferedCurveEngine->intersection( shiftedPoly.get(), &error ) );
1376 if ( intersection.get() )
1377 processTriangleLineIntersect( clampedPolygon.get(), intersection.get(), transformedParts, crossSectionParts );
1378 else
1379 QgsDebugMsgLevel( QStringLiteral( "processPolygon after shift bad geom! error: %1" ).arg( error ), 0 );
1380 }
1381 }
1382
1383 }
1384 else // ie. polygon / line intersection ==> need tessellation
1385 {
1386 QgsGeometry tessellation;
1387 if ( clampedPolygon->numInteriorRings() == 0 && clampedPolygon->exteriorRing() && clampedPolygon->exteriorRing()->numPoints() == 4 && clampedPolygon->exteriorRing()->isClosed() )
1388 {
1389 // special case -- polygon is already a triangle, so no need to tessellate
1390 std::unique_ptr< QgsMultiPolygon > multiPolygon = std::make_unique< QgsMultiPolygon >();
1391 multiPolygon->addGeometry( clampedPolygon.release() );
1392 tessellation = QgsGeometry( std::move( multiPolygon ) );
1393 }
1394 else
1395 {
1396 const QgsRectangle bounds = clampedPolygon->boundingBox();
1397 QgsTessellator t( bounds, false, false, false, false );
1398 t.addPolygon( *clampedPolygon, 0 );
1399
1400 tessellation = QgsGeometry( t.asMultiPolygon() );
1401 if ( mFeedback->isCanceled() )
1402 return;
1403
1404 tessellation.translate( bounds.xMinimum(), bounds.yMinimum() );
1405 }
1406
1407 // iterate through the tessellation, finding triangles which intersect the line
1408 const int numTriangles = qgsgeometry_cast< const QgsMultiPolygon * >( tessellation.constGet() )->numGeometries();
1409 for ( int i = 0; ! mFeedback->isCanceled() && i < numTriangles; ++i )
1410 {
1411 const QgsPolygon *triangle = qgsgeometry_cast< const QgsPolygon * >( qgsgeometry_cast< const QgsMultiPolygon * >( tessellation.constGet() )->geometryN( i ) );
1412
1413 if ( triangleIsCollinearInXYPlane( triangle ) )
1414 {
1415 wasCollinear = true;
1416 const QgsLineString *ring = qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() );
1417
1418 QString lastError;
1419 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( mProfileCurve.get() ) )
1420 {
1421 for ( int curveSegmentIndex = 0; curveSegmentIndex < mProfileCurve->numPoints() - 1; ++curveSegmentIndex )
1422 {
1423 const QgsPoint p1 = ls->pointN( curveSegmentIndex );
1424 const QgsPoint p2 = ls->pointN( curveSegmentIndex + 1 );
1425
1426 QgsPoint intersectionPoint;
1427 double minZ = std::numeric_limits< double >::max();
1428 double maxZ = std::numeric_limits< double >::lowest();
1429
1430 for ( auto vertexPair : std::array<std::pair<int, int>, 3> {{ { 0, 1}, {1, 2}, {2, 0} }} )
1431 {
1432 bool isIntersection = false;
1433 if ( QgsGeometryUtils::segmentIntersection( ring->pointN( vertexPair.first ), ring->pointN( vertexPair.second ), p1, p2, intersectionPoint, isIntersection ) )
1434 {
1435 const double fraction = QgsGeometryUtilsBase::pointFractionAlongLine( ring->xAt( vertexPair.first ), ring->yAt( vertexPair.first ), ring->xAt( vertexPair.second ), ring->yAt( vertexPair.second ), intersectionPoint.x(), intersectionPoint.y() );
1436 const double intersectionZ = ring->zAt( vertexPair.first ) + ( ring->zAt( vertexPair.second ) - ring->zAt( vertexPair.first ) ) * fraction;
1437 minZ = std::min( minZ, intersectionZ );
1438 maxZ = std::max( maxZ, intersectionZ );
1439 }
1440 }
1441
1442 if ( !intersectionPoint.isEmpty() )
1443 {
1444 // need z?
1445 mResults->mRawPoints.append( intersectionPoint );
1446 mResults->minZ = std::min( mResults->minZ, minZ );
1447 mResults->maxZ = std::max( mResults->maxZ, maxZ );
1448
1449 const double distance = mProfileCurveEngine->lineLocatePoint( intersectionPoint, &lastError );
1450
1451 crossSectionParts.append( QgsGeometry( new QgsLineString( QVector< double > {distance, distance}, QVector< double > {minZ, maxZ} ) ) );
1452
1453 mResults->mDistanceToHeightMap.insert( distance, minZ );
1454 mResults->mDistanceToHeightMap.insert( distance, maxZ );
1455 }
1456 }
1457 }
1458 else
1459 {
1460 // curved geometries, not supported yet, but not possible through the GUI anyway
1461 QgsDebugError( QStringLiteral( "Collinear triangles with curved profile lines are not supported yet" ) );
1462 }
1463 }
1464 else // not collinear
1465 {
1466 QString error;
1467 if ( mProfileBufferedCurveEngine->intersects( triangle, &error ) )
1468 {
1469 std::unique_ptr< QgsAbstractGeometry > intersection( mProfileBufferedCurveEngine->intersection( triangle, &error ) );
1470 processTriangleLineIntersect( triangle, intersection.get(), transformedParts, crossSectionParts );
1471 }
1472 }
1473 }
1474 }
1475 };
1476
1477 // ========= MAIN JOB
1478 QgsFeature feature;
1479 QgsFeatureIterator it = mSource->getFeatures( request );
1480 while ( ! mFeedback->isCanceled() && it.nextFeature( feature ) )
1481 {
1482 if ( !mProfileBufferedCurveEngine->intersects( feature.geometry().constGet() ) )
1483 continue;
1484
1485 mExpressionContext.setFeature( feature );
1486
1487 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ZOffset, mExpressionContext, mOffset );
1488 const QgsGeometry g = feature.geometry();
1489 QVector< QgsGeometry > transformedParts;
1490 QVector< QgsGeometry > crossSectionParts;
1491 bool wasCollinear = false;
1492
1493 // === process intersection of geometry feature parts with the mProfileBoxEngine
1494 for ( auto it = g.const_parts_begin(); ! mFeedback->isCanceled() && it != g.const_parts_end(); ++it )
1495 {
1496 if ( mProfileBufferedCurveEngine->intersects( *it ) )
1497 {
1498 processPolygon( qgsgeometry_cast< const QgsCurvePolygon * >( *it ), transformedParts, crossSectionParts, offset, wasCollinear );
1499 }
1500 }
1501
1502 if ( mFeedback->isCanceled() )
1503 return false;
1504
1505 // === aggregate results for this feature
1507 resultFeature.featureId = feature.id();
1508 resultFeature.geometry = transformedParts.size() > 1 ? QgsGeometry::collectGeometry( transformedParts ) : transformedParts.value( 0 );
1509 if ( !crossSectionParts.empty() )
1510 {
1511 if ( !wasCollinear )
1512 {
1513 QgsGeometry unioned = QgsGeometry::unaryUnion( crossSectionParts );
1514 if ( unioned.isEmpty() )
1515 {
1516 resultFeature.crossSectionGeometry = QgsGeometry::collectGeometry( crossSectionParts );
1517 }
1518 else
1519 {
1520 if ( unioned.type() == Qgis::GeometryType::Line )
1521 {
1522 unioned = unioned.mergeLines();
1523 }
1524 resultFeature.crossSectionGeometry = unioned;
1525 }
1526 }
1527 else
1528 {
1529 resultFeature.crossSectionGeometry = QgsGeometry::collectGeometry( crossSectionParts );
1530 }
1531 }
1532 mResults->features[resultFeature.featureId].append( resultFeature );
1533 }
1534 return true;
1535}
1536
1537double QgsVectorLayerProfileGenerator::terrainHeight( double x, double y )
1538{
1539 if ( !mTerrainProvider )
1540 return std::numeric_limits<double>::quiet_NaN();
1541
1542 // transform feature point to terrain provider crs
1543 try
1544 {
1545 double dummyZ = 0;
1546 mTargetToTerrainProviderTransform.transformInPlace( x, y, dummyZ );
1547 }
1548 catch ( QgsCsException & )
1549 {
1550 return std::numeric_limits<double>::quiet_NaN();
1551 }
1552
1553 return mTerrainProvider->heightAt( x, y );
1554}
1555
1556double QgsVectorLayerProfileGenerator::featureZToHeight( double x, double y, double z, double offset )
1557{
1558 switch ( mClamping )
1559 {
1561 break;
1562
1565 {
1566 const double terrainZ = terrainHeight( x, y );
1567 if ( !std::isnan( terrainZ ) )
1568 {
1569 switch ( mClamping )
1570 {
1572 if ( std::isnan( z ) )
1573 z = terrainZ;
1574 else
1575 z += terrainZ;
1576 break;
1577
1579 z = terrainZ;
1580 break;
1581
1583 break;
1584 }
1585 }
1586 break;
1587 }
1588 }
1589
1590 return ( std::isnan( z ) ? 0 : z ) * mScale + offset;
1591}
1592
1593void QgsVectorLayerProfileGenerator::clampAltitudes( QgsLineString *lineString, const QgsPoint &centroid, double offset )
1594{
1595 for ( int i = 0; i < lineString->nCoordinates(); ++i )
1596 {
1597 if ( mFeedback->isCanceled() )
1598 break;
1599
1600 double terrainZ = 0;
1601 switch ( mClamping )
1602 {
1605 {
1606 QgsPointXY pt;
1607 switch ( mBinding )
1608 {
1610 pt.setX( lineString->xAt( i ) );
1611 pt.setY( lineString->yAt( i ) );
1612 break;
1613
1615 pt.set( centroid.x(), centroid.y() );
1616 break;
1617 }
1618
1619 terrainZ = terrainHeight( pt.x(), pt.y() );
1620 break;
1621 }
1622
1624 break;
1625 }
1626
1627 double geomZ = 0;
1628
1629 switch ( mClamping )
1630 {
1633 geomZ = lineString->zAt( i );
1634 break;
1635
1637 break;
1638 }
1639
1640 const double z = ( terrainZ + ( std::isnan( geomZ ) ? 0 : geomZ ) ) * mScale + offset;
1641 lineString->setZAt( i, z );
1642 }
1643}
1644
1645bool QgsVectorLayerProfileGenerator::clampAltitudes( QgsPolygon *polygon, double offset )
1646{
1647 if ( !polygon->is3D() )
1648 polygon->addZValue( 0 );
1649
1651 switch ( mBinding )
1652 {
1654 break;
1655
1657 centroid = polygon->centroid();
1658 break;
1659 }
1660
1661 QgsCurve *curve = const_cast<QgsCurve *>( polygon->exteriorRing() );
1662 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( curve );
1663 if ( !lineString )
1664 return false;
1665
1666 clampAltitudes( lineString, centroid, offset );
1667
1668 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
1669 {
1670 if ( mFeedback->isCanceled() )
1671 break;
1672
1673 QgsCurve *curve = const_cast<QgsCurve *>( polygon->interiorRing( i ) );
1674 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( curve );
1675 if ( !lineString )
1676 return false;
1677
1678 clampAltitudes( lineString, centroid, offset );
1679 }
1680 return true;
1681}
@ 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:2477
@ 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:3475
@ 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.
@ Line
Line symbol.
@ Fill
Fill 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...
std::unique_ptr< QgsFillSymbol > mFillSymbol
std::unique_ptr< QgsLineSymbol > mLineSymbol
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.
Definition: qgsexception.h:67
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:35
virtual int numPoints() const =0
Returns the number of points in the curve.
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:231
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)
Fetch next feature and stores in f, returns true on success.
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
QgsGeometry geometry
Definition: qgsfeature.h:67
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
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
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...
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.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
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
Definition: qgsgeometry.h:165
QgsGeometry mergeLines() const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
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:98
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
int numPoints() const override
Returns the number of points in the curve.
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.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
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.
Definition: qgslinesymbol.h:30
A marker symbol type, for rendering Point and MultiPoint geometries.
Multi line string geometry collection.
Multi point geometry collection.
Definition: qgsmultipoint.h:29
Multi polygon geometry collection.
A class to represent a 2D point.
Definition: qgspointxy.h:60
void setY(double y)
Sets the y value of the point.
Definition: qgspointxy.h:130
void set(double x, double y)
Sets the x and y value of the point.
Definition: qgspointxy.h:137
double y
Definition: qgspointxy.h:64
Q_GADGET double x
Definition: qgspointxy.h:63
void setX(double x)
Sets the x value of the point.
Definition: qgspointxy.h:120
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Q_GADGET double x
Definition: qgspoint.h:52
QgsPoint * clone() const override
Clones the geometry by performing a deep copy.
Definition: qgspoint.cpp:105
double z
Definition: qgspoint.h:54
bool isEmpty() const override
Returns true if the geometry is empty.
Definition: qgspoint.cpp:733
double y
Definition: qgspoint.h:53
Polygon geometry type.
Definition: qgspolygon.h:33
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.
bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const final
Prepares the collection against a specified expression context.
QSet< QString > referencedFields(const QgsExpressionContext &context=QgsExpressionContext(), bool ignoreContext=false) const final
Returns the set of any fields referenced by the active properties from the collection.
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
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:371
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
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.
Definition: qgssymbol.cpp:902
qreal opacity() const
Returns the opacity for the symbol.
Definition: qgssymbol.h:495
QColor color() const
Returns the symbol's color.
Definition: qgssymbol.cpp:912
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:156
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...
Definition: qgswkbtypes.h:862
CORE_EXPORT QgsMeshVertex centroid(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns the centroid of the face.
#define BUILTIN_UNREACHABLE
Definition: qgis.h:5853
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#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.
Utility class for identifying a unique vertex within a geometry.
Definition: qgsvertexid.h:30