QGIS API Documentation 3.39.0-Master (d85f3c2a281)
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 ***************************************************************************/
19#include "qgsprofilerequest.h"
20#include "qgscurve.h"
21#include "qgsvectorlayer.h"
24#include "qgsgeos.h"
26#include "qgsterrainprovider.h"
27#include "qgspolygon.h"
28#include "qgstessellator.h"
29#include "qgsmultipolygon.h"
30#include "qgsmeshlayerutils.h"
31#include "qgsmultipoint.h"
32#include "qgsmultilinestring.h"
33#include "qgslinesymbol.h"
34#include "qgsfillsymbol.h"
35#include "qgsmarkersymbol.h"
36#include "qgsprofilepoint.h"
37#include "qgsprofilesnapping.h"
39#include <QPolygonF>
40
41//
42// QgsVectorLayerProfileResults
43//
44
46{
47 return QStringLiteral( "vector" );
48}
49
51{
52 QVector<QgsGeometry> res;
53 res.reserve( features.size() );
54 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
55 {
56 for ( const Feature &feature : it.value() )
57 {
58 res.append( feature.geometry );
59 }
60 }
61 return res;
62}
63
64QVector<QgsAbstractProfileResults::Feature> QgsVectorLayerProfileResults::asFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback ) const
65{
66 switch ( profileType )
67 {
70 return asIndividualFeatures( type, feedback );
71 // distance vs elevation table results are always handled like a continuous surface
72 [[fallthrough]];
73
76 }
78}
79
81{
82 switch ( profileType )
83 {
85 return snapPointToIndividualFeatures( point, context );
87 return QgsAbstractProfileSurfaceResults::snapPoint( point, context );
88 }
90}
91
92
93QVector<QgsProfileIdentifyResults> QgsVectorLayerProfileResults::identify( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const QgsProfileIdentifyContext & )
94{
95 QgsFeatureIds ids;
96 auto visitFeature = [&ids]( QgsFeatureId featureId )
97 {
98 ids << featureId;
99 };
100
101 visitFeaturesInRange( distanceRange, elevationRange, visitFeature );
102 if ( ids.empty() )
103 return {};
104
105 QVector< QVariantMap> idsList;
106 for ( auto it = ids.constBegin(); it != ids.constEnd(); ++it )
107 idsList.append( QVariantMap( {{QStringLiteral( "id" ), *it}} ) );
108
109 return { QgsProfileIdentifyResults( mLayer, idsList ) };
110}
111
112QVector<QgsProfileIdentifyResults> QgsVectorLayerProfileResults::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
113{
114 QHash< QgsFeatureId, QVariantMap > features;
115 auto visitFeature = [&features]( QgsFeatureId featureId, double delta, double distance, double elevation )
116 {
117 auto it = features.find( featureId );
118 if ( it == features.end() )
119 {
120 features[ featureId ] = QVariantMap( {{QStringLiteral( "id" ), featureId },
121 {QStringLiteral( "delta" ), delta },
122 {QStringLiteral( "distance" ), distance },
123 {QStringLiteral( "elevation" ), elevation }
124 } );
125 }
126 else
127 {
128 const double currentDelta = it.value().value( QStringLiteral( "delta" ) ).toDouble();
129 if ( delta < currentDelta )
130 {
131 *it = QVariantMap( {{QStringLiteral( "id" ), featureId },
132 {QStringLiteral( "delta" ), delta },
133 {QStringLiteral( "distance" ), distance },
134 {QStringLiteral( "elevation" ), elevation }
135 } );
136 }
137 }
138 };
139
140 visitFeaturesAtPoint( point, context.maximumPointDistanceDelta, context.maximumPointElevationDelta, context.maximumSurfaceElevationDelta, visitFeature, true );
141
142 QVector< QVariantMap> attributes;
143 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
144 attributes.append( *it );
145
146 QVector<QgsProfileIdentifyResults> res;
147
148 if ( !attributes.empty() )
149 res.append( QgsProfileIdentifyResults( mLayer, attributes ) );
150
152 {
153 const QVector<QgsProfileIdentifyResults> surfaceResults = QgsAbstractProfileSurfaceResults::identify( point, context );
154 res.reserve( surfaceResults.size() );
155 for ( const QgsProfileIdentifyResults &surfaceResult : surfaceResults )
156 {
157 res.append( QgsProfileIdentifyResults( mLayer, surfaceResult.results() ) );
158 }
159 }
160
161 return res;
162}
163
164QgsProfileSnapResult QgsVectorLayerProfileResults::snapPointToIndividualFeatures( const QgsProfilePoint &point, const QgsProfileSnapContext &context )
165{
167 double bestSnapDistance = std::numeric_limits< double >::max();
168
169 auto visitFeature = [&bestSnapDistance, &res]( QgsFeatureId, double delta, double distance, double elevation )
170 {
171 if ( distance < bestSnapDistance )
172 {
173 bestSnapDistance = delta;
174 res.snappedPoint = QgsProfilePoint( distance, elevation );
175 }
176 };
177
178 visitFeaturesAtPoint( point, context.maximumPointDistanceDelta, context.maximumPointElevationDelta, context.maximumSurfaceElevationDelta, visitFeature, false );
179
180 return res;
181}
182
183void 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 )
184{
185 // TODO -- add spatial index if performance is an issue
186
187 const QgsPoint targetPoint( point.distance(), point.elevation() );
188
189 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
190 {
191 for ( const Feature &feature : it.value() )
192 {
193 const QgsRectangle featureBounds = feature.crossSectionGeometry.boundingBox();
194 if ( ( featureBounds.xMinimum() - maximumPointDistanceDelta <= point.distance() ) && ( featureBounds.xMaximum() + maximumPointDistanceDelta >= point.distance() ) )
195 {
196 switch ( feature.crossSectionGeometry.type() )
197 {
199 {
200 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
201 {
202 if ( const QgsPoint *candidatePoint = qgsgeometry_cast< const QgsPoint * >( *partIt ) )
203 {
204 const double snapDistanceDelta = std::fabs( point.distance() - candidatePoint->x() );
205 if ( snapDistanceDelta > maximumPointDistanceDelta )
206 continue;
207
208 const double snapHeightDelta = std::fabs( point.elevation() - candidatePoint->y() );
209 if ( snapHeightDelta > maximumPointElevationDelta )
210 continue;
211
212 const double snapDistance = candidatePoint->distance( targetPoint );
213 visitor( feature.featureId, snapDistance, candidatePoint->x(), candidatePoint->y() );
214 }
215 }
216 break;
217 }
218
220 {
221 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
222 {
223 if ( const QgsCurve *line = qgsgeometry_cast< const QgsCurve * >( *partIt ) )
224 {
225 // might be a vertical line
226 if ( const QgsLineString *lineString = qgsgeometry_cast< const QgsLineString * >( line ) )
227 {
228 if ( lineString->numPoints() == 2 && qgsDoubleNear( lineString->pointN( 0 ).x(), lineString->pointN( 1 ).x() ) )
229 {
230 const double snapDistanceDelta = std::fabs( point.distance() - lineString->pointN( 0 ).x() );
231 if ( snapDistanceDelta > maximumPointDistanceDelta )
232 continue;
233
234 const double snapHeightDelta = std::fabs( point.elevation() - lineString->pointN( 0 ).y() );
235 if ( snapHeightDelta <= maximumPointElevationDelta )
236 {
237 const double snapDistanceP1 = lineString->pointN( 0 ).distance( targetPoint );
238 visitor( feature.featureId, snapDistanceP1, lineString->pointN( 0 ).x(), lineString->pointN( 0 ).y() );
239 }
240
241 const double snapHeightDelta2 = std::fabs( point.elevation() - lineString->pointN( 1 ).y() );
242 if ( snapHeightDelta2 <= maximumPointElevationDelta )
243 {
244 const double snapDistanceP2 = lineString->pointN( 1 ).distance( targetPoint );
245 visitor( feature.featureId, snapDistanceP2, lineString->pointN( 1 ).x(), lineString->pointN( 1 ).y() );
246 }
247
248 if ( visitWithin )
249 {
250 double elevation1 = lineString->pointN( 0 ).y();
251 double elevation2 = lineString->pointN( 1 ).y();
252 if ( elevation1 > elevation2 )
253 std::swap( elevation1, elevation2 );
254
255 if ( point.elevation() > elevation1 && point.elevation() < elevation2 )
256 {
257 const double snapDistance = std::fabs( lineString->pointN( 0 ).x() - point.distance() );
258 visitor( feature.featureId, snapDistance, lineString->pointN( 0 ).x(), point.elevation() );
259 }
260 }
261 continue;
262 }
263 }
264
265 const QgsRectangle partBounds = ( *partIt )->boundingBox();
266 if ( point.distance() < partBounds.xMinimum() - maximumPointDistanceDelta || point.distance() > partBounds.xMaximum() + maximumPointDistanceDelta )
267 continue;
268
269 const double snappedDistance = point.distance() < partBounds.xMinimum() ? partBounds.xMinimum()
270 : point.distance() > partBounds.xMaximum() ? partBounds.xMaximum() : point.distance();
271
272 const QgsGeometry cutLine( new QgsLineString( QgsPoint( snappedDistance, qgsDoubleNear( minZ, maxZ ) ? minZ - 1 : minZ ), QgsPoint( snappedDistance, maxZ ) ) );
273 QgsGeos cutLineGeos( cutLine.constGet() );
274
275 const QgsGeometry points( cutLineGeos.intersection( line ) );
276
277 for ( auto vertexIt = points.vertices_begin(); vertexIt != points.vertices_end(); ++vertexIt )
278 {
279 const double snapHeightDelta = std::fabs( point.elevation() - ( *vertexIt ).y() );
280 if ( snapHeightDelta > maximumSurfaceElevationDelta )
281 continue;
282
283 const double snapDistance = ( *vertexIt ).distance( targetPoint );
284 visitor( feature.featureId, snapDistance, ( *vertexIt ).x(), ( *vertexIt ).y() );
285 }
286 }
287 }
288 break;
289 }
290
292 {
293 if ( visitWithin )
294 {
295 if ( feature.crossSectionGeometry.intersects( QgsGeometry::fromPointXY( QgsPointXY( point.distance(), point.elevation() ) ) ) )
296 {
297 visitor( feature.featureId, 0, point.distance(), point.elevation() );
298 break;
299 }
300 }
301 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
302 {
303 if ( const QgsCurve *exterior = qgsgeometry_cast< const QgsPolygon * >( *partIt )->exteriorRing() )
304 {
305 const QgsRectangle partBounds = ( *partIt )->boundingBox();
306 if ( point.distance() < partBounds.xMinimum() - maximumPointDistanceDelta || point.distance() > partBounds.xMaximum() + maximumPointDistanceDelta )
307 continue;
308
309 const double snappedDistance = point.distance() < partBounds.xMinimum() ? partBounds.xMinimum()
310 : point.distance() > partBounds.xMaximum() ? partBounds.xMaximum() : point.distance();
311
312 const QgsGeometry cutLine( new QgsLineString( QgsPoint( snappedDistance, qgsDoubleNear( minZ, maxZ ) ? minZ - 1 : minZ ), QgsPoint( snappedDistance, maxZ ) ) );
313 QgsGeos cutLineGeos( cutLine.constGet() );
314
315 const QgsGeometry points( cutLineGeos.intersection( exterior ) );
316 for ( auto vertexIt = points.vertices_begin(); vertexIt != points.vertices_end(); ++vertexIt )
317 {
318 const double snapHeightDelta = std::fabs( point.elevation() - ( *vertexIt ).y() );
319 if ( snapHeightDelta > maximumSurfaceElevationDelta )
320 continue;
321
322 const double snapDistance = ( *vertexIt ).distance( targetPoint );
323 visitor( feature.featureId, snapDistance, ( *vertexIt ).x(), ( *vertexIt ).y() );
324 }
325 }
326 }
327 break;
328 }
331 break;
332 }
333 }
334 }
335 }
336}
337
338void QgsVectorLayerProfileResults::visitFeaturesInRange( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const std::function<void ( QgsFeatureId )> &visitor )
339{
340 // TODO -- add spatial index if performance is an issue
341 const QgsRectangle profileRange( distanceRange.lower(), elevationRange.lower(), distanceRange.upper(), elevationRange.upper() );
342 const QgsGeometry profileRangeGeometry = QgsGeometry::fromRect( profileRange );
343 QgsGeos profileRangeGeos( profileRangeGeometry.constGet() );
344 profileRangeGeos.prepareGeometry();
345
346 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
347 {
348 for ( const Feature &feature : it.value() )
349 {
350 if ( feature.crossSectionGeometry.boundingBoxIntersects( profileRange ) )
351 {
352 switch ( feature.crossSectionGeometry.type() )
353 {
355 {
356 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
357 {
358 if ( const QgsPoint *candidatePoint = qgsgeometry_cast< const QgsPoint * >( *partIt ) )
359 {
360 if ( profileRange.contains( candidatePoint->x(), candidatePoint->y() ) )
361 {
362 visitor( feature.featureId );
363 }
364 }
365 }
366 break;
367 }
368
371 {
372 if ( profileRangeGeos.intersects( feature.crossSectionGeometry.constGet() ) )
373 {
374 visitor( feature.featureId );
375 }
376 break;
377 }
378
381 break;
382 }
383 }
384 }
385 }
386}
387
389{
390 const QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), mLayer ? mLayer->createExpressionContextScope() : nullptr );
391 switch ( profileType )
392 {
394 renderResultsAsIndividualFeatures( context );
395 break;
399 renderMarkersOverContinuousSurfacePlot( context );
400 break;
401 }
402}
403
404void QgsVectorLayerProfileResults::renderResultsAsIndividualFeatures( QgsProfileRenderContext &context )
405{
406 QPainter *painter = context.renderContext().painter();
407 if ( !painter )
408 return;
409
410 const QgsScopedQPainterState painterState( painter );
411
412 painter->setBrush( Qt::NoBrush );
413 painter->setPen( Qt::NoPen );
414
415 const double minDistance = context.distanceRange().lower();
416 const double maxDistance = context.distanceRange().upper();
417 const double minZ = context.elevationRange().lower();
418 const double maxZ = context.elevationRange().upper();
419
420 const QRectF visibleRegion( minDistance, minZ, maxDistance - minDistance, maxZ - minZ );
421 QPainterPath clipPath;
422 clipPath.addPolygon( context.worldTransform().map( visibleRegion ) );
423 painter->setClipPath( clipPath, Qt::ClipOperation::IntersectClip );
424
425 const QgsRectangle clipPathRect( clipPath.boundingRect() );
426
427 auto renderResult = [&context, &clipPathRect]( const Feature & profileFeature, QgsMarkerSymbol * markerSymbol, QgsLineSymbol * lineSymbol, QgsFillSymbol * fillSymbol )
428 {
429 if ( profileFeature.crossSectionGeometry.isEmpty() )
430 return;
431
432 QgsGeometry transformed = profileFeature.crossSectionGeometry;
433 transformed.transform( context.worldTransform() );
434
435 if ( !transformed.boundingBoxIntersects( clipPathRect ) )
436 return;
437
438 // we can take some shortcuts here, because we know that the geometry will already be segmentized and can't be a curved type
439 switch ( transformed.type() )
440 {
442 {
443 if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( transformed.constGet() ) )
444 {
445 markerSymbol->renderPoint( QPointF( point->x(), point->y() ), nullptr, context.renderContext() );
446 }
447 else if ( const QgsMultiPoint *multipoint = qgsgeometry_cast< const QgsMultiPoint * >( transformed.constGet() ) )
448 {
449 const int numGeometries = multipoint->numGeometries();
450 for ( int i = 0; i < numGeometries; ++i )
451 {
452 markerSymbol->renderPoint( QPointF( multipoint->pointN( i )->x(), multipoint->pointN( i )->y() ), nullptr, context.renderContext() );
453 }
454 }
455 break;
456 }
457
459 {
460 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( transformed.constGet() ) )
461 {
462 lineSymbol->renderPolyline( line->asQPolygonF(), nullptr, context.renderContext() );
463 }
464 else if ( const QgsMultiLineString *multiLinestring = qgsgeometry_cast< const QgsMultiLineString * >( transformed.constGet() ) )
465 {
466 const int numGeometries = multiLinestring->numGeometries();
467 for ( int i = 0; i < numGeometries; ++i )
468 {
469 lineSymbol->renderPolyline( multiLinestring->lineStringN( i )->asQPolygonF(), nullptr, context.renderContext() );
470 }
471 }
472 break;
473 }
474
476 {
477 if ( const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( transformed.constGet() ) )
478 {
479 if ( const QgsCurve *exterior = polygon->exteriorRing() )
480 fillSymbol->renderPolygon( exterior->asQPolygonF(), nullptr, nullptr, context.renderContext() );
481 }
482 else if ( const QgsMultiPolygon *multiPolygon = qgsgeometry_cast< const QgsMultiPolygon * >( transformed.constGet() ) )
483 {
484 const int numGeometries = multiPolygon->numGeometries();
485 for ( int i = 0; i < numGeometries; ++i )
486 {
487 fillSymbol->renderPolygon( multiPolygon->polygonN( i )->exteriorRing()->asQPolygonF(), nullptr, nullptr, context.renderContext() );
488 }
489 }
490 break;
491 }
492
495 return;
496 }
497 };
498
500 req.setFilterFids( qgis::listToSet( features.keys() ) );
501
502 if ( respectLayerSymbology && mLayer && mLayer->renderer() )
503 {
504 std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer()->clone() );
505 renderer->startRender( context.renderContext(), mLayer->fields() );
506
507 // if we are respecting the layer's symbology then we'll fire off a feature request and iterate through
508 // features from the profile, rendering each in turn
509 QSet<QString> attributes = renderer->usedAttributes( context.renderContext() );
510
511 std::unique_ptr< QgsMarkerSymbol > marker( mMarkerSymbol->clone() );
512 std::unique_ptr< QgsLineSymbol > line( mLineSymbol->clone() );
513 std::unique_ptr< QgsFillSymbol > fill( mFillSymbol->clone() );
514 attributes.unite( marker->usedAttributes( context.renderContext() ) );
515 attributes.unite( line->usedAttributes( context.renderContext() ) );
516 attributes.unite( fill->usedAttributes( context.renderContext() ) );
517
518 req.setSubsetOfAttributes( attributes, mLayer->fields() );
519
520 QgsFeature feature;
521 QgsFeatureIterator it = mLayer->getFeatures( req );
522 while ( it.nextFeature( feature ) )
523 {
524 context.renderContext().expressionContext().setFeature( feature );
525 QgsSymbol *rendererSymbol = renderer->symbolForFeature( feature, context.renderContext() );
526 if ( !rendererSymbol )
527 continue;
528
529 marker->setColor( rendererSymbol->color() );
530 marker->setOpacity( rendererSymbol->opacity() );
531 line->setColor( rendererSymbol->color() );
532 line->setOpacity( rendererSymbol->opacity() );
533 fill->setColor( rendererSymbol->color() );
534 fill->setOpacity( rendererSymbol->opacity() );
535
536 marker->startRender( context.renderContext() );
537 line->startRender( context.renderContext() );
538 fill->startRender( context.renderContext() );
539
540 const QVector< Feature > profileFeatures = features.value( feature.id() );
541 for ( const Feature &profileFeature : profileFeatures )
542 {
543 renderResult( profileFeature,
544 rendererSymbol->type() == Qgis::SymbolType::Marker ? qgis::down_cast< QgsMarkerSymbol * >( rendererSymbol ) : marker.get(),
545 rendererSymbol->type() == Qgis::SymbolType::Line ? qgis::down_cast< QgsLineSymbol * >( rendererSymbol ) : line.get(),
546 rendererSymbol->type() == Qgis::SymbolType::Fill ? qgis::down_cast< QgsFillSymbol * >( rendererSymbol ) : fill.get() );
547 }
548
549 marker->stopRender( context.renderContext() );
550 line->stopRender( context.renderContext() );
551 fill->stopRender( context.renderContext() );
552 }
553
554 renderer->stopRender( context.renderContext() );
555 }
556 else if ( mLayer )
557 {
558 QSet<QString> attributes;
559 attributes.unite( mMarkerSymbol->usedAttributes( context.renderContext() ) );
560 attributes.unite( mFillSymbol->usedAttributes( context.renderContext() ) );
561 attributes.unite( mLineSymbol->usedAttributes( context.renderContext() ) );
562
563 mMarkerSymbol->startRender( context.renderContext() );
564 mFillSymbol->startRender( context.renderContext() );
565 mLineSymbol->startRender( context.renderContext() );
566 req.setSubsetOfAttributes( attributes, mLayer->fields() );
567
568 QgsFeature feature;
569 QgsFeatureIterator it = mLayer->getFeatures( req );
570 while ( it.nextFeature( feature ) )
571 {
572 context.renderContext().expressionContext().setFeature( feature );
573 const QVector< Feature > profileFeatures = features.value( feature.id() );
574 for ( const Feature &profileFeature : profileFeatures )
575 {
576 renderResult( profileFeature, mMarkerSymbol.get(), mLineSymbol.get(), mFillSymbol.get() );
577 }
578 }
579 mMarkerSymbol->stopRender( context.renderContext() );
580 mFillSymbol->stopRender( context.renderContext() );
581 mLineSymbol->stopRender( context.renderContext() );
582 }
583}
584
585void QgsVectorLayerProfileResults::renderMarkersOverContinuousSurfacePlot( QgsProfileRenderContext &context )
586{
587 QPainter *painter = context.renderContext().painter();
588 if ( !painter )
589 return;
590
591 const QgsScopedQPainterState painterState( painter );
592
593 painter->setBrush( Qt::NoBrush );
594 painter->setPen( Qt::NoPen );
595
596 const double minDistance = context.distanceRange().lower();
597 const double maxDistance = context.distanceRange().upper();
598 const double minZ = context.elevationRange().lower();
599 const double maxZ = context.elevationRange().upper();
600
601 const QRectF visibleRegion( minDistance, minZ, maxDistance - minDistance, maxZ - minZ );
602 QPainterPath clipPath;
603 clipPath.addPolygon( context.worldTransform().map( visibleRegion ) );
604 painter->setClipPath( clipPath, Qt::ClipOperation::IntersectClip );
605
606 mMarkerSymbol->startRender( context.renderContext() );
607
608 for ( auto pointIt = mDistanceToHeightMap.constBegin(); pointIt != mDistanceToHeightMap.constEnd(); ++pointIt )
609 {
610 if ( std::isnan( pointIt.value() ) )
611 continue;
612
613 mMarkerSymbol->renderPoint( context.worldTransform().map( QPointF( pointIt.key(), pointIt.value() ) ), nullptr, context.renderContext() );
614 }
615 mMarkerSymbol->stopRender( context.renderContext() );
616}
617
618QVector<QgsAbstractProfileResults::Feature> QgsVectorLayerProfileResults::asIndividualFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback ) const
619{
620 QVector<QgsAbstractProfileResults::Feature> res;
621 res.reserve( features.size() );
622 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
623 {
624 if ( feedback && feedback->isCanceled() )
625 break;
626
627 for ( const Feature &feature : it.value() )
628 {
629 if ( feedback && feedback->isCanceled() )
630 break;
631
633 outFeature.layerIdentifier = mId;
634 outFeature.attributes = {{QStringLiteral( "id" ), feature.featureId }};
635 switch ( type )
636 {
638 outFeature.geometry = feature.geometry;
639 break;
640
642 outFeature.geometry = feature.crossSectionGeometry;
643 break;
644
646 break; // unreachable
647 }
648 res << outFeature;
649 }
650 }
651 return res;
652}
653
655{
657 const QgsVectorLayerProfileGenerator *vlGenerator = qgis::down_cast< const QgsVectorLayerProfileGenerator * >( generator );
658
659 mId = vlGenerator->mId;
660 profileType = vlGenerator->mType;
661 respectLayerSymbology = vlGenerator->mRespectLayerSymbology;
662 mMarkerSymbol.reset( vlGenerator->mProfileMarkerSymbol->clone() );
663 mShowMarkerSymbolInSurfacePlots = vlGenerator->mShowMarkerSymbolInSurfacePlots;
664}
665
666//
667// QgsVectorLayerProfileGenerator
668//
669
672 , mId( layer->id() )
673 , mFeedback( std::make_unique< QgsFeedback >() )
674 , mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
675 , mTerrainProvider( request.terrainProvider() ? request.terrainProvider()->clone() : nullptr )
676 , mTolerance( request.tolerance() )
677 , mSourceCrs( layer->crs3D() )
678 , mTargetCrs( request.crs() )
679 , mTransformContext( request.transformContext() )
680 , mExtent( layer->extent() )
681 , mSource( std::make_unique< QgsVectorLayerFeatureSource >( layer ) )
682 , mOffset( layer->elevationProperties()->zOffset() )
683 , mScale( layer->elevationProperties()->zScale() )
684 , mType( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->type() )
685 , mClamping( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->clamping() )
686 , mBinding( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->binding() )
687 , mExtrusionEnabled( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->extrusionEnabled() )
688 , mExtrusionHeight( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->extrusionHeight() )
689 , mExpressionContext( request.expressionContext() )
690 , mFields( layer->fields() )
691 , mDataDefinedProperties( layer->elevationProperties()->dataDefinedProperties() )
692 , mWkbType( layer->wkbType() )
693 , mRespectLayerSymbology( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->respectLayerSymbology() )
694 , mProfileMarkerSymbol( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileMarkerSymbol()->clone() )
695 , mShowMarkerSymbolInSurfacePlots( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->showMarkerSymbolInSurfacePlots() )
696 , mLayer( layer )
697{
698 if ( mTerrainProvider )
699 mTerrainProvider->prepare(); // must be done on main thread
700
701 // make sure profile curve is always 2d, or we may get unwanted z value averaging for intersections from GEOS
702 if ( mProfileCurve )
703 mProfileCurve->dropZValue();
704
705 mSymbology = qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileSymbology();
706 mElevationLimit = qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->elevationLimit();
707
708 mLineSymbol.reset( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileLineSymbol()->clone() );
709 mFillSymbol.reset( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileFillSymbol()->clone() );
710}
711
713{
714 return mId;
715}
716
718
720{
721 if ( !mProfileCurve || mFeedback->isCanceled() )
722 return false;
723
724 // we need to transform the profile curve to the vector's CRS
725 mTransformedCurve.reset( mProfileCurve->clone() );
726 mLayerToTargetTransform = QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext );
727 if ( mTerrainProvider )
728 mTargetToTerrainProviderTransform = QgsCoordinateTransform( mTargetCrs, mTerrainProvider->crs(), mTransformContext );
729
730 try
731 {
732 mTransformedCurve->transform( mLayerToTargetTransform, Qgis::TransformDirection::Reverse );
733 }
734 catch ( QgsCsException & )
735 {
736 QgsDebugError( QStringLiteral( "Error transforming profile line to vector CRS" ) );
737 return false;
738 }
739
740 const QgsRectangle profileCurveBoundingBox = mTransformedCurve->boundingBox();
741 if ( !profileCurveBoundingBox.intersects( mExtent ) )
742 return false;
743
744 if ( mFeedback->isCanceled() )
745 return false;
746
747 mResults = std::make_unique< QgsVectorLayerProfileResults >();
748 mResults->mLayer = mLayer;
749 mResults->copyPropertiesFromGenerator( this );
750
751 mProfileCurveEngine.reset( new QgsGeos( mProfileCurve.get() ) );
752 mProfileCurveEngine->prepareGeometry();
753
754 if ( mTolerance == 0.0 ) // geos does not handle very well buffer with 0 size
755 {
756 mProfileBufferedCurve = std::unique_ptr<QgsAbstractGeometry>( mProfileCurve->clone() );
757 }
758 else
759 {
760 mProfileBufferedCurve = std::unique_ptr<QgsAbstractGeometry>( mProfileCurveEngine->buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) );
761 }
762
763 mProfileBufferedCurveEngine.reset( new QgsGeos( mProfileBufferedCurve.get() ) );
764 mProfileBufferedCurveEngine->prepareGeometry();
765
766 mDataDefinedProperties.prepare( mExpressionContext );
767
768 if ( mFeedback->isCanceled() )
769 return false;
770
771 switch ( QgsWkbTypes::geometryType( mWkbType ) )
772 {
774 if ( !generateProfileForPoints() )
775 return false;
776 break;
777
779 if ( !generateProfileForLines() )
780 return false;
781 break;
782
784 if ( !generateProfileForPolygons() )
785 return false;
786 break;
787
790 return false;
791 }
792
793 return true;
794}
795
800
802{
803 return mFeedback.get();
804}
805
806bool QgsVectorLayerProfileGenerator::generateProfileForPoints()
807{
808 // get features from layer
809 QgsFeatureRequest request;
810 request.setCoordinateTransform( QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext ) );
811 request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance );
812 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
813 request.setFeedback( mFeedback.get() );
814
815 // our feature request is using the optimised distance within check (allowing use of spatial index)
816 // BUT this will also include points which are within the tolerance distance before/after the end of line.
817 // So we also need to double check that they fall within the flat buffered curve too.
818
819 QgsFeature feature;
820 QgsFeatureIterator it = mSource->getFeatures( request );
821 while ( !mFeedback->isCanceled() && it.nextFeature( feature ) )
822 {
823 mExpressionContext.setFeature( feature );
824
825 const QgsGeometry g = feature.geometry();
826 for ( auto it = g.const_parts_begin(); !mFeedback->isCanceled() && it != g.const_parts_end(); ++it )
827 {
828 if ( mProfileBufferedCurveEngine->intersects( *it ) )
829 {
830 processIntersectionPoint( qgsgeometry_cast< const QgsPoint * >( *it ), feature );
831 }
832 }
833 }
834 return !mFeedback->isCanceled();
835}
836
837void QgsVectorLayerProfileGenerator::processIntersectionPoint( const QgsPoint *point, const QgsFeature &feature )
838{
839 QString error;
840 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ZOffset, mExpressionContext, mOffset );
841
842 const double height = featureZToHeight( point->x(), point->y(), point->z(), offset );
843 mResults->mRawPoints.append( QgsPoint( point->x(), point->y(), height ) );
844 mResults->minZ = std::min( mResults->minZ, height );
845 mResults->maxZ = std::max( mResults->maxZ, height );
846
847 const double distanceAlongProfileCurve = mProfileCurveEngine->lineLocatePoint( *point, &error );
848 mResults->mDistanceToHeightMap.insert( distanceAlongProfileCurve, height );
849
851 resultFeature.featureId = feature.id();
852 if ( mExtrusionEnabled )
853 {
854 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
855
856 resultFeature.geometry = QgsGeometry( new QgsLineString( QgsPoint( point->x(), point->y(), height ),
857 QgsPoint( point->x(), point->y(), height + extrusion ) ) );
858 resultFeature.crossSectionGeometry = QgsGeometry( new QgsLineString( QgsPoint( distanceAlongProfileCurve, height ),
859 QgsPoint( distanceAlongProfileCurve, height + extrusion ) ) );
860 mResults->minZ = std::min( mResults->minZ, height + extrusion );
861 mResults->maxZ = std::max( mResults->maxZ, height + extrusion );
862 }
863 else
864 {
865 resultFeature.geometry = QgsGeometry( new QgsPoint( point->x(), point->y(), height ) );
866 resultFeature.crossSectionGeometry = QgsGeometry( new QgsPoint( distanceAlongProfileCurve, height ) );
867 }
868
869 mResults->features[resultFeature.featureId].append( resultFeature );
870}
871
872void QgsVectorLayerProfileGenerator::processIntersectionCurve( const QgsLineString *intersectionCurve, const QgsFeature &feature )
873{
874 QString error;
875
877 resultFeature.featureId = feature.id();
878 double maxDistanceAlongProfileCurve = std::numeric_limits<double>::lowest();
879
880 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ZOffset, mExpressionContext, mOffset );
881 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
882
883 const int numPoints = intersectionCurve->numPoints();
884 QVector< double > newX( numPoints );
885 QVector< double > newY( numPoints );
886 QVector< double > newZ( numPoints );
887 QVector< double > newDistance( numPoints );
888
889 const double *inX = intersectionCurve->xData();
890 const double *inY = intersectionCurve->yData();
891 const double *inZ = intersectionCurve->is3D() ? intersectionCurve->zData() : nullptr;
892 double *outX = newX.data();
893 double *outY = newY.data();
894 double *outZ = newZ.data();
895 double *outDistance = newDistance.data();
896
897 QVector< double > extrudedZ;
898 double *extZOut = nullptr;
899 if ( mExtrusionEnabled )
900 {
901 extrudedZ.resize( numPoints );
902 extZOut = extrudedZ.data();
903 }
904
905 for ( int i = 0 ; ! mFeedback->isCanceled() && i < numPoints; ++i )
906 {
907 QgsPoint intersectionPoint( *inX, *inY, ( inZ ? *inZ : std::numeric_limits<double>::quiet_NaN() ) );
908
909 const double height = featureZToHeight( intersectionPoint.x(), intersectionPoint.y(), intersectionPoint.z(), offset );
910 const double distanceAlongProfileCurve = mProfileCurveEngine->lineLocatePoint( intersectionPoint, &error );
911
912 maxDistanceAlongProfileCurve = std::max( maxDistanceAlongProfileCurve, distanceAlongProfileCurve );
913
914 mResults->mRawPoints.append( QgsPoint( intersectionPoint.x(), intersectionPoint.y(), height ) );
915 mResults->minZ = std::min( mResults->minZ, height );
916 mResults->maxZ = std::max( mResults->maxZ, height );
917
918 mResults->mDistanceToHeightMap.insert( distanceAlongProfileCurve, height );
919 *outDistance++ = distanceAlongProfileCurve;
920
921 *outX++ = intersectionPoint.x();
922 *outY++ = intersectionPoint.y();
923 *outZ++ = height;
924 if ( extZOut )
925 *extZOut++ = height + extrusion;
926
927 if ( mExtrusionEnabled )
928 {
929 mResults->minZ = std::min( mResults->minZ, height + extrusion );
930 mResults->maxZ = std::max( mResults->maxZ, height + extrusion );
931 }
932 inX++;
933 inY++;
934 if ( inZ )
935 inZ++;
936 }
937
938 mResults->mDistanceToHeightMap.insert( maxDistanceAlongProfileCurve + 0.000001, std::numeric_limits<double>::quiet_NaN() );
939
940 if ( mFeedback->isCanceled() )
941 return;
942
943 // create geometries from vector data
944 if ( mExtrusionEnabled )
945 {
946 std::unique_ptr< QgsLineString > ring = std::make_unique< QgsLineString >( newX, newY, newZ );
947 std::unique_ptr< QgsLineString > extrudedRing = std::make_unique< QgsLineString >( newX, newY, extrudedZ );
948 std::unique_ptr< QgsLineString > reversedExtrusion( extrudedRing->reversed() );
949 ring->append( reversedExtrusion.get() );
950 ring->close();
951 resultFeature.geometry = QgsGeometry( new QgsPolygon( ring.release() ) );
952
953 std::unique_ptr< QgsLineString > distanceVHeightRing = std::make_unique< QgsLineString >( newDistance, newZ );
954 std::unique_ptr< QgsLineString > extrudedDistanceVHeightRing = std::make_unique< QgsLineString >( newDistance, extrudedZ );
955 std::unique_ptr< QgsLineString > reversedDistanceVHeightExtrusion( extrudedDistanceVHeightRing->reversed() );
956 distanceVHeightRing->append( reversedDistanceVHeightExtrusion.get() );
957 distanceVHeightRing->close();
958 resultFeature.crossSectionGeometry = QgsGeometry( new QgsPolygon( distanceVHeightRing.release() ) );
959 }
960 else
961 {
962 resultFeature.geometry = QgsGeometry( new QgsLineString( newX, newY, newZ ) ) ;
963 resultFeature.crossSectionGeometry = QgsGeometry( new QgsLineString( newDistance, newZ ) );
964 }
965
966 mResults->features[resultFeature.featureId].append( resultFeature );
967}
968
969bool QgsVectorLayerProfileGenerator::generateProfileForLines()
970{
971 // get features from layer
972 QgsFeatureRequest request;
973 request.setDestinationCrs( mTargetCrs, mTransformContext );
974 if ( mTolerance > 0 )
975 {
976 request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance );
977 }
978 else
979 {
980 request.setFilterRect( mProfileCurve->boundingBox() );
981 }
982 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
983 request.setFeedback( mFeedback.get() );
984
985 auto processCurve = [this]( const QgsFeature & feature, const QgsCurve * featGeomPart )
986 {
987 QString error;
988 std::unique_ptr< QgsAbstractGeometry > intersection( mProfileBufferedCurveEngine->intersection( featGeomPart, &error ) );
989 if ( !intersection )
990 return;
991
992 if ( mFeedback->isCanceled() )
993 return;
994
995
996 // Intersection is empty : GEOS issue for vertical intersection : use feature geometry as intersection
997 if ( intersection->isEmpty() )
998 {
999 intersection.reset( featGeomPart->clone() );
1000 }
1001
1002 QgsGeos featGeomPartGeos( featGeomPart );
1003 featGeomPartGeos.prepareGeometry();
1004
1005 for ( auto it = intersection->const_parts_begin();
1006 !mFeedback->isCanceled() && it != intersection->const_parts_end();
1007 ++it )
1008 {
1009 if ( const QgsPoint *intersectionPoint = qgsgeometry_cast< const QgsPoint * >( *it ) )
1010 {
1011 // unfortunately we need to do some work to interpolate the z value for the line -- GEOS doesn't give us this
1012 QString error;
1013 const double distance = featGeomPartGeos.lineLocatePoint( *intersectionPoint, &error );
1014 std::unique_ptr< QgsPoint > interpolatedPoint( featGeomPart->interpolatePoint( distance ) );
1015
1016 processIntersectionPoint( interpolatedPoint.get(), feature );
1017 }
1018 else if ( const QgsLineString *intersectionCurve = qgsgeometry_cast< const QgsLineString * >( *it ) )
1019 {
1020 processIntersectionCurve( intersectionCurve, feature );
1021 }
1022 }
1023 };
1024
1025 QgsFeature feature;
1026 QgsFeatureIterator it = mSource->getFeatures( request );
1027 while ( !mFeedback->isCanceled() && it.nextFeature( feature ) )
1028 {
1029 mExpressionContext.setFeature( feature );
1030
1031 const QgsGeometry g = feature.geometry();
1032 for ( auto it = g.const_parts_begin(); !mFeedback->isCanceled() && it != g.const_parts_end(); ++it )
1033 {
1034 if ( mProfileBufferedCurveEngine->intersects( *it ) )
1035 {
1036 processCurve( feature, qgsgeometry_cast< const QgsCurve * >( *it ) );
1037 }
1038 }
1039 }
1040
1041 return !mFeedback->isCanceled();
1042}
1043
1044QgsPoint QgsVectorLayerProfileGenerator::interpolatePointOnTriangle( const QgsPolygon *triangle, double x, double y ) const
1045{
1046 QgsPoint p1, p2, p3;
1048 triangle->exteriorRing()->pointAt( 0, p1, vt );
1049 triangle->exteriorRing()->pointAt( 1, p2, vt );
1050 triangle->exteriorRing()->pointAt( 2, p3, vt );
1051 const double z = QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, p1.z(), p2.z(), p3.z(), QgsPointXY( x, y ) );
1052 return QgsPoint( x, y, z );
1053};
1054
1055void QgsVectorLayerProfileGenerator::processTriangleIntersectForPoint( const QgsPolygon *triangle, const QgsPoint *p, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts )
1056{
1057 const QgsPoint interpolatedPoint = interpolatePointOnTriangle( triangle, p->x(), p->y() );
1058 mResults->mRawPoints.append( interpolatedPoint );
1059 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() );
1060 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() );
1061
1062 QString lastError;
1063 const double distance = mProfileCurveEngine->lineLocatePoint( *p, &lastError );
1064 mResults->mDistanceToHeightMap.insert( distance, interpolatedPoint.z() );
1065
1066 if ( mExtrusionEnabled )
1067 {
1068 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
1069
1070 transformedParts.append( QgsGeometry( new QgsLineString( interpolatedPoint,
1071 QgsPoint( interpolatedPoint.x(), interpolatedPoint.y(), interpolatedPoint.z() + extrusion ) ) ) );
1072 crossSectionParts.append( QgsGeometry( new QgsLineString( QgsPoint( distance, interpolatedPoint.z() ),
1073 QgsPoint( distance, interpolatedPoint.z() + extrusion ) ) ) );
1074 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() + extrusion );
1075 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() + extrusion );
1076 }
1077 else
1078 {
1079 transformedParts.append( QgsGeometry( new QgsPoint( interpolatedPoint ) ) );
1080 crossSectionParts.append( QgsGeometry( new QgsPoint( distance, interpolatedPoint.z() ) ) );
1081 }
1082}
1083
1084void QgsVectorLayerProfileGenerator::processTriangleIntersectForLine( const QgsPolygon *triangle, const QgsLineString *intersectionLine, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts )
1085{
1086 if ( triangle->exteriorRing()->numPoints() < 4 ) // not a polygon
1087 return;
1088
1089 int numPoints = intersectionLine->numPoints();
1090 QVector< double > newX( numPoints );
1091 QVector< double > newY( numPoints );
1092 QVector< double > newZ( numPoints );
1093 QVector< double > newDistance( numPoints );
1094
1095 const double *inX = intersectionLine->xData();
1096 const double *inY = intersectionLine->yData();
1097 const double *inZ = intersectionLine->is3D() ? intersectionLine->zData() : nullptr;
1098 double *outX = newX.data();
1099 double *outY = newY.data();
1100 double *outZ = newZ.data();
1101 double *outDistance = newDistance.data();
1102
1103 double lastDistanceAlongProfileCurve = 0.0;
1104 QVector< double > extrudedZ;
1105 double *extZOut = nullptr;
1106 double extrusion = 0;
1107
1108 if ( mExtrusionEnabled )
1109 {
1110 extrudedZ.resize( numPoints );
1111 extZOut = extrudedZ.data();
1112
1113 extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
1114 }
1115
1116 QString lastError;
1117 for ( int i = 0 ; ! mFeedback->isCanceled() && i < numPoints; ++i )
1118 {
1119 double x = *inX++;
1120 double y = *inY++;
1121 double z = inZ ? *inZ++ : 0;
1122
1123 QgsPoint interpolatedPoint( x, y, z ); // general case (not a triangle)
1124
1125 *outX++ = x;
1126 *outY++ = y;
1127 if ( triangle->exteriorRing()->numPoints() == 4 ) // triangle case
1128 {
1129 interpolatedPoint = interpolatePointOnTriangle( triangle, x, y );
1130 }
1131 double tempOutZ = std::isnan( interpolatedPoint.z() ) ? 0.0 : interpolatedPoint.z();
1132 *outZ++ = tempOutZ;
1133
1134 if ( mExtrusionEnabled )
1135 *extZOut++ = tempOutZ + extrusion;
1136
1137 mResults->mRawPoints.append( interpolatedPoint );
1138 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() );
1139 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() );
1140 if ( mExtrusionEnabled )
1141 {
1142 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() + extrusion );
1143 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() + extrusion );
1144 }
1145
1146 const double distance = mProfileCurveEngine->lineLocatePoint( interpolatedPoint, &lastError );
1147 *outDistance++ = distance;
1148
1149 mResults->mDistanceToHeightMap.insert( distance, interpolatedPoint.z() );
1150 lastDistanceAlongProfileCurve = distance;
1151 }
1152
1153 // insert nan point to end the line
1154 mResults->mDistanceToHeightMap.insert( lastDistanceAlongProfileCurve + 0.000001, std::numeric_limits<double>::quiet_NaN() );
1155
1156 if ( mFeedback->isCanceled() )
1157 return;
1158
1159 if ( mExtrusionEnabled )
1160 {
1161 std::unique_ptr< QgsLineString > ring = std::make_unique< QgsLineString >( newX, newY, newZ );
1162 std::unique_ptr< QgsLineString > extrudedRing = std::make_unique< QgsLineString >( newX, newY, extrudedZ );
1163 std::unique_ptr< QgsLineString > reversedExtrusion( extrudedRing->reversed() );
1164 ring->append( reversedExtrusion.get() );
1165 ring->close();
1166 transformedParts.append( QgsGeometry( new QgsPolygon( ring.release() ) ) );
1167
1168 std::unique_ptr< QgsLineString > distanceVHeightRing = std::make_unique< QgsLineString >( newDistance, newZ );
1169 std::unique_ptr< QgsLineString > extrudedDistanceVHeightRing = std::make_unique< QgsLineString >( newDistance, extrudedZ );
1170 std::unique_ptr< QgsLineString > reversedDistanceVHeightExtrusion( extrudedDistanceVHeightRing->reversed() );
1171 distanceVHeightRing->append( reversedDistanceVHeightExtrusion.get() );
1172 distanceVHeightRing->close();
1173 crossSectionParts.append( QgsGeometry( new QgsPolygon( distanceVHeightRing.release() ) ) );
1174 }
1175 else
1176 {
1177 transformedParts.append( QgsGeometry( new QgsLineString( newX, newY, newZ ) ) );
1178 crossSectionParts.append( QgsGeometry( new QgsLineString( newDistance, newZ ) ) );
1179 }
1180};
1181
1182void QgsVectorLayerProfileGenerator::processTriangleIntersectForPolygon( const QgsPolygon *sourcePolygon, const QgsPolygon *intersectionPolygon, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts )
1183{
1184 bool oldExtrusion = mExtrusionEnabled;
1185
1186 /* Polyone extrusion produces I or C or inverted C shapes because the starting and ending points are the same.
1187 We observe the same case with linestrings if the starting and ending points are not at the ends.
1188 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.
1189 In order to avoid having strange shapes that cannot be understood by the end user, extrusion is deactivated in the case of polygons.
1190
1191 .^..
1192 ./ | \..
1193 ../ | \...
1194 ../ | \...
1195 ../ | \.. ....^..
1196 ../ | ........\.../ \... ^
1197 ../ ......|......./ \... \.... .../ \
1198 /,........../ | \.. \... / \
1199 v | \... ..../ \... \
1200 | \ ./ \... \
1201 | v \.. \
1202 | `v
1203 |
1204 .^..
1205 ./ \..
1206 ../ \...
1207 ../ \...
1208 ../ \.. ....^..
1209 ../ ........\.../ \... ^
1210 ../ ............../ \... \.... .../ \
1211 /,........../ \.. \... / \
1212 v \... ..../ \... \
1213 \ ./ \... \
1214 v \.. \
1215 `v
1216 */
1217 mExtrusionEnabled = false;
1218 if ( mProfileBufferedCurveEngine->contains( sourcePolygon ) ) // sourcePolygon is entirely inside curve buffer, we keep it as whole
1219 {
1220 if ( const QgsCurve *exterior = sourcePolygon->exteriorRing() )
1221 {
1222 QgsLineString *exteriorLine = qgsgeometry_cast<QgsLineString *>( exterior );
1223 processTriangleIntersectForLine( sourcePolygon, exteriorLine, transformedParts, crossSectionParts );
1224 }
1225 for ( int i = 0; i < sourcePolygon->numInteriorRings(); ++i )
1226 {
1227 QgsLineString *interiorLine = qgsgeometry_cast<QgsLineString *>( sourcePolygon->interiorRing( i ) );
1228 processTriangleIntersectForLine( sourcePolygon, interiorLine, transformedParts, crossSectionParts );
1229 }
1230 }
1231 else // sourcePolygon is partially inside curve buffer, the intersectionPolygon is closed due to the intersection operation then
1232 // it must be 'reopened'
1233 {
1234 if ( const QgsCurve *exterior = intersectionPolygon->exteriorRing() )
1235 {
1236 QgsLineString *exteriorLine = qgsgeometry_cast<QgsLineString *>( exterior )->clone();
1237 exteriorLine->deleteVertex( QgsVertexId( 0, 0, exteriorLine->numPoints() - 1 ) ); // open linestring
1238 processTriangleIntersectForLine( sourcePolygon, exteriorLine, transformedParts, crossSectionParts );
1239 delete exteriorLine;
1240 }
1241 for ( int i = 0; i < intersectionPolygon->numInteriorRings(); ++i )
1242 {
1243 QgsLineString *interiorLine = qgsgeometry_cast<QgsLineString *>( intersectionPolygon->interiorRing( i ) );
1244 if ( mProfileBufferedCurveEngine->contains( interiorLine ) ) // interiorLine is entirely inside curve buffer
1245 {
1246 processTriangleIntersectForLine( sourcePolygon, interiorLine, transformedParts, crossSectionParts );
1247 }
1248 else
1249 {
1250 interiorLine = qgsgeometry_cast<QgsLineString *>( intersectionPolygon->interiorRing( i ) )->clone();
1251 interiorLine->deleteVertex( QgsVertexId( 0, 0, interiorLine->numPoints() - 1 ) ); // open linestring
1252 processTriangleIntersectForLine( sourcePolygon, interiorLine, transformedParts, crossSectionParts );
1253 delete interiorLine;
1254 }
1255 }
1256 }
1257
1258 mExtrusionEnabled = oldExtrusion;
1259};
1260
1261bool QgsVectorLayerProfileGenerator::generateProfileForPolygons()
1262{
1263 // get features from layer
1264 QgsFeatureRequest request;
1265 request.setDestinationCrs( mTargetCrs, mTransformContext );
1266 if ( mTolerance > 0 )
1267 {
1268 request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance );
1269 }
1270 else
1271 {
1272 request.setFilterRect( mProfileCurve->boundingBox() );
1273 }
1274 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
1275 request.setFeedback( mFeedback.get() );
1276
1277 std::function< void( const QgsPolygon *triangle, const QgsAbstractGeometry *intersect, QVector< QgsGeometry > &, QVector< QgsGeometry > & ) > processTriangleLineIntersect;
1278 processTriangleLineIntersect = [this]( const QgsPolygon * triangle, const QgsAbstractGeometry * intersection, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts )
1279 {
1280 for ( auto it = intersection->const_parts_begin();
1281 ! mFeedback->isCanceled() && it != intersection->const_parts_end();
1282 ++it )
1283 {
1284 // intersect may be a (multi)point or (multi)linestring
1285 switch ( QgsWkbTypes::geometryType( ( *it )->wkbType() ) )
1286 {
1288 if ( const QgsPoint *p = qgsgeometry_cast< const QgsPoint * >( *it ) )
1289 {
1290 processTriangleIntersectForPoint( triangle, p, transformedParts, crossSectionParts );
1291 }
1292 break;
1293
1295 if ( const QgsLineString *intersectionLine = qgsgeometry_cast< const QgsLineString * >( *it ) )
1296 {
1297 processTriangleIntersectForLine( triangle, intersectionLine, transformedParts, crossSectionParts );
1298 }
1299 break;
1300
1302 if ( const QgsPolygon *poly = qgsgeometry_cast< const QgsPolygon * >( *it ) )
1303 {
1304 processTriangleIntersectForPolygon( triangle, poly, transformedParts, crossSectionParts );
1305 }
1306 break;
1307
1310 return;
1311 }
1312 }
1313 };
1314
1315 auto triangleIsCollinearInXYPlane = []( const QgsPolygon * polygon )-> bool
1316 {
1317 const QgsLineString *ring = qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() );
1318 return QgsGeometryUtilsBase::pointsAreCollinear( ring->xAt( 0 ), ring->yAt( 0 ),
1319 ring->xAt( 1 ), ring->yAt( 1 ),
1320 ring->xAt( 2 ), ring->yAt( 2 ), 0.005 );
1321 };
1322
1323 auto processPolygon = [this, &processTriangleLineIntersect, &triangleIsCollinearInXYPlane]( const QgsCurvePolygon * polygon, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts, double offset, bool & wasCollinear )
1324 {
1325 std::unique_ptr< QgsPolygon > clampedPolygon;
1326 if ( const QgsPolygon *p = qgsgeometry_cast< const QgsPolygon * >( polygon ) )
1327 {
1328 clampedPolygon.reset( p->clone() );
1329 }
1330 else
1331 {
1332 clampedPolygon.reset( qgsgeometry_cast< QgsPolygon * >( polygon->segmentize() ) );
1333 }
1334 clampAltitudes( clampedPolygon.get(), offset );
1335
1336 if ( mFeedback->isCanceled() )
1337 return;
1338
1339 if ( mTolerance > 0.0 ) // if the tolerance is not 0.0 we will have a polygon / polygon intersection, we do not need tessellation
1340 {
1341 QString error;
1342 if ( mProfileBufferedCurveEngine->intersects( clampedPolygon.get(), &error ) )
1343 {
1344 std::unique_ptr< QgsAbstractGeometry > intersection;
1345 intersection.reset( mProfileBufferedCurveEngine->intersection( clampedPolygon.get(), &error ) );
1346 if ( error.isEmpty() )
1347 {
1348 processTriangleLineIntersect( clampedPolygon.get(), intersection.get(), transformedParts, crossSectionParts );
1349 }
1350 else
1351 {
1352 // this case may occur with vertical object as geos does not handle very well 3D data.
1353 // 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!
1354 // This piece of code is just a fix to handle this case, a better and real 3D capable library is needed (like SFCGAL).
1355 QgsLineString *ring = qgsgeometry_cast< QgsLineString * >( clampedPolygon->exteriorRing() );
1356 int numPoints = ring->numPoints();
1357 QVector< double > newX( numPoints );
1358 QVector< double > newY( numPoints );
1359 QVector< double > newZ( numPoints );
1360 double *outX = newX.data();
1361 double *outY = newY.data();
1362 double *outZ = newZ.data();
1363
1364 const double *inX = ring->xData();
1365 const double *inY = ring->yData();
1366 const double *inZ = ring->zData();
1367 for ( int i = 0 ; ! mFeedback->isCanceled() && i < ring->numPoints() - 1; ++i )
1368 {
1369 *outX++ = inX[i] + i * 1.0e-9;
1370 *outY++ = inY[i] + i * 1.0e-9;
1371 *outZ++ = inZ[i];
1372 }
1373 std::unique_ptr< QgsPolygon > shiftedPoly;
1374 shiftedPoly.reset( new QgsPolygon( new QgsLineString( newX, newY, newZ ) ) );
1375
1376 intersection.reset( mProfileBufferedCurveEngine->intersection( shiftedPoly.get(), &error ) );
1377 if ( intersection.get() )
1378 processTriangleLineIntersect( clampedPolygon.get(), intersection.get(), transformedParts, crossSectionParts );
1379 else
1380 QgsDebugMsgLevel( QStringLiteral( "processPolygon after shift bad geom! error: %1" ).arg( error ), 0 );
1381 }
1382 }
1383
1384 }
1385 else // ie. polygon / line intersection ==> need tessellation
1386 {
1387 QgsGeometry tessellation;
1388 if ( clampedPolygon->numInteriorRings() == 0 && clampedPolygon->exteriorRing() && clampedPolygon->exteriorRing()->numPoints() == 4 && clampedPolygon->exteriorRing()->isClosed() )
1389 {
1390 // special case -- polygon is already a triangle, so no need to tessellate
1391 std::unique_ptr< QgsMultiPolygon > multiPolygon = std::make_unique< QgsMultiPolygon >();
1392 multiPolygon->addGeometry( clampedPolygon.release() );
1393 tessellation = QgsGeometry( std::move( multiPolygon ) );
1394 }
1395 else
1396 {
1397 const QgsRectangle bounds = clampedPolygon->boundingBox();
1398 QgsTessellator t( bounds, false, false, false, false );
1399 t.addPolygon( *clampedPolygon, 0 );
1400
1401 tessellation = QgsGeometry( t.asMultiPolygon() );
1402 if ( mFeedback->isCanceled() )
1403 return;
1404
1405 tessellation.translate( bounds.xMinimum(), bounds.yMinimum() );
1406 }
1407
1408 // iterate through the tessellation, finding triangles which intersect the line
1409 const int numTriangles = qgsgeometry_cast< const QgsMultiPolygon * >( tessellation.constGet() )->numGeometries();
1410 for ( int i = 0; ! mFeedback->isCanceled() && i < numTriangles; ++i )
1411 {
1412 const QgsPolygon *triangle = qgsgeometry_cast< const QgsPolygon * >( qgsgeometry_cast< const QgsMultiPolygon * >( tessellation.constGet() )->geometryN( i ) );
1413
1414 if ( triangleIsCollinearInXYPlane( triangle ) )
1415 {
1416 wasCollinear = true;
1417 const QgsLineString *ring = qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() );
1418
1419 QString lastError;
1420 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( mProfileCurve.get() ) )
1421 {
1422 for ( int curveSegmentIndex = 0; curveSegmentIndex < mProfileCurve->numPoints() - 1; ++curveSegmentIndex )
1423 {
1424 const QgsPoint p1 = ls->pointN( curveSegmentIndex );
1425 const QgsPoint p2 = ls->pointN( curveSegmentIndex + 1 );
1426
1427 QgsPoint intersectionPoint;
1428 double minZ = std::numeric_limits< double >::max();
1429 double maxZ = std::numeric_limits< double >::lowest();
1430
1431 for ( auto vertexPair : std::array<std::pair<int, int>, 3> {{ { 0, 1}, {1, 2}, {2, 0} }} )
1432 {
1433 bool isIntersection = false;
1434 if ( QgsGeometryUtils::segmentIntersection( ring->pointN( vertexPair.first ), ring->pointN( vertexPair.second ), p1, p2, intersectionPoint, isIntersection ) )
1435 {
1436 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() );
1437 const double intersectionZ = ring->zAt( vertexPair.first ) + ( ring->zAt( vertexPair.second ) - ring->zAt( vertexPair.first ) ) * fraction;
1438 minZ = std::min( minZ, intersectionZ );
1439 maxZ = std::max( maxZ, intersectionZ );
1440 }
1441 }
1442
1443 if ( !intersectionPoint.isEmpty() )
1444 {
1445 // need z?
1446 mResults->mRawPoints.append( intersectionPoint );
1447 mResults->minZ = std::min( mResults->minZ, minZ );
1448 mResults->maxZ = std::max( mResults->maxZ, maxZ );
1449
1450 const double distance = mProfileCurveEngine->lineLocatePoint( intersectionPoint, &lastError );
1451
1452 crossSectionParts.append( QgsGeometry( new QgsLineString( QVector< double > {distance, distance}, QVector< double > {minZ, maxZ} ) ) );
1453
1454 mResults->mDistanceToHeightMap.insert( distance, minZ );
1455 mResults->mDistanceToHeightMap.insert( distance, maxZ );
1456 }
1457 }
1458 }
1459 else
1460 {
1461 // curved geometries, not supported yet, but not possible through the GUI anyway
1462 QgsDebugError( QStringLiteral( "Collinear triangles with curved profile lines are not supported yet" ) );
1463 }
1464 }
1465 else // not collinear
1466 {
1467 QString error;
1468 if ( mProfileBufferedCurveEngine->intersects( triangle, &error ) )
1469 {
1470 std::unique_ptr< QgsAbstractGeometry > intersection( mProfileBufferedCurveEngine->intersection( triangle, &error ) );
1471 processTriangleLineIntersect( triangle, intersection.get(), transformedParts, crossSectionParts );
1472 }
1473 }
1474 }
1475 }
1476 };
1477
1478 // ========= MAIN JOB
1479 QgsFeature feature;
1480 QgsFeatureIterator it = mSource->getFeatures( request );
1481 while ( ! mFeedback->isCanceled() && it.nextFeature( feature ) )
1482 {
1483 if ( !mProfileBufferedCurveEngine->intersects( feature.geometry().constGet() ) )
1484 continue;
1485
1486 mExpressionContext.setFeature( feature );
1487
1488 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::Property::ZOffset, mExpressionContext, mOffset );
1489 const QgsGeometry g = feature.geometry();
1490 QVector< QgsGeometry > transformedParts;
1491 QVector< QgsGeometry > crossSectionParts;
1492 bool wasCollinear = false;
1493
1494 // === process intersection of geometry feature parts with the mProfileBoxEngine
1495 for ( auto it = g.const_parts_begin(); ! mFeedback->isCanceled() && it != g.const_parts_end(); ++it )
1496 {
1497 if ( mProfileBufferedCurveEngine->intersects( *it ) )
1498 {
1499 if ( const QgsCurvePolygon *curvePolygon = qgsgeometry_cast< const QgsCurvePolygon * >( *it ) )
1500 {
1501 processPolygon( curvePolygon, transformedParts, crossSectionParts, offset, wasCollinear );
1502 }
1503 else if ( const QgsPolyhedralSurface *polySurface = qgsgeometry_cast< const QgsPolyhedralSurface * >( *it ) )
1504 {
1505 for ( int i = 0; i < polySurface->numPatches(); ++i )
1506 {
1507 const QgsPolygon *polygon = polySurface->patchN( i );
1508 if ( mProfileBufferedCurveEngine->intersects( polygon ) )
1509 {
1510 processPolygon( polygon, transformedParts, crossSectionParts, offset, wasCollinear );
1511 }
1512 }
1513 }
1514 else
1515 {
1516 QgsDebugError( QStringLiteral( "Unhandled Geometry type: %1" ).arg( ( *it )->wktTypeStr() ) );
1517 }
1518 }
1519 }
1520
1521 if ( mFeedback->isCanceled() )
1522 return false;
1523
1524 // === aggregate results for this feature
1526 resultFeature.featureId = feature.id();
1527 resultFeature.geometry = transformedParts.size() > 1 ? QgsGeometry::collectGeometry( transformedParts ) : transformedParts.value( 0 );
1528 if ( !crossSectionParts.empty() )
1529 {
1530 if ( !wasCollinear )
1531 {
1532 QgsGeometry unioned = QgsGeometry::unaryUnion( crossSectionParts );
1533 if ( unioned.isEmpty() )
1534 {
1535 resultFeature.crossSectionGeometry = QgsGeometry::collectGeometry( crossSectionParts );
1536 }
1537 else
1538 {
1539 if ( unioned.type() == Qgis::GeometryType::Line )
1540 {
1541 unioned = unioned.mergeLines();
1542 }
1543 resultFeature.crossSectionGeometry = unioned;
1544 }
1545 }
1546 else
1547 {
1548 resultFeature.crossSectionGeometry = QgsGeometry::collectGeometry( crossSectionParts );
1549 }
1550 }
1551 mResults->features[resultFeature.featureId].append( resultFeature );
1552 }
1553 return true;
1554}
1555
1556double QgsVectorLayerProfileGenerator::terrainHeight( double x, double y )
1557{
1558 if ( !mTerrainProvider )
1559 return std::numeric_limits<double>::quiet_NaN();
1560
1561 // transform feature point to terrain provider crs
1562 try
1563 {
1564 double dummyZ = 0;
1565 mTargetToTerrainProviderTransform.transformInPlace( x, y, dummyZ );
1566 }
1567 catch ( QgsCsException & )
1568 {
1569 return std::numeric_limits<double>::quiet_NaN();
1570 }
1571
1572 return mTerrainProvider->heightAt( x, y );
1573}
1574
1575double QgsVectorLayerProfileGenerator::featureZToHeight( double x, double y, double z, double offset )
1576{
1577 switch ( mClamping )
1578 {
1580 break;
1581
1584 {
1585 const double terrainZ = terrainHeight( x, y );
1586 if ( !std::isnan( terrainZ ) )
1587 {
1588 switch ( mClamping )
1589 {
1591 if ( std::isnan( z ) )
1592 z = terrainZ;
1593 else
1594 z += terrainZ;
1595 break;
1596
1598 z = terrainZ;
1599 break;
1600
1602 break;
1603 }
1604 }
1605 break;
1606 }
1607 }
1608
1609 return ( std::isnan( z ) ? 0 : z ) * mScale + offset;
1610}
1611
1612void QgsVectorLayerProfileGenerator::clampAltitudes( QgsLineString *lineString, const QgsPoint &centroid, double offset )
1613{
1614 for ( int i = 0; i < lineString->nCoordinates(); ++i )
1615 {
1616 if ( mFeedback->isCanceled() )
1617 break;
1618
1619 double terrainZ = 0;
1620 switch ( mClamping )
1621 {
1624 {
1625 QgsPointXY pt;
1626 switch ( mBinding )
1627 {
1629 pt.setX( lineString->xAt( i ) );
1630 pt.setY( lineString->yAt( i ) );
1631 break;
1632
1634 pt.set( centroid.x(), centroid.y() );
1635 break;
1636 }
1637
1638 terrainZ = terrainHeight( pt.x(), pt.y() );
1639 break;
1640 }
1641
1643 break;
1644 }
1645
1646 double geomZ = 0;
1647
1648 switch ( mClamping )
1649 {
1652 geomZ = lineString->zAt( i );
1653 break;
1654
1656 break;
1657 }
1658
1659 const double z = ( terrainZ + ( std::isnan( geomZ ) ? 0 : geomZ ) ) * mScale + offset;
1660 lineString->setZAt( i, z );
1661 }
1662}
1663
1664bool QgsVectorLayerProfileGenerator::clampAltitudes( QgsPolygon *polygon, double offset )
1665{
1666 if ( !polygon->is3D() )
1667 polygon->addZValue( 0 );
1668
1669 QgsPoint centroid;
1670 switch ( mBinding )
1671 {
1673 break;
1674
1676 centroid = polygon->centroid();
1677 break;
1678 }
1679
1680 QgsCurve *curve = const_cast<QgsCurve *>( polygon->exteriorRing() );
1681 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( curve );
1682 if ( !lineString )
1683 return false;
1684
1685 clampAltitudes( lineString, centroid, offset );
1686
1687 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
1688 {
1689 if ( mFeedback->isCanceled() )
1690 break;
1691
1692 QgsCurve *curve = const_cast<QgsCurve *>( polygon->interiorRing( i ) );
1693 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( curve );
1694 if ( !lineString )
1695 return false;
1696
1697 clampAltitudes( lineString, centroid, offset );
1698 }
1699 return true;
1700}
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:2833
@ 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:3907
@ 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: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 & setCoordinateTransform(const QgsCoordinateTransform &transform)
Sets the coordinate transform which will be used to transform the feature's geometries.
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:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
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.
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.
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
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:137
Line string geometry type, with support for z-dimension and m-values.
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.
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:60
void setY(double y)
Sets the y value of the point.
Definition qgspointxy.h:129
void set(double x, double y)
Sets the x and y value of the point.
Definition qgspointxy.h:136
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:119
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
QgsPoint * clone() const override
Clones the geometry by performing a deep copy.
Definition qgspoint.cpp:104
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:737
double y
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:33
Polyhedral surface geometry type.
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.
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:231
void setColor(const QColor &color) const
Sets the color for the symbol.
qreal opacity() const
Returns the opacity for the symbol.
Definition qgssymbol.h:632
QColor color() const
Returns the symbol's color.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:293
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:6511
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5857
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#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