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