Skip to content

Commit

Permalink
Add API to retrieve counts for classification classes
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Dec 2, 2020
1 parent 55035a7 commit ff672c4
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 9 deletions.
Expand Up @@ -103,6 +103,44 @@ in the metadata, and even for sources with statistical metadata only some ``stat
}
%End

virtual QVariantList metadataClasses( const QString &attribute ) const;
%Docstring
Returns a list of existing classes which are present for the specified ``attribute``, taken only from the
metadata of the point cloud data source.

This method will not perform any classification or scan for available classes, rather it will return only
precomputed classes which are included in the data source's metadata. Not all data sources include this information
in the metadata.
%End



SIP_PYOBJECT metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const;
%Docstring
Returns a statistic for one class ``value`` from the specified ``attribute``, taken only from the metadata of the point cloud
data source.
This method will not perform any statistical calculations, rather it will return only precomputed class
statistics which are included in the data source's metadata. Not all data sources include this information
in the metadata, and even for sources with statistical metadata only some ``statistic`` values may be available.

:raises ValueError: if no matching precalculated statistic is available for the attribute.
%End
%MethodCode
{
const QVariant res = sipCpp->metadataClassStatistic( *a0, *a1, a2 );
if ( !res.isValid() )
{
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Statistic is not available" ).toUtf8().constData() );
sipIsErr = 1;
}
else
{
QVariant *v = new QVariant( res );
sipRes = sipConvertFromNewType( v, sipType_QVariant, Py_None );
}
}
%End

