- Add tests for COPC

- Address some of Martin's reviews
This commit is contained in:
NEDJIMAbelgacem 2022-03-29 11:31:06 +01:00 committed by Martin Dobias
parent 458748cb4a
commit 7faea46b7b
15 changed files with 713 additions and 116 deletions

View File

@ -411,21 +411,19 @@ if(WITH_CORE)
message(STATUS "Qt WebKit support DISABLED.")
endif()
if (WITH_EPT) # EPT provider
find_package(ZSTD REQUIRED) # for decompression of point clouds
if (WITH_EPT OR WITH_COPC)
find_package(LazPerf) # for decompression of point clouds
if (NOT LazPerf_FOUND)
message(STATUS "Using embedded laz-perf")
endif()
endif()
if (WITH_EPT) # EPT provider
find_package(ZSTD REQUIRED) # for decompression of point clouds
set(HAVE_EPT TRUE) # used in qgsconfig.h
endif()
if (WITH_COPC) # COPC provider
find_package(ZSTD REQUIRED) # for decompression of point clouds
find_package(LazPerf) # for decompression of point clouds
if (NOT LazPerf_FOUND)
message(STATUS "Using embedded laz-perf")
endif()
set(HAVE_COPC TRUE) # used in qgsconfig.h
endif()

View File

