inital implementation of local COPC reading

This commit is contained in:
NEDJIMAbelgacem 2022-03-23 15:35:30 +01:00 committed by Martin Dobias
parent 995c2905ca
commit cc9f5fbf12
17 changed files with 1254 additions and 2 deletions

View File

@ -286,6 +286,8 @@ if(WITH_CORE)
set (WITH_EPT TRUE CACHE BOOL "Determines whether Entwine Point Cloud (EPT) support should be built")
set (WITH_COPC TRUE CACHE BOOL "Determines whether Cloud Optimized Point Cloud (COPC) support should be built")
set (WITH_THREAD_LOCAL TRUE CACHE BOOL "Determines whether std::thread_local should be used")
mark_as_advanced(WITH_THREAD_LOCAL)
@ -418,6 +420,15 @@ if(WITH_CORE)
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()
if (WITH_PDAL)
if (NOT WITH_EPT)
message(FATAL_ERROR "PDAL provider cannot be built with EPT disabled")

View File

@ -98,6 +98,8 @@
#cmakedefine HAVE_EPT
#cmakedefine HAVE_COPC
#cmakedefine HAVE_PDAL_QGIS
#define PDAL_VERSION "${PDAL_VERSION}"
#define PDAL_VERSION_MAJOR_INT ${PDAL_VERSION_MAJOR}

View File

