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