QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgspointcloudlayerprofilegenerator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointcloudlayerprofilegenerator.cpp
3 ---------------
4 begin : April 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
20#include "qgscurve.h"
21#include "qgsgeos.h"
22#include "qgsmessagelog.h"
24#include "qgspointcloudlayer.h"
28#include "qgsprofilepoint.h"
29#include "qgsprofilerequest.h"
30#include "qgsprofilesnapping.h"
31#include "qgsproject.h"
32
33#include <QString>
34
35using namespace Qt::StringLiterals;
36
37//
38// QgsPointCloudLayerProfileGenerator
39//
40
42{
43 mPointIndex = GEOSSTRtree_create_r( QgsGeosContext::get(), ( size_t )10 );
44}
45
47{
48 GEOSSTRtree_destroy_r( QgsGeosContext::get(), mPointIndex );
49 mPointIndex = nullptr;
50}
51
53{
54 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
55
56 const std::size_t size = results.size();
57 PointResult *pointData = results.data();
58 for ( std::size_t i = 0; i < size; ++i, ++pointData )
59 {
60 if ( feedback->isCanceled() )
61 break;
62
63 geos::unique_ptr geosPoint( GEOSGeom_createPointFromXY_r( geosctxt, pointData->distanceAlongCurve, pointData->z ) );
64
65 GEOSSTRtree_insert_r( geosctxt, mPointIndex, geosPoint.get(), pointData );
66 // only required for GEOS < 3.9
67 }
68}
69
71{
72 return u"pointcloud"_s;
73}
74
76{
77 // TODO -- cache?
78 QMap< double, double > res;
79 for ( const PointResult &point : results )
80 {
81 res.insert( point.distanceAlongCurve, point.z );
82 }
83 return res;
84}
85
87{
88 // TODO -- cache?
90 res.reserve( results.size() );
91 for ( const PointResult &point : results )
92 {
93 res.append( QgsPoint( point.x, point.y, point.z ) );
94 }
95 return res;
96}
97
99{
100 // TODO -- cache?
101 QVector< QgsGeometry > res;
102 res.reserve( results.size() );
103 for ( const PointResult &point : results )
104 {
105 res.append( QgsGeometry( new QgsPoint( point.x, point.y, point.z ) ) );
106 }
107 return res;
108}
109
110QVector<QgsAbstractProfileResults::Feature> QgsPointCloudLayerProfileResults::asFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback ) const
111{
112 QVector< QgsAbstractProfileResults::Feature > res;
113 res.reserve( static_cast< int >( results.size() ) );
114 switch ( type )
115 {
117 {
118 for ( const PointResult &point : results )
119 {
120 if ( feedback && feedback->isCanceled() )
121 break;
123 f.layerIdentifier = mLayerId;
124 f.geometry = QgsGeometry( std::make_unique< QgsPoint >( point.x, point.y, point.z ) );
125 res.append( f );
126 }
127 break;
128 }
129
131 {
132 for ( const PointResult &point : results )
133 {
134 if ( feedback && feedback->isCanceled() )
135 break;
137 f.layerIdentifier = mLayerId;
138 f.geometry = QgsGeometry( std::make_unique< QgsPoint >( point.distanceAlongCurve, point.z ) );
139 res.append( f );
140 }
141 break;
142 }
143
145 {
146 for ( const PointResult &point : results )
147 {
148 if ( feedback && feedback->isCanceled() )
149 break;
150
152 f.layerIdentifier = mLayerId;
153 f.attributes =
154 {
155 { u"distance"_s, point.distanceAlongCurve },
156 { u"elevation"_s, point.z }
157 };
158 f.geometry = QgsGeometry( std::make_unique< QgsPoint >( point.x, point.y, point.z ) );
159 res << f;
160 }
161 break;
162 }
163 }
164
165 return res;
166}
167
172
174{
175 QPainter *painter = context.renderContext().painter();
176 if ( !painter )
177 return;
178
179 const QgsScopedQPainterState painterState( painter );
180
181 painter->setBrush( Qt::NoBrush );
182 painter->setPen( Qt::NoPen );
183
184 switch ( pointSymbol )
185 {
187 // for square point we always disable antialiasing -- it's not critical here and we benefit from the performance boost disabling it gives
188 context.renderContext().painter()->setRenderHint( QPainter::Antialiasing, false );
189 break;
190
192 break;
193 }
194
195 const double minDistance = context.distanceRange().lower();
196 const double maxDistance = context.distanceRange().upper();
197 const double minZ = context.elevationRange().lower();
198 const double maxZ = context.elevationRange().upper();
199
200 const QRectF visibleRegion( minDistance, minZ, maxDistance - minDistance, maxZ - minZ );
201 QPainterPath clipPath;
202 clipPath.addPolygon( context.worldTransform().map( visibleRegion ) );
203 painter->setClipPath( clipPath, Qt::ClipOperation::IntersectClip );
204
205 const double penWidth = context.renderContext().convertToPainterUnits( pointSize, pointSizeUnit );
206
207 for ( const PointResult &point : std::as_const( results ) )
208 {
209 QPointF p = context.worldTransform().map( QPointF( point.distanceAlongCurve, point.z ) );
210 QColor color = respectLayerColors ? point.color : pointColor;
212 color.setAlphaF( color.alphaF() * ( 1.0 - std::pow( point.distanceFromCurve / tolerance, 0.5 ) ) );
213
214 switch ( pointSymbol )
215 {
217 painter->fillRect( QRectF( p.x() - penWidth * 0.5,
218 p.y() - penWidth * 0.5,
219 penWidth, penWidth ), color );
220 break;
221
223 painter->setBrush( QBrush( color ) );
224 painter->setPen( Qt::NoPen );
225 painter->drawEllipse( QRectF( p.x() - penWidth * 0.5,
226 p.y() - penWidth * 0.5,
227 penWidth, penWidth ) );
228 break;
229 }
230 }
231}
232
234{
235 QList< const QgsPointCloudLayerProfileResults::PointResult * > *list;
236};
237void _GEOSQueryCallback( void *item, void *userdata )
238{
239 reinterpret_cast<_GEOSQueryCallbackData *>( userdata )->list->append( reinterpret_cast<const QgsPointCloudLayerProfileResults::PointResult *>( item ) );
240}
241
243{
245
246 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
247
248 const double minDistance = point.distance() - context.maximumPointDistanceDelta;
249 const double maxDistance = point.distance() + context.maximumPointDistanceDelta;
250 const double minElevation = point.elevation() - context.maximumPointElevationDelta;
251 const double maxElevation = point.elevation() + context.maximumPointElevationDelta;
252
253 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
254 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, minDistance, minElevation );
255 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, maxDistance, maxElevation );
256 geos::unique_ptr searchDiagonal( GEOSGeom_createLineString_r( geosctxt, coord ) );
257
258 QList<const PointResult *> items;
259 struct _GEOSQueryCallbackData callbackData;
260 callbackData.list = &items;
261 GEOSSTRtree_query_r( geosctxt, mPointIndex, searchDiagonal.get(), _GEOSQueryCallback, &callbackData );
262 if ( items.empty() )
263 return result;
264
265 double bestMatchDistance = std::numeric_limits< double >::max();
266 const PointResult *bestMatch = nullptr;
267 for ( const PointResult *candidate : std::as_const( items ) )
268 {
269 const double distance = std::sqrt( std::pow( candidate->distanceAlongCurve - point.distance(), 2 )
270 + std::pow( ( candidate->z - point.elevation() ) / context.displayRatioElevationVsDistance, 2 ) );
271 if ( distance < bestMatchDistance )
272 {
273 bestMatchDistance = distance;
274 bestMatch = candidate;
275 }
276 }
277 if ( !bestMatch )
278 return result;
279
280 result.snappedPoint = QgsProfilePoint( bestMatch->distanceAlongCurve, bestMatch->z );
281 return result;
282}
283
284QVector<QgsProfileIdentifyResults> QgsPointCloudLayerProfileResults::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
285{
286 return identify( QgsDoubleRange( point.distance() - context.maximumPointDistanceDelta, point.distance() + context.maximumPointDistanceDelta ),
287 QgsDoubleRange( point.elevation() - context.maximumPointElevationDelta, point.elevation() + context.maximumPointElevationDelta ), context );
288}
289
290QVector<QgsProfileIdentifyResults> QgsPointCloudLayerProfileResults::identify( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const QgsProfileIdentifyContext &context )
291{
292 if ( !mLayer )
293 return {};
294
295 std::unique_ptr< QgsCurve > substring( mProfileCurve->curveSubstring( distanceRange.lower(), distanceRange.upper() ) );
296 QgsGeos substringGeos( substring.get() );
297 std::unique_ptr< QgsAbstractGeometry > searchGeometry( substringGeos.buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) );
298 if ( !searchGeometry )
299 return {};
300
301 const QgsCoordinateTransform curveToLayerTransform = QgsCoordinateTransform( mCurveCrs, mLayer->crs(), context.project ? context.project->transformContext() : QgsCoordinateTransformContext() );
302 try
303 {
304 searchGeometry->transform( curveToLayerTransform );
305 }
306 catch ( QgsCsException & )
307 {
308 return {};
309 }
310
311 // we have to adjust the elevation range to "undo" the z offset/z scale before we can hand to the provider
312 const QgsDoubleRange providerElevationRange( ( elevationRange.lower() - mZOffset ) / mZScale, ( elevationRange.upper() - mZOffset ) / mZScale );
313
314 const QgsGeometry pointCloudSearchGeometry( std::move( searchGeometry ) );
315 const QVector<QVariantMap> pointAttributes = mLayer->dataProvider()->identify( mMaxErrorInLayerCoordinates, pointCloudSearchGeometry, providerElevationRange );
316 if ( pointAttributes.empty() )
317 return {};
318
319 return { QgsProfileIdentifyResults( mLayer, pointAttributes )};
320}
321
323{
324 const QgsPointCloudLayerProfileGenerator *pcGenerator = qgis::down_cast< const QgsPointCloudLayerProfileGenerator *>( generator );
325 tolerance = pcGenerator->mTolerance;
326 pointSize = pcGenerator->mPointSize;
327 pointSizeUnit = pcGenerator->mPointSizeUnit;
328 pointSymbol = pcGenerator->mPointSymbol;
329 pointColor = pcGenerator->mPointColor;
330 respectLayerColors = static_cast< bool >( pcGenerator->mRenderer );
331 opacityByDistanceEffect = pcGenerator->mOpacityByDistanceEffect;
332
333 mLayer = pcGenerator->mLayer;
334 mLayerId = pcGenerator->mId;
335 mCurveCrs = pcGenerator->mTargetCrs;
336 mProfileCurve.reset( pcGenerator->mProfileCurve->clone() );
337 mTolerance = pcGenerator->mTolerance;
338
339 mZOffset = pcGenerator->mZOffset;
340 mZScale = pcGenerator->mZScale;
341}
342
343//
344// QgsPointCloudLayerProfileGenerator
345//
346
348 : mLayer( layer )
349 , mIndex( layer->index() )
350 , mSubIndexes( layer->dataProvider()->subIndexes() )
351 , mLayerAttributes( layer->attributes() )
352 , mRenderer( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->respectLayerColors() && mLayer->renderer() ? mLayer->renderer()->clone() : nullptr )
353 , mMaximumScreenError( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->maximumScreenError() )
354 , mMaximumScreenErrorUnit( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->maximumScreenErrorUnit() )
355 , mPointSize( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointSize() )
356 , mPointSizeUnit( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointSizeUnit() )
357 , mPointSymbol( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointSymbol() )
358 , mPointColor( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointColor() )
359 , mOpacityByDistanceEffect( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->applyOpacityByDistanceEffect() )
360 , mId( layer->id() )
361 , mFeedback( std::make_unique< QgsFeedback >() )
362 , mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
363 , mTolerance( request.tolerance() )
364 , mSourceCrs( layer->crs3D() )
365 , mTargetCrs( request.crs() )
366 , mTransformContext( request.transformContext() )
367 , mZOffset( layer->elevationProperties()->zOffset() )
368 , mZScale( layer->elevationProperties()->zScale() )
369 , mStepDistance( request.stepDistance() )
370{
371 if ( mIndex )
372 {
373 mScale = mIndex.scale();
374 mOffset = mIndex.offset();
375 }
376}
377
379{
380 return mId;
381}
382
387
389
391{
392 mGatheredPoints.clear();
393 if ( !mLayer || !mProfileCurve || mFeedback->isCanceled() )
394 return false;
395
396 QVector<QgsPointCloudIndex> indexes;
397 if ( mIndex && mIndex.isValid() )
398 indexes.append( mIndex );
399
400 // Gather all relevant sub-indexes
401 const QgsRectangle profileCurveBbox = mProfileCurve->boundingBox();
402 for ( const QgsPointCloudSubIndex &subidx : mSubIndexes )
403 {
404 QgsPointCloudIndex index = subidx.index();
405 if ( index && index.isValid() && subidx.polygonBounds().intersects( profileCurveBbox ) )
406 indexes.append( subidx.index() );
407 }
408
409 if ( indexes.empty() )
410 return false;
411
412 const double startDistanceOffset = std::max( !context.distanceRange().isInfinite() ? context.distanceRange().lower() : 0, 0.0 );
413 const double endDistance = context.distanceRange().upper();
414
415 std::unique_ptr< QgsCurve > trimmedCurve;
416 QgsCurve *sourceCurve = nullptr;
417 if ( startDistanceOffset > 0 || endDistance < mProfileCurve->length() )
418 {
419 trimmedCurve.reset( mProfileCurve->curveSubstring( startDistanceOffset, endDistance ) );
420 sourceCurve = trimmedCurve.get();
421 }
422 else
423 {
424 sourceCurve = mProfileCurve.get();
425 }
426
427 // we need to transform the profile curve and max search extent to the layer's CRS
428 QgsGeos originalCurveGeos( sourceCurve );
429 originalCurveGeos.prepareGeometry();
430 mSearchGeometryInLayerCrs.reset( originalCurveGeos.buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) );
431 mLayerToTargetTransform = QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext );
432
433 try
434 {
435 mSearchGeometryInLayerCrs->transform( mLayerToTargetTransform, Qgis::TransformDirection::Reverse );
436 }
437 catch ( QgsCsException & )
438 {
439 QgsDebugError( u"Error transforming profile line to layer CRS"_s );
440 return false;
441 }
442
443 if ( mFeedback->isCanceled() )
444 return false;
445
446 mSearchGeometryInLayerCrsGeometryEngine = std::make_unique< QgsGeos >( mSearchGeometryInLayerCrs.get() );
447 mSearchGeometryInLayerCrsGeometryEngine->prepareGeometry();
448 mMaxSearchExtentInLayerCrs = mSearchGeometryInLayerCrs->boundingBox();
449
450 double maximumErrorPixels = context.convertDistanceToPixels( mMaximumScreenError, mMaximumScreenErrorUnit );
451 if ( maximumErrorPixels < 0.0 )
452 {
453 QgsDebugError( u"Invalid maximum error in pixels"_s );
454 return false;
455 }
456
457 const double toleranceInPixels = context.convertDistanceToPixels( mTolerance, Qgis::RenderUnit::MapUnits );
458 // ensure that the maximum error is compatible with the tolerance size -- otherwise if the tolerance size
459 // is much smaller than the maximum error, we don't dig deep enough into the point cloud nodes to find
460 // points which are inside the tolerance.
461 // "4" is a magic number here, based purely on what "looks good" in the profile results!
462 if ( toleranceInPixels / 4 < maximumErrorPixels )
463 maximumErrorPixels = toleranceInPixels / 4;
464
465 QgsPointCloudRequest request;
470
471 if ( mRenderer )
472 {
473 mPreparedRendererData = mRenderer->prepare();
474 if ( mPreparedRendererData )
475 {
476 const QSet< QString > rendererAttributes = mPreparedRendererData->usedAttributes();
477 for ( const QString &attribute : std::as_const( rendererAttributes ) )
478 {
479 if ( attributes.indexOf( attribute ) >= 0 )
480 continue; // don't re-add attributes we are already going to fetch
481
482 const int layerIndex = mLayerAttributes.indexOf( attribute );
483 if ( layerIndex < 0 )
484 {
485 QgsMessageLog::logMessage( QObject::tr( "Required attribute %1 not found in layer" ).arg( attribute ), QObject::tr( "Point Cloud" ) );
486 continue;
487 }
488
489 attributes.push_back( mLayerAttributes.at( layerIndex ) );
490 }
491 }
492 }
493 else
494 {
495 mPreparedRendererData.reset();
496 }
497
498 request.setAttributes( attributes );
499
500 mResults = std::make_unique< QgsPointCloudLayerProfileResults >();
501 mResults->copyPropertiesFromGenerator( this );
502 mResults->mMaxErrorInLayerCoordinates = 0;
503
504 QgsCoordinateTransform extentTransform = mLayerToTargetTransform;
505 extentTransform.setBallparkTransformsAreAppropriate( true );
506
507 const double mapUnitsPerPixel = context.mapUnitsPerDistancePixel();
508 if ( mapUnitsPerPixel < 0.0 )
509 {
510 QgsDebugError( u"Invalid map units per pixel ratio"_s );
511 return false;
512 }
513
514 for ( QgsPointCloudIndex pc : std::as_const( indexes ) )
515 {
516 const QgsPointCloudNode root = pc.getNode( pc.root() );
517 const QgsRectangle rootNodeExtentLayerCoords = root.bounds().toRectangle();
518 QgsRectangle rootNodeExtentInCurveCrs;
519 try
520 {
521 rootNodeExtentInCurveCrs = extentTransform.transformBoundingBox( rootNodeExtentLayerCoords );
522 }
523 catch ( QgsCsException & )
524 {
525 QgsDebugError( u"Could not transform node extent to curve CRS"_s );
526 rootNodeExtentInCurveCrs = rootNodeExtentLayerCoords;
527 }
528
529 const double rootErrorInMapCoordinates = rootNodeExtentInCurveCrs.width() / pc.span(); // in curve coords
530 if ( rootErrorInMapCoordinates < 0.0 )
531 {
532 QgsDebugError( u"Invalid root node error"_s );
533 return false;
534 }
535
536 double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels
537 const QVector<QgsPointCloudNodeId> nodes = traverseTree( pc, pc.root(), maximumErrorPixels, rootErrorPixels, context.elevationRange() );
538
539 if ( nodes.empty() )
540 {
541 return false;
542 }
543
544 const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / pc.span();
545 const double maxErrorInMapCoordinates = maximumErrorPixels * mapUnitsPerPixel;
546
547 mResults->mMaxErrorInLayerCoordinates = std::max(
548 mResults->mMaxErrorInLayerCoordinates,
549 maxErrorInMapCoordinates * rootErrorInLayerCoordinates / rootErrorInMapCoordinates );
550
551 switch ( pc.accessType() )
552 {
554 {
555 visitNodesSync( nodes, pc, request, context.elevationRange() );
556 break;
557 }
559 {
560 visitNodesAsync( nodes, pc, request, context.elevationRange() );
561 break;
562 }
563 }
564
565 if ( mFeedback->isCanceled() )
566 return false;
567 }
568
569 if ( mGatheredPoints.empty() )
570 {
571 mResults = nullptr;
572 return false;
573 }
574
575 // convert x/y values back to distance/height values
576
577 QString lastError;
578 const QgsPointCloudLayerProfileResults::PointResult *pointData = mGatheredPoints.constData();
579 const int size = mGatheredPoints.size();
580 mResults->results.resize( size );
581 QgsPointCloudLayerProfileResults::PointResult *destData = mResults->results.data();
582 for ( int i = 0; i < size; ++i, ++pointData, ++destData )
583 {
584 if ( mFeedback->isCanceled() )
585 return false;
586
587 *destData = *pointData;
588 destData->distanceAlongCurve = startDistanceOffset + originalCurveGeos.lineLocatePoint( destData->x, destData->y, &lastError );
589 if ( mOpacityByDistanceEffect ) // don't calculate this if we don't need it
590 destData->distanceFromCurve = originalCurveGeos.distance( destData->x, destData->y );
591
592 mResults->minZ = std::min( destData->z, mResults->minZ );
593 mResults->maxZ = std::max( destData->z, mResults->maxZ );
594 }
595 mResults->finalize( mFeedback.get() );
596
597 return true;
598}
599
604
606{
607 return mFeedback.get();
608}
609
610QVector<QgsPointCloudNodeId> QgsPointCloudLayerProfileGenerator::traverseTree( const QgsPointCloudIndex &pc, QgsPointCloudNodeId n, double maxErrorPixels, double nodeErrorPixels, const QgsDoubleRange &zRange )
611{
612 QVector<QgsPointCloudNodeId> nodes;
613
614 if ( mFeedback->isCanceled() )
615 {
616 return nodes;
617 }
618
619 QgsPointCloudNode node = pc.getNode( n );
620 QgsBox3D nodeBounds = node.bounds();
621 const QgsDoubleRange nodeZRange( nodeBounds.zMinimum(), nodeBounds.zMaximum() );
622 if ( !zRange.isInfinite() && !zRange.overlaps( nodeZRange ) )
623 return nodes;
624
625 if ( !mMaxSearchExtentInLayerCrs.intersects( nodeBounds.toRectangle() ) )
626 return nodes;
627
628 const QgsGeometry nodeMapGeometry = QgsGeometry::fromRect( nodeBounds.toRectangle() );
629 if ( !mSearchGeometryInLayerCrsGeometryEngine->intersects( nodeMapGeometry.constGet() ) )
630 return nodes;
631
632 if ( node.pointCount() > 0 )
633 nodes.append( n );
634
635 double childrenErrorPixels = nodeErrorPixels / 2.0;
636 if ( childrenErrorPixels < maxErrorPixels )
637 return nodes;
638
639 for ( const QgsPointCloudNodeId &nn : node.children() )
640 {
641 nodes += traverseTree( pc, nn, maxErrorPixels, childrenErrorPixels, zRange );
642 }
643
644 return nodes;
645}
646
647int QgsPointCloudLayerProfileGenerator::visitNodesSync( const QVector<QgsPointCloudNodeId> &nodes, QgsPointCloudIndex &pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange )
648{
649 int nodesDrawn = 0;
650 for ( const QgsPointCloudNodeId &n : nodes )
651 {
652 if ( mFeedback->isCanceled() )
653 break;
654
655 std::unique_ptr<QgsPointCloudBlock> block( pc.nodeData( n, request ) );
656
657 if ( !block )
658 continue;
659
660 visitBlock( block.get(), zRange );
661
662 ++nodesDrawn;
663 }
664 return nodesDrawn;
665}
666
667int QgsPointCloudLayerProfileGenerator::visitNodesAsync( const QVector<QgsPointCloudNodeId> &nodes, QgsPointCloudIndex &pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange )
668{
669 if ( nodes.isEmpty() )
670 return 0;
671
672 int nodesDrawn = 0;
673
674 // see notes about this logic in QgsPointCloudLayerRenderer::renderNodesAsync
675
676 // Async loading of nodes
677 QVector<QgsPointCloudBlockRequest *> blockRequests;
678 QEventLoop loop;
679 QObject::connect( mFeedback.get(), &QgsFeedback::canceled, &loop, &QEventLoop::quit );
680
681 for ( int i = 0; i < nodes.size(); ++i )
682 {
683 const QgsPointCloudNodeId &n = nodes[i];
684 const QString nStr = n.toString();
685 QgsPointCloudBlockRequest *blockRequest = pc.asyncNodeData( n, request );
686 blockRequests.append( blockRequest );
687 QObject::connect( blockRequest, &QgsPointCloudBlockRequest::finished, &loop,
688 [ this, &nodesDrawn, &loop, &blockRequests, &zRange, nStr, blockRequest ]()
689 {
690 blockRequests.removeOne( blockRequest );
691
692 // If all blocks are loaded, exit the event loop
693 if ( blockRequests.isEmpty() )
694 loop.exit();
695
696 std::unique_ptr<QgsPointCloudBlock> block = blockRequest->takeBlock();
697
698 blockRequest->deleteLater();
699
700 if ( mFeedback->isCanceled() )
701 {
702 return;
703 }
704
705 if ( !block )
706 {
707 QgsDebugError( u"Unable to load node %1, error: %2"_s.arg( nStr, blockRequest->errorStr() ) );
708 return;
709 }
710
711 visitBlock( block.get(), zRange );
712 ++nodesDrawn;
713 } );
714 }
715
716 // Wait for all point cloud nodes to finish loading
717 if ( !blockRequests.isEmpty() )
718 loop.exec();
719
720 // Generation may have got canceled and the event loop exited before finished()
721 // was called for all blocks, so let's clean up anything that is left
722 for ( QgsPointCloudBlockRequest *blockRequest : std::as_const( blockRequests ) )
723 {
724 std::unique_ptr<QgsPointCloudBlock> block = blockRequest->takeBlock();
725 block.reset();
726 blockRequest->deleteLater();
727 }
728
729 return nodesDrawn;
730}
731
732void QgsPointCloudLayerProfileGenerator::visitBlock( const QgsPointCloudBlock *block, const QgsDoubleRange &zRange )
733{
734 const char *ptr = block->data();
735 int count = block->pointCount();
736
737 const QgsPointCloudAttributeCollection request = block->attributes();
738
739 const std::size_t recordSize = request.pointRecordSize();
740
741 const QgsPointCloudAttributeCollection blockAttributes = block->attributes();
742 int xOffset = 0, yOffset = 0, zOffset = 0;
743 const QgsPointCloudAttribute::DataType xType = blockAttributes.find( u"X"_s, xOffset )->type();
744 const QgsPointCloudAttribute::DataType yType = blockAttributes.find( u"Y"_s, yOffset )->type();
745 const QgsPointCloudAttribute::DataType zType = blockAttributes.find( u"Z"_s, zOffset )->type();
746
747 bool useRenderer = false;
748 if ( mPreparedRendererData )
749 {
750 useRenderer = mPreparedRendererData->prepareBlock( block );
751 }
752
753 QColor color;
754 const bool reproject = !mLayerToTargetTransform.isShortCircuited();
755 for ( int i = 0; i < count; ++i )
756 {
757 if ( mFeedback->isCanceled() )
758 {
759 break;
760 }
761
762 QgsPointCloudLayerProfileResults::PointResult res;
763 QgsPointCloudAttribute::getPointXYZ( ptr, i, recordSize, xOffset, xType, yOffset, yType, zOffset, zType, block->scale(), block->offset(), res.x, res.y, res.z );
764
765 res.z = res.z * mZScale + mZOffset;
766 if ( !zRange.contains( res.z ) )
767 continue;
768
769 if ( useRenderer )
770 {
771 color = mPreparedRendererData->pointColor( block, i, res.z );
772 if ( !color.isValid() )
773 continue;
774
775 res.color = color.rgba();
776 }
777 else
778 {
779 res.color = mPointColor.rgba();
780 }
781
782 if ( mSearchGeometryInLayerCrsGeometryEngine->contains( res.x, res.y ) )
783 {
784 if ( reproject )
785 {
786 try
787 {
788 mLayerToTargetTransform.transformInPlace( res.x, res.y, res.z );
789 }
790 catch ( QgsCsException & )
791 {
792 continue;
793 }
794 }
795
796 mGatheredPoints.append( res );
797 }
798 }
799}
800
801
@ RespectsMaximumErrorMapUnit
Generated profile respects the QgsProfileGenerationContext::maximumErrorMapUnits() property.
Definition qgis.h:4278
@ RespectsDistanceRange
Generated profile respects the QgsProfileGenerationContext::distanceRange() property.
Definition qgis.h:4279
@ Circle
Renders points as circles.
Definition qgis.h:4307
@ Square
Renders points as squares.
Definition qgis.h:4306
QFlags< ProfileGeneratorFlag > ProfileGeneratorFlags
Definition qgis.h:4283
@ Round
Use rounded joins.
Definition qgis.h:2180
@ MapUnits
Map units.
Definition qgis.h:5257
@ Flat
Flat cap (in line with start/end of line).
Definition qgis.h:2168
@ Local
Local means the source is a local file on the machine.
Definition qgis.h:6341
@ Remote
Remote means it's loaded through a protocol like HTTP.
Definition qgis.h:6342
ProfileExportType
Types of export for elevation profiles.
Definition qgis.h:4292
@ Profile2D
Export profiles as 2D profile lines, with elevation stored in exported geometry Y dimension and dista...
Definition qgis.h:4294
@ Features3D
Export profiles as 3D features, with elevation values stored in exported geometry Z values.
Definition qgis.h:4293
@ DistanceVsElevationTable
Export profiles as a table of sampled distance vs elevation values.
Definition qgis.h:4295
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2731
Abstract base class for objects which generate elevation profiles.
Abstract base class for storage of elevation profiles.
double zMaximum() const
Returns the maximum z value.
Definition qgsbox3d.h:261
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
Definition qgsbox3d.h:381
double zMinimum() const
Returns the minimum z value.
Definition qgsbox3d.h:254
Contains information about the context in which a coordinate transform is executed.
Handles coordinate transforms between two coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
QgsRange which stores a range of double values.
Definition qgsrange.h:236
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition qgsrange.h:290
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:55
void canceled()
Internal routines can connect to this signal if they use event loop.
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static GEOSContextHandle_t get()
Returns a thread local instance of a GEOS context, safe for use in the current thread.
Does vector analysis using the GEOS library and handles import, export, and exception handling.
Definition qgsgeos.h:141
QgsAbstractGeometry * buffer(double distance, int segments, QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2089
double distance(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Calculates the distance between this and geom.
Definition qgsgeos.cpp:580
void prepareGeometry() override
Prepares the geometry, so that subsequent calls to spatial relation methods are much faster.
Definition qgsgeos.cpp:288
double lineLocatePoint(const QgsPoint &point, QString *errorMsg=nullptr) const
Returns a distance representing the location along this linestring of the closest point on this lines...
Definition qgsgeos.cpp:3181
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
A collection of point cloud attributes.
void push_back(const QgsPointCloudAttribute &attribute)
Adds extra attribute.
int pointRecordSize() const
Returns total size of record.
const QgsPointCloudAttribute * find(const QString &attributeName, int &offset) const
Finds the attribute with the name.
int indexOf(const QString &name) const
Returns the index of the attribute with the specified name.
Attribute for point cloud data pair of name and size in bytes.
DataType
Systems of unit measurement.
static void getPointXYZ(const char *ptr, int i, std::size_t pointRecordSize, int xOffset, QgsPointCloudAttribute::DataType xType, int yOffset, QgsPointCloudAttribute::DataType yType, int zOffset, QgsPointCloudAttribute::DataType zType, const QgsVector3D &indexScale, const QgsVector3D &indexOffset, double &x, double &y, double &z)
Retrieves the x, y, z values for the point at index i.
DataType type() const
Returns the data type.
QString errorStr() const
Returns the error message string of the request.
void finished()
Emitted when the request processing has finished.
std::unique_ptr< QgsPointCloudBlock > takeBlock()
Returns the requested block.
Base class for storing raw data from point cloud nodes.
QgsVector3D scale() const
Returns the custom scale of the block.
const char * data() const
Returns raw pointer to data.
QgsPointCloudAttributeCollection attributes() const
Returns the attributes that are stored in the data block, along with their size.
int pointCount() const
Returns number of points that are stored in the block.
QgsVector3D offset() const
Returns the custom offset of the block.
Smart pointer for QgsAbstractPointCloudIndex.
QgsPointCloudBlockRequest * asyncNodeData(const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request)
Returns a handle responsible for loading a node data block.
bool isValid() const
Returns whether index is loaded and valid.
std::unique_ptr< QgsPointCloudBlock > nodeData(const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request)
Returns node data block.
QgsPointCloudNode getNode(const QgsPointCloudNodeId &id) const
Returns object for a given node.
Point cloud layer specific subclass of QgsMapLayerElevationProperties.
Qgis::ProfileGeneratorFlags flags() const override
Returns flags which reflect how the profile generator operates.
QgsPointCloudLayerProfileGenerator(QgsPointCloudLayer *layer, const QgsProfileRequest &request)
Constructor for QgsPointCloudLayerProfileGenerator.
QString sourceId() const override
Returns a unique identifier representing the source of the profile.
QgsFeedback * feedback() const override
Access to feedback object of the generator (may be nullptr).
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).
QgsProfileSnapResult snapPoint(const QgsProfilePoint &point, const QgsProfileSnapContext &context) override
Snaps a point to the generated elevation profile.
void finalize(QgsFeedback *feedback)
Finalizes results – should be called after last point is added.
QgsPointSequence sampledPoints() const override
Returns a list of sampled points, with their calculated elevation as the point z value.
QgsDoubleRange zRange() const override
Returns the range of the retrieved elevation values.
QMap< double, double > distanceToHeightMap() const override
Returns the map of distance (chainage) to height.
QVector< QgsGeometry > asGeometries() const override
Returns a list of geometries representing the calculated elevation results.
void copyPropertiesFromGenerator(const QgsAbstractProfileGenerator *generator) override
Copies properties from specified generator to the results object.
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context) override
Identify results visible at the specified profile point.
QString type() const override
Returns the unique string identifier for the results type.
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.
Represents a map layer supporting display of point clouds.
Represents an indexed point cloud node's position in octree.
QString toString() const
Encode node to string.
Keeps metadata for an indexed point cloud node.
QList< QgsPointCloudNodeId > children() const
Returns IDs of child nodes.
qint64 pointCount() const
Returns number of points contained in node data.
QgsBox3D bounds() const
Returns node's bounding cube in CRS coords.
Point cloud data request.
void setAttributes(const QgsPointCloudAttributeCollection &attributes)
Set attributes filter in the request.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
Encapsulates the context in which an elevation profile is to be generated.
double mapUnitsPerDistancePixel() const
Returns the number of map units per pixel in the distance dimension.
QgsDoubleRange elevationRange() const
Returns the range of elevations to include in the generation.
double convertDistanceToPixels(double size, Qgis::RenderUnit unit) const
Converts a distance size from the specified units to pixels.
QgsDoubleRange distanceRange() const
Returns the range of distances to include in the generation.
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.
QgsProject * project
Associated project.
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 maximumPointElevationDelta
Maximum allowed snapping delta for the elevation values when snapping to a point.
double displayRatioElevationVsDistance
Display ratio of elevation vs distance units.
Encapsulates results of snapping a profile point.
QgsProfilePoint snappedPoint
Snapped point.
QgsCoordinateTransformContext transformContext
Definition qgsproject.h:119
bool overlaps(const QgsRange< T > &other) const
Returns true if this range overlaps another range.
Definition qgsrange.h:179
bool contains(const QgsRange< T > &other) const
Returns true if this range contains another range.
Definition qgsrange.h:140
T lower() const
Returns the lower bound of the range.
Definition qgsrange.h:81
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:88
A rectangle specified with double values.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
Scoped object for saving and restoring a QPainter object's state.
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:114
QVector< QgsPoint > QgsPointSequence
#define QgsDebugError(str)
Definition qgslogger.h:59
void _GEOSQueryCallback(void *item, void *userdata)
Encapsulates information about a feature exported from the profile results.
QString layerIdentifier
Identifier for grouping output features.
QVariantMap attributes
Exported attributes.
QList< const QgsPointCloudLayerProfileResults::PointResult * > * list