mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-22 00:06:12 -05:00
inital implementation of local COPC reading
This commit is contained in:
parent
995c2905ca
commit
cc9f5fbf12
@ -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")
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
346
src/core/pointcloud/qgscopcpointcloudindex.cpp
Normal file
346
src/core/pointcloud/qgscopcpointcloudindex.cpp
Normal 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
|
93
src/core/pointcloud/qgscopcpointcloudindex.h
Normal file
93
src/core/pointcloud/qgscopcpointcloudindex.h
Normal 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
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
241
src/core/providers/copc/qgscopcprovider.cpp
Normal file
241
src/core/providers/copc/qgscopcprovider.cpp
Normal 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
|
||||
|
88
src/core/providers/copc/qgscopcprovider.h
Normal file
88
src/core/providers/copc/qgscopcprovider.h
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
28
src/gui/providers/copc/qgscopcproviderguimetadata.cpp
Normal file
28
src/gui/providers/copc/qgscopcproviderguimetadata.cpp
Normal 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
|
37
src/gui/providers/copc/qgscopcproviderguimetadata.h
Normal file
37
src/gui/providers/copc/qgscopcproviderguimetadata.h
Normal 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
|
@ -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;
|
||||
|
@ -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() )
|
||||
{
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user