static QMap< int, QString > lasClassificationCodes();
%Docstring
Returns the map of LAS classification code to untranslated string value, corresponding to the ASPRS Standard
Expand Down
10 changes: 10 additions & 0 deletions src/core/pointcloud/qgspointclouddataprovider.cpp
Expand Up @@ -118,3 +118,13 @@ QVariant QgsPointCloudDataProvider::metadataStatistic( const QString &, QgsStati
{
return QVariant();
}

QVariantList QgsPointCloudDataProvider::metadataClasses( const QString & ) const
{
return QVariantList();
}

QVariant QgsPointCloudDataProvider::metadataClassStatistic( const QString &, const QVariant &, QgsStatisticalSummary::Statistic ) const
{
return QVariant();
}
53 changes: 53 additions & 0 deletions src/core/pointcloud/qgspointclouddataprovider.h
Expand Up @@ -139,6 +139,59 @@ class CORE_EXPORT QgsPointCloudDataProvider: public QgsDataProvider
% End
#endif

/**
* Returns a list of existing classes which are present for the specified \a attribute, taken only from the
* metadata of the point cloud data source.
*
* This method will not perform any classification or scan for available classes, rather it will return only
* precomputed classes which are included in the data source's metadata. Not all data sources include this information
* in the metadata.
*/
virtual QVariantList metadataClasses( const QString &attribute ) const;


#ifndef SIP_RUN

/**
* Returns a statistic for one class \a value from the specified \a attribute, taken only from the metadata of the point cloud
* data source.
*
* This method will not perform any statistical calculations, rather it will return only precomputed class
* statistics which are included in the data source's metadata. Not all data sources include this information
* in the metadata, and even for sources with statistical metadata only some \a statistic values may be available.
*
* If no matching precalculated statistic is available then an invalid variant will be returned.
*/
virtual QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const;
#else

/**
* Returns a statistic for one class \a value from the specified \a attribute, taken only from the metadata of the point cloud
* data source.
* This method will not perform any statistical calculations, rather it will return only precomputed class
* statistics which are included in the data source's metadata. Not all data sources include this information
* in the metadata, and even for sources with statistical metadata only some \a statistic values may be available.
*
* \throws ValueError if no matching precalculated statistic is available for the attribute.
*/
SIP_PYOBJECT metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const;
% MethodCode
{
const QVariant res = sipCpp->metadataClassStatistic( *a0, *a1, a2 );
if ( !res.isValid() )
{
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Statistic is not available" ).toUtf8().constData() );
sipIsErr = 1;
}
else
{
QVariant *v = new QVariant( res );
sipRes = sipConvertFromNewType( v, sipType_QVariant, Py_None );
}
}
% End
#endif

/**
* Returns the map of LAS classification code to untranslated string value, corresponding to the ASPRS Standard
* Lidar Point Classes.
Expand Down
52 changes: 43 additions & 9 deletions src/core/providers/ept/qgseptpointcloudindex.cpp
Expand Up @@ -90,7 +90,7 @@ bool QgsEptPointCloudIndex::load( const QString &fileName )

for ( QJsonValue schemaItem : schemaArray )
{
QJsonObject schemaObj = schemaItem.toObject();
const QJsonObject schemaObj = schemaItem.toObject();
QString name = schemaObj.value( QLatin1String( "name" ) ).toString();
QString type = schemaObj.value( QLatin1String( "type" ) ).toString();

Expand Down Expand Up @@ -127,11 +127,11 @@ bool QgsEptPointCloudIndex::load( const QString &fileName )
}

float scale = 1.f;
if ( schemaObj.contains( "scale" ) )
if ( schemaObj.contains( QLatin1String( "scale" ) ) )
scale = schemaObj.value( QLatin1String( "scale" ) ).toDouble();

float offset = 0.f;
if ( schemaObj.contains( "offset" ) )
if ( schemaObj.contains( QLatin1String( "offset" ) ) )
offset = schemaObj.value( QLatin1String( "offset" ) ).toDouble();

if ( name == QLatin1String( "X" ) )
Expand All @@ -152,19 +152,31 @@ bool QgsEptPointCloudIndex::load( const QString &fileName )

// store any metadata stats which are present for the attribute
AttributeStatistics stats;
if ( schemaObj.contains( "count" ) )
if ( schemaObj.contains( QLatin1String( "count" ) ) )
stats.count = schemaObj.value( QLatin1String( "count" ) ).toInt();
if ( schemaObj.contains( "minimum" ) )
if ( schemaObj.contains( QLatin1String( "minimum" ) ) )
stats.minimum = schemaObj.value( QLatin1String( "minimum" ) ).toDouble();
if ( schemaObj.contains( "maximum" ) )
if ( schemaObj.contains( QLatin1String( "maximum" ) ) )
stats.maximum = schemaObj.value( QLatin1String( "maximum" ) ).toDouble();
if ( schemaObj.contains( "count" ) )
if ( schemaObj.contains( QLatin1String( "count" ) ) )
stats.mean = schemaObj.value( QLatin1String( "mean" ) ).toDouble();
if ( schemaObj.contains( "stddev" ) )
if ( schemaObj.contains( QLatin1String( "stddev" ) ) )
stats.stDev = schemaObj.value( QLatin1String( "stddev" ) ).toDouble();
if ( schemaObj.contains( "variance" ) )
if ( schemaObj.contains( QLatin1String( "variance" ) ) )
stats.variance = schemaObj.value( QLatin1String( "variance" ) ).toDouble();
mMetadataStats.insert( name, stats );

if ( schemaObj.contains( QLatin1String( "counts" ) ) )
{
QMap< int, int > classCounts;
const QJsonArray counts = schemaObj.value( QLatin1String( "counts" ) ).toArray();
for ( const QJsonValue &count : counts )
{
const QJsonObject countObj = count.toObject();
classCounts.insert( countObj.value( QLatin1String( "value" ) ).toInt(), countObj.value( QLatin1String( "count" ) ).toInt() );
}
mAttributeClasses.insert( name, classCounts );
}
}
setAttributes( attributes );

Expand Down Expand Up @@ -275,6 +287,28 @@ QVariant QgsEptPointCloudIndex::metadataStatistic( const QString &attribute, Qgs
return QVariant();
}

QVariantList QgsEptPointCloudIndex::metadataClasses( const QString &attribute ) const
{
QVariantList classes;
const QMap< int, int > values = mAttributeClasses.value( attribute );
for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
{
classes << it.key();
}
return classes;
}

QVariant QgsEptPointCloudIndex::metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const
{
if ( statistic != QgsStatisticalSummary::Count )
return QVariant();

const QMap< int, int > values = mAttributeClasses.value( attribute );
if ( !values.contains( value.toInt() ) )
return QVariant();
return values.value( value.toInt() );
}

bool QgsEptPointCloudIndex::loadHierarchy()
{
QQueue<QString> queue;
Expand Down
4 changes: 4 additions & 0 deletions src/core/providers/ept/qgseptpointcloudindex.h
Expand Up @@ -49,6 +49,8 @@ class QgsEptPointCloudIndex: public QgsPointCloudIndex

QgsCoordinateReferenceSystem crs() const;
QVariant metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const;
QVariantList metadataClasses( const QString &attribute ) const;
QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const;

private:
bool loadHierarchy();
Expand All @@ -68,6 +70,8 @@ class QgsEptPointCloudIndex: public QgsPointCloudIndex
};

QMap< QString, AttributeStatistics > mMetadataStats;

QMap< QString, QMap< int, int > > mAttributeClasses;
};

///@endcond
Expand Down
10 changes: 10 additions & 0 deletions src/core/providers/ept/qgseptprovider.cpp
Expand Up @@ -78,6 +78,16 @@ QgsPointCloudIndex *QgsEptProvider::index() const
return mIndex.get();
}

QVariantList QgsEptProvider::metadataClasses( const QString &attribute ) const
{
return mIndex->metadataClasses( attribute );
}

QVariant QgsEptProvider::metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const
{
return mIndex->metadataClassStatistic( attribute, value, statistic );
}

QVariant QgsEptProvider::metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const
{
return mIndex->metadataStatistic( attribute, statistic );
Expand Down
2 changes: 2 additions & 0 deletions src/core/providers/ept/qgseptprovider.h
Expand Up @@ -53,6 +53,8 @@ class QgsEptProvider: public QgsPointCloudDataProvider

QgsPointCloudIndex *index() const override;
QVariant metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const override;
QVariantList metadataClasses( const QString &attribute ) const override;
QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const override;

private:
std::unique_ptr<QgsEptPointCloudIndex> mIndex;
Expand Down
30 changes: 30 additions & 0 deletions tests/src/python/test_qgspointcloudprovider.py
Expand Up @@ -64,6 +64,36 @@ def testStatistics(self):
self.assertAlmostEqual(layer.dataProvider().metadataStatistic('Intensity', QgsStatisticalSummary.StDev),
440.9652417017358, 5)

@unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
def testMetadataClasses(self):
layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/sunshine-coast/ept.json', 'test', 'ept')
self.assertTrue(layer.isValid())

self.assertEqual(layer.dataProvider().metadataClasses('X'), [])
self.assertCountEqual(layer.dataProvider().metadataClasses('Classification'), [1, 2, 3, 5])

@unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
def testMetadataClassStatistics(self):
layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/sunshine-coast/ept.json', 'test', 'ept')
self.assertTrue(layer.isValid())

with self.assertRaises(ValueError):
self.assertEqual(layer.dataProvider().metadataClassStatistic('X', 0, QgsStatisticalSummary.Count), [])

with self.assertRaises(ValueError):
self.assertEqual(layer.dataProvider().metadataClassStatistic('Classification', 0, QgsStatisticalSummary.Count), [])

with self.assertRaises(ValueError):
self.assertEqual(layer.dataProvider().metadataClassStatistic('Classification', 1, QgsStatisticalSummary.Sum), [])

self.assertEqual(layer.dataProvider().metadataClassStatistic('Classification', 1, QgsStatisticalSummary.Count), 1)
self.assertEqual(layer.dataProvider().metadataClassStatistic('Classification', 2, QgsStatisticalSummary.Count),
160)
self.assertEqual(layer.dataProvider().metadataClassStatistic('Classification', 3, QgsStatisticalSummary.Count),
89)
self.assertEqual(layer.dataProvider().metadataClassStatistic('Classification', 5, QgsStatisticalSummary.Count),
3)


if __name__ == '__main__':
unittest.main()

0 comments on commit ff672c4

Please sign in to comment.