@ -1929,43 +1929,13 @@ if (WITH_EPT)
${ZSTD_INCLUDE_DIR}
)
if (LazPerf_FOUND)
# Use system laz-perf
include_directories(SYSTEM
${LazPerf_INCLUDE_DIR}
)
else()
# Use embedded laz-perf from external/laz-perf
include_directories(SYSTEM
)
set(QGIS_CORE_SRCS ${QGIS_CORE_SRCS}
${CMAKE_SOURCE_DIR}/external/lazperf/charbuf.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/filestream.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/header.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/lazperf.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/readers.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/vlr.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_byte10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_byte14.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_gpstime10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_nir14.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_point10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_point14.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_rgb10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_rgb14.cpp
)
endif()
set(QGIS_CORE_SRCS ${QGIS_CORE_SRCS}
providers/ept/qgseptprovider.cpp
pointcloud/qgseptdecoder.cpp
pointcloud/qgseptpointcloudindex.cpp
pointcloud/qgsremoteeptpointcloudindex.cpp
)
set(QGIS_CORE_HDRS ${QGIS_CORE_HDRS}
providers/ept/qgseptprovider.h
pointcloud/qgseptdecoder.h
pointcloud/qgseptpointcloudindex.h
pointcloud/qgsremoteeptpointcloudindex.h
)
@ -1976,52 +1946,55 @@ endif()
if (WITH_COPC)
include_directories(providers/copc)
include_directories(SYSTEM
${ZSTD_INCLUDE_DIR}
)
if (LazPerf_FOUND)
# Use system laz-perf
include_directories(SYSTEM
${LazPerf_INCLUDE_DIR}
)
else()
# Use embedded laz-perf from external/laz-perf
include_directories(SYSTEM
)
set(QGIS_CORE_SRCS ${QGIS_CORE_SRCS}
${CMAKE_SOURCE_DIR}/external/lazperf/charbuf.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/filestream.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/header.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/lazperf.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/readers.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/vlr.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_byte10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_byte14.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_gpstime10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_nir14.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_point10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_point14.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_rgb10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_rgb14.cpp
)
endif()
set(QGIS_CORE_SRCS ${QGIS_CORE_SRCS}
providers/copc/qgscopcprovider.cpp
pointcloud/qgseptdecoder.cpp
pointcloud/qgscopcpointcloudindex.cpp
)
set(QGIS_CORE_HDRS ${QGIS_CORE_HDRS}
providers/copc/qgscopcprovider.h
pointcloud/qgseptdecoder.h
pointcloud/qgscopcpointcloudindex.h
)
add_definitions( -DWITH_COPC )
endif()
if (WITH_EPT OR WITH_COPC)
if (LazPerf_FOUND)
# Use system laz-perf
include_directories(SYSTEM
${LazPerf_INCLUDE_DIR}
)
else()
# Use embedded laz-perf from external/laz-perf
include_directories(SYSTEM
)
set(QGIS_CORE_SRCS ${QGIS_CORE_SRCS}
${CMAKE_SOURCE_DIR}/external/lazperf/charbuf.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/filestream.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/header.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/lazperf.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/readers.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/vlr.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_byte10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_byte14.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_gpstime10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_nir14.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_point10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_point14.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_rgb10.cpp
${CMAKE_SOURCE_DIR}/external/lazperf/detail/field_rgb14.cpp
)
endif()
set(QGIS_CORE_SRCS ${QGIS_CORE_SRCS}
pointcloud/qgseptdecoder.cpp
)
set(QGIS_CORE_HDRS ${QGIS_CORE_HDRS}
pointcloud/qgseptdecoder.h
)
endif()
if (APPLE)
# Libtasn1 is for DER-encoded PKI ASN.1 parsing/extracting workarounds
include_directories(SYSTEM
@ -2278,15 +2251,9 @@ if (WITH_EPT)
target_link_libraries(qgis_core
${ZSTD_LIBRARY}
)
if (LazPerf_FOUND)
target_link_libraries(qgis_core ${LazPerf_LIBRARY})
endif()
endif()
if (WITH_COPC)
target_link_libraries(qgis_core
${ZSTD_LIBRARY}
)
if (WITH_EPT OR WITH_COPC)
if (LazPerf_FOUND)
target_link_libraries(qgis_core ${LazPerf_LIBRARY})
endif()

View File

@ -17,12 +17,6 @@
#include "qgscopcpointcloudindex.h"
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTime>
#include <QtDebug>
#include <QQueue>
@ -100,22 +94,20 @@ bool QgsCopcPointCloudIndex::loadSchema( const QString &filename )
// Attributes for COPC format
// COPC supports only PDRF 6, 7 and 8
// TODO: How to handle bitfields in LAZ
QgsPointCloudAttributeCollection attributes;
attributes.push_back( QgsPointCloudAttribute( "X", ( QgsPointCloudAttribute::DataType ) 9 ) );
attributes.push_back( QgsPointCloudAttribute( "Y", ( QgsPointCloudAttribute::DataType ) 9 ) );
attributes.push_back( QgsPointCloudAttribute( "Z", ( QgsPointCloudAttribute::DataType ) 9 ) );
attributes.push_back( QgsPointCloudAttribute( "Classification", ( QgsPointCloudAttribute::DataType ) 0 ) );
attributes.push_back( QgsPointCloudAttribute( "Intensity", ( QgsPointCloudAttribute::DataType ) 3 ) );
attributes.push_back( QgsPointCloudAttribute( "ReturnNumber", ( QgsPointCloudAttribute::DataType ) 0 ) );
attributes.push_back( QgsPointCloudAttribute( "NumberOfReturns", ( QgsPointCloudAttribute::DataType ) 0 ) );
attributes.push_back( QgsPointCloudAttribute( "ScanDirectionFlag", ( QgsPointCloudAttribute::DataType ) 0 ) );
attributes.push_back( QgsPointCloudAttribute( "EdgeOfFlightLine", ( QgsPointCloudAttribute::DataType ) 0 ) );
attributes.push_back( QgsPointCloudAttribute( "ScanAngleRank", ( QgsPointCloudAttribute::DataType ) 8 ) );
attributes.push_back( QgsPointCloudAttribute( "UserData", ( QgsPointCloudAttribute::DataType ) 0 ) );
attributes.push_back( QgsPointCloudAttribute( "PointSourceId", ( QgsPointCloudAttribute::DataType ) 3 ) );
attributes.push_back( QgsPointCloudAttribute( "GpsTime", ( QgsPointCloudAttribute::DataType ) 9 ) );
attributes.push_back( QgsPointCloudAttribute( "X", QgsPointCloudAttribute::Int32 ) );
attributes.push_back( QgsPointCloudAttribute( "Y", QgsPointCloudAttribute::Int32 ) );
attributes.push_back( QgsPointCloudAttribute( "Z", QgsPointCloudAttribute::Int32 ) );
attributes.push_back( QgsPointCloudAttribute( "Intensity", QgsPointCloudAttribute::UShort ) );
attributes.push_back( QgsPointCloudAttribute( "ReturnNumber", QgsPointCloudAttribute::Char ) );
attributes.push_back( QgsPointCloudAttribute( "NumberOfReturns", QgsPointCloudAttribute::Char ) );
attributes.push_back( QgsPointCloudAttribute( "ScanDirectionFlag", QgsPointCloudAttribute::Char ) );
attributes.push_back( QgsPointCloudAttribute( "EdgeOfFlightLine", QgsPointCloudAttribute::Char ) );
attributes.push_back( QgsPointCloudAttribute( "Classification", QgsPointCloudAttribute::Char ) );
attributes.push_back( QgsPointCloudAttribute( "ScanAngleRank", QgsPointCloudAttribute::Short ) );
attributes.push_back( QgsPointCloudAttribute( "UserData", QgsPointCloudAttribute::Char ) );
attributes.push_back( QgsPointCloudAttribute( "PointSourceId", QgsPointCloudAttribute::UShort ) );
attributes.push_back( QgsPointCloudAttribute( "GpsTime", QgsPointCloudAttribute::Double ) );
switch ( f.header().point_format_id )
{
@ -136,7 +128,11 @@ bool QgsCopcPointCloudIndex::loadSchema( const QString &filename )
return false;
}
// TODO: add extrabyte attributes
QVector<QgsEptDecoder::ExtraBytesAttributeDetails> extrabyteAttributes = QgsEptDecoder::readExtraByteAttributes( file );
for ( QgsEptDecoder::ExtraBytesAttributeDetails attr : extrabyteAttributes )
{
attributes.push_back( QgsPointCloudAttribute( attr.attribute, attr.type ) );
}
setAttributes( attributes );
@ -216,6 +212,21 @@ qint64 QgsCopcPointCloudIndex::pointCount() const
QVariant QgsCopcPointCloudIndex::metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const
{
if ( attribute == QStringLiteral( "X" ) && statistic == QgsStatisticalSummary::Min )
return mExtent.xMinimum();
if ( attribute == QStringLiteral( "X" ) && statistic == QgsStatisticalSummary::Max )
return mExtent.xMaximum();
if ( attribute == QStringLiteral( "Y" ) && statistic == QgsStatisticalSummary::Min )
return mExtent.yMinimum();
if ( attribute == QStringLiteral( "Y" ) && statistic == QgsStatisticalSummary::Max )
return mExtent.yMaximum();
if ( attribute == QStringLiteral( "Z" ) && statistic == QgsStatisticalSummary::Min )
return mZMin;
if ( attribute == QStringLiteral( "Z" ) && statistic == QgsStatisticalSummary::Max )
return mZMax;
if ( !mMetadataStats.contains( attribute ) )
return QVariant();

View File

@ -262,7 +262,7 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject
QgsDoubleRange nodeZRange( const IndexedPointCloudNode &node ) const;
//! Returns node's error in map units (used to determine in whether the node has enough detail for the current view)
virtual float nodeError( const IndexedPointCloudNode &n ) const;
float nodeError( const IndexedPointCloudNode &n ) const;
//! Returns scale
QgsVector3D scale() const;

View File

@ -145,7 +145,7 @@ QgsCopcProvider *QgsCopcProviderMetadata::createProvider( const QString &uri, co
QList<QgsProviderSublayerDetails> QgsCopcProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags, QgsFeedback * ) const
{
const QVariantMap parts = decodeUri( uri );
if ( parts.value( QStringLiteral( "isCopc" ), false ).toBool() )
if ( parts.value( QStringLiteral( "path" ) ).toString().endsWith( ".copc.laz", Qt::CaseSensitivity::CaseInsensitive ) )
{
QgsProviderSublayerDetails details;
details.setUri( uri );
@ -164,8 +164,8 @@ int QgsCopcProviderMetadata::priorityForUri( const QString &uri ) const
{
const QVariantMap parts = decodeUri( uri );
const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );
if ( fi.exists() && parts.value( QStringLiteral( "isCopc" ), false ).toBool() )
return 100;
if ( parts.value( QStringLiteral( "path" ) ).toString().endsWith( ".copc.laz", Qt::CaseSensitivity::CaseInsensitive ) )
return 101;
return 0;
}
@ -174,7 +174,7 @@ QList<QgsMapLayerType> QgsCopcProviderMetadata::validLayerTypesForUri( const QSt
{
const QVariantMap parts = decodeUri( uri );
const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );
if ( fi.exists() && parts.value( QStringLiteral( "isCopc" ), false ).toBool() )
if ( parts.value( QStringLiteral( "path" ) ).toString().endsWith( ".copc.laz", Qt::CaseSensitivity::CaseInsensitive ) )
return QList< QgsMapLayerType>() << QgsMapLayerType::PointCloudLayer;
return QList< QgsMapLayerType>();
@ -189,7 +189,7 @@ bool QgsCopcProviderMetadata::uriIsBlocklisted( const QString &uri ) const
const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );
// internal details only
if ( fi.exists() && parts.value( QStringLiteral( "isCopc" ), false ).toBool() )
if ( parts.value( QStringLiteral( "path" ) ).toString().endsWith( ".copc.laz", Qt::CaseSensitivity::CaseInsensitive ) )
return true;
return false;
@ -200,7 +200,6 @@ QVariantMap QgsCopcProviderMetadata::decodeUri( const QString &uri ) const
const QString path = uri;
QVariantMap uriComponents;
uriComponents.insert( QStringLiteral( "path" ), path );
uriComponents.insert( QStringLiteral( "isCopc" ), uri.endsWith( ".copc.laz" ) );
return uriComponents;
}
@ -215,7 +214,7 @@ QString QgsCopcProviderMetadata::filters( QgsProviderMetadata::FilterType type )
return QString();
case QgsProviderMetadata::FilterType::FilterPointCloud:
return QObject::tr( "COPC Point Clouds" ) + QStringLiteral( "COPC LAZ files (*.copc.laz *.COPC.LAZ)" );
return QObject::tr( "COPC Point Clouds" ) + QStringLiteral( " (*.copc.laz *.COPC.LAZ)" );
}
return QString();
}

