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