QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
qgsvectorlayerprofilegenerator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectorlayerprofilegenerator.cpp
3 ---------------
4 begin : March 2022
5 copyright : (C) 2022 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
18#include "qgsprofilerequest.h"
19#include "qgscurve.h"
20#include "qgsvectorlayer.h"
23#include "qgsgeos.h"
25#include "qgsterrainprovider.h"
26#include "qgspolygon.h"
27#include "qgstessellator.h"
28#include "qgsmultipolygon.h"
29#include "qgsmeshlayerutils.h"
30#include "qgsmultipoint.h"
31#include "qgsmultilinestring.h"
32#include "qgslinesymbol.h"
33#include "qgsfillsymbol.h"
34#include "qgsmarkersymbol.h"
35#include "qgsprofilepoint.h"
36#include "qgsprofilesnapping.h"
38#include <QPolygonF>
39
40//
41// QgsVectorLayerProfileResults
42//
43
45{
46 return QStringLiteral( "vector" );
47}
48
50{
51 QVector<QgsGeometry> res;
52 res.reserve( features.size() );
53 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
54 {
55 for ( const Feature &feature : it.value() )
56 {
57 res.append( feature.geometry );
58 }
59 }
60 return res;
61}
62
63QVector<QgsAbstractProfileResults::Feature> QgsVectorLayerProfileResults::asFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback ) const
64{
65 switch ( profileType )
66 {
69 return asIndividualFeatures( type, feedback );
70 // distance vs elevation table results are always handled like a continuous surface
72
75 }
77}
78
80{
81 switch ( profileType )
82 {
84 return snapPointToIndividualFeatures( point, context );
86 return QgsAbstractProfileSurfaceResults::snapPoint( point, context );
87 }
89}
90
91
92QVector<QgsProfileIdentifyResults> QgsVectorLayerProfileResults::identify( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const QgsProfileIdentifyContext & )
93{
94 QgsFeatureIds ids;
95 auto visitFeature = [&ids]( QgsFeatureId featureId )
96 {
97 ids << featureId;
98 };
99
100 visitFeaturesInRange( distanceRange, elevationRange, visitFeature );
101 if ( ids.empty() )
102 return {};
103
104 QVector< QVariantMap> idsList;
105 for ( auto it = ids.constBegin(); it != ids.constEnd(); ++it )
106 idsList.append( QVariantMap( {{QStringLiteral( "id" ), *it}} ) );
107
108 return { QgsProfileIdentifyResults( mLayer, idsList ) };
109}
110
111QVector<QgsProfileIdentifyResults> QgsVectorLayerProfileResults::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
112{
113 QHash< QgsFeatureId, QVariantMap > features;
114 auto visitFeature = [&features]( QgsFeatureId featureId, double delta, double distance, double elevation )
115 {
116 auto it = features.find( featureId );
117 if ( it == features.end() )
118 {
119 features[ featureId ] = QVariantMap( {{QStringLiteral( "id" ), featureId },
120 {QStringLiteral( "delta" ), delta },
121 {QStringLiteral( "distance" ), distance },
122 {QStringLiteral( "elevation" ), elevation }
123 } );
124 }
125 else
126 {
127 const double currentDelta = it.value().value( QStringLiteral( "delta" ) ).toDouble();
128 if ( delta < currentDelta )
129 {
130 *it = QVariantMap( {{QStringLiteral( "id" ), featureId },
131 {QStringLiteral( "delta" ), delta },
132 {QStringLiteral( "distance" ), distance },
133 {QStringLiteral( "elevation" ), elevation }
134 } );
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 {
170 if ( distance < bestSnapDistance )
171 {
172 bestSnapDistance = delta;
173 res.snappedPoint = QgsProfilePoint( distance, elevation );
174 }
175 };
176
177 visitFeaturesAtPoint( point, context.maximumPointDistanceDelta, context.maximumPointElevationDelta, context.maximumSurfaceElevationDelta, visitFeature, false );
178
179 return res;
180}
181
182void QgsVectorLayerProfileResults::visitFeaturesAtPoint( const QgsProfilePoint &point, double maximumPointDistanceDelta, double maximumPointElevationDelta, double maximumSurfaceElevationDelta, const std::function< void( QgsFeatureId, double delta, double distance, double elevation ) > &visitor, bool visitWithin )
183{
184 // TODO -- add spatial index if performance is an issue
185
186 const QgsPoint targetPoint( point.distance(), point.elevation() );
187
188 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
189 {
190 for ( const Feature &feature : it.value() )
191 {
192 const QgsRectangle featureBounds = feature.crossSectionGeometry.boundingBox();
193 if ( ( featureBounds.xMinimum() - maximumPointDistanceDelta <= point.distance() ) && ( featureBounds.xMaximum() + maximumPointDistanceDelta >= point.distance() ) )
194 {
195 switch ( feature.crossSectionGeometry.type() )
196 {
197 case Qgis::GeometryType::Point:
198 {
199 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
200 {
201 if ( const QgsPoint *candidatePoint = qgsgeometry_cast< const QgsPoint * >( *partIt ) )
202 {
203 const double snapDistanceDelta = std::fabs( point.distance() - candidatePoint->x() );
204 if ( snapDistanceDelta > maximumPointDistanceDelta )
205 continue;
206
207 const double snapHeightDelta = std::fabs( point.elevation() - candidatePoint->y() );
208 if ( snapHeightDelta > maximumPointElevationDelta )
209 continue;
210
211 const double snapDistance = candidatePoint->distance( targetPoint );
212 visitor( feature.featureId, snapDistance, candidatePoint->x(), candidatePoint->y() );
213 }
214 }
215 break;
216 }
217
218 case Qgis::GeometryType::Line:
219 {
220 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
221 {
222 if ( const QgsCurve *line = qgsgeometry_cast< const QgsCurve * >( *partIt ) )
223 {
224 // might be a vertical line
225 if ( const QgsLineString *lineString = qgsgeometry_cast< const QgsLineString * >( line ) )
226 {
227 if ( lineString->numPoints() == 2 && qgsDoubleNear( lineString->pointN( 0 ).x(), lineString->pointN( 1 ).x() ) )
228 {
229 const double snapDistanceDelta = std::fabs( point.distance() - lineString->pointN( 0 ).x() );
230 if ( snapDistanceDelta > maximumPointDistanceDelta )
231 continue;
232
233 const double snapHeightDelta = std::fabs( point.elevation() - lineString->pointN( 0 ).y() );
234 if ( snapHeightDelta <= maximumPointElevationDelta )
235 {
236 const double snapDistanceP1 = lineString->pointN( 0 ).distance( targetPoint );
237 visitor( feature.featureId, snapDistanceP1, lineString->pointN( 0 ).x(), lineString->pointN( 0 ).y() );
238 }
239
240 const double snapHeightDelta2 = std::fabs( point.elevation() - lineString->pointN( 1 ).y() );
241 if ( snapHeightDelta2 <= maximumPointElevationDelta )
242 {
243 const double snapDistanceP2 = lineString->pointN( 1 ).distance( targetPoint );
244 visitor( feature.featureId, snapDistanceP2, lineString->pointN( 1 ).x(), lineString->pointN( 1 ).y() );
245 }
246
247 if ( visitWithin )
248 {
249 double elevation1 = lineString->pointN( 0 ).y();
250 double elevation2 = lineString->pointN( 1 ).y();
251 if ( elevation1 > elevation2 )
252 std::swap( elevation1, elevation2 );
253
254 if ( point.elevation() > elevation1 && point.elevation() < elevation2 )
255 {
256 const double snapDistance = std::fabs( lineString->pointN( 0 ).x() - point.distance() );
257 visitor( feature.featureId, snapDistance, lineString->pointN( 0 ).x(), point.elevation() );
258 }
259 }
260 continue;
261 }
262 }
263
264 const QgsRectangle partBounds = ( *partIt )->boundingBox();
265 if ( point.distance() < partBounds.xMinimum() - maximumPointDistanceDelta || point.distance() > partBounds.xMaximum() + maximumPointDistanceDelta )
266 continue;
267
268 const double snappedDistance = point.distance() < partBounds.xMinimum() ? partBounds.xMinimum()
269 : point.distance() > partBounds.xMaximum() ? partBounds.xMaximum() : point.distance();
270
271 const QgsGeometry cutLine( new QgsLineString( QgsPoint( snappedDistance, minZ ), QgsPoint( snappedDistance, maxZ ) ) );
272 QgsGeos cutLineGeos( cutLine.constGet() );
273
274 const QgsGeometry points( cutLineGeos.intersection( line ) );
275
276 for ( auto vertexIt = points.vertices_begin(); vertexIt != points.vertices_end(); ++vertexIt )
277 {
278 const double snapHeightDelta = std::fabs( point.elevation() - ( *vertexIt ).y() );
279 if ( snapHeightDelta > maximumSurfaceElevationDelta )
280 continue;
281
282 const double snapDistance = ( *vertexIt ).distance( targetPoint );
283 visitor( feature.featureId, snapDistance, ( *vertexIt ).x(), ( *vertexIt ).y() );
284 }
285 }
286 }
287 break;
288 }
289
290 case Qgis::GeometryType::Polygon:
291 {
292 if ( visitWithin )
293 {
294 if ( feature.crossSectionGeometry.intersects( QgsGeometry::fromPointXY( QgsPointXY( point.distance(), point.elevation() ) ) ) )
295 {
296 visitor( feature.featureId, 0, point.distance(), point.elevation() );
297 break;
298 }
299 }
300 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
301 {
302 if ( const QgsCurve *exterior = qgsgeometry_cast< const QgsPolygon * >( *partIt )->exteriorRing() )
303 {
304 const QgsRectangle partBounds = ( *partIt )->boundingBox();
305 if ( point.distance() < partBounds.xMinimum() - maximumPointDistanceDelta || point.distance() > partBounds.xMaximum() + maximumPointDistanceDelta )
306 continue;
307
308 const double snappedDistance = point.distance() < partBounds.xMinimum() ? partBounds.xMinimum()
309 : point.distance() > partBounds.xMaximum() ? partBounds.xMaximum() : point.distance();
310
311 const QgsGeometry cutLine( new QgsLineString( QgsPoint( snappedDistance, minZ ), QgsPoint( snappedDistance, maxZ ) ) );
312 QgsGeos cutLineGeos( cutLine.constGet() );
313
314 const QgsGeometry points( cutLineGeos.intersection( exterior ) );
315 for ( auto vertexIt = points.vertices_begin(); vertexIt != points.vertices_end(); ++vertexIt )
316 {
317 const double snapHeightDelta = std::fabs( point.elevation() - ( *vertexIt ).y() );
318 if ( snapHeightDelta > maximumSurfaceElevationDelta )
319 continue;
320
321 const double snapDistance = ( *vertexIt ).distance( targetPoint );
322 visitor( feature.featureId, snapDistance, ( *vertexIt ).x(), ( *vertexIt ).y() );
323 }
324 }
325 }
326 break;
327 }
328 case Qgis::GeometryType::Unknown:
329 case Qgis::GeometryType::Null:
330 break;
331 }
332 }
333 }
334 }
335}
336
337void QgsVectorLayerProfileResults::visitFeaturesInRange( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const std::function<void ( QgsFeatureId )> &visitor )
338{
339 // TODO -- add spatial index if performance is an issue
340 const QgsRectangle profileRange( distanceRange.lower(), elevationRange.lower(), distanceRange.upper(), elevationRange.upper() );
341 const QgsGeometry profileRangeGeometry = QgsGeometry::fromRect( profileRange );
342 QgsGeos profileRangeGeos( profileRangeGeometry.constGet() );
343 profileRangeGeos.prepareGeometry();
344
345 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
346 {
347 for ( const Feature &feature : it.value() )
348 {
349 if ( feature.crossSectionGeometry.boundingBoxIntersects( profileRange ) )
350 {
351 switch ( feature.crossSectionGeometry.type() )
352 {
353 case Qgis::GeometryType::Point:
354 {
355 for ( auto partIt = feature.crossSectionGeometry.const_parts_begin(); partIt != feature.crossSectionGeometry.const_parts_end(); ++partIt )
356 {
357 if ( const QgsPoint *candidatePoint = qgsgeometry_cast< const QgsPoint * >( *partIt ) )
358 {
359 if ( profileRange.contains( candidatePoint->x(), candidatePoint->y() ) )
360 {
361 visitor( feature.featureId );
362 }
363 }
364 }
365 break;
366 }
367
368 case Qgis::GeometryType::Line:
369 case Qgis::GeometryType::Polygon:
370 {
371 if ( profileRangeGeos.intersects( feature.crossSectionGeometry.constGet() ) )
372 {
373 visitor( feature.featureId );
374 }
375 break;
376 }
377
378 case Qgis::GeometryType::Unknown:
379 case Qgis::GeometryType::Null:
380 break;
381 }
382 }
383 }
384 }
385}
386
388{
389 const QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), mLayer ? mLayer->createExpressionContextScope() : nullptr );
390 switch ( profileType )
391 {
393 renderResultsAsIndividualFeatures( context );
394 break;
398 renderMarkersOverContinuousSurfacePlot( context );
399 break;
400 }
401}
402
403void QgsVectorLayerProfileResults::renderResultsAsIndividualFeatures( QgsProfileRenderContext &context )
404{
405 QPainter *painter = context.renderContext().painter();
406 if ( !painter )
407 return;
408
409 const QgsScopedQPainterState painterState( painter );
410
411 painter->setBrush( Qt::NoBrush );
412 painter->setPen( Qt::NoPen );
413
414 const double minDistance = context.distanceRange().lower();
415 const double maxDistance = context.distanceRange().upper();
416 const double minZ = context.elevationRange().lower();
417 const double maxZ = context.elevationRange().upper();
418
419 const QRectF visibleRegion( minDistance, minZ, maxDistance - minDistance, maxZ - minZ );
420 QPainterPath clipPath;
421 clipPath.addPolygon( context.worldTransform().map( visibleRegion ) );
422 painter->setClipPath( clipPath, Qt::ClipOperation::IntersectClip );
423
424 const QgsRectangle clipPathRect( clipPath.boundingRect() );
425
426 auto renderResult = [&context, &clipPathRect]( const Feature & profileFeature, QgsMarkerSymbol * markerSymbol, QgsLineSymbol * lineSymbol, QgsFillSymbol * fillSymbol )
427 {
428 if ( profileFeature.crossSectionGeometry.isEmpty() )
429 return;
430
431 QgsGeometry transformed = profileFeature.crossSectionGeometry;
432 transformed.transform( context.worldTransform() );
433
434 if ( !transformed.boundingBoxIntersects( clipPathRect ) )
435 return;
436
437 // we can take some shortcuts here, because we know that the geometry will already be segmentized and can't be a curved type
438 switch ( transformed.type() )
439 {
440 case Qgis::GeometryType::Point:
441 {
442 if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( transformed.constGet() ) )
443 {
444 markerSymbol->renderPoint( QPointF( point->x(), point->y() ), nullptr, context.renderContext() );
445 }
446 else if ( const QgsMultiPoint *multipoint = qgsgeometry_cast< const QgsMultiPoint * >( transformed.constGet() ) )
447 {
448 const int numGeometries = multipoint->numGeometries();
449 for ( int i = 0; i < numGeometries; ++i )
450 {
451 markerSymbol->renderPoint( QPointF( multipoint->pointN( i )->x(), multipoint->pointN( i )->y() ), nullptr, context.renderContext() );
452 }
453 }
454 break;
455 }
456
457 case Qgis::GeometryType::Line:
458 {
459 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( transformed.constGet() ) )
460 {
461 lineSymbol->renderPolyline( line->asQPolygonF(), nullptr, context.renderContext() );
462 }
463 else if ( const QgsMultiLineString *multiLinestring = qgsgeometry_cast< const QgsMultiLineString * >( transformed.constGet() ) )
464 {
465 const int numGeometries = multiLinestring->numGeometries();
466 for ( int i = 0; i < numGeometries; ++i )
467 {
468 lineSymbol->renderPolyline( multiLinestring->lineStringN( i )->asQPolygonF(), nullptr, context.renderContext() );
469 }
470 }
471 break;
472 }
473
474 case Qgis::GeometryType::Polygon:
475 {
476 if ( const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( transformed.constGet() ) )
477 {
478 if ( const QgsCurve *exterior = polygon->exteriorRing() )
479 fillSymbol->renderPolygon( exterior->asQPolygonF(), nullptr, nullptr, context.renderContext() );
480 }
481 else if ( const QgsMultiPolygon *multiPolygon = qgsgeometry_cast< const QgsMultiPolygon * >( transformed.constGet() ) )
482 {
483 const int numGeometries = multiPolygon->numGeometries();
484 for ( int i = 0; i < numGeometries; ++i )
485 {
486 fillSymbol->renderPolygon( multiPolygon->polygonN( i )->exteriorRing()->asQPolygonF(), nullptr, nullptr, context.renderContext() );
487 }
488 }
489 break;
490 }
491
492 case Qgis::GeometryType::Unknown:
493 case Qgis::GeometryType::Null:
494 return;
495 }
496 };
497
499 req.setFilterFids( qgis::listToSet( features.keys() ) );
500
501 if ( respectLayerSymbology && mLayer && mLayer->renderer() )
502 {
503 std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer()->clone() );
504 renderer->startRender( context.renderContext(), mLayer->fields() );
505
506 // if we are respecting the layer's symbology then we'll fire off a feature request and iterate through
507 // features from the profile, rendering each in turn
508 QSet<QString> attributes = renderer->usedAttributes( context.renderContext() );
509
510 std::unique_ptr< QgsMarkerSymbol > marker( mMarkerSymbol->clone() );
511 std::unique_ptr< QgsLineSymbol > line( mLineSymbol->clone() );
512 std::unique_ptr< QgsFillSymbol > fill( mFillSymbol->clone() );
513 attributes.unite( marker->usedAttributes( context.renderContext() ) );
514 attributes.unite( line->usedAttributes( context.renderContext() ) );
515 attributes.unite( fill->usedAttributes( context.renderContext() ) );
516
517 req.setSubsetOfAttributes( attributes, mLayer->fields() );
518
519 QgsFeature feature;
520 QgsFeatureIterator it = mLayer->getFeatures( req );
521 while ( it.nextFeature( feature ) )
522 {
523 context.renderContext().expressionContext().setFeature( feature );
524 QgsSymbol *rendererSymbol = renderer->symbolForFeature( feature, context.renderContext() );
525 if ( !rendererSymbol )
526 continue;
527
528 marker->setColor( rendererSymbol->color() );
529 marker->setOpacity( rendererSymbol->opacity() );
530 line->setColor( rendererSymbol->color() );
531 line->setOpacity( rendererSymbol->opacity() );
532 fill->setColor( rendererSymbol->color() );
533 fill->setOpacity( rendererSymbol->opacity() );
534
535 marker->startRender( context.renderContext() );
536 line->startRender( context.renderContext() );
537 fill->startRender( context.renderContext() );
538
539 const QVector< Feature > profileFeatures = features.value( feature.id() );
540 for ( const Feature &profileFeature : profileFeatures )
541 {
542 renderResult( profileFeature,
543 rendererSymbol->type() == Qgis::SymbolType::Marker ? qgis::down_cast< QgsMarkerSymbol * >( rendererSymbol ) : marker.get(),
544 rendererSymbol->type() == Qgis::SymbolType::Line ? qgis::down_cast< QgsLineSymbol * >( rendererSymbol ) : line.get(),
545 rendererSymbol->type() == Qgis::SymbolType::Fill ? qgis::down_cast< QgsFillSymbol * >( rendererSymbol ) : fill.get() );
546 }
547
548 marker->stopRender( context.renderContext() );
549 line->stopRender( context.renderContext() );
550 fill->stopRender( context.renderContext() );
551 }
552
553 renderer->stopRender( context.renderContext() );
554 }
555 else if ( mLayer )
556 {
557 QSet<QString> attributes;
558 attributes.unite( mMarkerSymbol->usedAttributes( context.renderContext() ) );
559 attributes.unite( mFillSymbol->usedAttributes( context.renderContext() ) );
560 attributes.unite( mLineSymbol->usedAttributes( context.renderContext() ) );
561
562 mMarkerSymbol->startRender( context.renderContext() );
563 mFillSymbol->startRender( context.renderContext() );
564 mLineSymbol->startRender( context.renderContext() );
565 req.setSubsetOfAttributes( attributes, mLayer->fields() );
566
567 QgsFeature feature;
568 QgsFeatureIterator it = mLayer->getFeatures( req );
569 while ( it.nextFeature( feature ) )
570 {
571 context.renderContext().expressionContext().setFeature( feature );
572 const QVector< Feature > profileFeatures = features.value( feature.id() );
573 for ( const Feature &profileFeature : profileFeatures )
574 {
575 renderResult( profileFeature, mMarkerSymbol.get(), mLineSymbol.get(), mFillSymbol.get() );
576 }
577 }
578 mMarkerSymbol->stopRender( context.renderContext() );
579 mFillSymbol->stopRender( context.renderContext() );
580 mLineSymbol->stopRender( context.renderContext() );
581 }
582}
583
584void QgsVectorLayerProfileResults::renderMarkersOverContinuousSurfacePlot( QgsProfileRenderContext &context )
585{
586 QPainter *painter = context.renderContext().painter();
587 if ( !painter )
588 return;
589
590 const QgsScopedQPainterState painterState( painter );
591
592 painter->setBrush( Qt::NoBrush );
593 painter->setPen( Qt::NoPen );
594
595 const double minDistance = context.distanceRange().lower();
596 const double maxDistance = context.distanceRange().upper();
597 const double minZ = context.elevationRange().lower();
598 const double maxZ = context.elevationRange().upper();
599
600 const QRectF visibleRegion( minDistance, minZ, maxDistance - minDistance, maxZ - minZ );
601 QPainterPath clipPath;
602 clipPath.addPolygon( context.worldTransform().map( visibleRegion ) );
603 painter->setClipPath( clipPath, Qt::ClipOperation::IntersectClip );
604
605 mMarkerSymbol->startRender( context.renderContext() );
606
607 for ( auto pointIt = mDistanceToHeightMap.constBegin(); pointIt != mDistanceToHeightMap.constEnd(); ++pointIt )
608 {
609 if ( std::isnan( pointIt.value() ) )
610 continue;
611
612 mMarkerSymbol->renderPoint( context.worldTransform().map( QPointF( pointIt.key(), pointIt.value() ) ), nullptr, context.renderContext() );
613 }
614 mMarkerSymbol->stopRender( context.renderContext() );
615}
616
617QVector<QgsAbstractProfileResults::Feature> QgsVectorLayerProfileResults::asIndividualFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback ) const
618{
619 QVector<QgsAbstractProfileResults::Feature> res;
620 res.reserve( features.size() );
621 for ( auto it = features.constBegin(); it != features.constEnd(); ++it )
622 {
623 if ( feedback && feedback->isCanceled() )
624 break;
625
626 for ( const Feature &feature : it.value() )
627 {
628 if ( feedback && feedback->isCanceled() )
629 break;
630
632 outFeature.layerIdentifier = mId;
633 outFeature.attributes = {{QStringLiteral( "id" ), feature.featureId }};
634 switch ( type )
635 {
637 outFeature.geometry = feature.geometry;
638 break;
639
641 outFeature.geometry = feature.crossSectionGeometry;
642 break;
643
645 break; // unreachable
646 }
647 res << outFeature;
648 }
649 }
650 return res;
651}
652
654{
656 const QgsVectorLayerProfileGenerator *vlGenerator = qgis::down_cast< const QgsVectorLayerProfileGenerator * >( generator );
657
658 mId = vlGenerator->mId;
659 profileType = vlGenerator->mType;
660 respectLayerSymbology = vlGenerator->mRespectLayerSymbology;
661 mMarkerSymbol.reset( vlGenerator->mProfileMarkerSymbol->clone() );
662 mShowMarkerSymbolInSurfacePlots = vlGenerator->mShowMarkerSymbolInSurfacePlots;
663}
664
665//
666// QgsVectorLayerProfileGenerator
667//
668
671 , mId( layer->id() )
672 , mFeedback( std::make_unique< QgsFeedback >() )
673 , mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
674 , mTerrainProvider( request.terrainProvider() ? request.terrainProvider()->clone() : nullptr )
675 , mTolerance( request.tolerance() )
676 , mSourceCrs( layer->crs() )
677 , mTargetCrs( request.crs() )
678 , mTransformContext( request.transformContext() )
679 , mExtent( layer->extent() )
680 , mSource( std::make_unique< QgsVectorLayerFeatureSource >( layer ) )
681 , mOffset( layer->elevationProperties()->zOffset() )
682 , mScale( layer->elevationProperties()->zScale() )
683 , mType( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->type() )
684 , mClamping( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->clamping() )
685 , mBinding( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->binding() )
686 , mExtrusionEnabled( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->extrusionEnabled() )
687 , mExtrusionHeight( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->extrusionHeight() )
688 , mExpressionContext( request.expressionContext() )
689 , mFields( layer->fields() )
690 , mDataDefinedProperties( layer->elevationProperties()->dataDefinedProperties() )
691 , mWkbType( layer->wkbType() )
692 , mRespectLayerSymbology( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->respectLayerSymbology() )
693 , mProfileMarkerSymbol( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileMarkerSymbol()->clone() )
694 , mShowMarkerSymbolInSurfacePlots( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->showMarkerSymbolInSurfacePlots() )
695 , mLayer( layer )
696{
697 if ( mTerrainProvider )
698 mTerrainProvider->prepare(); // must be done on main thread
699
700 mSymbology = qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileSymbology();
701 mElevationLimit = qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->elevationLimit();
702
703 mLineSymbol.reset( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileLineSymbol()->clone() );
704 mFillSymbol.reset( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->profileFillSymbol()->clone() );
705}
706
708{
709 return mId;
710}
711
713
715{
716 if ( !mProfileCurve || mFeedback->isCanceled() )
717 return false;
718
719 // we need to transform the profile curve to the vector's CRS
720 mTransformedCurve.reset( mProfileCurve->clone() );
721 mLayerToTargetTransform = QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext );
722 if ( mTerrainProvider )
723 mTargetToTerrainProviderTransform = QgsCoordinateTransform( mTargetCrs, mTerrainProvider->crs(), mTransformContext );
724
725 try
726 {
727 mTransformedCurve->transform( mLayerToTargetTransform, Qgis::TransformDirection::Reverse );
728 }
729 catch ( QgsCsException & )
730 {
731 QgsDebugError( QStringLiteral( "Error transforming profile line to vector CRS" ) );
732 return false;
733 }
734
735 const QgsRectangle profileCurveBoundingBox = mTransformedCurve->boundingBox();
736 if ( !profileCurveBoundingBox.intersects( mExtent ) )
737 return false;
738
739 if ( mFeedback->isCanceled() )
740 return false;
741
742 mResults = std::make_unique< QgsVectorLayerProfileResults >();
743 mResults->mLayer = mLayer;
744 mResults->copyPropertiesFromGenerator( this );
745
746 mProfileCurveEngine.reset( new QgsGeos( mProfileCurve.get() ) );
747 mProfileCurveEngine->prepareGeometry();
748
749 mDataDefinedProperties.prepare( mExpressionContext );
750
751 if ( mFeedback->isCanceled() )
752 return false;
753
754 switch ( QgsWkbTypes::geometryType( mWkbType ) )
755 {
756 case Qgis::GeometryType::Point:
757 if ( !generateProfileForPoints() )
758 return false;
759 break;
760
761 case Qgis::GeometryType::Line:
762 if ( !generateProfileForLines() )
763 return false;
764 break;
765
766 case Qgis::GeometryType::Polygon:
767 if ( !generateProfileForPolygons() )
768 return false;
769 break;
770
771 case Qgis::GeometryType::Unknown:
772 case Qgis::GeometryType::Null:
773 return false;
774 }
775
776 return true;
777}
778
780{
781 return mResults.release();
782}
783
785{
786 return mFeedback.get();
787}
788
789bool QgsVectorLayerProfileGenerator::generateProfileForPoints()
790{
791 // get features from layer
792 QgsFeatureRequest request;
793 request.setDestinationCrs( mTargetCrs, mTransformContext );
794 request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance );
795 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
796 request.setFeedback( mFeedback.get() );
797
798 // our feature request is using the optimised distance within check (allowing use of spatial index)
799 // BUT this will also include points which are within the tolerance distance before/after the end of line.
800 // So we also need to double check that they fall within the flat buffered curve too.
801 std::unique_ptr< QgsAbstractGeometry > bufferedCurve( mProfileCurveEngine->buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) );
802 QgsGeos bufferedCurveEngine( bufferedCurve.get() );
803 bufferedCurveEngine.prepareGeometry();
804
805 auto processPoint = [this, &bufferedCurveEngine]( const QgsFeature & feature, const QgsPoint * point )
806 {
807 if ( !bufferedCurveEngine.intersects( point ) )
808 return;
809
810 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ZOffset, mExpressionContext, mOffset );
811
812 const double height = featureZToHeight( point->x(), point->y(), point->z(), offset );
813 mResults->mRawPoints.append( QgsPoint( point->x(), point->y(), height ) );
814 mResults->minZ = std::min( mResults->minZ, height );
815 mResults->maxZ = std::max( mResults->maxZ, height );
816
817 QString lastError;
818 const double distance = mProfileCurveEngine->lineLocatePoint( *point, &lastError );
819 mResults->mDistanceToHeightMap.insert( distance, height );
820
822 resultFeature.featureId = feature.id();
823 if ( mExtrusionEnabled )
824 {
825 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
826
827 resultFeature.geometry = QgsGeometry( new QgsLineString( QgsPoint( point->x(), point->y(), height ),
828 QgsPoint( point->x(), point->y(), height + extrusion ) ) );
829 resultFeature.crossSectionGeometry = QgsGeometry( new QgsLineString( QgsPoint( distance, height ),
830 QgsPoint( distance, height + extrusion ) ) );
831 mResults->minZ = std::min( mResults->minZ, height + extrusion );
832 mResults->maxZ = std::max( mResults->maxZ, height + extrusion );
833 }
834 else
835 {
836 resultFeature.geometry = QgsGeometry( new QgsPoint( point->x(), point->y(), height ) );
837 resultFeature.crossSectionGeometry = QgsGeometry( new QgsPoint( distance, height ) );
838 }
839 mResults->features[resultFeature.featureId].append( resultFeature );
840 };
841
842 QgsFeature feature;
843 QgsFeatureIterator it = mSource->getFeatures( request );
844 while ( it.nextFeature( feature ) )
845 {
846 if ( mFeedback->isCanceled() )
847 return false;
848
849 mExpressionContext.setFeature( feature );
850
851 const QgsGeometry g = feature.geometry();
852 if ( g.isMultipart() )
853 {
854 for ( auto it = g.const_parts_begin(); it != g.const_parts_end(); ++it )
855 {
856 processPoint( feature, qgsgeometry_cast< const QgsPoint * >( *it ) );
857 }
858 }
859 else
860 {
861 processPoint( feature, qgsgeometry_cast< const QgsPoint * >( g.constGet() ) );
862 }
863 }
864 return true;
865}
866
867bool QgsVectorLayerProfileGenerator::generateProfileForLines()
868{
869 // get features from layer
870 QgsFeatureRequest request;
871 request.setDestinationCrs( mTargetCrs, mTransformContext );
872 request.setFilterRect( mProfileCurve->boundingBox() );
873 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
874 request.setFeedback( mFeedback.get() );
875
876 auto processCurve = [this]( const QgsFeature & feature, const QgsCurve * curve )
877 {
878 QString error;
879 std::unique_ptr< QgsAbstractGeometry > intersection( mProfileCurveEngine->intersection( curve, &error ) );
880 if ( !intersection )
881 return;
882
883 if ( mFeedback->isCanceled() )
884 return;
885
886 QgsGeos curveGeos( curve );
887 curveGeos.prepareGeometry();
888
889 if ( mFeedback->isCanceled() )
890 return;
891
892 for ( auto it = intersection->const_parts_begin(); it != intersection->const_parts_end(); ++it )
893 {
894 if ( mFeedback->isCanceled() )
895 return;
896
897 if ( const QgsPoint *intersectionPoint = qgsgeometry_cast< const QgsPoint * >( *it ) )
898 {
899 // unfortunately we need to do some work to interpolate the z value for the line -- GEOS doesn't give us this
900 const double distance = curveGeos.lineLocatePoint( *intersectionPoint, &error );
901 std::unique_ptr< QgsPoint > interpolatedPoint( curve->interpolatePoint( distance ) );
902
903 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ZOffset, mExpressionContext, mOffset );
904
905 const double height = featureZToHeight( interpolatedPoint->x(), interpolatedPoint->y(), interpolatedPoint->z(), offset );
906 mResults->mRawPoints.append( QgsPoint( interpolatedPoint->x(), interpolatedPoint->y(), height ) );
907 mResults->minZ = std::min( mResults->minZ, height );
908 mResults->maxZ = std::max( mResults->maxZ, height );
909
910 const double distanceAlongProfileCurve = mProfileCurveEngine->lineLocatePoint( *interpolatedPoint, &error );
911 mResults->mDistanceToHeightMap.insert( distanceAlongProfileCurve, height );
912
914 resultFeature.featureId = feature.id();
915 if ( mExtrusionEnabled )
916 {
917 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
918
919 resultFeature.geometry = QgsGeometry( new QgsLineString( QgsPoint( interpolatedPoint->x(), interpolatedPoint->y(), height ),
920 QgsPoint( interpolatedPoint->x(), interpolatedPoint->y(), height + extrusion ) ) );
921 resultFeature.crossSectionGeometry = QgsGeometry( new QgsLineString( QgsPoint( distanceAlongProfileCurve, height ),
922 QgsPoint( distanceAlongProfileCurve, height + extrusion ) ) );
923 mResults->minZ = std::min( mResults->minZ, height + extrusion );
924 mResults->maxZ = std::max( mResults->maxZ, height + extrusion );
925 }
926 else
927 {
928 resultFeature.geometry = QgsGeometry( new QgsPoint( interpolatedPoint->x(), interpolatedPoint->y(), height ) );
929 resultFeature.crossSectionGeometry = QgsGeometry( new QgsPoint( distanceAlongProfileCurve, height ) );
930 }
931 mResults->features[resultFeature.featureId].append( resultFeature );
932 }
933 }
934 };
935
936 QgsFeature feature;
937 QgsFeatureIterator it = mSource->getFeatures( request );
938 while ( it.nextFeature( feature ) )
939 {
940 if ( mFeedback->isCanceled() )
941 return false;
942
943 if ( !mProfileCurveEngine->intersects( feature.geometry().constGet() ) )
944 continue;
945
946 mExpressionContext.setFeature( feature );
947
948 const QgsGeometry g = feature.geometry();
949 if ( g.isMultipart() )
950 {
951 for ( auto it = g.const_parts_begin(); it != g.const_parts_end(); ++it )
952 {
953 if ( !mProfileCurveEngine->intersects( *it ) )
954 continue;
955
956 processCurve( feature, qgsgeometry_cast< const QgsCurve * >( *it ) );
957 }
958 }
959 else
960 {
961 processCurve( feature, qgsgeometry_cast< const QgsCurve * >( g.constGet() ) );
962 }
963 }
964 return true;
965}
966
967bool QgsVectorLayerProfileGenerator::generateProfileForPolygons()
968{
969 // get features from layer
970 QgsFeatureRequest request;
971 request.setDestinationCrs( mTargetCrs, mTransformContext );
972 request.setFilterRect( mProfileCurve->boundingBox() );
973 request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields );
974 request.setFeedback( mFeedback.get() );
975
976 auto interpolatePointOnTriangle = []( const QgsPolygon * triangle, double x, double y ) -> QgsPoint
977 {
978 QgsPoint p1, p2, p3;
980 triangle->exteriorRing()->pointAt( 0, p1, vt );
981 triangle->exteriorRing()->pointAt( 1, p2, vt );
982 triangle->exteriorRing()->pointAt( 2, p3, vt );
983 const double z = QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, p1.z(), p2.z(), p3.z(), QgsPointXY( x, y ) );
984 return QgsPoint( x, y, z );
985 };
986
987 std::function< void( const QgsPolygon *triangle, const QgsAbstractGeometry *intersect, QVector< QgsGeometry > &, QVector< QgsGeometry > & ) > processTriangleLineIntersect;
988 processTriangleLineIntersect = [this, &interpolatePointOnTriangle, &processTriangleLineIntersect]( const QgsPolygon * triangle, const QgsAbstractGeometry * intersect, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts )
989 {
990 // intersect may be a (multi)point or (multi)linestring
991 switch ( QgsWkbTypes::geometryType( intersect->wkbType() ) )
992 {
993 case Qgis::GeometryType::Point:
994 if ( const QgsMultiPoint *mp = qgsgeometry_cast< const QgsMultiPoint * >( intersect ) )
995 {
996 const int numPoint = mp->numGeometries();
997 for ( int i = 0; i < numPoint; ++i )
998 {
999 processTriangleLineIntersect( triangle, mp->geometryN( i ), transformedParts, crossSectionParts );
1000 }
1001 }
1002 else if ( const QgsPoint *p = qgsgeometry_cast< const QgsPoint * >( intersect ) )
1003 {
1004 const QgsPoint interpolatedPoint = interpolatePointOnTriangle( triangle, p->x(), p->y() );
1005 mResults->mRawPoints.append( interpolatedPoint );
1006 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() );
1007 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() );
1008
1009 QString lastError;
1010 const double distance = mProfileCurveEngine->lineLocatePoint( *p, &lastError );
1011 mResults->mDistanceToHeightMap.insert( distance, interpolatedPoint.z() );
1012
1013 if ( mExtrusionEnabled )
1014 {
1015 const double extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
1016
1017 transformedParts.append( QgsGeometry( new QgsLineString( interpolatedPoint,
1018 QgsPoint( interpolatedPoint.x(), interpolatedPoint.y(), interpolatedPoint.z() + extrusion ) ) ) );
1019 crossSectionParts.append( QgsGeometry( new QgsLineString( QgsPoint( distance, interpolatedPoint.z() ),
1020 QgsPoint( distance, interpolatedPoint.z() + extrusion ) ) ) );
1021 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() + extrusion );
1022 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() + extrusion );
1023 }
1024 else
1025 {
1026 transformedParts.append( QgsGeometry( new QgsPoint( interpolatedPoint ) ) );
1027 crossSectionParts.append( QgsGeometry( new QgsPoint( distance, interpolatedPoint.z() ) ) );
1028 }
1029 }
1030 break;
1031 case Qgis::GeometryType::Line:
1032 if ( const QgsMultiLineString *ml = qgsgeometry_cast< const QgsMultiLineString * >( intersect ) )
1033 {
1034 const int numLines = ml->numGeometries();
1035 for ( int i = 0; i < numLines; ++i )
1036 {
1037 processTriangleLineIntersect( triangle, ml->geometryN( i ), transformedParts, crossSectionParts );
1038 }
1039 }
1040 else if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( intersect ) )
1041 {
1042 const int numPoints = ls->numPoints();
1043 QVector< double > newX;
1044 newX.resize( numPoints );
1045 QVector< double > newY;
1046 newY.resize( numPoints );
1047 QVector< double > newZ;
1048 newZ.resize( numPoints );
1049 QVector< double > newDistance;
1050 newDistance.resize( numPoints );
1051
1052 const double *inX = ls->xData();
1053 const double *inY = ls->yData();
1054 double *outX = newX.data();
1055 double *outY = newY.data();
1056 double *outZ = newZ.data();
1057 double *outDistance = newDistance.data();
1058
1059 QVector< double > extrudedZ;
1060 double *extZOut = nullptr;
1061 double extrusion = 0;
1062 if ( mExtrusionEnabled )
1063 {
1064 extrudedZ.resize( numPoints );
1065 extZOut = extrudedZ.data();
1066
1067 extrusion = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ExtrusionHeight, mExpressionContext, mExtrusionHeight );
1068 }
1069
1070 QString lastError;
1071 for ( int i = 0 ; i < numPoints; ++i )
1072 {
1073 double x = *inX++;
1074 double y = *inY++;
1075
1076 QgsPoint interpolatedPoint = interpolatePointOnTriangle( triangle, x, y );
1077 *outX++ = x;
1078 *outY++ = y;
1079 *outZ++ = interpolatedPoint.z();
1080 if ( extZOut )
1081 *extZOut++ = interpolatedPoint.z() + extrusion;
1082
1083 mResults->mRawPoints.append( interpolatedPoint );
1084 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() );
1085 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() );
1086 if ( mExtrusionEnabled )
1087 {
1088 mResults->minZ = std::min( mResults->minZ, interpolatedPoint.z() + extrusion );
1089 mResults->maxZ = std::max( mResults->maxZ, interpolatedPoint.z() + extrusion );
1090 }
1091
1092 const double distance = mProfileCurveEngine->lineLocatePoint( interpolatedPoint, &lastError );
1093 *outDistance++ = distance;
1094
1095 mResults->mDistanceToHeightMap.insert( distance, interpolatedPoint.z() );
1096 }
1097
1098 if ( mExtrusionEnabled )
1099 {
1100 std::unique_ptr< QgsLineString > ring = std::make_unique< QgsLineString >( newX, newY, newZ );
1101 std::unique_ptr< QgsLineString > extrudedRing = std::make_unique< QgsLineString >( newX, newY, extrudedZ );
1102 std::unique_ptr< QgsLineString > reversedExtrusion( extrudedRing->reversed() );
1103 ring->append( reversedExtrusion.get() );
1104 ring->close();
1105 transformedParts.append( QgsGeometry( new QgsPolygon( ring.release() ) ) );
1106
1107
1108 std::unique_ptr< QgsLineString > distanceVHeightRing = std::make_unique< QgsLineString >( newDistance, newZ );
1109 std::unique_ptr< QgsLineString > extrudedDistanceVHeightRing = std::make_unique< QgsLineString >( newDistance, extrudedZ );
1110 std::unique_ptr< QgsLineString > reversedDistanceVHeightExtrusion( extrudedDistanceVHeightRing->reversed() );
1111 distanceVHeightRing->append( reversedDistanceVHeightExtrusion.get() );
1112 distanceVHeightRing->close();
1113 crossSectionParts.append( QgsGeometry( new QgsPolygon( distanceVHeightRing.release() ) ) );
1114 }
1115 else
1116 {
1117 transformedParts.append( QgsGeometry( new QgsLineString( newX, newY, newZ ) ) );
1118 crossSectionParts.append( QgsGeometry( new QgsLineString( newDistance, newZ ) ) );
1119 }
1120 }
1121 break;
1122
1123 case Qgis::GeometryType::Polygon:
1124 case Qgis::GeometryType::Unknown:
1125 case Qgis::GeometryType::Null:
1126 return;
1127 }
1128 };
1129
1130 auto processPolygon = [this, &processTriangleLineIntersect]( const QgsCurvePolygon * polygon, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts, double offset )
1131 {
1132 std::unique_ptr< QgsPolygon > clampedPolygon;
1133 if ( const QgsPolygon *p = qgsgeometry_cast< const QgsPolygon * >( polygon ) )
1134 {
1135 clampedPolygon.reset( p->clone() );
1136 }
1137 else
1138 {
1139 clampedPolygon.reset( qgsgeometry_cast< QgsPolygon * >( polygon->segmentize() ) );
1140 }
1141 clampAltitudes( clampedPolygon.get(), offset );
1142
1143 if ( mFeedback->isCanceled() )
1144 return;
1145
1146 const QgsRectangle bounds = clampedPolygon->boundingBox();
1147 QgsTessellator t( bounds, false, false, false, false );
1148 t.addPolygon( *clampedPolygon, 0 );
1149
1150 QgsGeometry tessellation( t.asMultiPolygon() );
1151 if ( mFeedback->isCanceled() )
1152 return;
1153
1154 tessellation.translate( bounds.xMinimum(), bounds.yMinimum() );
1155
1156 // iterate through the tessellation, finding triangles which intersect the line
1157 const int numTriangles = qgsgeometry_cast< const QgsMultiPolygon * >( tessellation.constGet() )->numGeometries();
1158 for ( int i = 0; i < numTriangles; ++i )
1159 {
1160 if ( mFeedback->isCanceled() )
1161 return;
1162
1163 const QgsPolygon *triangle = qgsgeometry_cast< const QgsPolygon * >( qgsgeometry_cast< const QgsMultiPolygon * >( tessellation.constGet() )->geometryN( i ) );
1164 if ( !mProfileCurveEngine->intersects( triangle ) )
1165 continue;
1166
1167 QString error;
1168 std::unique_ptr< QgsAbstractGeometry > intersection( mProfileCurveEngine->intersection( triangle, &error ) );
1169 if ( !intersection )
1170 continue;
1171
1172 processTriangleLineIntersect( triangle, intersection.get(), transformedParts, crossSectionParts );
1173 }
1174 };
1175
1176 QgsFeature feature;
1177 QgsFeatureIterator it = mSource->getFeatures( request );
1178 while ( it.nextFeature( feature ) )
1179 {
1180 if ( mFeedback->isCanceled() )
1181 return false;
1182
1183 if ( !mProfileCurveEngine->intersects( feature.geometry().constGet() ) )
1184 continue;
1185
1186 mExpressionContext.setFeature( feature );
1187
1188 const double offset = mDataDefinedProperties.valueAsDouble( QgsMapLayerElevationProperties::ZOffset, mExpressionContext, mOffset );
1189
1190 const QgsGeometry g = feature.geometry();
1191 QVector< QgsGeometry > transformedParts;
1192 QVector< QgsGeometry > crossSectionParts;
1193 if ( g.isMultipart() )
1194 {
1195 for ( auto it = g.const_parts_begin(); it != g.const_parts_end(); ++it )
1196 {
1197 if ( mFeedback->isCanceled() )
1198 break;
1199
1200 if ( !mProfileCurveEngine->intersects( *it ) )
1201 continue;
1202
1203 processPolygon( qgsgeometry_cast< const QgsCurvePolygon * >( *it ), transformedParts, crossSectionParts, offset );
1204 }
1205 }
1206 else
1207 {
1208 processPolygon( qgsgeometry_cast< const QgsCurvePolygon * >( g.constGet() ), transformedParts, crossSectionParts, offset );
1209 }
1210
1211 if ( mFeedback->isCanceled() )
1212 return false;
1213
1215 resultFeature.featureId = feature.id();
1216 resultFeature.geometry = transformedParts.size() > 1 ? QgsGeometry::collectGeometry( transformedParts ) : transformedParts.value( 0 );
1217 if ( !crossSectionParts.empty() )
1218 {
1219 QgsGeometry unioned = QgsGeometry::unaryUnion( crossSectionParts );
1220 if ( unioned.type() == Qgis::GeometryType::Line )
1221 unioned = unioned.mergeLines();
1222 resultFeature.crossSectionGeometry = unioned;
1223 }
1224 mResults->features[resultFeature.featureId].append( resultFeature );
1225 }
1226 return true;
1227}
1228
1229double QgsVectorLayerProfileGenerator::terrainHeight( double x, double y )
1230{
1231 if ( !mTerrainProvider )
1232 return std::numeric_limits<double>::quiet_NaN();
1233
1234 // transform feature point to terrain provider crs
1235 try
1236 {
1237 double dummyZ = 0;
1238 mTargetToTerrainProviderTransform.transformInPlace( x, y, dummyZ );
1239 }
1240 catch ( QgsCsException & )
1241 {
1242 return std::numeric_limits<double>::quiet_NaN();
1243 }
1244
1245 return mTerrainProvider->heightAt( x, y );
1246}
1247
1248double QgsVectorLayerProfileGenerator::featureZToHeight( double x, double y, double z, double offset )
1249{
1250 switch ( mClamping )
1251 {
1253 break;
1254
1257 {
1258 const double terrainZ = terrainHeight( x, y );
1259 if ( !std::isnan( terrainZ ) )
1260 {
1261 switch ( mClamping )
1262 {
1264 if ( std::isnan( z ) )
1265 z = terrainZ;
1266 else
1267 z += terrainZ;
1268 break;
1269
1271 z = terrainZ;
1272 break;
1273
1275 break;
1276 }
1277 }
1278 break;
1279 }
1280 }
1281
1282 return ( std::isnan( z ) ? 0 : z ) * mScale + offset;
1283}
1284
1285void QgsVectorLayerProfileGenerator::clampAltitudes( QgsLineString *lineString, const QgsPoint &centroid, double offset )
1286{
1287 for ( int i = 0; i < lineString->nCoordinates(); ++i )
1288 {
1289 if ( mFeedback->isCanceled() )
1290 break;
1291
1292 double terrainZ = 0;
1293 switch ( mClamping )
1294 {
1297 {
1298 QgsPointXY pt;
1299 switch ( mBinding )
1300 {
1302 pt.setX( lineString->xAt( i ) );
1303 pt.setY( lineString->yAt( i ) );
1304 break;
1305
1307 pt.set( centroid.x(), centroid.y() );
1308 break;
1309 }
1310
1311 terrainZ = terrainHeight( pt.x(), pt.y() );
1312 break;
1313 }
1314
1316 break;
1317 }
1318
1319 double geomZ = 0;
1320
1321 switch ( mClamping )
1322 {
1325 geomZ = lineString->zAt( i );
1326 break;
1327
1329 break;
1330 }
1331
1332 const double z = ( terrainZ + ( std::isnan( geomZ ) ? 0 : geomZ ) ) * mScale + offset;
1333 lineString->setZAt( i, z );
1334 }
1335}
1336
1337bool QgsVectorLayerProfileGenerator::clampAltitudes( QgsPolygon *polygon, double offset )
1338{
1339 if ( !polygon->is3D() )
1340 polygon->addZValue( 0 );
1341
1343 switch ( mBinding )
1344 {
1346 break;
1347
1349 centroid = polygon->centroid();
1350 break;
1351 }
1352
1353 QgsCurve *curve = const_cast<QgsCurve *>( polygon->exteriorRing() );
1354 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( curve );
1355 if ( !lineString )
1356 return false;
1357
1358 clampAltitudes( lineString, centroid, offset );
1359
1360 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
1361 {
1362 if ( mFeedback->isCanceled() )
1363 break;
1364
1365 QgsCurve *curve = const_cast<QgsCurve *>( polygon->interiorRing( i ) );
1366 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( curve );
1367 if ( !lineString )
1368 return false;
1369
1370 clampAltitudes( lineString, centroid, offset );
1371 }
1372 return true;
1373}
1374
@ Relative
Elevation is relative to terrain height (final elevation = terrain elevation + feature elevation)
@ Terrain
Elevation is clamped to terrain (final elevation = terrain elevation)
@ Absolute
Elevation is taken directly from feature and is independent of terrain height (final elevation = feat...
VertexType
Types of vertex.
Definition: qgis.h:2053
@ Centroid
Clamp just centroid of feature.
@ Vertex
Clamp every vertex of feature.
@ ContinuousSurface
The features should be treated as representing values on a continuous surface (eg contour lines)
@ IndividualFeatures
Treat each feature as an individual object (eg buildings)
ProfileExportType
Types of export for elevation profiles.
Definition: qgis.h:2677
@ Profile2D
Export profiles as 2D profile lines, with elevation stored in exported geometry Y dimension and dista...
@ Features3D
Export profiles as 3D features, with elevation values stored in exported geometry Z values.
@ DistanceVsElevationTable
Export profiles as a table of sampled distance vs elevation values.
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
Abstract base class for all geometries.
bool is3D() const SIP_HOLDGIL
Returns true if the geometry is 3D and contains a z-value.
virtual QgsPoint centroid() const
Returns the centroid of the geometry.
Abstract base class for objects which generate elevation profiles.
Abstract base class for storage of elevation profiles.
Abstract base class for objects which generate elevation profiles which represent a continuous surfac...
std::unique_ptr< QgsFillSymbol > mFillSymbol
std::unique_ptr< QgsLineSymbol > mLineSymbol
void renderResults(QgsProfileRenderContext &context) override
Renders the results to the specified context.
void copyPropertiesFromGenerator(const QgsAbstractProfileGenerator *generator) override
Copies properties from specified generator to the results object.
QVector< QgsAbstractProfileResults::Feature > asFeatures(Qgis::ProfileExportType type, QgsFeedback *feedback=nullptr) const override
Returns a list of features representing the calculated elevation results.
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context) override
Identify results visible at the specified profile point.
QgsProfileSnapResult snapPoint(const QgsProfilePoint &point, const QgsProfileSnapContext &context) override
Snaps a point to the generated elevation profile.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
Class for doing transforms between two map coordinate systems.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:67
Curve polygon geometry type.
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
QgsAbstractGeometry * segmentize(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a geometry without curves.
int numInteriorRings() const SIP_HOLDGIL
Returns the number of interior rings contained with the curve polygon.
Abstract base class for curved geometry type.
Definition: qgscurve.h:36
virtual bool pointAt(int node, QgsPoint &point, Qgis::VertexType &type) const =0
Returns the point and vertex id of a point within the curve.
QgsRange which stores a range of double values.
Definition: qgsrange.h:203
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)
This class wraps a request for features to a vector layer (or directly its vector data provider).
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:56
QgsGeometry geometry
Definition: qgsfeature.h:67
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
Definition: qgsfillsymbol.h:30
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
bool boundingBoxIntersects(const QgsRectangle &rectangle) const
Returns true if the bounding box of this geometry intersects with a rectangle.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
static QgsGeometry fromRect(const QgsRectangle &rect) SIP_HOLDGIL
Creates a new geometry from a QgsRectangle.
bool isMultipart() const SIP_HOLDGIL
Returns true if WKB of the geometry is of WKBMulti* type.
Qgis::GeometryType type
Definition: qgsgeometry.h:167
static QgsGeometry fromPointXY(const QgsPointXY &point) SIP_HOLDGIL
Creates a new geometry from a QgsPointXY object.
QgsGeometry mergeLines() const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition: qgsgeos.h:99
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
void setZAt(int index, double z)
Sets the z-coordinate of the specified node in the line string.
int nCoordinates() const override SIP_HOLDGIL
Returns the number of nodes contained in the geometry.
double zAt(int index) const override
Returns the z-coordinate of the specified node in the line string.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgslinesymbol.h:30
A marker symbol type, for rendering Point and MultiPoint geometries.
Multi line string geometry collection.
Multi point geometry collection.
Definition: qgsmultipoint.h:30
Multi polygon geometry collection.
A class to represent a 2D point.
Definition: qgspointxy.h:59
void set(double x, double y) SIP_HOLDGIL
Sets the x and y value of the point.
Definition: qgspointxy.h:139
void setX(double x) SIP_HOLDGIL
Sets the x value of the point.
Definition: qgspointxy.h:122
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
void setY(double y) SIP_HOLDGIL
Sets the y value of the point.
Definition: qgspointxy.h:132
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Q_GADGET double x
Definition: qgspoint.h:52
double z
Definition: qgspoint.h:54
double y
Definition: qgspoint.h:53
Polygon geometry type.
Definition: qgspolygon.h:34
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 SIP_HOLDGIL
Returns the elevation of the point.
double distance() const SIP_HOLDGIL
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 override
Returns the set of any fields referenced by the active properties from the collection.
bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const override
Prepares the collection against a specified expression context.
T lower() const
Returns the lower bound of the range.
Definition: qgsrange.h:66
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:73
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool intersects(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:349
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
Scoped object for saving and restoring a QPainter object's state.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:94
void setColor(const QColor &color) const
Sets the color for the symbol.
Definition: qgssymbol.cpp:901
qreal opacity() const
Returns the opacity for the symbol.
Definition: qgssymbol.h:497
QColor color() const
Returns the symbol's color.
Definition: qgssymbol.cpp:911
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:153
Class that takes care of tessellation of polygons into triangles.
Vector layer specific subclass of QgsMapLayerElevationProperties.
Partial snapshot of vector layer's state (only the members necessary for access to features)
Implementation of QgsAbstractProfileGenerator for vector layers.
QgsAbstractProfileResults * takeResults() override
Takes results from the generator.
bool generateProfile(const QgsProfileGenerationContext &context=QgsProfileGenerationContext()) override
Generate the profile (based on data stored in the class).
QString sourceId() const override
Returns a unique identifier representing the source of the profile.
~QgsVectorLayerProfileGenerator() override
QgsFeedback * feedback() const override
Access to feedback object of the generator (may be nullptr)
QgsVectorLayerProfileGenerator(QgsVectorLayer *layer, const QgsProfileRequest &request)
Constructor for QgsVectorLayerProfileGenerator.
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context) override
Identify results visible at the specified profile point.
std::unique_ptr< QgsMarkerSymbol > mMarkerSymbol
QVector< QgsGeometry > asGeometries() const override
Returns a list of geometries representing the calculated elevation results.
void renderResults(QgsProfileRenderContext &context) override
Renders the results to the specified context.
QVector< QgsAbstractProfileResults::Feature > asFeatures(Qgis::ProfileExportType type, QgsFeedback *feedback=nullptr) const override
Returns a list of features representing the calculated elevation results.
QgsProfileSnapResult snapPoint(const QgsProfilePoint &point, const QgsProfileSnapContext &context) override
Snaps a point to the generated elevation profile.
QString type() const override
Returns the unique string identifier for the results type.
QHash< QgsFeatureId, QVector< Feature > > features
void copyPropertiesFromGenerator(const QgsAbstractProfileGenerator *generator) override
Copies properties from specified generator to the results object.
Represents a vector layer which manages a vector based data sets.
QgsMapLayerElevationProperties * elevationProperties() override
Returns the layer's elevation properties.
static Qgis::GeometryType geometryType(Qgis::WkbType type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:865
CORE_EXPORT QgsMeshVertex centroid(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns the centroid of the face.
#define FALLTHROUGH
Definition: qgis.h:4599
#define BUILTIN_UNREACHABLE
Definition: qgis.h:4659
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3988
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
#define QgsDebugError(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
Encapsulates information about a feature exported from the profile results.
QString layerIdentifier
Identifier for grouping output features.
QVariantMap attributes
Exported attributes.
QgsGeometry crossSectionGeometry
Cross section distance vs height geometry for feature.
QgsGeometry geometry
Feature's geometry with any terrain height adjustment and extrusion applied.