View File

@ -78,10 +78,6 @@ void QgsPointCloudSourceSelect::addButtonClicked()
// auto determine preferred provider for each path
const QList< QgsProviderRegistry::ProviderCandidateDetails > preferredProviders = QgsProviderRegistry::instance()->preferredProvidersForUri( mPath );
for ( QgsProviderRegistry::ProviderCandidateDetails p : preferredProviders )
{
qDebug() << p.metadata()->key();
}
// maybe we should raise an assert if preferredProviders size is 0 or >1? Play it safe for now...
if ( preferredProviders.empty() )
continue;

View File

@ -58,10 +58,9 @@ if (WITH_EPT)
add_qgis_test(testqgseptprovider.cpp MODULE provider LINKEDLIBRARIES qgis_core)
endif()
# TODO: test COPC
#if (WITH_COPC)
# add_qgis_test(testqgscopcprovider.cpp MODULE provider LINKEDLIBRARIES qgis_core)
#endif()
if (WITH_COPC)
add_qgis_test(testqgscopcprovider.cpp MODULE provider LINKEDLIBRARIES qgis_core)
endif()
if (WITH_PDAL)
include_directories(

View File

@ -0,0 +1,627 @@
/***************************************************************************
testqgseptprovider.cpp
--------------------------------------
Date : November 2020
Copyright : (C) 2020 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <limits>
#include "qgstest.h"
#include <QObject>
#include <QString>
#include <QStringList>
#include <QApplication>
#include <QFileInfo>
#include <QDir>
#include <fstream>
#include <QVector>
//qgis includes...
#include "qgis.h"
#include "qgsapplication.h"
#include "qgsproviderregistry.h"
#include "qgscopcprovider.h"
#include "qgseptprovider.h"
#include "qgspointcloudlayer.h"
#include "qgspointcloudindex.h"
#include "qgspointcloudlayerelevationproperties.h"
#include "qgsprovidersublayerdetails.h"
#include "qgsgeometry.h"
#include "qgseptdecoder.h"
/**
* \ingroup UnitTests
* This is a unit test for the COPC provider
*/
class TestQgsCopcProvider : public QObject
{
Q_OBJECT
private slots:
void initTestCase();// will be called before the first testfunction is executed.
void cleanupTestCase();// will be called after the last testfunction was executed.
void init() {}// will be called before each testfunction is executed.
void cleanup() {}// will be called after every testfunction.
void filters();
void encodeUri();
void decodeUri();
void preferredUri();
void layerTypesForUri();
void uriIsBlocklisted();
void querySublayers();
void brokenPath();
void validLayer();
void validLayerWithCopcHierarchy();
void attributes();
void calculateZRange();
void testIdentify_data();
void testIdentify();
// void testExtraBytesAttributesExtraction();
// void testExtraBytesAttributesValues();
void testPointCloudIndex();
private:
QString mTestDataDir;
QString mReport;
};
//runs before all tests
void TestQgsCopcProvider::initTestCase()
{
// init QGIS's paths - true means that all path will be inited from prefix
QgsApplication::init();
QgsApplication::initQgis();
mTestDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
mReport = QStringLiteral( "<h1>COPC Provider Tests</h1>\n" );
}
//runs after all tests
void TestQgsCopcProvider::cleanupTestCase()
{
QgsApplication::exitQgis();
const QString myReportFile = QDir::tempPath() + "/qgistest.html";
QFile myFile( myReportFile );
if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
{
QTextStream myQTextStream( &myFile );
myQTextStream << mReport;
myFile.close();
}
}
void TestQgsCopcProvider::filters()
{
QgsProviderMetadata *metadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "copc" ) );
QVERIFY( metadata );
QCOMPARE( metadata->filters( QgsProviderMetadata::FilterType::FilterPointCloud ), QStringLiteral( "COPC Point Clouds (*.copc.laz *.COPC.LAZ)" ) );
QCOMPARE( metadata->filters( QgsProviderMetadata::FilterType::FilterVector ), QString() );
const QString registryPointCloudFilters = QgsProviderRegistry::instance()->filePointCloudFilters();
QVERIFY( registryPointCloudFilters.contains( "(*.copc.laz *.COPC.LAZ)" ) );
}
void TestQgsCopcProvider::encodeUri()
{
QgsProviderMetadata *metadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "copc" ) );
QVERIFY( metadata );
QVariantMap parts;
parts.insert( QStringLiteral( "path" ), QStringLiteral( "/home/point_clouds/dataset.copc.laz" ) );
QCOMPARE( metadata->encodeUri( parts ), QStringLiteral( "/home/point_clouds/dataset.copc.laz" ) );
}
void TestQgsCopcProvider::decodeUri()
{
QgsProviderMetadata *metadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "copc" ) );
QVERIFY( metadata );
const QVariantMap parts = metadata->decodeUri( QStringLiteral( "/home/point_clouds/dataset.copc.laz" ) );
QCOMPARE( parts.value( QStringLiteral( "path" ) ).toString(), QStringLiteral( "/home/point_clouds/dataset.copc.laz" ) );
}
void TestQgsCopcProvider::preferredUri()
{
QgsProviderMetadata *copcMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "copc" ) );
QVERIFY( copcMetadata->capabilities() & QgsProviderMetadata::PriorityForUri );
// test that COPC is the preferred provider for .copc.laz uris
QList<QgsProviderRegistry::ProviderCandidateDetails> candidates = QgsProviderRegistry::instance()->preferredProvidersForUri( QStringLiteral( "/home/test/dataset.copc.laz" ) );
QCOMPARE( candidates.size(), 1 );
QCOMPARE( candidates.at( 0 ).metadata()->key(), QStringLiteral( "copc" ) );
QCOMPARE( candidates.at( 0 ).layerTypes(), QList< QgsMapLayerType >() << QgsMapLayerType::PointCloudLayer );
candidates = QgsProviderRegistry::instance()->preferredProvidersForUri( QStringLiteral( "/home/test/dataset.COPC.LAZ" ) );
QCOMPARE( candidates.size(), 1 );
QCOMPARE( candidates.at( 0 ).metadata()->key(), QStringLiteral( "copc" ) );
QCOMPARE( candidates.at( 0 ).layerTypes(), QList< QgsMapLayerType >() << QgsMapLayerType::PointCloudLayer );
QVERIFY( !QgsProviderRegistry::instance()->shouldDeferUriForOtherProviders( QStringLiteral( "/home/test/dataset.copc.laz" ), QStringLiteral( "copc" ) ) );
QVERIFY( QgsProviderRegistry::instance()->shouldDeferUriForOtherProviders( QStringLiteral( "/home/test/dataset.copc.laz" ), QStringLiteral( "ogr" ) ) );
}
void TestQgsCopcProvider::layerTypesForUri()
{
QgsProviderMetadata *copcMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "copc" ) );
QVERIFY( copcMetadata->capabilities() & QgsProviderMetadata::LayerTypesForUri );
QCOMPARE( copcMetadata->validLayerTypesForUri( QStringLiteral( "/home/test/cloud.copc.laz" ) ), QList< QgsMapLayerType >() << QgsMapLayerType::PointCloudLayer );
QCOMPARE( copcMetadata->validLayerTypesForUri( QStringLiteral( "/home/test/ept.json" ) ), QList< QgsMapLayerType >() );
}
void TestQgsCopcProvider::uriIsBlocklisted()
{
QVERIFY( !QgsProviderRegistry::instance()->uriIsBlocklisted( QStringLiteral( "/home/test/ept.json" ) ) );
QVERIFY( QgsProviderRegistry::instance()->uriIsBlocklisted( QStringLiteral( "/home/test/dataset.copc.laz" ) ) );
}
void TestQgsCopcProvider::querySublayers()
{
// test querying sub layers for a ept layer
QgsProviderMetadata *eptMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "copc" ) );
// invalid uri
QList< QgsProviderSublayerDetails >res = eptMetadata->querySublayers( QString() );
QVERIFY( res.empty() );
// not a copc layer
res = eptMetadata->querySublayers( QString( TEST_DATA_DIR ) + "/lines.shp" );
QVERIFY( res.empty() );
// valid copc layer
res = eptMetadata->querySublayers( mTestDataDir + "/point_clouds/copc/sunshine-coast.copc.laz" );
QCOMPARE( res.count(), 1 );
QCOMPARE( res.at( 0 ).name(), QStringLiteral( "sunshine-coast.copc" ) );
QCOMPARE( res.at( 0 ).uri(), mTestDataDir + "/point_clouds/copc/sunshine-coast.copc.laz" );
QCOMPARE( res.at( 0 ).providerKey(), QStringLiteral( "copc" ) );
QCOMPARE( res.at( 0 ).type(), QgsMapLayerType::PointCloudLayer );
// make sure result is valid to load layer from
const QgsProviderSublayerDetails::LayerOptions options{ QgsCoordinateTransformContext() };
std::unique_ptr< QgsPointCloudLayer > ml( qgis::down_cast< QgsPointCloudLayer * >( res.at( 0 ).toLayer( options ) ) );
QVERIFY( ml->isValid() );
}
void TestQgsCopcProvider::brokenPath()
{
// test loading a bad layer URI
std::unique_ptr< QgsPointCloudLayer > layer = std::make_unique< QgsPointCloudLayer >( QStringLiteral( "not valid" ), QStringLiteral( "layer" ), QStringLiteral( "copc" ) );
QVERIFY( !layer->isValid() );
}
void TestQgsCopcProvider::validLayer()
{
std::unique_ptr< QgsPointCloudLayer > layer = std::make_unique< QgsPointCloudLayer >( mTestDataDir + QStringLiteral( "point_clouds/copc/sunshine-coast.copc.laz" ), QStringLiteral( "layer" ), QStringLiteral( "copc" ) );
QVERIFY( layer->isValid() );
QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:28356" ) );
QGSCOMPARENEAR( layer->extent().xMinimum(), 498062.0, 0.1 );
QGSCOMPARENEAR( layer->extent().yMinimum(), 7050992.84, 0.1 );
QGSCOMPARENEAR( layer->extent().xMaximum(), 498067.39, 0.1 );
QGSCOMPARENEAR( layer->extent().yMaximum(), 7050997.04, 0.1 );
QCOMPARE( layer->dataProvider()->polygonBounds().asWkt( 0 ), QStringLiteral( "Polygon ((498062 7050993, 498067 7050993, 498067 7050997, 498062 7050997, 498062 7050993))" ) );
QCOMPARE( layer->dataProvider()->pointCount(), 253 );
QCOMPARE( layer->pointCount(), 253 );
QVERIFY( layer->dataProvider()->index() );
// all hierarchy is stored in a single node
QVERIFY( layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "0-0-0-0" ) ) );
QVERIFY( !layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "1-0-0-0" ) ) );
}
#include "qgscopcpointcloudindex.h"
void TestQgsCopcProvider::validLayerWithCopcHierarchy()
{
std::unique_ptr< QgsPointCloudLayer > layer = std::make_unique< QgsPointCloudLayer >( mTestDataDir + QStringLiteral( "point_clouds/copc/lone-star.copc.laz" ), QStringLiteral( "layer" ), QStringLiteral( "copc" ) );
QVERIFY( layer->isValid() );
QGSCOMPARENEAR( layer->extent().xMinimum(), 515368.6022, 0.1 );
QGSCOMPARENEAR( layer->extent().yMinimum(), 4918340.364, 0.1 );
QGSCOMPARENEAR( layer->extent().xMaximum(), 515401.043, 0.1 );
QGSCOMPARENEAR( layer->extent().yMaximum(), 4918381.124, 0.1 );
QVERIFY( layer->dataProvider()->index() );
// all hierarchy is stored in multiple nodes
QVERIFY( layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "1-1-1-0" ) ) );
QVERIFY( layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "2-3-3-1" ) ) );
}
void TestQgsCopcProvider::attributes()
{
std::unique_ptr< QgsPointCloudLayer > layer = std::make_unique< QgsPointCloudLayer >( mTestDataDir + QStringLiteral( "point_clouds/copc/sunshine-coast.copc.laz" ), QStringLiteral( "layer" ), QStringLiteral( "copc" ) );
QVERIFY( layer->isValid() );
const QgsPointCloudAttributeCollection attributes = layer->attributes();
QCOMPARE( attributes.count(), 16 );
QCOMPARE( attributes.at( 0 ).name(), QStringLiteral( "X" ) );
QCOMPARE( attributes.at( 0 ).type(), QgsPointCloudAttribute::Int32 );
QCOMPARE( attributes.at( 1 ).name(), QStringLiteral( "Y" ) );
QCOMPARE( attributes.at( 1 ).type(), QgsPointCloudAttribute::Int32 );
QCOMPARE( attributes.at( 2 ).name(), QStringLiteral( "Z" ) );
QCOMPARE( attributes.at( 2 ).type(), QgsPointCloudAttribute::Int32 );
QCOMPARE( attributes.at( 3 ).name(), QStringLiteral( "Intensity" ) );
QCOMPARE( attributes.at( 3 ).type(), QgsPointCloudAttribute::UShort );
QCOMPARE( attributes.at( 4 ).name(), QStringLiteral( "ReturnNumber" ) );
QCOMPARE( attributes.at( 4 ).type(), QgsPointCloudAttribute::Char );
QCOMPARE( attributes.at( 5 ).name(), QStringLiteral( "NumberOfReturns" ) );
QCOMPARE( attributes.at( 5 ).type(), QgsPointCloudAttribute::Char );
QCOMPARE( attributes.at( 6 ).name(), QStringLiteral( "ScanDirectionFlag" ) );
QCOMPARE( attributes.at( 6 ).type(), QgsPointCloudAttribute::Char );
QCOMPARE( attributes.at( 7 ).name(), QStringLiteral( "EdgeOfFlightLine" ) );
QCOMPARE( attributes.at( 7 ).type(), QgsPointCloudAttribute::Char );
QCOMPARE( attributes.at( 8 ).name(), QStringLiteral( "Classification" ) );
QCOMPARE( attributes.at( 8 ).type(), QgsPointCloudAttribute::Char );
QCOMPARE( attributes.at( 9 ).name(), QStringLiteral( "ScanAngleRank" ) );
QCOMPARE( attributes.at( 9 ).type(), QgsPointCloudAttribute::Short );
QCOMPARE( attributes.at( 10 ).name(), QStringLiteral( "UserData" ) );
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( "GpsTime" ) );
QCOMPARE( attributes.at( 12 ).type(), QgsPointCloudAttribute::Double );
QCOMPARE( attributes.at( 13 ).name(), QStringLiteral( "Red" ) );
QCOMPARE( attributes.at( 13 ).type(), QgsPointCloudAttribute::UShort );
QCOMPARE( attributes.at( 14 ).name(), QStringLiteral( "Green" ) );
QCOMPARE( attributes.at( 14 ).type(), QgsPointCloudAttribute::UShort );
QCOMPARE( attributes.at( 15 ).name(), QStringLiteral( "Blue" ) );
QCOMPARE( attributes.at( 15 ).type(), QgsPointCloudAttribute::UShort );
}
void TestQgsCopcProvider::calculateZRange()
{
std::unique_ptr< QgsPointCloudLayer > layer = std::make_unique< QgsPointCloudLayer >( mTestDataDir + QStringLiteral( "point_clouds/copc/sunshine-coast.copc.laz" ), QStringLiteral( "layer" ), QStringLiteral( "copc" ) );
QVERIFY( layer->isValid() );
QgsDoubleRange range = layer->elevationProperties()->calculateZRange( layer.get() );
QGSCOMPARENEAR( range.lower(), 74.34, 0.01 );
QGSCOMPARENEAR( range.upper(), 80.02, 0.01 );
static_cast< QgsPointCloudLayerElevationProperties * >( layer->elevationProperties() )->setZScale( 2 );
static_cast< QgsPointCloudLayerElevationProperties * >( layer->elevationProperties() )->setZOffset( 0.5 );
range = layer->elevationProperties()->calculateZRange( layer.get() );
QGSCOMPARENEAR( range.lower(), 149.18, 0.01 );
QGSCOMPARENEAR( range.upper(), 160.54, 0.01 );
}
void TestQgsCopcProvider::testIdentify_data()
{
QTest::addColumn<QString>( "datasetPath" );
QTest::newRow( "copc" ) << mTestDataDir + QStringLiteral( "point_clouds/copc/sunshine-coast.copc.laz" );
}
void TestQgsCopcProvider::testIdentify()
{
QFETCH( QString, datasetPath );
std::unique_ptr< QgsPointCloudLayer > layer = std::make_unique< QgsPointCloudLayer >( datasetPath, QStringLiteral( "layer" ), QStringLiteral( "copc" ) );
QVERIFY( layer->isValid() );
// identify 1 point click (rectangular point shape)
{
QgsPolygonXY polygon;
QVector<QgsPointXY> ring;
ring.push_back( QgsPointXY( 498062.50018404237926, 7050996.5845294082537 ) );
ring.push_back( QgsPointXY( 498062.5405028705718, 7050996.5845294082537 ) );
ring.push_back( QgsPointXY( 498062.5405028705718, 7050996.6248482363299 ) );
ring.push_back( QgsPointXY( 498062.50018404237926, 7050996.6248482363299 ) );
ring.push_back( QgsPointXY( 498062.50018404237926, 7050996.5845294082537 ) );
polygon.push_back( ring );
const float maxErrorInMapCoords = 0.0022857920266687870026;
QVector<QMap<QString, QVariant>> points = layer->dataProvider()->identify( maxErrorInMapCoords, QgsGeometry::fromPolygonXY( polygon ) );
QCOMPARE( points.size(), 1 );
const QMap<QString, QVariant> identifiedPoint = points[0];
QMap<QString, QVariant> expected;
expected[ QStringLiteral( "Blue" ) ] = 0;
expected[ QStringLiteral( "Classification" ) ] = 2;
expected[ QStringLiteral( "EdgeOfFlightLine" ) ] = 0;
expected[ QStringLiteral( "GpsTime" ) ] = 268793.37257748609409;
expected[ QStringLiteral( "Green" ) ] = 0;
expected[ QStringLiteral( "Intensity" ) ] = 1765;
expected[ QStringLiteral( "NumberOfReturns" ) ] = 1;
expected[ QStringLiteral( "PointSourceId" ) ] = 7041;
expected[ QStringLiteral( "Red" ) ] = 0;
expected[ QStringLiteral( "ReturnNumber" ) ] = 1;
expected[ QStringLiteral( "ScanAngleRank" ) ] = -59;
expected[ QStringLiteral( "ScanDirectionFlag" ) ] = 1;
expected[ QStringLiteral( "UserData" ) ] = 17;
expected[ QStringLiteral( "X" ) ] = 498062.52;
expected[ QStringLiteral( "Y" ) ] = 7050996.61;
expected[ QStringLiteral( "Z" ) ] = 75.0;
QVERIFY( identifiedPoint == expected );
}
// identify 1 point (circular point shape)
{
QPolygonF polygon;
polygon.push_back( QPointF( 498066.28873652569018, 7050994.9709538575262 ) );
polygon.push_back( QPointF( 498066.21890226693358, 7050995.0112726856023 ) );
polygon.push_back( QPointF( 498066.21890226693358, 7050995.0919103417546 ) );
polygon.push_back( QPointF( 498066.28873652569018, 7050995.1322291698307 ) );
polygon.push_back( QPointF( 498066.35857078444678, 7050995.0919103417546 ) );
polygon.push_back( QPointF( 498066.35857078444678, 7050995.0112726856023 ) );
polygon.push_back( QPointF( 498066.28873652569018, 7050994.9709538575262 ) );
const float maxErrorInMapCoords = 0.0091431681066751480103;
const QVector<QMap<QString, QVariant>> identifiedPoints = layer->dataProvider()->identify( maxErrorInMapCoords, QgsGeometry::fromQPolygonF( polygon ) );
QVector<QMap<QString, QVariant>> expected;
{
QMap<QString, QVariant> point;
point[ QStringLiteral( "Blue" ) ] = "0" ;
point[ QStringLiteral( "Classification" ) ] = "2" ;
point[ QStringLiteral( "EdgeOfFlightLine" ) ] = "0" ;
point[ QStringLiteral( "GpsTime" ) ] = "268793.3373408913" ;
point[ QStringLiteral( "Green" ) ] = "0" ;
point[ QStringLiteral( "Intensity" ) ] = "278" ;
point[ QStringLiteral( "NumberOfReturns" ) ] = "1" ;
point[ QStringLiteral( "PointSourceId" ) ] = "7041" ;
point[ QStringLiteral( "Red" ) ] = "0" ;
point[ QStringLiteral( "ReturnNumber" ) ] = "1" ;
point[ QStringLiteral( "ScanAngleRank" ) ] = "-59" ;
point[ QStringLiteral( "ScanDirectionFlag" ) ] = "1" ;
point[ QStringLiteral( "UserData" ) ] = "17" ;
point[ QStringLiteral( "X" ) ] = "498066.27" ;
point[ QStringLiteral( "Y" ) ] = "7050995.06" ;
point[ QStringLiteral( "Z" ) ] = "74.60" ;
expected.push_back( point );
}
// compare values using toDouble() so that fuzzy comparison is used in case of
// tiny rounding errors (e.g. 74.6 vs 74.60000000000001)
QCOMPARE( identifiedPoints.count(), 1 );
const QStringList keys = expected[0].keys();
for ( const QString &k : keys )
QCOMPARE( identifiedPoints[0][k].toDouble(), expected[0][k].toDouble() );
}
// test rectangle selection
{
QPolygonF polygon;
polygon.push_back( QPointF( 498063.24382022250211, 7050996.8638040581718 ) );
polygon.push_back( QPointF( 498063.02206666755956, 7050996.8638040581718 ) );
polygon.push_back( QPointF( 498063.02206666755956, 7050996.6360026793554 ) );
polygon.push_back( QPointF( 498063.24382022250211, 7050996.6360026793554 ) );
polygon.push_back( QPointF( 498063.24382022250211, 7050996.8638040581718 ) );
const float maxErrorInMapCoords = 0.0022857920266687870026;
const QVector<QMap<QString, QVariant>> identifiedPoints = layer->dataProvider()->identify( maxErrorInMapCoords, QgsGeometry::fromQPolygonF( polygon ) );
QVector<QMap<QString, QVariant>> expected;
{
QMap<QString, QVariant> point;
point[ QStringLiteral( "Blue" ) ] = "0" ;
point[ QStringLiteral( "Classification" ) ] = "2" ;
point[ QStringLiteral( "EdgeOfFlightLine" ) ] = "0" ;
point[ QStringLiteral( "GpsTime" ) ] = "268793.3813974548" ;
point[ QStringLiteral( "Green" ) ] = "0" ;
point[ QStringLiteral( "Intensity" ) ] = "1142" ;
point[ QStringLiteral( "NumberOfReturns" ) ] = "1" ;
point[ QStringLiteral( "PointSourceId" ) ] = "7041" ;
point[ QStringLiteral( "Red" ) ] = "0" ;
point[ QStringLiteral( "ReturnNumber" ) ] = "1" ;
point[ QStringLiteral( "ScanAngleRank" ) ] = "-59" ;
point[ QStringLiteral( "ScanDirectionFlag" ) ] = "1" ;
point[ QStringLiteral( "UserData" ) ] = "17" ;
point[ QStringLiteral( "X" ) ] = "498063.14" ;
point[ QStringLiteral( "Y" ) ] = "7050996.79" ;
point[ QStringLiteral( "Z" ) ] = "74.89" ;
expected.push_back( point );
}
{
QMap<QString, QVariant> point;
point[ QStringLiteral( "Blue" ) ] = "0" ;
point[ QStringLiteral( "Classification" ) ] = "3" ;
point[ QStringLiteral( "EdgeOfFlightLine" ) ] = "0" ;
point[ QStringLiteral( "GpsTime" ) ] = "269160.5176644815" ;
point[ QStringLiteral( "Green" ) ] = "0" ;
point[ QStringLiteral( "Intensity" ) ] = "1631" ;
point[ QStringLiteral( "NumberOfReturns" ) ] = "1" ;
point[ QStringLiteral( "PointSourceId" ) ] = "7042" ;
point[ QStringLiteral( "Red" ) ] = "0" ;
point[ QStringLiteral( "ReturnNumber" ) ] = "1" ;
point[ QStringLiteral( "ScanAngleRank" ) ] = "48" ;
point[ QStringLiteral( "ScanDirectionFlag" ) ] = "1" ;
point[ QStringLiteral( "UserData" ) ] = "17" ;
point[ QStringLiteral( "X" ) ] = "498063.11" ;
point[ QStringLiteral( "Y" ) ] = "7050996.75" ;
point[ QStringLiteral( "Z" ) ] = "74.90" ;
expected.push_back( point );
}
QVERIFY( expected.size() == identifiedPoints.size() );
const QStringList keys = expected[0].keys();
for ( int i = 0; i < expected.size(); ++i )
{
for ( const QString &k : keys )
QCOMPARE( identifiedPoints[i][k].toDouble(), expected[i][k].toDouble() );
}
}
}
// TODO: fix extrabytes tests
//void TestQgsCopcProvider::testExtraBytesAttributesExtraction()
//{
// {
// QString dataPath = mTestDataDir + QStringLiteral( "point_clouds/copc/extrabytes-dataset.copc.laz" );
// std::ifstream file( dataPath.toStdString(), std::ios::binary );
// QVector<QgsEptDecoder::ExtraBytesAttributeDetails> attributes = QgsEptDecoder::readExtraByteAttributes<std::ifstream>( file );
// for ( QgsEptDecoder::ExtraBytesAttributeDetails attr : attributes )
// {
// qDebug() << attr.attribute << " " << attr.type << " " << attr.size << " " << attr.offset;
// }
// QCOMPARE( attributes.size(), 4 );
// QCOMPARE( attributes[0].attribute, QStringLiteral( "Amplitude" ) );
// QCOMPARE( attributes[1].attribute, QStringLiteral( "Reflectance" ) );
// QCOMPARE( attributes[2].attribute, QStringLiteral( "ClassFlags" ) );
// QCOMPARE( attributes[3].attribute, QStringLiteral( "Deviation" ) );
// QCOMPARE( attributes[0].type, QgsPointCloudAttribute::Float );
// QCOMPARE( attributes[1].type, QgsPointCloudAttribute::Float );
// QCOMPARE( attributes[2].type, QgsPointCloudAttribute::UChar );
// QCOMPARE( attributes[3].type, QgsPointCloudAttribute::Float );
// QCOMPARE( attributes[0].size, 4 );
// QCOMPARE( attributes[1].size, 4 );
// QCOMPARE( attributes[2].size, 1 );
// QCOMPARE( attributes[3].size, 4 );
// QCOMPARE( attributes[0].offset, 43 );
// QCOMPARE( attributes[1].offset, 39 );
// QCOMPARE( attributes[2].offset, 38 );
// QCOMPARE( attributes[3].offset, 34 );
// }
// {
// QString dataPath = mTestDataDir + QStringLiteral( "point_clouds/copc/no-extrabytes-dataset.copc.laz" );
// std::ifstream file( dataPath.toStdString(), std::ios::binary );
// QVector<QgsEptDecoder::ExtraBytesAttributeDetails> attributes = QgsEptDecoder::readExtraByteAttributes<std::ifstream>( file );
// QCOMPARE( attributes.size(), 0 );
// }
//}
//void TestQgsCopcProvider::testExtraBytesAttributesValues()
//{
// QString dataPath = mTestDataDir + 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() );
// {
// for ( QgsPointCloudAttribute attr : layer->attributes().attributes() )
// {
// qDebug() << attr.name() << " " << attr.type();
// }
// const float maxErrorInMapCoords = 0.0015207174f;
// QPolygonF polygon;
// polygon.push_back( QPointF( 527919.2459517354, 6210983.5918774214 ) );
// polygon.push_back( QPointF( 527919.0742796324, 6210983.5918774214 ) );
// polygon.push_back( QPointF( 527919.0742796324, 6210983.4383113598 ) );
// polygon.push_back( QPointF( 527919.2459517354, 6210983.4383113598 ) );
// polygon.push_back( QPointF( 527919.2459517354, 6210983.5918774214 ) );
// const QVector<QMap<QString, QVariant>> identifiedPoints = layer->dataProvider()->identify( maxErrorInMapCoords, QgsGeometry::fromQPolygonF( polygon ) );
// QVector<QMap<QString, QVariant>> expectedPoints;
// {
// QMap<QString, QVariant> point;
// point[ QStringLiteral( "Amplitude" ) ] = "4.409999847412109" ;
// point[ QStringLiteral( "Blue" ) ] = "0" ;
// point[ QStringLiteral( "ClassFlags" ) ] = "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" ) ] = "-6" ;
// point[ QStringLiteral( "ScanDirectionFlag" ) ] = "0" ;
// point[ QStringLiteral( "UserData" ) ] = "0" ;
// point[ QStringLiteral( "X" ) ] = "527919.18" ;
// point[ QStringLiteral( "Y" ) ] = "6210983.47" ;
// point[ QStringLiteral( "Z" ) ] = "149.341" ;
// expectedPoints.push_back( point );
// }
// {
// QMap<QString, QVariant> point;
// point[ QStringLiteral( "Amplitude" ) ] = "14.170000076293945" ;
// point[ QStringLiteral( "Blue" ) ] = "0" ;
// point[ QStringLiteral( "ClassFlags" ) ] = "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" ) ] = "-6" ;
// point[ QStringLiteral( "ScanDirectionFlag" ) ] = "0" ;
// point[ QStringLiteral( "UserData" ) ] = "0" ;
// point[ QStringLiteral( "X" ) ] = "527919.11" ;
// point[ QStringLiteral( "Y" ) ] = "6210983.55" ;
// point[ QStringLiteral( "Z" ) ] = "147.111" ;
// expectedPoints.push_back( point );
// }
// QCOMPARE( identifiedPoints, expectedPoints );
// }
//}
void TestQgsCopcProvider::testPointCloudIndex()
{
std::unique_ptr< QgsPointCloudLayer > layer = std::make_unique< QgsPointCloudLayer >( mTestDataDir + QStringLiteral( "point_clouds/copc/lone-star.copc.laz" ), QStringLiteral( "layer" ), QStringLiteral( "copc" ) );
QVERIFY( layer->isValid() );
QgsPointCloudIndex *index = layer->dataProvider()->index();
QVERIFY( index->isValid() );
QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "0-0-0-0" ) ) ), 56721 );
QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "1-1-1-1" ) ) ), -1 );
QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "2-3-3-1" ) ) ), 446 );
QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "9-9-9-9" ) ) ), -1 );
QCOMPARE( index->pointCount(), 518862 );
QCOMPARE( index->zMin(), 2322.89625 );
QCOMPARE( index->zMax(), 2338.5755 );
QCOMPARE( index->scale().toVector3D(), QVector3D( 0.0001, 0.0001, 0.0001 ) );
QCOMPARE( index->offset().toVector3D(), QVector3D( 515385, 4918361, 2330.5 ) );
QCOMPARE( index->span(), 128 );
QCOMPARE( index->nodeError( IndexedPointCloudNode::fromString( QStringLiteral( "0-0-0-0" ) ) ), 0.328125 );
QCOMPARE( index->nodeError( IndexedPointCloudNode::fromString( QStringLiteral( "1-1-1-1" ) ) ), 0.1640625 );
QCOMPARE( index->nodeError( IndexedPointCloudNode::fromString( QStringLiteral( "2-3-3-1" ) ) ), 0.08203125 );
{
QgsPointCloudDataBounds bounds = index->nodeBounds( IndexedPointCloudNode::fromString( QStringLiteral( "0-0-0-0" ) ) );
QCOMPARE( bounds.xMin(), -170000 );
QCOMPARE( bounds.yMin(), -210000 );
QCOMPARE( bounds.zMin(), -85000 );
QCOMPARE( bounds.xMax(), 250000 );
QCOMPARE( bounds.yMax(), 210000 );
QCOMPARE( bounds.zMax(), 335000 );
}
{
QgsPointCloudDataBounds bounds = index->nodeBounds( IndexedPointCloudNode::fromString( QStringLiteral( "1-1-1-1" ) ) );
QCOMPARE( bounds.xMin(), 40000 );
QCOMPARE( bounds.yMin(), 0 );
QCOMPARE( bounds.zMin(), 125000 );
QCOMPARE( bounds.xMax(), 250000 );
QCOMPARE( bounds.yMax(), 210000 );
QCOMPARE( bounds.zMax(), 335000 );
}
{
QgsPointCloudDataBounds bounds = index->nodeBounds( IndexedPointCloudNode::fromString( QStringLiteral( "2-3-3-1" ) ) );
QCOMPARE( bounds.xMin(), 145000 );
QCOMPARE( bounds.yMin(), 105000 );
QCOMPARE( bounds.zMin(), 20000 );
QCOMPARE( bounds.xMax(), 250000 );
QCOMPARE( bounds.yMax(), 210000 );
QCOMPARE( bounds.zMax(), 125000 );
}
}
QGSTEST_MAIN( TestQgsCopcProvider )
#include "testqgscopcprovider.moc"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.