@ -1973,6 +1973,55 @@ if (WITH_EPT)
add_definitions( -DWITH_EPT )
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 (APPLE)
# Libtasn1 is for DER-encoded PKI ASN.1 parsing/extracting workarounds
include_directories(SYSTEM
@ -2109,6 +2158,11 @@ if (WITH_EPT)
${CMAKE_SOURCE_DIR}/src/core/providers/ept)
endif()
if (WITH_COPC)
target_include_directories(qgis_core PUBLIC
${CMAKE_SOURCE_DIR}/src/core/providers/copc)
endif()
GENERATE_EXPORT_HEADER(
qgis_core
BASE_NAME CORE
@ -2229,6 +2283,15 @@ if (WITH_EPT)
endif()
endif()
if (WITH_COPC)
target_link_libraries(qgis_core
${ZSTD_LIBRARY}
)
if (LazPerf_FOUND)
target_link_libraries(qgis_core ${LazPerf_LIBRARY})
endif()
endif()
if (WITH_PDAL)
target_link_libraries(qgis_core
${PDAL_LIBRARIES}

View File

@ -0,0 +1,346 @@
/***************************************************************************
qgscopcpointcloudindex.cpp
--------------------
begin : March 2022
copyright : (C) 2022 by Belgacem Nedjima
email : belgacem dot nedjima 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 "qgscopcpointcloudindex.h"
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTime>
#include <QtDebug>
#include <QQueue>
#include "qgseptdecoder.h"
#include "qgscoordinatereferencesystem.h"
#include "qgspointcloudrequest.h"
#include "qgspointcloudattribute.h"
#include "qgslogger.h"
#include "qgsfeedback.h"
#include "qgsmessagelog.h"
#include "qgspointcloudexpression.h"
#include "lazperf/lazperf.hpp"
#include "lazperf/readers.hpp"
///@cond PRIVATE
#define PROVIDER_KEY QStringLiteral( "copc" )
#define PROVIDER_DESCRIPTION QStringLiteral( "COPC point cloud provider" )
QgsCopcPointCloudIndex::QgsCopcPointCloudIndex() = default;
QgsCopcPointCloudIndex::~QgsCopcPointCloudIndex() = default;
void QgsCopcPointCloudIndex::load( const QString &fileName )
{
QFile f( fileName );
if ( !f.open( QIODevice::ReadOnly ) )
{
QgsMessageLog::logMessage( tr( "Unable to open %1 for reading" ).arg( fileName ) );
mIsValid = false;
return;
}
mFileName = fileName;
bool success = loadSchema( fileName );
if ( success )
{
success = loadHierarchy( fileName );
}
mIsValid = success;
}
bool QgsCopcPointCloudIndex::loadSchema( const QString &filename )
{
std::ifstream file( filename.toStdString() );
lazperf::reader::generic_file f( file );
mDataType = QStringLiteral( "copc" );
mPointCount = f.header().point_count;
mScale = QgsVector3D( f.header().scale.x, f.header().scale.y, f.header().scale.z );
mOffset = QgsVector3D( f.header().offset.x, f.header().offset.y, f.header().offset.z );
// The COPC format only uses PDRF 6, 7 or 8. So there should be a OGC Coordinate System WKT record
mWkt = QString();
std::vector<char> wktRecordData = f.vlrData( "LASF_Projection", 2112 );
if ( !wktRecordData.empty() )
{
lazperf::wkt_vlr wktVlr;
wktVlr.fill( wktRecordData.data(), wktRecordData.size() );
mWkt = QString::fromStdString( wktVlr.wkt );
}
mExtent.set( f.header().minx, f.header().miny, f.header().maxx, f.header().maxy );
mZMin = f.header().minz;
mZMax = f.header().maxz;
// 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 ) );
switch ( f.header().point_format_id )
{
case 6:
break;
case 7:
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Red" ), QgsPointCloudAttribute::UShort ) );
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Green" ), QgsPointCloudAttribute::UShort ) );
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Blue" ), QgsPointCloudAttribute::UShort ) );
break;
case 8:
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Red" ), QgsPointCloudAttribute::UShort ) );
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Green" ), QgsPointCloudAttribute::UShort ) );
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "Blue" ), QgsPointCloudAttribute::UShort ) );
attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "NIR" ), QgsPointCloudAttribute::UShort ) );
break;
default:
return false;
}
// TODO: add extrabyte attributes
setAttributes( attributes );
std::vector<char> copcInfoVlrData = f.vlrData( "copc", 1 );
lazperf::copc_info_vlr copcInfoVlr;
copcInfoVlr.fill( copcInfoVlrData.data(), copcInfoVlrData.size() );
const double xmin = copcInfoVlr.center_x - copcInfoVlr.halfsize;
const double ymin = copcInfoVlr.center_y - copcInfoVlr.halfsize;
const double zmin = copcInfoVlr.center_z - copcInfoVlr.halfsize;
const double xmax = copcInfoVlr.center_x + copcInfoVlr.halfsize;
const double ymax = copcInfoVlr.center_y + copcInfoVlr.halfsize;
const double zmax = copcInfoVlr.center_z + copcInfoVlr.halfsize;
mRootBounds = QgsPointCloudDataBounds(
( xmin - mOffset.x() ) / mScale.x(),
( ymin - mOffset.y() ) / mScale.y(),
( zmin - mOffset.z() ) / mScale.z(),
( xmax - mOffset.x() ) / mScale.x(),
( ymax - mOffset.y() ) / mScale.y(),
( zmax - mOffset.z() ) / mScale.z()
);
double calculatedSpan = nodeMapExtent( root() ).width() / copcInfoVlr.spacing;
mSpan = calculatedSpan;
#ifdef QGIS_DEBUG
double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
QgsDebugMsgLevel( QStringLiteral( "lvl0 node size in CRS units: %1 %2 %3" ).arg( dx ).arg( dy ).arg( dz ), 2 ); // all dims should be the same
QgsDebugMsgLevel( QStringLiteral( "res at lvl0 %1" ).arg( dx / mSpan ), 2 );
QgsDebugMsgLevel( QStringLiteral( "res at lvl1 %1" ).arg( dx / mSpan / 2 ), 2 );
QgsDebugMsgLevel( QStringLiteral( "res at lvl2 %1 with node size %2" ).arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
#endif
return true;
}
QgsPointCloudBlock *QgsCopcPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
{
mHierarchyMutex.lock();
const bool found = mHierarchy.contains( n );
int pointCount = mHierarchy[n];
uint64_t blockOffset = mHierarchyNodeOffset[n];
int32_t blockSize = mHierarchyNodeByteSize[n];
mHierarchyMutex.unlock();
if ( !found )
return nullptr;
// we need to create a copy of the expression to pass to the decoder
// as the same QgsPointCloudExpression object mighgt be concurrently
// used on another thread, for example in a 3d view
QgsPointCloudExpression filterExpression = mFilterExpression;
QgsPointCloudAttributeCollection requestAttributes = request.attributes();
requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
return QgsEptDecoder::decompressCopc( mFileName, blockOffset, blockSize, pointCount, attributes(), requestAttributes, scale(), offset(), filterExpression );
}
QgsPointCloudBlockRequest *QgsCopcPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
{
Q_UNUSED( n );
Q_UNUSED( request );
Q_ASSERT( false );
return nullptr; // unsupported
}
QgsCoordinateReferenceSystem QgsCopcPointCloudIndex::crs() const
{
return QgsCoordinateReferenceSystem::fromWkt( mWkt );
}
qint64 QgsCopcPointCloudIndex::pointCount() const
{
return mPointCount;
}
QVariant QgsCopcPointCloudIndex::metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const
{
if ( !mMetadataStats.contains( attribute ) )
return QVariant();
const AttributeStatistics &stats = mMetadataStats[ attribute ];
switch ( statistic )
{
case QgsStatisticalSummary::Count:
return stats.count >= 0 ? QVariant( stats.count ) : QVariant();
case QgsStatisticalSummary::Mean:
return std::isnan( stats.mean ) ? QVariant() : QVariant( stats.mean );
case QgsStatisticalSummary::StDev:
return std::isnan( stats.stDev ) ? QVariant() : QVariant( stats.stDev );
case QgsStatisticalSummary::Min:
return stats.minimum;
case QgsStatisticalSummary::Max:
return stats.maximum;
case QgsStatisticalSummary::Range:
return stats.minimum.isValid() && stats.maximum.isValid() ? QVariant( stats.maximum.toDouble() - stats.minimum.toDouble() ) : QVariant();
case QgsStatisticalSummary::CountMissing:
case QgsStatisticalSummary::Sum:
case QgsStatisticalSummary::Median:
case QgsStatisticalSummary::StDevSample:
case QgsStatisticalSummary::Minority:
case QgsStatisticalSummary::Majority:
case QgsStatisticalSummary::Variety:
case QgsStatisticalSummary::FirstQuartile:
case QgsStatisticalSummary::ThirdQuartile:
case QgsStatisticalSummary::InterQuartileRange:
case QgsStatisticalSummary::First:
case QgsStatisticalSummary::Last:
case QgsStatisticalSummary::All:
return QVariant();
}
return QVariant();
}
QVariantList QgsCopcPointCloudIndex::metadataClasses( const QString &attribute ) const
{
QVariantList classes;
const QMap< int, int > values = mAttributeClasses.value( attribute );
for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
{
classes << it.key();
}
return classes;
}
QVariant QgsCopcPointCloudIndex::metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const
{
if ( statistic != QgsStatisticalSummary::Count )
return QVariant();
const QMap< int, int > values = mAttributeClasses.value( attribute );
if ( !values.contains( value.toInt() ) )
return QVariant();
return values.value( value.toInt() );
}
bool QgsCopcPointCloudIndex::loadHierarchy( const QString &filename )
{
std::ifstream file( filename.toStdString() );
lazperf::reader::generic_file f( file );
std::vector<char> copcInfoVlrData = f.vlrData( "copc", 1 );
lazperf::copc_info_vlr copcInfoVlr;
copcInfoVlr.fill( copcInfoVlrData.data(), copcInfoVlrData.size() );
QQueue<std::pair<uint64_t, uint64_t>> queue;
queue.push_back( std::make_pair( copcInfoVlr.root_hier_offset, copcInfoVlr.root_hier_size ) );
struct CopcVoxelKey
{
int32_t level;
int32_t x;
int32_t y;
int32_t z;
};
struct CopcEntry
{
CopcVoxelKey key;
uint64_t offset;
int32_t byteSize;
int32_t pointCount;
};
while ( !queue.isEmpty() )
{
auto [offset, size] = queue.dequeue();
file.seekg( offset );
std::unique_ptr<char> data( new char[ offset ] );
file.read( data.get(), size );
for ( uint64_t i = 0; i < size; i += sizeof( CopcEntry ) )
{
CopcEntry *entry = reinterpret_cast<CopcEntry *>( data.get() + i );
if ( entry->pointCount < 0 )
{
queue.enqueue( std::make_pair( entry->offset, entry->byteSize ) );
}
else if ( entry->pointCount > 0 )
{
const IndexedPointCloudNode nodeId( entry->key.level, entry->key.x, entry->key.y, entry->key.z );
mHierarchyMutex.lock();
mHierarchy[nodeId] = entry->pointCount;
mHierarchyNodeOffset[nodeId] = entry->offset;
mHierarchyNodeByteSize[nodeId] = entry->byteSize;
mHierarchyMutex.unlock();
}
}
}
return true;
}
bool QgsCopcPointCloudIndex::isValid() const
{
return mIsValid;
}
///@endcond

View File

@ -0,0 +1,93 @@
/***************************************************************************
qgscopcpointcloudindex.h
--------------------
begin : March 2022
copyright : (C) 2022 by Belgacem Nedjima
email : belgacem dot nedjima 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. *
* *
***************************************************************************/
#ifndef QGSCOPCPOINTCLOUDINDEX_H
#define QGSCOPCPOINTCLOUDINDEX_H
#include <QObject>
#include <QString>
#include <QHash>
#include <QStringList>
#include <QVector>
#include <QList>
#include <QFile>
#include "qgspointcloudindex.h"
#include "qgspointcloudattribute.h"
#include "qgsstatisticalsummary.h"
#include "qgis_sip.h"
///@cond PRIVATE
#define SIP_NO_FILE
class QgsCoordinateReferenceSystem;
class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex
{
Q_OBJECT
public:
explicit QgsCopcPointCloudIndex();
~QgsCopcPointCloudIndex();
void load( const QString &fileName ) override;
QgsPointCloudBlock *nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override;
QgsPointCloudBlockRequest *asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override;
QgsCoordinateReferenceSystem crs() const override;
qint64 pointCount() const override;
QVariant metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const override;
QVariantList metadataClasses( const QString &attribute ) const override;
QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const override;
QVariantMap originalMetadata() const override { return mOriginalMetadata; }
bool isValid() const override;
QgsPointCloudIndex::AccessType accessType() const override { return QgsPointCloudIndex::Local; };
protected:
bool loadSchema( const QString &filename );
bool loadHierarchy( const QString &filename );
bool mIsValid = false;
QString mDataType;
QString mFileName;
QString mWkt;
qint64 mPointCount = 0;
mutable QHash<IndexedPointCloudNode, uint64_t> mHierarchyNodeOffset; //!< Additional data hierarchy for COPC
mutable QHash<IndexedPointCloudNode, int32_t> mHierarchyNodeByteSize; //!< Additional data hierarchy for COPC
struct AttributeStatistics
{
int count = -1;
QVariant minimum;
QVariant maximum;
double mean = std::numeric_limits< double >::quiet_NaN();
double stDev = std::numeric_limits< double >::quiet_NaN();
double variance = std::numeric_limits< double >::quiet_NaN();
};
QMap< QString, AttributeStatistics > mMetadataStats;
QMap< QString, QMap< int, int > > mAttributeClasses;
QVariantMap mOriginalMetadata;
};
///@endcond
#endif // QGSCOPCPOINTCLOUDINDEX_H

View File

@ -720,4 +720,303 @@ QgsPointCloudBlock *QgsEptDecoder::decompressLaz( const QByteArray &byteArrayDat
return __decompressLaz<std::istringstream>( file, attributes, requestedAttributes, scale, offset, filterExpression );
}
QgsPointCloudBlock *QgsEptDecoder::decompressCopc( const QString &filename, uint64_t blockOffset, uint64_t blockSize, int32_t pointCount, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &_scale, const QgsVector3D &_offset, QgsPointCloudExpression &filterExpression )
{
Q_UNUSED( attributes );
Q_UNUSED( _scale );
Q_UNUSED( _offset );
std::ifstream file( filename.toStdString(), std::ios::binary );
lazperf::reader::generic_file f( file );
std::unique_ptr<char> data( new char[ blockSize ] );
file.seekg( blockOffset );
file.read( data.get(), blockSize );
std::unique_ptr<char> decodedData( new char[ f.header().point_record_length ] );
lazperf::reader::chunk_decompressor decompressor( f.header().pointFormat(), f.header().ebCount(), data.get() );
const QgsVector3D hScale( f.header().scale.x, f.header().scale.y, f.header().scale.z );
const QgsVector3D hOffset( f.header().offset.x, f.header().offset.y, f.header().offset.z );
const size_t requestedPointRecordSize = requestedAttributes.pointRecordSize();
QByteArray blockData;
blockData.resize( requestedPointRecordSize * pointCount );
char *dataBuffer = blockData.data();
const QVector<QgsPointCloudAttribute> requestedAttributesVector = requestedAttributes.attributes();
std::size_t outputOffset = 0;
enum class LazAttribute
{
X,
Y,
Z,
Classification,
Intensity,
ReturnNumber,
NumberOfReturns,
ScanDirectionFlag,
EdgeOfFlightLine,
ScanAngleRank,
UserData,
PointSourceId,
GpsTime,
Red,
Green,
Blue,
ExtraBytes,
MissingOrUnknown
};
struct RequestedAttributeDetails
{
RequestedAttributeDetails( LazAttribute attribute, QgsPointCloudAttribute::DataType type, int size, int offset = -1 )
: attribute( attribute )
, type( type )
, size( size )
, offset( offset )
{}
LazAttribute attribute;
QgsPointCloudAttribute::DataType type;
int size;
int offset; // Used in case the attribute is an extra byte attribute
};
QVector<QgsEptDecoder::ExtraBytesAttributeDetails> extrabytesAttr = QgsEptDecoder::readExtraByteAttributes<std::ifstream>( file );
std::vector< RequestedAttributeDetails > requestedAttributeDetails;
requestedAttributeDetails.reserve( requestedAttributesVector.size() );
for ( const QgsPointCloudAttribute &requestedAttribute : requestedAttributesVector )
{
if ( requestedAttribute.name().compare( QLatin1String( "X" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::X, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "Y" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Y, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "Z" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Z, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "Classification" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Classification, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "Intensity" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Intensity, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "ReturnNumber" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ReturnNumber, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "NumberOfReturns" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::NumberOfReturns, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "ScanDirectionFlag" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ScanDirectionFlag, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "EdgeOfFlightLine" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::EdgeOfFlightLine, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "ScanAngleRank" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ScanAngleRank, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "UserData" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::UserData, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "PointSourceId" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::PointSourceId, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "GpsTime" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::GpsTime, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "Red" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Red, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "Green" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Green, requestedAttribute.type(), requestedAttribute.size() ) );
}
else if ( requestedAttribute.name().compare( QLatin1String( "Blue" ), Qt::CaseInsensitive ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::Blue, requestedAttribute.type(), requestedAttribute.size() ) );
}
else
{
bool foundAttr = false;
for ( QgsEptDecoder::ExtraBytesAttributeDetails &eba : extrabytesAttr )
{
if ( requestedAttribute.name().compare( eba.attribute.trimmed() ) == 0 )
{
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::ExtraBytes, eba.type, eba.size, eba.offset ) );
foundAttr = true;
break;
}
}
if ( !foundAttr )
{
// this can possibly happen -- e.g. if a style built using a different point cloud format references an attribute which isn't available from the laz file
requestedAttributeDetails.emplace_back( RequestedAttributeDetails( LazAttribute::MissingOrUnknown, requestedAttribute.type(), requestedAttribute.size() ) );
}
}
}
std::unique_ptr< QgsPointCloudBlock > block = std::make_unique< QgsPointCloudBlock >(
pointCount,
requestedAttributes,
blockData, hScale, hOffset
);
int skippedPoints = 0;
const bool filterIsValid = filterExpression.isValid();
if ( !filterExpression.prepare( block.get() ) && filterIsValid )
{
// skip processing if the expression cannot be prepared
block->setPointCount( 0 );
return block.release();
}
lazperf::las::point14 p;
lazperf::las::gpstime gps;
lazperf::las::rgb rgb;
for ( int i = 0 ; i < pointCount; ++i )
{
decompressor.decompress( decodedData.get() );
char *buf = decodedData.get();
p.unpack( buf );
gps.unpack( buf + sizeof( lazperf::las::point14 ) );
rgb.unpack( buf + sizeof( lazperf::las::point14 ) + sizeof( lazperf::las::gpstime ) );
for ( const RequestedAttributeDetails &requestedAttribute : requestedAttributeDetails )
{
switch ( requestedAttribute.attribute )
{
case LazAttribute::X:
_storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, p.x() );
break;
case LazAttribute::Y:
_storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, p.y() );
break;
case LazAttribute::Z:
_storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, p.z() );
break;
case LazAttribute::Classification:
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.classification() );
break;
case LazAttribute::Intensity:
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, p.intensity() );
break;
case LazAttribute::ReturnNumber:
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.returnNum() );
break;
case LazAttribute::NumberOfReturns:
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.numReturns() );
break;
case LazAttribute::ScanDirectionFlag:
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.scanDirFlag() );
break;
case LazAttribute::EdgeOfFlightLine:
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.eofFlag() );
break;
case LazAttribute::ScanAngleRank:
_storeToStream<char>( dataBuffer, outputOffset, requestedAttribute.type, p.scanAngle() );
break;
case LazAttribute::UserData:
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, p.userData() );
break;
case LazAttribute::PointSourceId:
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, p.pointSourceID() );
break;
case LazAttribute::GpsTime:
// lazperf internally stores gps value as int64 field, but in fact it is a double value
_storeToStream<double>( dataBuffer, outputOffset, requestedAttribute.type,
*reinterpret_cast<const double *>( reinterpret_cast<const void *>( &gps.value ) ) );
break;
case LazAttribute::Red:
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.r );
break;
case LazAttribute::Green:
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.g );
break;
case LazAttribute::Blue:
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, rgb.b );
break;
case LazAttribute::ExtraBytes:
{
switch ( requestedAttribute.type )
{
case QgsPointCloudAttribute::Char:
_storeToStream<char>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<char * >( &buf[requestedAttribute.offset] ) );
break;
case QgsPointCloudAttribute::UChar:
_storeToStream<unsigned char>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<unsigned char * >( &buf[requestedAttribute.offset] ) );
break;
case QgsPointCloudAttribute::Short:
_storeToStream<qint16>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint16 * >( &buf[requestedAttribute.offset] ) );
break;
case QgsPointCloudAttribute::UShort:
_storeToStream<quint16>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint16 * >( &buf[requestedAttribute.offset] ) );
break;
case QgsPointCloudAttribute::Int32:
_storeToStream<qint32>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint32 * >( &buf[requestedAttribute.offset] ) );
break;
case QgsPointCloudAttribute::UInt32:
_storeToStream<quint32>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint32 * >( &buf[requestedAttribute.offset] ) );
break;
case QgsPointCloudAttribute::Int64:
_storeToStream<qint64>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<qint64 * >( &buf[requestedAttribute.offset] ) );
break;
case QgsPointCloudAttribute::UInt64:
_storeToStream<quint64>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<quint64 * >( &buf[requestedAttribute.offset] ) );
break;
case QgsPointCloudAttribute::Float:
_storeToStream<float>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<float * >( &buf[requestedAttribute.offset] ) );
break;
case QgsPointCloudAttribute::Double:
_storeToStream<double>( dataBuffer, outputOffset, requestedAttribute.type, *reinterpret_cast<double * >( &buf[requestedAttribute.offset] ) );
break;
}
}
break;
case LazAttribute::MissingOrUnknown:
// just store 0 for unknown/missing attributes
_storeToStream<unsigned short>( dataBuffer, outputOffset, requestedAttribute.type, 0 );
break;
}
outputOffset += requestedAttribute.size;
}
// check if point needs to be filtered out
if ( filterIsValid )
{
// we're always evaluating the last written point in the buffer
double eval = filterExpression.evaluate( i - skippedPoints );
if ( !eval || std::isnan( eval ) )
{
// if the point is filtered out, rewind the offset so the next point is written over it
outputOffset -= requestedPointRecordSize;
++skippedPoints;
}
}
}
block->setPointCount( pointCount - skippedPoints );
return block.release();
}
///@endcond

