mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-06 00:07:29 -04:00
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:
parent
7dd83dd9e7
commit
6281d0140f
@ -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 ) );
|
||||
}
|
||||
|
@ -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 )
|
||||
|
@ -61,7 +61,10 @@ class QgsLazDecoder
|
||||
Green,
|
||||
Blue,
|
||||
ScannerChannel,
|
||||
ClassificationFlags,
|
||||
Synthetic,
|
||||
KeyPoint,
|
||||
Withheld,
|
||||
Overlap,
|
||||
NIR,
|
||||
ExtraBytes,
|
||||
MissingOrUnknown
|
||||
|
@ -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 )
|
||||
{
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
{
|
||||
|
@ -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 );
|
||||
}
|
||||
|
@ -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" ),
|
||||
|
@ -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;
|
||||
|
@ -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" )
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
{
|
||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user