QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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//
34// QgsPointCloudLayerProfileGenerator
35//
36
38{
39 mPointIndex = GEOSSTRtree_create_r( QgsGeosContext::get(), ( size_t )10 );
40}
41
43{
44 GEOSSTRtree_destroy_r( QgsGeosContext::get(), mPointIndex );
45 mPointIndex = nullptr;
46}
47
49{
50 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
51
52 const std::size_t size = results.size();
53 PointResult *pointData = results.data();
54 for ( std::size_t i = 0; i < size; ++i, ++pointData )
55 {
56 if ( feedback->isCanceled() )
57 break;
58
59 geos::unique_ptr geosPoint( GEOSGeom_createPointFromXY_r( geosctxt, pointData->distanceAlongCurve, pointData->z ) );
60
61 GEOSSTRtree_insert_r( geosctxt, mPointIndex, geosPoint.get(), pointData );
62 // only required for GEOS < 3.9
63 }
64}
65
67{
68 return QStringLiteral( "pointcloud" );
69}
70
72{
73 // TODO -- cache?
74 QMap< double, double > res;
75 for ( const PointResult &point : results )
76 {
77 res.insert( point.distanceAlongCurve, point.z );
78 }
79 return res;
80}
81
83{
84 // TODO -- cache?
86 res.reserve( results.size() );
87 for ( const PointResult &point : results )
88 {
89 res.append( QgsPoint( point.x, point.y, point.z ) );
90 }
91 return res;
92}
93
95{
96 // TODO -- cache?
97 QVector< QgsGeometry > res;
98 res.reserve( results.size() );
99 for ( const PointResult &point : results )
100 {
101 res.append( QgsGeometry( new QgsPoint( point.x, point.y, point.z ) ) );
102 }
103 return res;
104}
105
106QVector<QgsAbstractProfileResults::Feature> QgsPointCloudLayerProfileResults::asFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback ) const
107{
108 QVector< QgsAbstractProfileResults::Feature > res;
109 res.reserve( static_cast< int >( results.size() ) );
110 switch ( type )
111 {
113 {
114 for ( const PointResult &point : results )
115 {
116 if ( feedback && feedback->isCanceled() )
117 break;
119 f.layerIdentifier = mLayerId;
120 f.geometry = QgsGeometry( std::make_unique< QgsPoint >( point.x, point.y, point.z ) );
121 res.append( f );
122 }
123 break;
124 }
125
127 {
128 for ( const PointResult &point : results )
129 {
130 if ( feedback && feedback->isCanceled() )
131 break;
133 f.layerIdentifier = mLayerId;
134 f.geometry = QgsGeometry( std::make_unique< QgsPoint >( point.distanceAlongCurve, point.z ) );
135 res.append( f );
136 }
137 break;
138 }
139
141 {
142 for ( const PointResult &point : results )
143 {
144 if ( feedback && feedback->isCanceled() )
145 break;
146
148 f.layerIdentifier = mLayerId;
149 f.attributes =
150 {
151 { QStringLiteral( "distance" ), point.distanceAlongCurve },
152 { QStringLiteral( "elevation" ), point.z }
153 };
154 f.geometry = QgsGeometry( std::make_unique< QgsPoint >( point.x, point.y, point.z ) );
155 res << f;
156 }
157 break;
158 }
159 }
160
161 return res;
162}
163
168
170{
171 QPainter *painter = context.renderContext().painter();
172 if ( !painter )
173 return;
174
175 const QgsScopedQPainterState painterState( painter );
176
177 painter->setBrush( Qt::NoBrush );
178 painter->setPen( Qt::NoPen );
179
180 switch ( pointSymbol )
181 {
183 // for square point we always disable antialiasing -- it's not critical here and we benefit from the performance boost disabling it gives
184 context.renderContext().painter()->setRenderHint( QPainter::Antialiasing, false );
185 break;
186
188 break;
189 }
190
191 const double minDistance = context.distanceRange().lower();
192 const double maxDistance = context.distanceRange().upper();
193 const double minZ = context.elevationRange().lower();
194 const double maxZ = context.elevationRange().upper();
195
196 const QRectF visibleRegion( minDistance, minZ, maxDistance - minDistance, maxZ - minZ );
197 QPainterPath clipPath;
198 clipPath.addPolygon( context.worldTransform().map( visibleRegion ) );
199 painter->setClipPath( clipPath, Qt::ClipOperation::IntersectClip );
200
201 const double penWidth = context.renderContext().convertToPainterUnits( pointSize, pointSizeUnit );
202
203 for ( const PointResult &point : std::as_const( results ) )
204 {
205 QPointF p = context.worldTransform().map( QPointF( point.distanceAlongCurve, point.z ) );
206 QColor color = respectLayerColors ? point.color : pointColor;
208 color.setAlphaF( color.alphaF() * ( 1.0 - std::pow( point.distanceFromCurve / tolerance, 0.5 ) ) );
209
210 switch ( pointSymbol )
211 {
213 painter->fillRect( QRectF( p.x() - penWidth * 0.5,
214 p.y() - penWidth * 0.5,
215 penWidth, penWidth ), color );
216 break;
217
219 painter->setBrush( QBrush( color ) );
220 painter->setPen( Qt::NoPen );
221 painter->drawEllipse( QRectF( p.x() - penWidth * 0.5,
222 p.y() - penWidth * 0.5,
223 penWidth, penWidth ) );
224 break;
225 }
226 }
227}
228
230{
231 QList< const QgsPointCloudLayerProfileResults::PointResult * > *list;
232};
233void _GEOSQueryCallback( void *item, void *userdata )
234{
235 reinterpret_cast<_GEOSQueryCallbackData *>( userdata )->list->append( reinterpret_cast<const QgsPointCloudLayerProfileResults::PointResult *>( item ) );
236}
237
239{
241
242 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
243
244 const double minDistance = point.distance() - context.maximumPointDistanceDelta;
245 const double maxDistance = point.distance() + context.maximumPointDistanceDelta;
246 const double minElevation = point.elevation() - context.maximumPointElevationDelta;
247 const double maxElevation = point.elevation() + context.maximumPointElevationDelta;
248
249 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
250 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, minDistance, minElevation );
251 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, maxDistance, maxElevation );
252 geos::unique_ptr searchDiagonal( GEOSGeom_createLineString_r( geosctxt, coord ) );
253
254 QList<const PointResult *> items;
255 struct _GEOSQueryCallbackData callbackData;
256 callbackData.list = &items;
257 GEOSSTRtree_query_r( geosctxt, mPointIndex, searchDiagonal.get(), _GEOSQueryCallback, &callbackData );
258 if ( items.empty() )
259 return result;
260
261 double bestMatchDistance = std::numeric_limits< double >::max();
262 const PointResult *bestMatch = nullptr;
263 for ( const PointResult *candidate : std::as_const( items ) )
264 {
265 const double distance = std::sqrt( std::pow( candidate->distanceAlongCurve - point.distance(), 2 )
266 + std::pow( ( candidate->z - point.elevation() ) / context.displayRatioElevationVsDistance, 2 ) );
267 if ( distance < bestMatchDistance )
268 {
269 bestMatchDistance = distance;
270 bestMatch = candidate;
271 }
272 }
273 if ( !bestMatch )
274 return result;
275
276 result.snappedPoint = QgsProfilePoint( bestMatch->distanceAlongCurve, bestMatch->z );
277 return result;
278}
279
280QVector<QgsProfileIdentifyResults> QgsPointCloudLayerProfileResults::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
281{
282 return identify( QgsDoubleRange( point.distance() - context.maximumPointDistanceDelta, point.distance() + context.maximumPointDistanceDelta ),
283 QgsDoubleRange( point.elevation() - context.maximumPointElevationDelta, point.elevation() + context.maximumPointElevationDelta ), context );
284}
285
286QVector<QgsProfileIdentifyResults> QgsPointCloudLayerProfileResults::identify( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const QgsProfileIdentifyContext &context )
287{
288 if ( !mLayer )
289 return {};
290
291 std::unique_ptr< QgsCurve > substring( mProfileCurve->curveSubstring( distanceRange.lower(), distanceRange.upper() ) );
292 QgsGeos substringGeos( substring.get() );
293 std::unique_ptr< QgsAbstractGeometry > searchGeometry( substringGeos.buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) );
294 if ( !searchGeometry )
295 return {};
296
297 const QgsCoordinateTransform curveToLayerTransform = QgsCoordinateTransform( mCurveCrs, mLayer->crs(), context.project ? context.project->transformContext() : QgsCoordinateTransformContext() );
298 try
299 {
300 searchGeometry->transform( curveToLayerTransform );
301 }
302 catch ( QgsCsException & )
303 {
304 return {};
305 }
306
307 // we have to adjust the elevation range to "undo" the z offset/z scale before we can hand to the provider
308 const QgsDoubleRange providerElevationRange( ( elevationRange.lower() - mZOffset ) / mZScale, ( elevationRange.upper() - mZOffset ) / mZScale );
309
310 const QgsGeometry pointCloudSearchGeometry( std::move( searchGeometry ) );
311 const QVector<QVariantMap> pointAttributes = mLayer->dataProvider()->identify( mMaxErrorInLayerCoordinates, pointCloudSearchGeometry, providerElevationRange );
312 if ( pointAttributes.empty() )
313 return {};
314
315 return { QgsProfileIdentifyResults( mLayer, pointAttributes )};
316}
317
319{
320 const QgsPointCloudLayerProfileGenerator *pcGenerator = qgis::down_cast< const QgsPointCloudLayerProfileGenerator *>( generator );
321 tolerance = pcGenerator->mTolerance;
322 pointSize = pcGenerator->mPointSize;
323 pointSizeUnit = pcGenerator->mPointSizeUnit;
324 pointSymbol = pcGenerator->mPointSymbol;
325 pointColor = pcGenerator->mPointColor;
326 respectLayerColors = static_cast< bool >( pcGenerator->mRenderer );
327 opacityByDistanceEffect = pcGenerator->mOpacityByDistanceEffect;
328
329 mLayer = pcGenerator->mLayer;
330 mLayerId = pcGenerator->mId;
331 mCurveCrs = pcGenerator->mTargetCrs;
332 mProfileCurve.reset( pcGenerator->mProfileCurve->clone() );
333 mTolerance = pcGenerator->mTolerance;
334
335 mZOffset = pcGenerator->mZOffset;
336 mZScale = pcGenerator->mZScale;
337}
338
339//
340// QgsPointCloudLayerProfileGenerator
341//
342
344 : mLayer( layer )
345 , mIndex( layer->index() )
346 , mSubIndexes( layer->dataProvider()->subIndexes() )
347 , mLayerAttributes( layer->attributes() )
348 , mRenderer( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->respectLayerColors() && mLayer->renderer() ? mLayer->renderer()->clone() : nullptr )
349 , mMaximumScreenError( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->maximumScreenError() )
350 , mMaximumScreenErrorUnit( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->maximumScreenErrorUnit() )
351 , mPointSize( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointSize() )
352 , mPointSizeUnit( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointSizeUnit() )
353 , mPointSymbol( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointSymbol() )
354 , mPointColor( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointColor() )
355 , mOpacityByDistanceEffect( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->applyOpacityByDistanceEffect() )
356 , mId( layer->id() )
357 , mFeedback( std::make_unique< QgsFeedback >() )
358 , mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
359 , mTolerance( request.tolerance() )
360 , mSourceCrs( layer->crs3D() )
361 , mTargetCrs( request.crs() )
362 , mTransformContext( request.transformContext() )
363 , mZOffset( layer->elevationProperties()->zOffset() )
364 , mZScale( layer->elevationProperties()->zScale() )
365 , mStepDistance( request.stepDistance() )
366{
367 if ( mIndex )
368 {
369 mScale = mIndex.scale();
370 mOffset = mIndex.offset();
371 }
372}
373
375{
376 return mId;
377}
378
383
385
387{
388 mGatheredPoints.clear();
389 if ( !mLayer || !mProfileCurve || mFeedback->isCanceled() )
390 return false;
391
392 QVector<QgsPointCloudIndex> indexes;
393 if ( mIndex && mIndex.isValid() )
394 indexes.append( mIndex );
395
396 // Gather all relevant sub-indexes
397 const QgsRectangle profileCurveBbox = mProfileCurve->boundingBox();
398 for ( const QgsPointCloudSubIndex &subidx : mSubIndexes )
399 {
400 QgsPointCloudIndex index = subidx.index();
401 if ( index && index.isValid() && subidx.polygonBounds().intersects( profileCurveBbox ) )
402 indexes.append( subidx.index() );
403 }
404
405 if ( indexes.empty() )
406 return false;
407
408 const double startDistanceOffset = std::max( !context.distanceRange().isInfinite() ? context.distanceRange().lower() : 0, 0.0 );
409 const double endDistance = context.distanceRange().upper();
410
411 std::unique_ptr< QgsCurve > trimmedCurve;
412 QgsCurve *sourceCurve = nullptr;
413 if ( startDistanceOffset > 0 || endDistance < mProfileCurve->length() )
414 {
415 trimmedCurve.reset( mProfileCurve->curveSubstring( startDistanceOffset, endDistance ) );
416 sourceCurve = trimmedCurve.get();
417 }
418 else
419 {
420 sourceCurve = mProfileCurve.get();
421 }
422
423 // we need to transform the profile curve and max search extent to the layer's CRS
424 QgsGeos originalCurveGeos( sourceCurve );
425 originalCurveGeos.prepareGeometry();
426 mSearchGeometryInLayerCrs.reset( originalCurveGeos.buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) );
427 mLayerToTargetTransform = QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext );
428
429 try
430 {
431 mSearchGeometryInLayerCrs->transform( mLayerToTargetTransform, Qgis::TransformDirection::Reverse );
432 }
433 catch ( QgsCsException & )
434 {
435 QgsDebugError( QStringLiteral( "Error transforming profile line to layer CRS" ) );
436 return false;
437 }
438
439 if ( mFeedback->isCanceled() )
440 return false;
441
442 mSearchGeometryInLayerCrsGeometryEngine = std::make_unique< QgsGeos >( mSearchGeometryInLayerCrs.get() );
443 mSearchGeometryInLayerCrsGeometryEngine->prepareGeometry();
444 mMaxSearchExtentInLayerCrs = mSearchGeometryInLayerCrs->boundingBox();
445
446 double maximumErrorPixels = context.convertDistanceToPixels( mMaximumScreenError, mMaximumScreenErrorUnit );
447 if ( maximumErrorPixels < 0.0 )
448 {
449 QgsDebugError( QStringLiteral( "Invalid maximum error in pixels" ) );
450 return false;
451 }
452
453 const double toleranceInPixels = context.convertDistanceToPixels( mTolerance, Qgis::RenderUnit::MapUnits );
454 // ensure that the maximum error is compatible with the tolerance size -- otherwise if the tolerance size
455 // is much smaller than the maximum error, we don't dig deep enough into the point cloud nodes to find
456 // points which are inside the tolerance.
457 // "4" is a magic number here, based purely on what "looks good" in the profile results!
458 if ( toleranceInPixels / 4 < maximumErrorPixels )
459 maximumErrorPixels = toleranceInPixels / 4;
460
461 QgsPointCloudRequest request;
463 attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "X" ), QgsPointCloudAttribute::Int32 ) );
464 attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Y" ), QgsPointCloudAttribute::Int32 ) );
465 attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Z" ), QgsPointCloudAttribute::Int32 ) );
466
467 if ( mRenderer )
468 {
469 mPreparedRendererData = mRenderer->prepare();
470 if ( mPreparedRendererData )
471 {
472 const QSet< QString > rendererAttributes = mPreparedRendererData->usedAttributes();
473 for ( const QString &attribute : std::as_const( rendererAttributes ) )
474 {
475 if ( attributes.indexOf( attribute ) >= 0 )
476 continue; // don't re-add attributes we are already going to fetch
477
478 const int layerIndex = mLayerAttributes.indexOf( attribute );
479 if ( layerIndex < 0 )
480 {
481 QgsMessageLog::logMessage( QObject::tr( "Required attribute %1 not found in layer" ).arg( attribute ), QObject::tr( "Point Cloud" ) );
482 continue;
483 }
484
485 attributes.push_back( mLayerAttributes.at( layerIndex ) );
486 }
487 }
488 }
489 else
490 {
491 mPreparedRendererData.reset();
492 }
493
494 request.setAttributes( attributes );
495
496 mResults = std::make_unique< QgsPointCloudLayerProfileResults >();
497 mResults->copyPropertiesFromGenerator( this );
498 mResults->mMaxErrorInLayerCoordinates = 0;
499
500 QgsCoordinateTransform extentTransform = mLayerToTargetTransform;
501 extentTransform.setBallparkTransformsAreAppropriate( true );
502
503 const double mapUnitsPerPixel = context.mapUnitsPerDistancePixel();
504 if ( mapUnitsPerPixel < 0.0 )
505 {
506 QgsDebugError( QStringLiteral( "Invalid map units per pixel ratio" ) );
507 return false;
508 }
509
510 for ( QgsPointCloudIndex pc : std::as_const( indexes ) )
511 {
512 const QgsPointCloudNode root = pc.getNode( pc.root() );
513 const QgsRectangle rootNodeExtentLayerCoords = root.bounds().toRectangle();
514 QgsRectangle rootNodeExtentInCurveCrs;
515 try
516 {
517 rootNodeExtentInCurveCrs = extentTransform.transformBoundingBox( rootNodeExtentLayerCoords );
518 }
519 catch ( QgsCsException & )
520 {
521 QgsDebugError( QStringLiteral( "Could not transform node extent to curve CRS" ) );
522 rootNodeExtentInCurveCrs = rootNodeExtentLayerCoords;
523 }
524
525 const double rootErrorInMapCoordinates = rootNodeExtentInCurveCrs.width() / pc.span(); // in curve coords
526 if ( rootErrorInMapCoordinates < 0.0 )
527 {
528 QgsDebugError( QStringLiteral( "Invalid root node error" ) );
529 return false;
530 }
531
532 double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels
533 const QVector<QgsPointCloudNodeId> nodes = traverseTree( pc, pc.root(), maximumErrorPixels, rootErrorPixels, context.elevationRange() );
534
535 if ( nodes.empty() )
536 {
537 return false;
538 }
539
540 const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / pc.span();
541 const double maxErrorInMapCoordinates = maximumErrorPixels * mapUnitsPerPixel;
542
543 mResults->mMaxErrorInLayerCoordinates = std::max(
544 mResults->mMaxErrorInLayerCoordinates,
545 maxErrorInMapCoordinates * rootErrorInLayerCoordinates / rootErrorInMapCoordinates );
546
547 switch ( pc.accessType() )
548 {
550 {
551 visitNodesSync( nodes, pc, request, context.elevationRange() );
552 break;
553 }
555 {
556 visitNodesAsync( nodes, pc, request, context.elevationRange() );
557 break;
558 }
559 }
560
561 if ( mFeedback->isCanceled() )
562 return false;
563 }
564
565 if ( mGatheredPoints.empty() )
566 {
567 mResults = nullptr;
568 return false;
569 }
570
571 // convert x/y values back to distance/height values
572
573 QString lastError;
574 const QgsPointCloudLayerProfileResults::PointResult *pointData = mGatheredPoints.constData();
575 const int size = mGatheredPoints.size();
576 mResults->results.resize( size );
577 QgsPointCloudLayerProfileResults::PointResult *destData = mResults->results.data();
578 for ( int i = 0; i < size; ++i, ++pointData, ++destData )
579 {
580 if ( mFeedback->isCanceled() )
581 return false;
582
583 *destData = *pointData;
584 destData->distanceAlongCurve = startDistanceOffset + originalCurveGeos.lineLocatePoint( destData->x, destData->y, &lastError );
585 if ( mOpacityByDistanceEffect ) // don't calculate this if we don't need it
586 destData->distanceFromCurve = originalCurveGeos.distance( destData->x, destData->y );
587
588 mResults->minZ = std::min( destData->z, mResults->minZ );
589 mResults->maxZ = std::max( destData->z, mResults->maxZ );
590 }
591 mResults->finalize( mFeedback.get() );
592
593 return true;
594}
595
600
602{
603 return mFeedback.get();
604}
605
606QVector<QgsPointCloudNodeId> QgsPointCloudLayerProfileGenerator::traverseTree( const QgsPointCloudIndex &pc, QgsPointCloudNodeId n, double maxErrorPixels, double nodeErrorPixels, const QgsDoubleRange &zRange )
607{
608 QVector<QgsPointCloudNodeId> nodes;
609
610 if ( mFeedback->isCanceled() )
611 {
612 return nodes;
613 }
614
615 QgsPointCloudNode node = pc.getNode( n );
616 QgsBox3D nodeBounds = node.bounds();
617 const QgsDoubleRange nodeZRange( nodeBounds.zMinimum(), nodeBounds.zMaximum() );
618 if ( !zRange.isInfinite() && !zRange.overlaps( nodeZRange ) )
619 return nodes;
620
621 if ( !mMaxSearchExtentInLayerCrs.intersects( nodeBounds.toRectangle() ) )
622 return nodes;
623
624 const QgsGeometry nodeMapGeometry = QgsGeometry::fromRect( nodeBounds.toRectangle() );
625 if ( !mSearchGeometryInLayerCrsGeometryEngine->intersects( nodeMapGeometry.constGet() ) )
626 return nodes;
627
628 if ( node.pointCount() > 0 )
629 nodes.append( n );
630
631 double childrenErrorPixels = nodeErrorPixels / 2.0;
632 if ( childrenErrorPixels < maxErrorPixels )
633 return nodes;
634
635 for ( const QgsPointCloudNodeId &nn : node.children() )
636 {
637 nodes += traverseTree( pc, nn, maxErrorPixels, childrenErrorPixels, zRange );
638 }
639
640 return nodes;
641}
642
643int QgsPointCloudLayerProfileGenerator::visitNodesSync( const QVector<QgsPointCloudNodeId> &nodes, QgsPointCloudIndex &pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange )
644{
645 int nodesDrawn = 0;
646 for ( const QgsPointCloudNodeId &n : nodes )
647 {
648 if ( mFeedback->isCanceled() )
649 break;
650
651 std::unique_ptr<QgsPointCloudBlock> block( pc.nodeData( n, request ) );
652
653 if ( !block )
654 continue;
655
656 visitBlock( block.get(), zRange );
657
658 ++nodesDrawn;
659 }
660 return nodesDrawn;
661}
662
663int QgsPointCloudLayerProfileGenerator::visitNodesAsync( const QVector<QgsPointCloudNodeId> &nodes, QgsPointCloudIndex &pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange )
664{
665 if ( nodes.isEmpty() )
666 return 0;
667
668 int nodesDrawn = 0;
669
670 // see notes about this logic in QgsPointCloudLayerRenderer::renderNodesAsync
671
672 // Async loading of nodes
673 QVector<QgsPointCloudBlockRequest *> blockRequests;
674 QEventLoop loop;
675 QObject::connect( mFeedback.get(), &QgsFeedback::canceled, &loop, &QEventLoop::quit );
676
677 for ( int i = 0; i < nodes.size(); ++i )
678 {
679 const QgsPointCloudNodeId &n = nodes[i];
680 const QString nStr = n.toString();
681 QgsPointCloudBlockRequest *blockRequest = pc.asyncNodeData( n, request );
682 blockRequests.append( blockRequest );
683 QObject::connect( blockRequest, &QgsPointCloudBlockRequest::finished, &loop,
684 [ this, &nodesDrawn, &loop, &blockRequests, &zRange, nStr, blockRequest ]()
685 {
686 blockRequests.removeOne( blockRequest );
687
688 // If all blocks are loaded, exit the event loop
689 if ( blockRequests.isEmpty() )
690 loop.exit();
691
692 std::unique_ptr<QgsPointCloudBlock> block = blockRequest->takeBlock();
693
694 blockRequest->deleteLater();
695
696 if ( mFeedback->isCanceled() )
697 {
698 return;
699 }
700
701 if ( !block )
702 {
703 QgsDebugError( QStringLiteral( "Unable to load node %1, error: %2" ).arg( nStr, blockRequest->errorStr() ) );
704 return;
705 }
706
707 visitBlock( block.get(), zRange );
708 ++nodesDrawn;
709 } );
710 }
711
712 // Wait for all point cloud nodes to finish loading
713 if ( !blockRequests.isEmpty() )
714 loop.exec();
715
716 // Generation may have got canceled and the event loop exited before finished()
717 // was called for all blocks, so let's clean up anything that is left
718 for ( QgsPointCloudBlockRequest *blockRequest : std::as_const( blockRequests ) )
719 {
720 std::unique_ptr<QgsPointCloudBlock> block = blockRequest->takeBlock();
721 block.reset();
722 blockRequest->deleteLater();
723 }
724
725 return nodesDrawn;
726}
727
728void QgsPointCloudLayerProfileGenerator::visitBlock( const QgsPointCloudBlock *block, const QgsDoubleRange &zRange )
729{
730 const char *ptr = block->data();
731 int count = block->pointCount();
732
733 const QgsPointCloudAttributeCollection request = block->attributes();
734
735 const std::size_t recordSize = request.pointRecordSize();
736
737 const QgsPointCloudAttributeCollection blockAttributes = block->attributes();
738 int xOffset = 0, yOffset = 0, zOffset = 0;
739 const QgsPointCloudAttribute::DataType xType = blockAttributes.find( QStringLiteral( "X" ), xOffset )->type();
740 const QgsPointCloudAttribute::DataType yType = blockAttributes.find( QStringLiteral( "Y" ), yOffset )->type();
741 const QgsPointCloudAttribute::DataType zType = blockAttributes.find( QStringLiteral( "Z" ), zOffset )->type();
742
743 bool useRenderer = false;
744 if ( mPreparedRendererData )
745 {
746 useRenderer = mPreparedRendererData->prepareBlock( block );
747 }
748
749 QColor color;
750 const bool reproject = !mLayerToTargetTransform.isShortCircuited();
751 for ( int i = 0; i < count; ++i )
752 {
753 if ( mFeedback->isCanceled() )
754 {
755 break;
756 }
757
758 QgsPointCloudLayerProfileResults::PointResult res;
759 QgsPointCloudAttribute::getPointXYZ( ptr, i, recordSize, xOffset, xType, yOffset, yType, zOffset, zType, block->scale(), block->offset(), res.x, res.y, res.z );
760
761 res.z = res.z * mZScale + mZOffset;
762 if ( !zRange.contains( res.z ) )
763 continue;
764
765 if ( useRenderer )
766 {
767 color = mPreparedRendererData->pointColor( block, i, res.z );
768 if ( !color.isValid() )
769 continue;
770
771 res.color = color.rgba();
772 }
773 else
774 {
775 res.color = mPointColor.rgba();
776 }
777
778 if ( mSearchGeometryInLayerCrsGeometryEngine->contains( res.x, res.y ) )
779 {
780 if ( reproject )
781 {
782 try
783 {
784 mLayerToTargetTransform.transformInPlace( res.x, res.y, res.z );
785 }
786 catch ( QgsCsException & )
787 {
788 continue;
789 }
790 }
791
792 mGatheredPoints.append( res );
793 }
794 }
795}
796
797
@ RespectsMaximumErrorMapUnit
Generated profile respects the QgsProfileGenerationContext::maximumErrorMapUnits() property.
Definition qgis.h:4219
@ RespectsDistanceRange
Generated profile respects the QgsProfileGenerationContext::distanceRange() property.
Definition qgis.h:4220
@ Circle
Renders points as circles.
Definition qgis.h:4248
@ Square
Renders points as squares.
Definition qgis.h:4247
QFlags< ProfileGeneratorFlag > ProfileGeneratorFlags
Definition qgis.h:4224
@ Round
Use rounded joins.
Definition qgis.h:2122
@ MapUnits
Map units.
Definition qgis.h:5185
@ Flat
Flat cap (in line with start/end of line).
Definition qgis.h:2110
@ Local
Local means the source is a local file on the machine.
Definition qgis.h:6075
@ Remote
Remote means it's loaded through a protocol like HTTP.
Definition qgis.h:6076
ProfileExportType
Types of export for elevation profiles.
Definition qgis.h:4233
@ Profile2D
Export profiles as 2D profile lines, with elevation stored in exported geometry Y dimension and dista...
Definition qgis.h:4235
@ Features3D
Export profiles as 3D features, with elevation values stored in exported geometry Z values.
Definition qgis.h:4234
@ DistanceVsElevationTable
Export profiles as a table of sampled distance vs elevation values.
Definition qgis.h:4236
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2673
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:258
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
Definition qgsbox3d.h:378
double zMinimum() const
Returns the minimum z value.
Definition qgsbox3d.h:251
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:233
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition qgsrange.h:287
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:53
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:2093
double distance(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Calculates the distance between this and geom.
Definition qgsgeos.cpp:584
void prepareGeometry() override
Prepares the geometry, so that subsequent calls to spatial relation methods are much faster.
Definition qgsgeos.cpp:292
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:3185
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:49
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:116
bool overlaps(const QgsRange< T > &other) const
Returns true if this range overlaps another range.
Definition qgsrange.h:176
bool contains(const QgsRange< T > &other) const
Returns true if this range contains another range.
Definition qgsrange.h:137
T lower() const
Returns the lower bound of the range.
Definition qgsrange.h:78
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:85
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:57
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