View File

@ -50,6 +50,7 @@ namespace QgsEptDecoder
QgsPointCloudBlock *decompressZStandard( const QByteArray &data, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression );
QgsPointCloudBlock *decompressLaz( const QString &filename, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression );
QgsPointCloudBlock *decompressLaz( const QByteArray &data, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression );
QgsPointCloudBlock *decompressCopc( const QString &filename, uint64_t blockOffset, uint64_t blockSize, int32_t pointCount, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &_scale, const QgsVector3D &_offset, QgsPointCloudExpression &filterExpression );
//! Returns the list of extrabytes attributes with their type, size and offsets represented in the LAS file
template<typename FileType>

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)
float nodeError( const IndexedPointCloudNode &n ) const;
virtual float nodeError( const IndexedPointCloudNode &n ) const;
//! Returns scale
QgsVector3D scale() const;

View File

@ -0,0 +1,241 @@
/***************************************************************************
qgscopcdataprovider.cpp
-----------------------
begin : March 2022
copyright : (C) 2022 by Belgacem Nedjima
email : belgacem dot nedjima 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 "qgis.h"
#include "qgslogger.h"
#include "qgsproviderregistry.h"
#include "qgscopcprovider.h"
#include "qgscopcpointcloudindex.h"
#include "qgsruntimeprofiler.h"
#include "qgsapplication.h"
#include "qgsprovidersublayerdetails.h"
#include "qgsproviderutils.h"
#include <QFileInfo>
///@cond PRIVATE
#define PROVIDER_KEY QStringLiteral( "copc" )
#define PROVIDER_DESCRIPTION QStringLiteral( "COPC point cloud data provider" )
QgsCopcProvider::QgsCopcProvider(
const QString &uri,
const QgsDataProvider::ProviderOptions &options,
QgsDataProvider::ReadFlags flags )
: QgsPointCloudDataProvider( uri, options, flags )
{
if ( uri.startsWith( QStringLiteral( "http" ), Qt::CaseSensitivity::CaseInsensitive ) )
mIndex.reset( nullptr );
else
mIndex.reset( new QgsCopcPointCloudIndex );
std::unique_ptr< QgsScopedRuntimeProfile > profile;
if ( QgsApplication::profiler()->groupIsActive( QStringLiteral( "projectload" ) ) )
profile = std::make_unique< QgsScopedRuntimeProfile >( tr( "Open data source" ), QStringLiteral( "projectload" ) );
loadIndex( );
}
QgsCopcProvider::~QgsCopcProvider() = default;
QgsCoordinateReferenceSystem QgsCopcProvider::crs() const
{
return mIndex->crs();
}
QgsRectangle QgsCopcProvider::extent() const
{
return mIndex->extent();
}
QgsPointCloudAttributeCollection QgsCopcProvider::attributes() const
{
return mIndex->attributes();
}
bool QgsCopcProvider::isValid() const
{
if ( !mIndex.get() )
{
return false;
}
return mIndex->isValid();
}
QString QgsCopcProvider::name() const
{
return QStringLiteral( "copc" );
}
QString QgsCopcProvider::description() const
{
return QStringLiteral( "Point Clouds COPC" );
}
QgsPointCloudIndex *QgsCopcProvider::index() const
{
return mIndex.get();
}
qint64 QgsCopcProvider::pointCount() const
{
return mIndex->pointCount();
}
QVariantList QgsCopcProvider::metadataClasses( const QString &attribute ) const
{
return mIndex->metadataClasses( attribute );
}
QVariant QgsCopcProvider::metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const
{
return mIndex->metadataClassStatistic( attribute, value, statistic );
}
void QgsCopcProvider::loadIndex( )
{
if ( mIndex->isValid() )
return;
mIndex->load( dataSourceUri() );
// TODO: Read metadata from LAZ into mOriginalMetadata
// NOTE: This is not done in EPT provider either
}
QVariantMap QgsCopcProvider::originalMetadata() const
{
return mOriginalMetadata;
}
void QgsCopcProvider::generateIndex()
{
//no-op, index is always generated
}
QVariant QgsCopcProvider::metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const
{
return mIndex->metadataStatistic( attribute, statistic );
}
QgsCopcProviderMetadata::QgsCopcProviderMetadata():
QgsProviderMetadata( PROVIDER_KEY, PROVIDER_DESCRIPTION )
{
}
QgsCopcProvider *QgsCopcProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
{
return new QgsCopcProvider( uri, options, flags );
}
QList<QgsProviderSublayerDetails> QgsCopcProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags, QgsFeedback * ) const
{
const QVariantMap parts = decodeUri( uri );
if ( parts.value( QStringLiteral( "isCopc" ), false ).toBool() )
{
QgsProviderSublayerDetails details;
details.setUri( uri );
details.setProviderKey( QStringLiteral( "copc" ) );
details.setType( QgsMapLayerType::PointCloudLayer );
details.setName( QgsProviderUtils::suggestLayerNameFromFilePath( uri ) );
return {details};
}
else
{
return {};
}
}
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;
return 0;
}
QList<QgsMapLayerType> QgsCopcProviderMetadata::validLayerTypesForUri( 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 QList< QgsMapLayerType>() << QgsMapLayerType::PointCloudLayer;
return QList< QgsMapLayerType>();
}
bool QgsCopcProviderMetadata::uriIsBlocklisted( const QString &uri ) const
{
const QVariantMap parts = decodeUri( uri );
if ( !parts.contains( QStringLiteral( "path" ) ) )
return false;
const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );
// internal details only
if ( fi.exists() && parts.value( QStringLiteral( "isCopc" ), false ).toBool() )
return true;
return false;
}
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;
}
QString QgsCopcProviderMetadata::filters( QgsProviderMetadata::FilterType type )
{
switch ( type )
{
case QgsProviderMetadata::FilterType::FilterVector:
case QgsProviderMetadata::FilterType::FilterRaster:
case QgsProviderMetadata::FilterType::FilterMesh:
case QgsProviderMetadata::FilterType::FilterMeshDataset:
return QString();
case QgsProviderMetadata::FilterType::FilterPointCloud:
return QObject::tr( "COPC Point Clouds" ) + QStringLiteral( "COPC LAZ files (*.copc.laz *.COPC.LAZ)" );
}
return QString();
}
QgsProviderMetadata::ProviderCapabilities QgsCopcProviderMetadata::providerCapabilities() const
{
return FileBasedUris;
}
QString QgsCopcProviderMetadata::encodeUri( const QVariantMap &parts ) const
{
const QString path = parts.value( QStringLiteral( "path" ) ).toString();
return path;
}
QgsProviderMetadata::ProviderMetadataCapabilities QgsCopcProviderMetadata::capabilities() const
{
return ProviderMetadataCapability::LayerTypesForUri
| ProviderMetadataCapability::PriorityForUri
| ProviderMetadataCapability::QuerySublayers;
}
///@endcond

View File

@ -0,0 +1,88 @@
/***************************************************************************
qgscopcdataprovider.h
---------------------
begin : March 2022
copyright : (C) 2022 by Belgacem Nedjima
email : belgacem dot nedjima 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. *
* *
***************************************************************************/
#ifndef QGSCOPCPROVIDER_H
#define QGSCOPCPROVIDER_H
#include "qgis_core.h"
#include "qgspointclouddataprovider.h"
#include "qgsprovidermetadata.h"
#include <memory>
#include "qgis_sip.h"
///@cond PRIVATE
#define SIP_NO_FILE
class QgsCopcPointCloudIndex;
class QgsRemoteCopcPointCloudIndex;
class QgsCopcProvider: public QgsPointCloudDataProvider
{
Q_OBJECT
public:
QgsCopcProvider( const QString &uri,
const QgsDataProvider::ProviderOptions &providerOptions,
QgsDataProvider::ReadFlags flags = QgsDataProvider::ReadFlags() );
~QgsCopcProvider();
QgsCoordinateReferenceSystem crs() const override;
QgsRectangle extent() const override;
QgsPointCloudAttributeCollection attributes() const override;
bool isValid() const override;
QString name() const override;
QString description() const override;
QgsPointCloudIndex *index() const override;
qint64 pointCount() const override;
QVariant metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const override;
QVariantList metadataClasses( const QString &attribute ) const override;
QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const override;
QVariantMap originalMetadata() const override;
void loadIndex( ) override;
void generateIndex( ) override;
PointCloudIndexGenerationState indexingState( ) override { return PointCloudIndexGenerationState::Indexed; }
private:
QVariantMap mOriginalMetadata;
std::unique_ptr<QgsPointCloudIndex> mIndex;
QgsRectangle mExtent;
uint64_t mPointCount;
QgsCoordinateReferenceSystem mCrs;
};
class QgsCopcProviderMetadata : public QgsProviderMetadata
{
public:
QgsCopcProviderMetadata();
QgsProviderMetadata::ProviderMetadataCapabilities capabilities() const override;
QgsCopcProvider *createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags = QgsDataProvider::ReadFlags() ) override;
QList< QgsProviderSublayerDetails > querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags = Qgis::SublayerQueryFlags(), QgsFeedback *feedback = nullptr ) const override;
int priorityForUri( const QString &uri ) const override;
QList< QgsMapLayerType > validLayerTypesForUri( const QString &uri ) const override;
bool uriIsBlocklisted( const QString &uri ) const override;
QString encodeUri( const QVariantMap &parts ) const override;
QVariantMap decodeUri( const QString &uri ) const override;
QString filters( FilterType type ) override;
ProviderCapabilities providerCapabilities() const override;
};
///@endcond
#endif // QGSCOPCPROVIDER_H

