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