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