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