QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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
108{
109 return QgsDoubleRange( minZ, maxZ );
110}
111
113{
114 QPainter *painter = context.renderContext().painter();
115 if ( !painter )
116 return;
117
118 const QgsScopedQPainterState painterState( painter );
119
120 painter->setBrush( Qt::NoBrush );
121 painter->setPen( Qt::NoPen );
122
123 switch ( pointSymbol )
124 {
126 // for square point we always disable antialiasing -- it's not critical here and we benefit from the performance boost disabling it gives
127 context.renderContext().painter()->setRenderHint( QPainter::Antialiasing, false );
128 break;
129
131 break;
132 }
133
134 const double minDistance = context.distanceRange().lower();
135 const double maxDistance = context.distanceRange().upper();
136 const double minZ = context.elevationRange().lower();
137 const double maxZ = context.elevationRange().upper();
138
139 const QRectF visibleRegion( minDistance, minZ, maxDistance - minDistance, maxZ - minZ );
140 QPainterPath clipPath;
141 clipPath.addPolygon( context.worldTransform().map( visibleRegion ) );
142 painter->setClipPath( clipPath, Qt::ClipOperation::IntersectClip );
143
144 const double penWidth = context.renderContext().convertToPainterUnits( pointSize, pointSizeUnit );
145
146 for ( const PointResult &point : std::as_const( results ) )
147 {
148 QPointF p = context.worldTransform().map( QPointF( point.distanceAlongCurve, point.z ) );
149 QColor color = respectLayerColors ? point.color : pointColor;
151 color.setAlphaF( color.alphaF() * ( 1.0 - std::pow( point.distanceFromCurve / tolerance, 0.5 ) ) );
152
153 switch ( pointSymbol )
154 {
156 painter->fillRect( QRectF( p.x() - penWidth * 0.5,
157 p.y() - penWidth * 0.5,
158 penWidth, penWidth ), color );
159 break;
160
162 painter->setBrush( QBrush( color ) );
163 painter->setPen( Qt::NoPen );
164 painter->drawEllipse( QRectF( p.x() - penWidth * 0.5,
165 p.y() - penWidth * 0.5,
166 penWidth, penWidth ) );
167 break;
168 }
169 }
170}
171
173{
174 QList< const QgsPointCloudLayerProfileResults::PointResult * > *list;
175};
176void _GEOSQueryCallback( void *item, void *userdata )
177{
178 reinterpret_cast<_GEOSQueryCallbackData *>( userdata )->list->append( reinterpret_cast<const QgsPointCloudLayerProfileResults::PointResult *>( item ) );
179}
180
182{
184
185 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
186
187 const double minDistance = point.distance() - context.maximumPointDistanceDelta;
188 const double maxDistance = point.distance() + context.maximumPointDistanceDelta;
189 const double minElevation = point.elevation() - context.maximumPointElevationDelta;
190 const double maxElevation = point.elevation() + context.maximumPointElevationDelta;
191
192 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
193 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, minDistance, minElevation );
194 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, maxDistance, maxElevation );
195 geos::unique_ptr searchDiagonal( GEOSGeom_createLineString_r( geosctxt, coord ) );
196
197 QList<const PointResult *> items;
198 struct _GEOSQueryCallbackData callbackData;
199 callbackData.list = &items;
200 GEOSSTRtree_query_r( geosctxt, mPointIndex, searchDiagonal.get(), _GEOSQueryCallback, &callbackData );
201 if ( items.empty() )
202 return result;
203
204 double bestMatchDistance = std::numeric_limits< double >::max();
205 const PointResult *bestMatch = nullptr;
206 for ( const PointResult *candidate : std::as_const( items ) )
207 {
208 const double distance = std::sqrt( std::pow( candidate->distanceAlongCurve - point.distance(), 2 )
209 + std::pow( ( candidate->z - point.elevation() ) / context.displayRatioElevationVsDistance, 2 ) );
210 if ( distance < bestMatchDistance )
211 {
212 bestMatchDistance = distance;
213 bestMatch = candidate;
214 }
215 }
216 if ( !bestMatch )
217 return result;
218
219 result.snappedPoint = QgsProfilePoint( bestMatch->distanceAlongCurve, bestMatch->z );
220 return result;
221}
222
223QVector<QgsProfileIdentifyResults> QgsPointCloudLayerProfileResults::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
224{
225 return identify( QgsDoubleRange( point.distance() - context.maximumPointDistanceDelta, point.distance() + context.maximumPointDistanceDelta ),
226 QgsDoubleRange( point.elevation() - context.maximumPointElevationDelta, point.elevation() + context.maximumPointElevationDelta ), context );
227}
228
229QVector<QgsProfileIdentifyResults> QgsPointCloudLayerProfileResults::identify( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const QgsProfileIdentifyContext &context )
230{
231 if ( !mLayer )
232 return {};
233
234 std::unique_ptr< QgsCurve > substring( mProfileCurve->curveSubstring( distanceRange.lower(), distanceRange.upper() ) );
235 QgsGeos substringGeos( substring.get() );
236 std::unique_ptr< QgsAbstractGeometry > searchGeometry( substringGeos.buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) );
237 if ( !searchGeometry )
238 return {};
239
240 const QgsCoordinateTransform curveToLayerTransform = QgsCoordinateTransform( mCurveCrs, mLayer->crs(), context.project ? context.project->transformContext() : QgsCoordinateTransformContext() );
241 try
242 {
243 searchGeometry->transform( curveToLayerTransform );
244 }
245 catch ( QgsCsException & )
246 {
247 return {};
248 }
249
250 // we have to adjust the elevation range to "undo" the z offset/z scale before we can hand to the provider
251 const QgsDoubleRange providerElevationRange( ( elevationRange.lower() - mZOffset ) / mZScale, ( elevationRange.upper() - mZOffset ) / mZScale );
252
253 const QgsGeometry pointCloudSearchGeometry( std::move( searchGeometry ) );
254 const QVector<QVariantMap> pointAttributes = mLayer->dataProvider()->identify( mMaxErrorInLayerCoordinates, pointCloudSearchGeometry, providerElevationRange );
255 if ( pointAttributes.empty() )
256 return {};
257
258 return { QgsProfileIdentifyResults( mLayer, pointAttributes )};
259}
260
262{
263 const QgsPointCloudLayerProfileGenerator *pcGenerator = qgis::down_cast< const QgsPointCloudLayerProfileGenerator *>( generator );
264 tolerance = pcGenerator->mTolerance;
265 pointSize = pcGenerator->mPointSize;
266 pointSizeUnit = pcGenerator->mPointSizeUnit;
267 pointSymbol = pcGenerator->mPointSymbol;
268 pointColor = pcGenerator->mPointColor;
269 respectLayerColors = static_cast< bool >( pcGenerator->mRenderer );
270 opacityByDistanceEffect = pcGenerator->mOpacityByDistanceEffect;
271
272 mLayer = pcGenerator->mLayer;
273 mCurveCrs = pcGenerator->mTargetCrs;
274 mProfileCurve.reset( pcGenerator->mProfileCurve->clone() );
275 mTolerance = pcGenerator->mTolerance;
276
277 mZOffset = pcGenerator->mZOffset;
278 mZScale = pcGenerator->mZScale;
279}
280
281//
282// QgsPointCloudLayerProfileGenerator
283//
284
286 : mLayer( layer )
287 , mLayerAttributes( layer->attributes() )
288 , mRenderer( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->respectLayerColors() && mLayer->renderer() ? mLayer->renderer()->clone() : nullptr )
289 , mMaximumScreenError( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->maximumScreenError() )
290 , mMaximumScreenErrorUnit( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->maximumScreenErrorUnit() )
291 , mPointSize( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointSize() )
292 , mPointSizeUnit( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointSizeUnit() )
293 , mPointSymbol( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointSymbol() )
294 , mPointColor( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->pointColor() )
295 , mOpacityByDistanceEffect( qgis::down_cast< QgsPointCloudLayerElevationProperties* >( layer->elevationProperties() )->applyOpacityByDistanceEffect() )
296 , mId( layer->id() )
297 , mFeedback( std::make_unique< QgsFeedback >() )
298 , mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
299 , mTolerance( request.tolerance() )
300 , mSourceCrs( layer->crs() )
301 , mTargetCrs( request.crs() )
302 , mTransformContext( request.transformContext() )
303 , mZOffset( layer->elevationProperties()->zOffset() )
304 , mZScale( layer->elevationProperties()->zScale() )
305 , mStepDistance( request.stepDistance() )
306{
307 if ( mLayer->dataProvider()->index() )
308 {
309 mScale = mLayer->dataProvider()->index()->scale();
310 mOffset = mLayer->dataProvider()->index()->offset();
311 }
312}
313
315{
316 return mId;
317}
318
319Qgis::ProfileGeneratorFlags QgsPointCloudLayerProfileGenerator::flags() const
320{
322}
323
325
327{
328 mGatheredPoints.clear();
329 if ( !mLayer || !mProfileCurve || mFeedback->isCanceled() )
330 return false;
331
332 // this is not AT ALL thread safe, but it's what QgsPointCloudLayerRenderer does !
333 // TODO: fix when QgsPointCloudLayerRenderer is made thread safe to use same approach
334
335 QgsPointCloudIndex *pc = mLayer->dataProvider()->index();
336 if ( !pc || !pc->isValid() )
337 {
338 return false;
339 }
340
341 const double startDistanceOffset = std::max( !context.distanceRange().isInfinite() ? context.distanceRange().lower() : 0, 0.0 );
342 const double endDistance = context.distanceRange().upper();
343
344 std::unique_ptr< QgsCurve > trimmedCurve;
345 QgsCurve *sourceCurve = nullptr;
346 if ( startDistanceOffset > 0 || endDistance < mProfileCurve->length() )
347 {
348 trimmedCurve.reset( mProfileCurve->curveSubstring( startDistanceOffset, endDistance ) );
349 sourceCurve = trimmedCurve.get();
350 }
351 else
352 {
353 sourceCurve = mProfileCurve.get();
354 }
355
356 // we need to transform the profile curve and max search extent to the layer's CRS
357 QgsGeos originalCurveGeos( sourceCurve );
358 originalCurveGeos.prepareGeometry();
359 mSearchGeometryInLayerCrs.reset( originalCurveGeos.buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) );
360 mLayerToTargetTransform = QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext );
361
362 try
363 {
364 mSearchGeometryInLayerCrs->transform( mLayerToTargetTransform, Qgis::TransformDirection::Reverse );
365 }
366 catch ( QgsCsException & )
367 {
368 QgsDebugMsg( QStringLiteral( "Error transforming profile line to layer CRS" ) );
369 return false;
370 }
371
372 if ( mFeedback->isCanceled() )
373 return false;
374
375 mSearchGeometryInLayerCrsGeometryEngine = std::make_unique< QgsGeos >( mSearchGeometryInLayerCrs.get() );
376 mSearchGeometryInLayerCrsGeometryEngine->prepareGeometry();
377 mMaxSearchExtentInLayerCrs = mSearchGeometryInLayerCrs->boundingBox();
378
379 const IndexedPointCloudNode root = pc->root();
380
381 double maximumErrorPixels = context.convertDistanceToPixels( mMaximumScreenError, mMaximumScreenErrorUnit );
382 const double toleranceInPixels = context.convertDistanceToPixels( mTolerance, QgsUnitTypes::RenderMapUnits );
383 // ensure that the maximum error is compatible with the tolerance size -- otherwise if the tolerance size
384 // is much smaller than the maximum error, we don't dig deep enough into the point cloud nodes to find
385 // points which are inside the tolerance.
386 // "4" is a magic number here, based purely on what "looks good" in the profile results!
387 if ( toleranceInPixels / 4 < maximumErrorPixels )
388 maximumErrorPixels = toleranceInPixels / 4;
389
390 const QgsRectangle rootNodeExtentLayerCoords = pc->nodeMapExtent( root );
391 QgsRectangle rootNodeExtentInCurveCrs;
392 try
393 {
394 QgsCoordinateTransform extentTransform = mLayerToTargetTransform;
395 extentTransform.setBallparkTransformsAreAppropriate( true );
396 rootNodeExtentInCurveCrs = extentTransform.transformBoundingBox( rootNodeExtentLayerCoords );
397 }
398 catch ( QgsCsException & )
399 {
400 QgsDebugMsg( QStringLiteral( "Could not transform node extent to curve CRS" ) );
401 rootNodeExtentInCurveCrs = rootNodeExtentLayerCoords;
402 }
403
404 const double rootErrorInMapCoordinates = rootNodeExtentInCurveCrs.width() / pc->span(); // in curve coords
405
406 const double mapUnitsPerPixel = context.mapUnitsPerDistancePixel();
407 if ( ( rootErrorInMapCoordinates < 0.0 ) || ( mapUnitsPerPixel < 0.0 ) || ( maximumErrorPixels < 0.0 ) )
408 {
409 QgsDebugMsg( QStringLiteral( "invalid screen error" ) );
410 return false;
411 }
412 double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels
413 const QVector<IndexedPointCloudNode> nodes = traverseTree( pc, pc->root(), maximumErrorPixels, rootErrorPixels, context.elevationRange() );
414
415 const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / pc->span();
416 const double maxErrorInMapCoordinates = maximumErrorPixels * mapUnitsPerPixel;
417
418 mResults = std::make_unique< QgsPointCloudLayerProfileResults >();
419 mResults->copyPropertiesFromGenerator( this );
420 mResults->mMaxErrorInLayerCoordinates = maxErrorInMapCoordinates * rootErrorInLayerCoordinates / rootErrorInMapCoordinates;
421
422 QgsPointCloudRequest request;
424 attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "X" ), QgsPointCloudAttribute::Int32 ) );
425 attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Y" ), QgsPointCloudAttribute::Int32 ) );
426 attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Z" ), QgsPointCloudAttribute::Int32 ) );
427
428 if ( mRenderer )
429 {
430 mPreparedRendererData = mRenderer->prepare();
431 if ( mPreparedRendererData )
432 {
433 const QSet< QString > rendererAttributes = mPreparedRendererData->usedAttributes();
434 for ( const QString &attribute : std::as_const( rendererAttributes ) )
435 {
436 if ( attributes.indexOf( attribute ) >= 0 )
437 continue; // don't re-add attributes we are already going to fetch
438
439 const int layerIndex = mLayerAttributes.indexOf( attribute );
440 if ( layerIndex < 0 )
441 {
442 QgsMessageLog::logMessage( QObject::tr( "Required attribute %1 not found in layer" ).arg( attribute ), QObject::tr( "Point Cloud" ) );
443 continue;
444 }
445
446 attributes.push_back( mLayerAttributes.at( layerIndex ) );
447 }
448 }
449 }
450 else
451 {
452 mPreparedRendererData.reset();
453 }
454
455 request.setAttributes( attributes );
456
457 switch ( pc->accessType() )
458 {
459 case QgsPointCloudIndex::AccessType::Local:
460 {
461 visitNodesSync( nodes, pc, request, context.elevationRange() );
462 break;
463 }
464 case QgsPointCloudIndex::AccessType::Remote:
465 {
466 visitNodesAsync( nodes, pc, request, context.elevationRange() );
467 break;
468 }
469 }
470
471 if ( mFeedback->isCanceled() )
472 return false;
473
474 // convert x/y values back to distance/height values
475
476 QString lastError;
477 const QgsPointCloudLayerProfileResults::PointResult *pointData = mGatheredPoints.constData();
478 const int size = mGatheredPoints.size();
479 mResults->results.resize( size );
480 QgsPointCloudLayerProfileResults::PointResult *destData = mResults->results.data();
481 for ( int i = 0; i < size; ++i, ++pointData, ++destData )
482 {
483 if ( mFeedback->isCanceled() )
484 return false;
485
486 *destData = *pointData;
487 destData->distanceAlongCurve = startDistanceOffset + originalCurveGeos.lineLocatePoint( destData->x, destData->y, &lastError );
488 if ( mOpacityByDistanceEffect ) // don't calculate this if we don't need it
489 destData->distanceFromCurve = originalCurveGeos.distance( destData->x, destData->y );
490
491 mResults->minZ = std::min( destData->z, mResults->minZ );
492 mResults->maxZ = std::max( destData->z, mResults->maxZ );
493 }
494 mResults->finalize( mFeedback.get() );
495
496 return true;
497}
498
500{
501 return mResults.release();
502}
503
505{
506 return mFeedback.get();
507}
508
509QVector<IndexedPointCloudNode> QgsPointCloudLayerProfileGenerator::traverseTree( const QgsPointCloudIndex *pc, IndexedPointCloudNode n, double maxErrorPixels, double nodeErrorPixels, const QgsDoubleRange &zRange )
510{
511 QVector<IndexedPointCloudNode> nodes;
512
513 if ( mFeedback->isCanceled() )
514 {
515 return nodes;
516 }
517
518 const QgsDoubleRange nodeZRange = pc->nodeZRange( n );
519 const QgsDoubleRange adjustedNodeZRange = QgsDoubleRange( nodeZRange.lower() * mZScale + mZOffset, nodeZRange.upper() * mZScale + mZOffset );
520 if ( !zRange.isInfinite() && !zRange.overlaps( adjustedNodeZRange ) )
521 return nodes;
522
523 const QgsRectangle nodeMapExtent = pc->nodeMapExtent( n );
524 if ( !mMaxSearchExtentInLayerCrs.intersects( nodeMapExtent ) )
525 return nodes;
526
527 const QgsGeometry nodeMapGeometry = QgsGeometry::fromRect( nodeMapExtent );
528 if ( !mSearchGeometryInLayerCrsGeometryEngine->intersects( nodeMapGeometry.constGet() ) )
529 return nodes;
530
531 nodes.append( n );
532
533 double childrenErrorPixels = nodeErrorPixels / 2.0;
534 if ( childrenErrorPixels < maxErrorPixels )
535 return nodes;
536
537 const QList<IndexedPointCloudNode> children = pc->nodeChildren( n );
538 for ( const IndexedPointCloudNode &nn : children )
539 {
540 nodes += traverseTree( pc, nn, maxErrorPixels, childrenErrorPixels, zRange );
541 }
542
543 return nodes;
544}
545
546int QgsPointCloudLayerProfileGenerator::visitNodesSync( const QVector<IndexedPointCloudNode> &nodes, QgsPointCloudIndex *pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange )
547{
548 int nodesDrawn = 0;
549 for ( const IndexedPointCloudNode &n : nodes )
550 {
551 if ( mFeedback->isCanceled() )
552 break;
553
554 std::unique_ptr<QgsPointCloudBlock> block( pc->nodeData( n, request ) );
555
556 if ( !block )
557 continue;
558
559 visitBlock( block.get(), zRange );
560
561 ++nodesDrawn;
562 }
563 return nodesDrawn;
564}
565
566int QgsPointCloudLayerProfileGenerator::visitNodesAsync( const QVector<IndexedPointCloudNode> &nodes, QgsPointCloudIndex *pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange )
567{
568 int nodesDrawn = 0;
569
570 // see notes about this logic in QgsPointCloudLayerRenderer::renderNodesAsync
571
572 // Async loading of nodes
573 QVector<QgsPointCloudBlockRequest *> blockRequests;
574 QEventLoop loop;
575 QObject::connect( mFeedback.get(), &QgsFeedback::canceled, &loop, &QEventLoop::quit );
576
577 for ( int i = 0; i < nodes.size(); ++i )
578 {
579 const IndexedPointCloudNode &n = nodes[i];
580 const QString nStr = n.toString();
581 QgsPointCloudBlockRequest *blockRequest = pc->asyncNodeData( n, request );
582 blockRequests.append( blockRequest );
583 QObject::connect( blockRequest, &QgsPointCloudBlockRequest::finished, &loop,
584 [ this, &nodesDrawn, &loop, &blockRequests, &zRange, nStr, blockRequest ]()
585 {
586 blockRequests.removeOne( blockRequest );
587
588 // If all blocks are loaded, exit the event loop
589 if ( blockRequests.isEmpty() )
590 loop.exit();
591
592 std::unique_ptr<QgsPointCloudBlock> block( blockRequest->block() );
593
594 blockRequest->deleteLater();
595
596 if ( mFeedback->isCanceled() )
597 {
598 return;
599 }
600
601 if ( !block )
602 {
603 QgsDebugMsg( QStringLiteral( "Unable to load node %1, error: %2" ).arg( nStr, blockRequest->errorStr() ) );
604 return;
605 }
606
607 visitBlock( block.get(), zRange );
608 ++nodesDrawn;
609 } );
610 }
611
612 // Wait for all point cloud nodes to finish loading
613 loop.exec();
614
615 // Generation may have got canceled and the event loop exited before finished()
616 // was called for all blocks, so let's clean up anything that is left
617 for ( QgsPointCloudBlockRequest *blockRequest : std::as_const( blockRequests ) )
618 {
619 delete blockRequest->block();
620 blockRequest->deleteLater();
621 }
622
623 return nodesDrawn;
624}
625
626void QgsPointCloudLayerProfileGenerator::visitBlock( const QgsPointCloudBlock *block, const QgsDoubleRange &zRange )
627{
628 const char *ptr = block->data();
629 int count = block->pointCount();
630
631 const QgsPointCloudAttributeCollection request = block->attributes();
632
633 const std::size_t recordSize = request.pointRecordSize();
634
635 const QgsPointCloudAttributeCollection blockAttributes = block->attributes();
636 int xOffset = 0, yOffset = 0, zOffset = 0;
637 const QgsPointCloudAttribute::DataType xType = blockAttributes.find( QStringLiteral( "X" ), xOffset )->type();
638 const QgsPointCloudAttribute::DataType yType = blockAttributes.find( QStringLiteral( "Y" ), yOffset )->type();
639 const QgsPointCloudAttribute::DataType zType = blockAttributes.find( QStringLiteral( "Z" ), zOffset )->type();
640
641 bool useRenderer = false;
642 if ( mPreparedRendererData )
643 {
644 useRenderer = mPreparedRendererData->prepareBlock( block );
645 }
646
647 QColor color;
648 const bool reproject = !mLayerToTargetTransform.isShortCircuited();
649 for ( int i = 0; i < count; ++i )
650 {
651 if ( mFeedback->isCanceled() )
652 {
653 break;
654 }
655
657 QgsPointCloudAttribute::getPointXYZ( ptr, i, recordSize, xOffset, xType, yOffset, yType, zOffset, zType, block->scale(), block->offset(), res.x, res.y, res.z );
658
659 res.z = res.z * mZScale + mZOffset;
660 if ( !zRange.contains( res.z ) )
661 continue;
662
663 if ( useRenderer )
664 {
665 color = mPreparedRendererData->pointColor( block, i, res.z );
666 if ( !color.isValid() )
667 continue;
668
669 res.color = color.rgba();
670 }
671 else
672 {
673 res.color = mPointColor.rgba();
674 }
675
676 if ( mSearchGeometryInLayerCrsGeometryEngine->contains( res.x, res.y ) )
677 {
678 if ( reproject )
679 {
680 try
681 {
682 mLayerToTargetTransform.transformInPlace( res.x, res.y, res.z );
683 }
684 catch ( QgsCsException & )
685 {
686 continue;
687 }
688 }
689
690 mGatheredPoints.append( res );
691 }
692 }
693}
694
695
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.
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.
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Abstract base class for curved geometry type.
Definition: qgscurve.h:36
QgsRange which stores a range of double values.
Definition: qgsrange.h:203
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition: qgsrange.h:247
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
void canceled()
Internal routines can connect to this signal if they use event loop.
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromRect(const QgsRectangle &rect) SIP_HOLDGIL
Creates a new geometry from a QgsRectangle.
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition: qgsgeos.h:99
QgsAbstractGeometry * buffer(double distance, int segments, QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:1861
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:3446
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:2810
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.
QgsPointCloudBlock * block()
Returns the requested block.
void finished()
Emitted when the request processing has finished.
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.
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.
virtual QgsPointCloudBlock * nodeData(const IndexedPointCloudNode &n, const QgsPointCloudRequest &request)=0
Returns node data block.
IndexedPointCloudNode root()
Returns root node of the index.
QgsDoubleRange nodeZRange(const IndexedPointCloudNode &node) const
Returns the z range of a node.
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.
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.
QgsDoubleRange distanceRange() const
Returns the range of distances to include in the generation.
double convertDistanceToPixels(double size, QgsUnitTypes::RenderUnit unit) const
Converts a distance size from the specified units to pixels.
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 SIP_HOLDGIL
Returns the elevation of the point.
double distance() const SIP_HOLDGIL
Returns the distance of the point.
Abstract base class for storage of elevation profiles.
const QTransform & worldTransform() const
Returns the transform from world coordinates to painter coordinates.
QgsDoubleRange elevationRange() const
Returns the range of elevations to include in the render.
QgsDoubleRange distanceRange() const
Returns the range of distances to include in the render.
QgsRenderContext & renderContext()
Returns a reference to the component QgsRenderContext.
Encapsulates properties and constraints relating to fetching elevation profiles from different source...
Encapsulates the context of snapping a profile point.
double maximumPointDistanceDelta
Maximum allowed snapping delta for the distance values when snapping to a point.
double 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:110
bool overlaps(const QgsRange< T > &other) const
Returns true if this range overlaps another range.
Definition: qgsrange.h:147
bool contains(const QgsRange< T > &other) const
Returns true if this range contains another range.
Definition: qgsrange.h:108
T lower() const
Returns the lower bound of the range.
Definition: qgsrange.h:66
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:73
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool intersects(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:349
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
QPainter * painter()
Returns the destination QPainter for the render operation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
Scoped object for saving and restoring a QPainter object's state.
@ RenderMapUnits
Map units.
Definition: qgsunittypes.h:170
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:74
QVector< QgsPoint > QgsPointSequence
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void _GEOSQueryCallback(void *item, void *userdata)
const QgsCoordinateReferenceSystem & crs
QList< const QgsPointCloudLayerProfileResults::PointResult * > * list