View File

@ -39,6 +39,10 @@
#include "providers/ept/qgseptprovider.h"
#endif
#ifdef HAVE_COPC
#include "providers/copc/qgscopcprovider.h"
#endif
#include "qgsruntimeprofiler.h"
#include "qgsfileutils.h"
@ -197,7 +201,13 @@ void QgsProviderRegistry::init()
mProviders[ pc->key() ] = pc;
}
#endif
#ifdef HAVE_COPC
{
const QgsScopedRuntimeProfile profile( QObject::tr( "Create COPC point cloud provider" ) );
QgsProviderMetadata *pc = new QgsCopcProviderMetadata();
mProviders[ pc->key() ] = pc;
}
#endif
registerUnusableUriHandler( new PdalUnusableUriHandlerInterface() );
#ifdef HAVE_STATIC_PROVIDERS

View File

@ -1427,6 +1427,15 @@ if (WITH_EPT)
)
endif()
if (WITH_COPC)
set(QGIS_GUI_SRCS ${QGIS_GUI_SRCS}
providers/copc/qgscopcproviderguimetadata.cpp
)
set(QGIS_GUI_HDRS ${QGIS_GUI_HDRS}
providers/copc/qgscopcproviderguimetadata.h
)
endif()
# disable deprecation warnings for classes re-exporting deprecated methods
if(MSVC)
@ -1512,6 +1521,12 @@ if (WITH_EPT)
)
endif()
if (WITH_COPC)
target_include_directories(qgis_gui PUBLIC
${CMAKE_SOURCE_DIR}/src/gui/providers/copc
)
endif()
GENERATE_EXPORT_HEADER(
qgis_gui

View File

@ -0,0 +1,28 @@
/***************************************************************************
qgscopcproviderguimetadata.cpp
--------------------
begin : March 2022
copyright : (C) 2022 by Belgacem Nedjima
email : belgacem dot nedjima 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 "qgsapplication.h"
#include "qgscopcproviderguimetadata.h"
///@cond PRIVATE
QgsCopcProviderGuiMetadata::QgsCopcProviderGuiMetadata()
: QgsProviderGuiMetadata( QStringLiteral( "copc" ) )
{
}
///@endcond

View File

@ -0,0 +1,37 @@
/***************************************************************************
qgscopcproviderguimetadata.h
--------------------
begin : March 2022
copyright : (C) 2020 by Belgacem Nedjima
email : belgacem dot nedjima 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. *
* *
***************************************************************************/
#ifndef QGSCOPCPROVIDERGUIMETADATA_H
#define QGSCOPCPROVIDERGUIMETADATA_H
///@cond PRIVATE
#define SIP_NO_FILE
#include <QList>
#include <QMainWindow>
#include "qgsproviderguimetadata.h"
class QgsCopcProviderGuiMetadata: public QgsProviderGuiMetadata
{
public:
QgsCopcProviderGuiMetadata();
};
///@endcond
#endif // QGSCopcPROVIDERGUIMETADATA_H

View File

@ -78,6 +78,10 @@ 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

@ -33,6 +33,10 @@
#include "qgseptproviderguimetadata.h"
#endif
#ifdef HAVE_COPC
#include "qgscopcproviderguimetadata.h"
#endif
#ifdef HAVE_STATIC_PROVIDERS
#include "qgswmsprovidergui.h"
#include "qgswcsprovidergui.h"
@ -92,6 +96,11 @@ void QgsProviderGuiRegistry::loadStaticProviders( )
mProviders[ ept->key() ] = ept;
#endif
#ifdef HAVE_COPC
QgsProviderGuiMetadata *copc = new QgsCopcProviderGuiMetadata();
mProviders[ ept->key() ] = copc;
#endif
// only show point cloud option if we have at least one point cloud provider available!
if ( !QgsProviderRegistry::instance()->filePointCloudFilters().isEmpty() )
{

View File

@ -58,6 +58,11 @@ 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_PDAL)
include_directories(
${CMAKE_SOURCE_DIR}/src/providers/pdal