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