Hide classificationFlags attribute and expose distinct Synthetic, Keypoint, Withheld and Overlap attributes (#55299)

* Hide classificationFlags attribute and expose its bits as
new attributes: Synthetic, Keypoint, Withheld, Overlap.

* fix tests

* vpc uses classFlags dimension name, not classificationFlags

* replace classFlags in ept files too

* Claculate stats for the new attributes

* Populate renderer widget with classes for the new attributes

* update tests

* make ClassFlag attributes uchar
Synthetic, Withheld, KeyPoint and Overlap also exist in legacy PDRFs
Show real classification value for legacy PDRFs when ClassFlags are set
Adjust tests

* add tests

* fix test
This commit is contained in:
Stefanos Natsis 2023-12-05 14:56:05 +02:00 committed by GitHub
parent 7dd83dd9e7
commit 6281d0140f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 378 additions and 39 deletions

View File

@ -166,7 +166,14 @@ bool QgsEptPointCloudIndex::loadSchema( const QByteArray &dataJson )
const int size = schemaObj.value( QLatin1String( "size" ) ).toInt();
if ( type == QLatin1String( "float" ) && ( size == 4 ) )
if ( name == QLatin1String( "ClassFlags" ) && size == 1 )
{
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Synthetic" ), QgsPointCloudAttribute::UChar ) );
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "KeyPoint" ), QgsPointCloudAttribute::UChar ) );
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Withheld" ), QgsPointCloudAttribute::UChar ) );
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Overlap" ), QgsPointCloudAttribute::UChar ) );
}
else if ( type == QLatin1String( "float" ) && ( size == 4 ) )
{
attributes.push_back( QgsPointCloudAttribute( name, QgsPointCloudAttribute::Float ) );
}

View File

