QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgspointcloudstatscalculator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointcloudstatscalculator.cpp
3 --------------------
4 begin : April 2022
5 copyright : (C) 2022 by Belgacem Nedjima
6 email : belgacem dot nedjima 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 ***************************************************************************/
17
19
21
22#include "qgspointcloudindex.h"
23#include "qgsmessagelog.h"
26
28
29#include "qgsapplication.h"
31#include "qgsfeedback.h"
33
34#include <QQueue>
35#include <QtConcurrent/QtConcurrentMap>
36
38{
41
42 StatsProcessor( QgsPointCloudIndex *index, QgsPointCloudRequest request, QgsFeedback *feedback, double progressValue )
43 : mIndex( index->clone().release() ), mRequest( request ), mFeedback( feedback ), mProgressValue( progressValue )
44 {
45 }
46
47 StatsProcessor( const StatsProcessor &processor )
48 : mIndex( processor.mIndex->clone().release() ), mRequest( processor.mRequest ), mFeedback( processor.mFeedback ), mProgressValue( processor.mProgressValue )
49 {
50 }
51
53 {
54 mIndex.reset( rhs.mIndex->clone().release() );
55 mRequest = rhs.mRequest;
56 mFeedback = rhs.mFeedback;
57 mProgressValue = rhs.mProgressValue;
58 return *this;
59 }
60
62 {
63 std::unique_ptr<QgsPointCloudBlock> block = nullptr;
64 if ( mIndex->accessType() == QgsPointCloudIndex::Local )
65 {
66 block.reset( mIndex->nodeData( node, mRequest ) );
67 }
68 else
69 {
70 QgsPointCloudBlockRequest *request = mIndex->asyncNodeData( node, mRequest );
71 QEventLoop loop;
72 QObject::connect( request, &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
73 QObject::connect( mFeedback, &QgsFeedback::canceled, &loop, &QEventLoop::quit );
74 loop.exec();
75 if ( !mFeedback->isCanceled() )
76 block.reset( request->block() );
77 if ( !request->block() )
78 {
79 QgsMessageLog::logMessage( QObject::tr( "Unable to calculate statistics for node %1, error: \"%2\"" ).arg( node.toString() ).arg( request->errorStr() ) );
80 }
81 }
82
83 if ( !block.get() )
84 {
85 updateFeedback();
87 }
88
89 const QgsPointCloudAttributeCollection attributesCollection = block->attributes();
90 const QVector<QgsPointCloudAttribute> attributes = attributesCollection.attributes();
91 const char *ptr = block->data();
92 int count = block->pointCount();
93 int recordSize = attributesCollection.pointRecordSize();
94
95 QMap<QString, QgsPointCloudAttributeStatistics> statsMap;
96 for ( const QgsPointCloudAttribute &attribute : attributes )
97 {
99 summary.minimum = std::numeric_limits<double>::max();
100 summary.maximum = std::numeric_limits<double>::lowest();
101 summary.count = 0;
102 summary.mean = 0;
103 summary.stDev = std::numeric_limits<double>::quiet_NaN();
104 summary.classCount.clear();
105 statsMap[ attribute.name() ] = summary;
106 }
107
108 QVector<int> attributeOffsetVector;
109 QSet<int> classifiableAttributesOffsetSet;
110 for ( const QgsPointCloudAttribute &attribute : attributes )
111 {
112 int attributeOffset = 0;
113 attributesCollection.find( attribute.name(), attributeOffset );
114 attributeOffsetVector.push_back( attributeOffset );
115 if ( attribute.name() == QLatin1String( "ScannerChannel" ) ||
116 attribute.name() == QLatin1String( "ReturnNumber" ) ||
117 attribute.name() == QLatin1String( "NumberOfReturns" ) ||
118 attribute.name() == QLatin1String( "ScanDirectionFlag" ) ||
119 attribute.name() == QLatin1String( "Classification" ) ||
120 attribute.name() == QLatin1String( "EdgeOfFlightLine" ) ||
121 attribute.name() == QLatin1String( "PointSourceId" ) )
122 {
123 classifiableAttributesOffsetSet.insert( attributeOffset );
124 }
125 }
126
127 for ( int i = 0; i < count; ++i )
128 {
129 for ( int j = 0; j < attributes.size(); ++j )
130 {
131 if ( mFeedback->isCanceled() )
132 {
134 }
135 QString attributeName = attributes.at( j ).name();
136 QgsPointCloudAttribute::DataType attributeType = attributes.at( j ).type();
137
138 double attributeValue = 0;
139 int attributeOffset = attributeOffsetVector[ j ];
140
141 QgsPointCloudAttributeStatistics &stats = statsMap[ attributeName ];
142 QgsPointCloudRenderContext::getAttribute( ptr, i * recordSize + attributeOffset, attributeType, attributeValue );
143 stats.minimum = std::min( stats.minimum, attributeValue );
144 stats.maximum = std::max( stats.maximum, attributeValue );
145 stats.mean += attributeValue / count;
146 // TODO: add stDev calculation
147 stats.count++;
148 if ( classifiableAttributesOffsetSet.contains( attributeOffset ) )
149 {
150 stats.classCount[( int )attributeValue ]++;
151 }
152 }
153 }
154 updateFeedback();
155 return QgsPointCloudStatistics( count, statsMap );
156 }
157 private:
158 std::unique_ptr<QgsPointCloudIndex> mIndex = nullptr;
159 QgsPointCloudRequest mRequest;
160 QgsFeedback *mFeedback = nullptr;
161 double mProgressValue = 0.0;
162
163 void updateFeedback()
164 {
165 QMutexLocker locker( &sStatsProcessorFeedbackMutex );
166 mFeedback->setProgress( mFeedback->progress() + mProgressValue );
167 }
168};
169
171
173 : mIndex( index->clone() )
174{
175
176}
177
178bool QgsPointCloudStatsCalculator::calculateStats( QgsFeedback *feedback, const QVector<QgsPointCloudAttribute> &attributes, qint64 pointsLimit )
179{
180 if ( !mIndex->isValid() )
181 {
182 QgsMessageLog::logMessage( QObject::tr( "Unable to calculate statistics of an invalid index" ) );
183 return false;
184 }
185 mRequest.setAttributes( attributes );
186
187 qint64 pointCount = 0;
188 QVector<IndexedPointCloudNode> nodes;
189 QQueue<IndexedPointCloudNode> queue;
190 queue.push_back( mIndex->root() );
191 while ( !queue.empty() )
192 {
193 IndexedPointCloudNode node = queue.front();
194 queue.pop_front();
195 if ( !mProcessedNodes.contains( node ) )
196 pointCount += mIndex->nodePointCount( node );
197 if ( pointsLimit != -1 && pointCount > pointsLimit )
198 break;
199 if ( !mProcessedNodes.contains( node ) )
200 {
201 nodes.push_back( node );
202 mProcessedNodes.insert( node );
203 }
204 for ( const IndexedPointCloudNode &child : mIndex->nodeChildren( node ) )
205 {
206 queue.push_back( child );
207 }
208 }
209
210 feedback->setProgress( 0 );
211
212 QVector<QgsPointCloudStatistics> list = QtConcurrent::blockingMapped( nodes, StatsProcessor( mIndex.get(), mRequest, feedback, 100.0 / ( double )nodes.size() ) );
213
214 for ( QgsPointCloudStatistics &s : list )
215 {
216 mStats.combineWith( s );
217 }
218 return !feedback->isCanceled() && mStats.sampledPointsCount() != 0;
219}
Represents a indexed point cloud node in octree.
QString toString() const
Encode node to string.
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.
double progress() const SIP_HOLDGIL
Returns the current progress reported by the feedback object.
Definition: qgsfeedback.h:80
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
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.
int pointRecordSize() const
Returns total size of record.
const QgsPointCloudAttribute * find(const QString &attributeName, int &offset) const
Finds the attribute with the name.
QVector< QgsPointCloudAttribute > attributes() const
Returns all attributes.
Attribute for point cloud data pair of name and size in bytes.
DataType
Systems of unit measurement.
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.
Represents a indexed point clouds data in octree.
@ Local
Local means the source is a local file on the machine.
static void getAttribute(const char *data, std::size_t offset, QgsPointCloudAttribute::DataType type, T &value)
Retrieves the attribute value from data at the specified offset, where type indicates the original da...
Point cloud data request.
void setAttributes(const QgsPointCloudAttributeCollection &attributes)
Set attributes filter in the request.
Class used to store statistics of a point cloud dataset.
void combineWith(const QgsPointCloudStatistics &stats)
Merges the current statistics with the statistics from stats.
int sampledPointsCount() const
Returns the number of points used to calculate the statistics.
QgsPointCloudStatsCalculator(QgsPointCloudIndex *index)
Constructor.
bool calculateStats(QgsFeedback *feedback, const QVector< QgsPointCloudAttribute > &attributes, qint64 pointsLimit=-1)
Calculates the statistics of given attributes attributes up to new pointsLimit points Note: the alrea...
Class used to store statistics of one attribute of a point cloud dataset.
StatsProcessor(QgsPointCloudIndex *index, QgsPointCloudRequest request, QgsFeedback *feedback, double progressValue)
static QMutex sStatsProcessorFeedbackMutex
StatsProcessor(const StatsProcessor &processor)
QgsPointCloudStatistics result_type
StatsProcessor & operator=(const StatsProcessor &rhs)
QgsPointCloudStatistics operator()(IndexedPointCloudNode node)