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