@ -192,6 +192,7 @@ std::vector< QgsLazDecoder::RequestedAttributeDetails > prepareRequestedAttribut
std::vector< QgsLazDecoder::RequestedAttributeDetails > requestedAttributeDetails;
requestedAttributeDetails.reserve( requestedAttributesVector.size() );
for ( const QgsPointCloudAttribute &requestedAttribute : requestedAttributesVector )
{
if ( requestedAttribute.name().compare( QLatin1String( "X" ), Qt::CaseInsensitive ) == 0 )
@ -262,9 +263,21 @@ std::vector< QgsLazDecoder::RequestedAttributeDetails > prepareRequestedAttribut
{
requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ScannerChannel, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "ClassificationFlags" ), Qt::CaseInsensitive ) == 0 )
else if ( requestedAttribute.name().compare( QLatin1String( "Synthetic" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::ClassificationFlags, requestedAttribute.type(), requestedAttribute.size() ) );
requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Synthetic, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "KeyPoint" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::KeyPoint, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "Withheld" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Withheld, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "Overlap" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( QgsLazDecoder::RequestedAttributeDetails( QgsLazDecoder::LazAttribute::Overlap, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "Infrared" ), Qt::CaseInsensitive ) == 0 )
{
@ -354,8 +367,18 @@ void decodePoint( char *buf, int lasPointFormat, char *dataBuffer, std::size_t &
lazStoreToStream_<qint32>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.z() : p10.z );
break;
case QgsLazDecoder::LazAttribute::Classification:
lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.classification() : p10.classification );
{
if ( isLas14 )
{
lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p14.classification() );
}
else
{
// p10 format encoded "Overlap" as Classification=12, so in that case we set Classification=0 (Never classified) and will set Overlap=1 a few lines below
lazStoreToStream_<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, ( p10.classification & 0x1F ) == 12 ? 0 : p10.classification & 0x1F );
}
break;
}
case QgsLazDecoder::LazAttribute::Intensity:
lazStoreToStream_<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? p14.intensity() : p10.intensity );
break;
@ -397,9 +420,28 @@ void decodePoint( char *buf, int lasPointFormat, char *dataBuffer, std::size_t &
case QgsLazDecoder::LazAttribute::ScannerChannel:
lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, char( p14.scannerChannel() ) );
break;
case QgsLazDecoder::LazAttribute::ClassificationFlags:
lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, char( p14.classFlags() ) );
case QgsLazDecoder::LazAttribute::Synthetic:
lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? char( ( p14.classFlags() >> 0 ) & 0x01 ) : char( ( p10.classification >> 5 ) & 0x01 ) );
break;
case QgsLazDecoder::LazAttribute::KeyPoint:
lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? char( ( p14.classFlags() >> 1 ) & 0x01 ) : char( ( p10.classification >> 6 ) & 0x01 ) );
break;
case QgsLazDecoder::LazAttribute::Withheld:
lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? char( ( p14.classFlags() >> 2 ) & 0x01 ) : char( ( p10.classification >> 7 ) & 0x01 ) );
break;
case QgsLazDecoder::LazAttribute::Overlap:
{
if ( isLas14 )
{
lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, char( ( p14.classFlags() >> 3 ) & 0x01 ) );
}
else
{
// p10 format encoded "Overlap" as Classification=12, so in that case we set Overlap=1 (we have already set Classification=0)
lazStoreToStream_<char>( dataBuffer, outputOffset, requestedAttribute.type, ( p10.classification & 0x1F ) == 12 ? 1 : 0 );
}
break;
}
case QgsLazDecoder::LazAttribute::NIR:
{
if ( lasPointFormat == 8 || lasPointFormat == 10 )

View File

@ -61,7 +61,10 @@ class QgsLazDecoder
Green,
Blue,
ScannerChannel,
ClassificationFlags,
Synthetic,
KeyPoint,
Withheld,
Overlap,
NIR,
ExtraBytes,
MissingOrUnknown

View File

@ -170,11 +170,14 @@ void QgsLazInfo::parseLazAttributes()
mAttributes.push_back( QgsPointCloudAttribute( "ScanAngleRank", QgsPointCloudAttribute::Short ) );
mAttributes.push_back( QgsPointCloudAttribute( "UserData", QgsPointCloudAttribute::Char ) );
mAttributes.push_back( QgsPointCloudAttribute( "PointSourceId", QgsPointCloudAttribute::UShort ) );
mAttributes.push_back( QgsPointCloudAttribute( "Synthetic", QgsPointCloudAttribute::UChar ) );
mAttributes.push_back( QgsPointCloudAttribute( "KeyPoint", QgsPointCloudAttribute::UChar ) );
mAttributes.push_back( QgsPointCloudAttribute( "Withheld", QgsPointCloudAttribute::UChar ) );
mAttributes.push_back( QgsPointCloudAttribute( "Overlap", QgsPointCloudAttribute::UChar ) );
if ( mPointFormat == 6 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 9 || mPointFormat == 10 )
{
mAttributes.push_back( QgsPointCloudAttribute( "ScannerChannel", QgsPointCloudAttribute::Char ) );
mAttributes.push_back( QgsPointCloudAttribute( "ClassificationFlags", QgsPointCloudAttribute::Char ) );
}
if ( mPointFormat != 0 && mPointFormat != 2 )
{

View File

@ -594,7 +594,11 @@ void QgsPointCloudLayerExporter::ExporterPdal::handlePoint( double x, double y,
if ( mPointFormat == 6 || mPointFormat == 7 || mPointFormat == 8 || mPointFormat == 9 || mPointFormat == 10 )
{
mView->setField( pdal::Dimension::Id::ScanChannel, pointNumber, map[ QStringLiteral( "ScannerChannel" ) ].toInt() );
mView->setField( pdal::Dimension::Id::ClassFlags, pointNumber, map[ QStringLiteral( "ClassificationFlags" ) ].toInt() );
const int classificationFlags = ( map[ QStringLiteral( "Synthetic" ) ].toInt() & 0x01 ) << 0 |
( map[ QStringLiteral( "KeyPoint" ) ].toInt() & 0x01 ) << 1 |
( map[ QStringLiteral( "Withheld" ) ].toInt() & 0x01 ) << 2 |
( map[ QStringLiteral( "Overlap" ) ].toInt() & 0x01 ) << 3;
mView->setField( pdal::Dimension::Id::ClassFlags, pointNumber, classificationFlags );
}
if ( mPointFormat != 0 && mPointFormat != 2 )

View File

@ -205,6 +205,9 @@ QgsPointCloudCategoryList QgsPointCloudRendererRegistry::classificationAttribute
const QList<int> layerClasses = stats.classesOf( QStringLiteral( "Classification" ) );
const QgsPointCloudCategoryList defaultCategories = QgsPointCloudClassifiedRenderer::defaultCategories();
if ( layerClasses.isEmpty() )
return defaultCategories;
QgsPointCloudCategoryList categories;
for ( const int &layerClass : layerClasses )
{

View File

@ -118,7 +118,11 @@ struct StatsProcessor
attribute.name() == QLatin1String( "ScanDirectionFlag" ) ||
attribute.name() == QLatin1String( "Classification" ) ||
attribute.name() == QLatin1String( "EdgeOfFlightLine" ) ||
attribute.name() == QLatin1String( "PointSourceId" ) )
attribute.name() == QLatin1String( "PointSourceId" ) ||
attribute.name() == QLatin1String( "Synthetic" ) ||
attribute.name() == QLatin1String( "KeyPoint" ) ||
attribute.name() == QLatin1String( "Withheld" ) ||
attribute.name() == QLatin1String( "Overlap" ) )
{
classifiableAttributesOffsetSet.insert( attributeOffset );
}

View File

@ -439,8 +439,18 @@ void QgsVirtualPointCloudProvider::populateAttributeCollection( QSet<QString> na
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "PointSourceId" ), QgsPointCloudAttribute::UShort ) );
if ( names.contains( QLatin1String( "ScannerChannel" ) ) )
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "ScannerChannel" ), QgsPointCloudAttribute::Char ) );
if ( names.contains( QLatin1String( "ClassificationFlags" ) ) )
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "ClassificationFlags" ), QgsPointCloudAttribute::Char ) );
if ( names.contains( QLatin1String( "Synthetic" ) ) ||
names.contains( QLatin1String( "ClassFlags" ) ) )
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Synthetic" ), QgsPointCloudAttribute::UChar ) );
if ( names.contains( QLatin1String( "KeyPoint" ) ) ||
names.contains( QLatin1String( "ClassFlags" ) ) )
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "KeyPoint" ), QgsPointCloudAttribute::UChar ) );
if ( names.contains( QLatin1String( "Withheld" ) ) ||
names.contains( QLatin1String( "ClassFlags" ) ) )
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Withheld" ), QgsPointCloudAttribute::UChar ) );
if ( names.contains( QLatin1String( "Overlap" ) ) ||
names.contains( QLatin1String( "ClassFlags" ) ) )
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Overlap" ), QgsPointCloudAttribute::UChar ) );
if ( names.contains( QLatin1String( "GpsTime" ) ) )
mAttributes.push_back( QgsPointCloudAttribute( QStringLiteral( "GpsTime" ), QgsPointCloudAttribute::Double ) );
if ( names.contains( QLatin1String( "Red" ) ) )
@ -464,7 +474,11 @@ void QgsVirtualPointCloudProvider::populateAttributeCollection( QSet<QString> na
QLatin1String( "UserData" ),
QLatin1String( "PointSourceId" ),
QLatin1String( "ScannerChannel" ),
QLatin1String( "ClassificationFlags" ),
QLatin1String( "ClassFlags" ),
QLatin1String( "Synthetic" ),
QLatin1String( "KeyPoint" ),
QLatin1String( "Withheld" ),
QLatin1String( "Overlap" ),
QLatin1String( "GpsTime" ),
QLatin1String( "Red" ),
QLatin1String( "Green" ),

View File

@ -487,15 +487,27 @@ void QgsPointCloudClassifiedRendererWidget::addCategories()
const QString currentAttribute = mAttributeComboBox->currentAttribute();
const QgsPointCloudStatistics stats = mLayer->statistics();
const QList<int> providerCategories = stats.classesOf( currentAttribute );
const QgsPointCloudCategoryList currentCategories = mModel->categories();
const bool isClassificationAttribute = ! currentAttribute.compare( QStringLiteral( "Classification" ), Qt::CaseInsensitive );
const bool isClassificationAttribute = ( 0 == currentAttribute.compare( QStringLiteral( "Classification" ), Qt::CaseInsensitive ) );
const bool isBooleanAttribute = ( 0 == currentAttribute.compare( QStringLiteral( "Synthetic" ), Qt::CaseInsensitive ) ||
0 == currentAttribute.compare( QStringLiteral( "KeyPoint" ), Qt::CaseInsensitive ) ||
0 == currentAttribute.compare( QStringLiteral( "Withheld" ), Qt::CaseInsensitive ) ||
0 == currentAttribute.compare( QStringLiteral( "Overlap" ), Qt::CaseInsensitive ) );
QList<int> providerCategories = stats.classesOf( currentAttribute );
// for 0/1 attributes we should always show both 0 and 1 categories, unless we have full stats
// so we can show categories that are actually available
if ( isBooleanAttribute &&
( providerCategories.isEmpty() || stats.sampledPointsCount() < mLayer->pointCount() ) )
providerCategories = { 0, 1 };
const QgsPointCloudCategoryList defaultLayerCategories = isClassificationAttribute ? QgsPointCloudRendererRegistry::classificationAttributeCategories( mLayer ) : QgsPointCloudCategoryList();
mBlockChangedSignal = true;
for ( const int &providerCategory : providerCategories )
for ( const int &providerCategory : std::as_const( providerCategories ) )
{
// does this category already exist?
bool found = false;

View File

@ -10855,8 +10855,11 @@ void TestProcessingGui::testPointCloudAttributeWrapper()
<< QStringLiteral( "ScanAngleRank" )
<< QStringLiteral( "UserData" )
<< QStringLiteral( "PointSourceId" )
<< QStringLiteral( "Synthetic" )
<< QStringLiteral( "KeyPoint" )
<< QStringLiteral( "Withheld" )
<< QStringLiteral( "Overlap" )
<< QStringLiteral( "ScannerChannel" )
<< QStringLiteral( "ClassificationFlags" )
<< QStringLiteral( "GpsTime" )
<< QStringLiteral( "Red" )
<< QStringLiteral( "Green" )

View File

@ -81,6 +81,7 @@ class TestQgsCopcProvider : public QgsTest
void testIdentify();
void testExtraBytesAttributesExtraction();
void testExtraBytesAttributesValues();
void testClassFlagsValues();
void testPointCloudIndex();
void testStatsCalculator();
void testSaveLoadStats();
@ -296,7 +297,7 @@ void TestQgsCopcProvider::attributes()
QVERIFY( layer->isValid() );
const QgsPointCloudAttributeCollection attributes = layer->attributes();
QCOMPARE( attributes.count(), 18 );
QCOMPARE( attributes.count(), 21 );
QCOMPARE( attributes.at( 0 ).name(), QStringLiteral( "X" ) );
QCOMPARE( attributes.at( 0 ).type(), QgsPointCloudAttribute::Int32 );
QCOMPARE( attributes.at( 1 ).name(), QStringLiteral( "Y" ) );
@ -321,18 +322,24 @@ void TestQgsCopcProvider::attributes()
QCOMPARE( attributes.at( 10 ).type(), QgsPointCloudAttribute::Char );
QCOMPARE( attributes.at( 11 ).name(), QStringLiteral( "PointSourceId" ) );
QCOMPARE( attributes.at( 11 ).type(), QgsPointCloudAttribute::UShort );
QCOMPARE( attributes.at( 12 ).name(), QStringLiteral( "ScannerChannel" ) );
QCOMPARE( attributes.at( 12 ).type(), QgsPointCloudAttribute::Char );
QCOMPARE( attributes.at( 13 ).name(), QStringLiteral( "ClassificationFlags" ) );
QCOMPARE( attributes.at( 13 ).type(), QgsPointCloudAttribute::Char );
QCOMPARE( attributes.at( 14 ).name(), QStringLiteral( "GpsTime" ) );
QCOMPARE( attributes.at( 14 ).type(), QgsPointCloudAttribute::Double );
QCOMPARE( attributes.at( 15 ).name(), QStringLiteral( "Red" ) );
QCOMPARE( attributes.at( 15 ).type(), QgsPointCloudAttribute::UShort );
QCOMPARE( attributes.at( 16 ).name(), QStringLiteral( "Green" ) );
QCOMPARE( attributes.at( 16 ).type(), QgsPointCloudAttribute::UShort );
QCOMPARE( attributes.at( 17 ).name(), QStringLiteral( "Blue" ) );
QCOMPARE( attributes.at( 17 ).type(), QgsPointCloudAttribute::UShort );
QCOMPARE( attributes.at( 12 ).name(), QStringLiteral( "Synthetic" ) );
QCOMPARE( attributes.at( 12 ).type(), QgsPointCloudAttribute::UChar );
QCOMPARE( attributes.at( 13 ).name(), QStringLiteral( "KeyPoint" ) );
QCOMPARE( attributes.at( 13 ).type(), QgsPointCloudAttribute::UChar );
QCOMPARE( attributes.at( 14 ).name(), QStringLiteral( "Withheld" ) );
QCOMPARE( attributes.at( 14 ).type(), QgsPointCloudAttribute::UChar );
QCOMPARE( attributes.at( 15 ).name(), QStringLiteral( "Overlap" ) );
QCOMPARE( attributes.at( 15 ).type(), QgsPointCloudAttribute::UChar );
QCOMPARE( attributes.at( 16 ).name(), QStringLiteral( "ScannerChannel" ) );
QCOMPARE( attributes.at( 16 ).type(), QgsPointCloudAttribute::Char );
QCOMPARE( attributes.at( 17 ).name(), QStringLiteral( "GpsTime" ) );
QCOMPARE( attributes.at( 17 ).type(), QgsPointCloudAttribute::Double );
QCOMPARE( attributes.at( 18 ).name(), QStringLiteral( "Red" ) );
QCOMPARE( attributes.at( 18 ).type(), QgsPointCloudAttribute::UShort );
QCOMPARE( attributes.at( 19 ).name(), QStringLiteral( "Green" ) );
QCOMPARE( attributes.at( 19 ).type(), QgsPointCloudAttribute::UShort );
QCOMPARE( attributes.at( 20 ).name(), QStringLiteral( "Blue" ) );
QCOMPARE( attributes.at( 20 ).type(), QgsPointCloudAttribute::UShort );
}
void TestQgsCopcProvider::calculateZRange()
@ -530,8 +537,8 @@ void TestQgsCopcProvider::testExtraBytesAttributesExtraction()
QVector<QgsLazInfo::ExtraBytesAttributeDetails> attributes = lazInfo.extrabytes();
QCOMPARE( attributes.size(), 3 );
QCOMPARE( attributes[0].attribute, QStringLiteral( "Reflectance" ) );
QCOMPARE( attributes[1].attribute, QStringLiteral( "Amplitude" ) );
QCOMPARE( attributes[0].attribute, QStringLiteral( "Amplitude" ) );
QCOMPARE( attributes[1].attribute, QStringLiteral( "Reflectance" ) );
QCOMPARE( attributes[2].attribute, QStringLiteral( "Deviation" ) );
QCOMPARE( attributes[0].type, QgsPointCloudAttribute::Float );
@ -641,6 +648,179 @@ void TestQgsCopcProvider::testExtraBytesAttributesValues()
}
}
void TestQgsCopcProvider::testClassFlagsValues()
{
const QString dataPath = copyTestData( QStringLiteral( "/point_clouds/copc/extrabytes-dataset.copc.laz" ) );
std::unique_ptr< QgsPointCloudLayer > layer = std::make_unique< QgsPointCloudLayer >( dataPath, QStringLiteral( "layer" ), QStringLiteral( "copc" ) );
QVERIFY( layer->isValid() );
{
const float maxErrorInMapCoords = 0.0015207174f;
QPolygonF polygon;
polygon.push_back( QPointF( 527919.6, 6210983.6 ) );
polygon.push_back( QPointF( 527919.0, 6210983.6 ) );
polygon.push_back( QPointF( 527919.0, 6210983.4 ) );
polygon.push_back( QPointF( 527919.6, 6210983.4 ) );
polygon.push_back( QPointF( 527919.6, 6210983.6 ) );
QVector<QMap<QString, QVariant>> identifiedPoints = layer->dataProvider()->identify( maxErrorInMapCoords, QgsGeometry::fromQPolygonF( polygon ) );
QVector<QMap<QString, QVariant>> expectedPoints;
{
QMap<QString, QVariant> point;
point[ QStringLiteral( "Amplitude" ) ] = "14.170000076293945" ;
point[ QStringLiteral( "Blue" ) ] = "0" ;
point[ QStringLiteral( "Classification" ) ] = "2" ;
point[ QStringLiteral( "Deviation" ) ] = "0" ;
point[ QStringLiteral( "EdgeOfFlightLine" ) ] = "0" ;
point[ QStringLiteral( "GpsTime" ) ] = "302522582.235839" ;
point[ QStringLiteral( "Green" ) ] = "0" ;
point[ QStringLiteral( "Intensity" ) ] = "1417" ;
point[ QStringLiteral( "NumberOfReturns" ) ] = "3" ;
point[ QStringLiteral( "PointSourceId" ) ] = "15017" ;
point[ QStringLiteral( "Red" ) ] = "0" ;
point[ QStringLiteral( "Reflectance" ) ] = "-8.050000190734863" ;
point[ QStringLiteral( "ReturnNumber" ) ] = "3" ;
point[ QStringLiteral( "ScanAngleRank" ) ] = "24" ;
point[ QStringLiteral( "ScanDirectionFlag" ) ] = "0" ;
point[ QStringLiteral( "UserData" ) ] = "0" ;
point[ QStringLiteral( "Synthetic" ) ] = "1" ;
point[ QStringLiteral( "KeyPoint" ) ] = "0" ;
point[ QStringLiteral( "Withheld" ) ] = "0" ;
point[ QStringLiteral( "Overlap" ) ] = "0" ;
point[ QStringLiteral( "X" ) ] = "527919.11" ;
point[ QStringLiteral( "Y" ) ] = "6210983.55" ;
point[ QStringLiteral( "Z" ) ] = "147.111" ;
expectedPoints.push_back( point );
}
{
QMap<QString, QVariant> point;
point[ QStringLiteral( "Amplitude" ) ] = "4.409999847412109" ;
point[ QStringLiteral( "Blue" ) ] = "0" ;
point[ QStringLiteral( "Classification" ) ] = "5" ;
point[ QStringLiteral( "Deviation" ) ] = "2" ;
point[ QStringLiteral( "EdgeOfFlightLine" ) ] = "0" ;
point[ QStringLiteral( "GpsTime" ) ] = "302522582.235838" ;
point[ QStringLiteral( "Green" ) ] = "0" ;
point[ QStringLiteral( "Intensity" ) ] = "441" ;
point[ QStringLiteral( "NumberOfReturns" ) ] = "3" ;
point[ QStringLiteral( "PointSourceId" ) ] = "15017" ;
point[ QStringLiteral( "Red" ) ] = "0" ;
point[ QStringLiteral( "Reflectance" ) ] = "-17.829999923706055" ;
point[ QStringLiteral( "ReturnNumber" ) ] = "2" ;
point[ QStringLiteral( "ScanAngleRank" ) ] = "24" ;
point[ QStringLiteral( "ScanDirectionFlag" ) ] = "0" ;
point[ QStringLiteral( "UserData" ) ] = "0" ;
point[ QStringLiteral( "Synthetic" ) ] = "1" ;
point[ QStringLiteral( "KeyPoint" ) ] = "1" ;
point[ QStringLiteral( "Withheld" ) ] = "0" ;
point[ QStringLiteral( "Overlap" ) ] = "0" ;
point[ QStringLiteral( "X" ) ] = "527919.1799999999" ;
point[ QStringLiteral( "Y" ) ] = "6210983.47" ;
point[ QStringLiteral( "Z" ) ] = "149.341" ;
expectedPoints.push_back( point );
}
{
QMap<QString, QVariant> point;
point[ QStringLiteral( "Amplitude" ) ] = "7.539999961853027" ;
point[ QStringLiteral( "Blue" ) ] = "0" ;
point[ QStringLiteral( "Classification" ) ] = "5" ;
point[ QStringLiteral( "Deviation" ) ] = "8" ;
point[ QStringLiteral( "EdgeOfFlightLine" ) ] = "0" ;
point[ QStringLiteral( "GpsTime" ) ] = "302522582.235837" ;
point[ QStringLiteral( "Green" ) ] = "0" ;
point[ QStringLiteral( "Intensity" ) ] = "754" ;
point[ QStringLiteral( "NumberOfReturns" ) ] = "3" ;
point[ QStringLiteral( "PointSourceId" ) ] = "15017" ;
point[ QStringLiteral( "Red" ) ] = "0" ;
point[ QStringLiteral( "Reflectance" ) ] = "-14.720000267028809" ;
point[ QStringLiteral( "ReturnNumber" ) ] = "2" ;
point[ QStringLiteral( "ScanAngleRank" ) ] = "24" ;
point[ QStringLiteral( "ScanDirectionFlag" ) ] = "0" ;
point[ QStringLiteral( "UserData" ) ] = "0" ;
point[ QStringLiteral( "Synthetic" ) ] = "1" ;
point[ QStringLiteral( "KeyPoint" ) ] = "1" ;
point[ QStringLiteral( "Withheld" ) ] = "1" ;
point[ QStringLiteral( "Overlap" ) ] = "1" ;
point[ QStringLiteral( "X" ) ] = "527919.31" ;
point[ QStringLiteral( "Y" ) ] = "6210983.42" ;
point[ QStringLiteral( "Z" ) ] = "150.99099999999999" ;
expectedPoints.push_back( point );
}
{
QMap<QString, QVariant> point;
point[ QStringLiteral( "Amplitude" ) ] = "15.390000343322754" ;
point[ QStringLiteral( "Blue" ) ] = "0" ;
point[ QStringLiteral( "Classification" ) ] = "2" ;
point[ QStringLiteral( "Deviation" ) ] = "6" ;
point[ QStringLiteral( "EdgeOfFlightLine" ) ] = "0" ;
point[ QStringLiteral( "GpsTime" ) ] = "302522582.235838" ;
point[ QStringLiteral( "Green" ) ] = "0" ;
point[ QStringLiteral( "Intensity" ) ] = "1539" ;
point[ QStringLiteral( "NumberOfReturns" ) ] = "3" ;
point[ QStringLiteral( "PointSourceId" ) ] = "15017" ;
point[ QStringLiteral( "Red" ) ] = "0" ;
point[ QStringLiteral( "Reflectance" ) ] = "-6.829999923706055" ;
point[ QStringLiteral( "ReturnNumber" ) ] = "3" ;
point[ QStringLiteral( "ScanAngleRank" ) ] = "24" ;
point[ QStringLiteral( "ScanDirectionFlag" ) ] = "0" ;
point[ QStringLiteral( "UserData" ) ] = "0" ;
point[ QStringLiteral( "Synthetic" ) ] = "1" ;
point[ QStringLiteral( "KeyPoint" ) ] = "1" ;
point[ QStringLiteral( "Withheld" ) ] = "1" ;
point[ QStringLiteral( "Overlap" ) ] = "0" ;
point[ QStringLiteral( "X" ) ] = "527919.39" ;
point[ QStringLiteral( "Y" ) ] = "6210983.56" ;
point[ QStringLiteral( "Z" ) ] = "147.101" ;
expectedPoints.push_back( point );
}
{
QMap<QString, QVariant> point;
point[ QStringLiteral( "Amplitude" ) ] = "11.710000038146973" ;
point[ QStringLiteral( "Blue" ) ] = "0" ;
point[ QStringLiteral( "Classification" ) ] = "5" ;
point[ QStringLiteral( "Deviation" ) ] = "43" ;
point[ QStringLiteral( "EdgeOfFlightLine" ) ] = "0" ;
point[ QStringLiteral( "GpsTime" ) ] = "302522582.23583597" ;
point[ QStringLiteral( "Green" ) ] = "0" ;
point[ QStringLiteral( "Intensity" ) ] = "1171" ;
point[ QStringLiteral( "NumberOfReturns" ) ] = "3" ;
point[ QStringLiteral( "PointSourceId" ) ] = "15017" ;
point[ QStringLiteral( "Red" ) ] = "0" ;
point[ QStringLiteral( "Reflectance" ) ] = "-10.550000190734863" ;
point[ QStringLiteral( "ReturnNumber" ) ] = "1" ;
point[ QStringLiteral( "ScanAngleRank" ) ] = "24" ;
point[ QStringLiteral( "ScanDirectionFlag" ) ] = "0" ;
point[ QStringLiteral( "UserData" ) ] = "0" ;
point[ QStringLiteral( "Synthetic" ) ] = "0" ;
point[ QStringLiteral( "KeyPoint" ) ] = "0" ;
point[ QStringLiteral( "Withheld" ) ] = "0" ;
point[ QStringLiteral( "Overlap" ) ] = "0" ;
point[ QStringLiteral( "X" ) ] = "527919.58" ;
point[ QStringLiteral( "Y" ) ] = "6210983.42" ;
point[ QStringLiteral( "Z" ) ] = "151.131" ;
expectedPoints.push_back( point );
}
auto cmp = []( const QMap<QString, QVariant> &p1, const QMap<QString, QVariant> &p2 )
{
return qgsVariantLessThan( p1.value( QStringLiteral( "X" ), 0 ), p2.value( QStringLiteral( "X" ), 0 ) );
};
std::sort( expectedPoints.begin(), expectedPoints.end(), cmp );
std::sort( identifiedPoints.begin(), identifiedPoints.end(), cmp );
QVERIFY( identifiedPoints.size() == expectedPoints.size() );
const QStringList keys = expectedPoints[0].keys();
for ( int i = 0; i < identifiedPoints.size(); ++i )
{
for ( const QString &k : keys )
{
QCOMPARE( identifiedPoints[i][k].toDouble(), expectedPoints[i][k].toDouble() );
}
}
}
}
void TestQgsCopcProvider::testPointCloudIndex()
{
const QString dataPath = copyTestData( QStringLiteral( "/point_clouds/copc/lone-star.copc.laz" ) );
@ -708,7 +888,10 @@ void TestQgsCopcProvider::testStatsCalculator()
QVector<QgsPointCloudAttribute> attributes;
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Deviation" ), QgsPointCloudAttribute::Float ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "ClassFlags" ), QgsPointCloudAttribute::Char ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Synthetic" ), QgsPointCloudAttribute::UChar ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "KeyPoint" ), QgsPointCloudAttribute::UChar ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Withheld" ), QgsPointCloudAttribute::UChar ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Overlap" ), QgsPointCloudAttribute::UChar ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Red" ), QgsPointCloudAttribute::UShort ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "EdgeOfFlightLine" ), QgsPointCloudAttribute::Char ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Blue" ), QgsPointCloudAttribute::UShort ) );
@ -744,9 +927,35 @@ void TestQgsCopcProvider::testStatsCalculator()
}
{
QgsPointCloudAttributeStatistics s = stats.statisticsOf( QStringLiteral( "ClassFlags" ) );
QgsPointCloudAttributeStatistics s = stats.statisticsOf( QStringLiteral( "Synthetic" ) );
QCOMPARE( ( float )s.minimum, 0 );
QCOMPARE( ( float )s.maximum, 0 );
QCOMPARE( ( float )s.maximum, 1 );
QMap<int, int> classCount = s.classCount;
QCOMPARE( classCount.size(), 2 );
}
{
QgsPointCloudAttributeStatistics s = stats.statisticsOf( QStringLiteral( "KeyPoint" ) );
QCOMPARE( ( float )s.minimum, 0 );
QCOMPARE( ( float )s.maximum, 1 );
QMap<int, int> classCount = s.classCount;
QCOMPARE( classCount.size(), 2 );
}
{
QgsPointCloudAttributeStatistics s = stats.statisticsOf( QStringLiteral( "Withheld" ) );
QCOMPARE( ( float )s.minimum, 0 );
QCOMPARE( ( float )s.maximum, 1 );
QMap<int, int> classCount = s.classCount;
QCOMPARE( classCount.size(), 2 );
}
{
QgsPointCloudAttributeStatistics s = stats.statisticsOf( QStringLiteral( "Overlap" ) );
QCOMPARE( ( float )s.minimum, 0 );
QCOMPARE( ( float )s.maximum, 1 );
QMap<int, int> classCount = s.classCount;
QCOMPARE( classCount.size(), 2 );
}
{

View File

@ -564,7 +564,10 @@ void TestQgsEptProvider::testExtraBytesAttributesValues()
QMap<QString, QVariant> point;
point[ QStringLiteral( "Amplitude" ) ] = "4.409999847412109" ;
point[ QStringLiteral( "Blue" ) ] = "0" ;
point[ QStringLiteral( "ClassFlags" ) ] = "0" ;
point[ QStringLiteral( "Synthetic" ) ] = "0" ;
point[ QStringLiteral( "KeyPoint" ) ] = "0" ;
point[ QStringLiteral( "Withheld" ) ] = "0" ;
point[ QStringLiteral( "Overlap" ) ] = "0" ;
point[ QStringLiteral( "Classification" ) ] = "5" ;
point[ QStringLiteral( "Deviation" ) ] = "2" ;
point[ QStringLiteral( "EdgeOfFlightLine" ) ] = "0" ;
@ -588,7 +591,10 @@ void TestQgsEptProvider::testExtraBytesAttributesValues()
QMap<QString, QVariant> point;
point[ QStringLiteral( "Amplitude" ) ] = "14.170000076293945" ;
point[ QStringLiteral( "Blue" ) ] = "0" ;
point[ QStringLiteral( "ClassFlags" ) ] = "0" ;
point[ QStringLiteral( "Synthetic" ) ] = "0" ;
point[ QStringLiteral( "KeyPoint" ) ] = "0" ;
point[ QStringLiteral( "Withheld" ) ] = "0" ;
point[ QStringLiteral( "Overlap" ) ] = "0" ;
point[ QStringLiteral( "Classification" ) ] = "2" ;
point[ QStringLiteral( "Deviation" ) ] = "0" ;
point[ QStringLiteral( "EdgeOfFlightLine" ) ] = "0" ;
@ -750,7 +756,10 @@ void TestQgsEptProvider::testStatsCalculator()
QVector<QgsPointCloudAttribute> attributes;
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Deviation" ), QgsPointCloudAttribute::Float ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "ClassFlags" ), QgsPointCloudAttribute::Char ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Synthetic" ), QgsPointCloudAttribute::UChar ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "KeyPoint" ), QgsPointCloudAttribute::UChar ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Withheld" ), QgsPointCloudAttribute::UChar ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Overlap" ), QgsPointCloudAttribute::UChar ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Red" ), QgsPointCloudAttribute::UShort ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "EdgeOfFlightLine" ), QgsPointCloudAttribute::Char ) );
attributes.append( QgsPointCloudAttribute( QStringLiteral( "Blue" ), QgsPointCloudAttribute::UShort ) );
@ -786,9 +795,35 @@ void TestQgsEptProvider::testStatsCalculator()
}
{
QgsPointCloudAttributeStatistics s = stats.statisticsOf( QStringLiteral( "ClassFlags" ) );
QgsPointCloudAttributeStatistics s = stats.statisticsOf( QStringLiteral( "Synthetic" ) );
QCOMPARE( ( float )s.minimum, 0 );
QCOMPARE( ( float )s.maximum, 0 );
QMap<int, int> classCount = s.classCount;
QCOMPARE( classCount.size(), 1 );
}
{
QgsPointCloudAttributeStatistics s = stats.statisticsOf( QStringLiteral( "KeyPoint" ) );
QCOMPARE( ( float )s.minimum, 0 );
QCOMPARE( ( float )s.maximum, 0 );
QMap<int, int> classCount = s.classCount;
QCOMPARE( classCount.size(), 1 );
}
{
QgsPointCloudAttributeStatistics s = stats.statisticsOf( QStringLiteral( "Withheld" ) );
QCOMPARE( ( float )s.minimum, 0 );
QCOMPARE( ( float )s.maximum, 0 );
QMap<int, int> classCount = s.classCount;
QCOMPARE( classCount.size(), 1 );
}
{
QgsPointCloudAttributeStatistics s = stats.statisticsOf( QStringLiteral( "Overlap" ) );
QCOMPARE( ( float )s.minimum, 0 );
QCOMPARE( ( float )s.maximum, 0 );
QMap<int, int> classCount = s.classCount;
QCOMPARE( classCount.size(), 1 );
}
{