24#include <QMutexLocker> 
   25#include <QJsonDocument> 
   36#include "qgspointcloudexpression.h" 
   38#include "lazperf/lazperf.hpp" 
   39#include "lazperf/readers.hpp" 
   40#include "lazperf/vlr.hpp" 
   44#define PROVIDER_KEY QStringLiteral( "copc" )
 
   45#define PROVIDER_DESCRIPTION QStringLiteral( "COPC point cloud provider" )
 
   47QgsCopcPointCloudIndex::QgsCopcPointCloudIndex() = 
default;
 
   49QgsCopcPointCloudIndex::~QgsCopcPointCloudIndex() = 
default;
 
   51std::unique_ptr<QgsPointCloudIndex> QgsCopcPointCloudIndex::clone()
 const 
   53  QgsCopcPointCloudIndex *clone = 
new QgsCopcPointCloudIndex;
 
   54  QMutexLocker locker( &mHierarchyMutex );
 
   55  copyCommonProperties( clone );
 
   56  return std::unique_ptr<QgsPointCloudIndex>( clone );
 
   59void QgsCopcPointCloudIndex::load( 
const QString &fileName )
 
   62  mCopcFile.open( QgsLazDecoder::toNativePath( fileName ), std::ios::binary );
 
   64  if ( !mCopcFile.is_open() || !mCopcFile.good() )
 
   66    mError = tr( 
"Unable to open %1 for reading" ).arg( fileName );
 
   72  mIsValid = mLazInfo->isValid();
 
   75    mIsValid = loadSchema( *mLazInfo.get() );
 
   79    mError = tr( 
"Unable to recognize %1 as a LAZ file: \"%2\"" ).arg( fileName, mLazInfo->error() );
 
   86bool QgsCopcPointCloudIndex::loadSchema( 
QgsLazInfo &lazInfo )
 
   88  QByteArray copcInfoVlrData = lazInfo.
vlrData( QStringLiteral( 
"copc" ), 1 );
 
   89  if ( copcInfoVlrData.isEmpty() )
 
   91    mError = tr( 
"Invalid COPC file" );
 
   94  mCopcInfoVlr.fill( copcInfoVlrData.data(), copcInfoVlrData.size() );
 
   96  mScale = lazInfo.
scale();
 
   97  mOffset = lazInfo.
offset();
 
  103  mExtent.
set( minCoords.
x(), minCoords.
y(), maxCoords.
x(), maxCoords.
y() );
 
  104  mZMin = minCoords.
z();
 
  105  mZMax = maxCoords.
z();
 
  109  const double xmin = mCopcInfoVlr.center_x - mCopcInfoVlr.halfsize;
 
  110  const double ymin = mCopcInfoVlr.center_y - mCopcInfoVlr.halfsize;
 
  111  const double zmin = mCopcInfoVlr.center_z - mCopcInfoVlr.halfsize;
 
  112  const double xmax = mCopcInfoVlr.center_x + mCopcInfoVlr.halfsize;
 
  113  const double ymax = mCopcInfoVlr.center_y + mCopcInfoVlr.halfsize;
 
  114  const double zmax = mCopcInfoVlr.center_z + mCopcInfoVlr.halfsize;
 
  117                  ( xmin - mOffset.x() ) / mScale.x(),
 
  118                  ( ymin - mOffset.y() ) / mScale.y(),
 
  119                  ( zmin - mOffset.z() ) / mScale.z(),
 
  120                  ( xmax - mOffset.x() ) / mScale.x(),
 
  121                  ( ymax - mOffset.y() ) / mScale.y(),
 
  122                  ( zmax - mOffset.z() ) / mScale.z()
 
  125  double calculatedSpan = nodeMapExtent( root() ).width() / mCopcInfoVlr.spacing;
 
  126  mSpan = calculatedSpan;
 
  129  double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
 
  130  QgsDebugMsgLevel( QStringLiteral( 
"lvl0 node size in CRS units: %1 %2 %3" ).arg( dx ).arg( dy ).arg( dz ), 2 );    
 
  131  QgsDebugMsgLevel( QStringLiteral( 
"res at lvl0 %1" ).arg( dx / mSpan ), 2 );
 
  132  QgsDebugMsgLevel( QStringLiteral( 
"res at lvl1 %1" ).arg( dx / mSpan / 2 ), 2 );
 
  133  QgsDebugMsgLevel( QStringLiteral( 
"res at lvl2 %1 with node size %2" ).arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
 
  141  const bool found = fetchNodeHierarchy( n );
 
  144  mHierarchyMutex.lock();
 
  145  int pointCount = mHierarchy.value( n );
 
  146  auto [blockOffset, blockSize] = mHierarchyNodePos.value( n );
 
  147  mHierarchyMutex.unlock();
 
  152  QgsPointCloudExpression filterExpression = mFilterExpression;
 
  154  requestAttributes.
extend( attributes(), filterExpression.referencedAttributes() );
 
  156  QByteArray rawBlockData( blockSize, Qt::Initialization::Uninitialized );
 
  157  std::ifstream file( QgsLazDecoder::toNativePath( mFileName ), std::ios::binary );
 
  158  file.seekg( blockOffset );
 
  159  file.read( rawBlockData.data(), blockSize );
 
  162    QgsDebugMsg( QStringLiteral( 
"Could not read file %1" ).arg( mFileName ) );
 
  166  return QgsLazDecoder::decompressCopc( rawBlockData, *mLazInfo.get(), pointCount, requestAttributes, filterExpression );
 
  179  return mLazInfo->crs();
 
  182qint64 QgsCopcPointCloudIndex::pointCount()
 const 
  184  return mLazInfo->pointCount();
 
  187bool QgsCopcPointCloudIndex::loadHierarchy()
 
  189  QMutexLocker locker( &mHierarchyMutex );
 
  190  fetchHierarchyPage( mCopcInfoVlr.root_hier_offset, mCopcInfoVlr.root_hier_size );
 
  196  if ( mLazInfo->version() != qMakePair<uint8_t, uint8_t>( 1, 4 ) )
 
  203  QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
 
  204  if ( !statisticsEvlrData.isEmpty() )
 
  206    QgsMessageLog::logMessage( tr( 
"Can't write statistics to \"%1\": file already contains COPC statistics!" ).arg( mFileName ) );
 
  210  lazperf::evlr_header statsEvlrHeader;
 
  211  statsEvlrHeader.user_id = 
"qgis";
 
  212  statsEvlrHeader.record_id = 0;
 
  213  statsEvlrHeader.description = 
"Contains calculated statistics";
 
  215  statsEvlrHeader.data_length = statsJson.size();
 
  219  std::fstream copcFile;
 
  220  copcFile.open( QgsLazDecoder::toNativePath( mFileName ), std::ios_base::binary | std::iostream::in | std::iostream::out );
 
  221  if ( copcFile.is_open() && copcFile.good() )
 
  224    lazperf::header14 header = mLazInfo->header();
 
  225    header.evlr_count = header.evlr_count + 1;
 
  227    header.write( copcFile );
 
  230    copcFile.seekg( 0, std::ios::end );
 
  232    statsEvlrHeader.write( copcFile );
 
  233    copcFile.write( statsJson.data(), statsEvlrHeader.data_length );
 
  241  mCopcFile.open( QgsLazDecoder::toNativePath( mFileName ), std::ios::binary );
 
  247  QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
 
  249  if ( statisticsEvlrData.isEmpty() )
 
  257bool QgsCopcPointCloudIndex::isValid()
 const 
  264  QMutexLocker locker( &mHierarchyMutex );
 
  266  QVector<IndexedPointCloudNode> ancestors;
 
  268  while ( !mHierarchy.contains( foundRoot ) )
 
  270    ancestors.push_front( foundRoot );
 
  273  ancestors.push_front( foundRoot );
 
  276    auto hierarchyIt = mHierarchy.constFind( n );
 
  277    if ( hierarchyIt == mHierarchy.constEnd() )
 
  279    int nodesCount = *hierarchyIt;
 
  280    if ( nodesCount < 0 )
 
  282      auto hierarchyNodePos = mHierarchyNodePos.constFind( n );
 
  283      fetchHierarchyPage( hierarchyNodePos->first, hierarchyNodePos->second );
 
  289void QgsCopcPointCloudIndex::fetchHierarchyPage( uint64_t offset, uint64_t byteSize )
 const 
  291  mCopcFile.seekg( offset );
 
  292  std::unique_ptr<char []> data( 
new char[ byteSize ] );
 
  293  mCopcFile.read( data.get(), byteSize );
 
  311  for ( uint64_t i = 0; i < byteSize; i += 
sizeof( CopcEntry ) )
 
  313    CopcEntry *entry = 
reinterpret_cast<CopcEntry *
>( data.get() + i );
 
  315    mHierarchy[nodeId] = entry->pointCount;
 
  316    mHierarchyNodePos.insert( nodeId, QPair<uint64_t, int32_t>( entry->offset, entry->byteSize ) );
 
  322  fetchNodeHierarchy( n );
 
  323  mHierarchyMutex.lock();
 
  325  auto it = mHierarchy.constFind( n );
 
  326  const bool found = it != mHierarchy.constEnd() && ( *it ) >= 0;
 
  327  mHierarchyMutex.unlock();
 
  331QList<IndexedPointCloudNode> QgsCopcPointCloudIndex::nodeChildren( 
const IndexedPointCloudNode &n )
 const 
  333  fetchNodeHierarchy( n );
 
  335  mHierarchyMutex.lock();
 
  337  auto hierarchyIt = mHierarchy.constFind( n );
 
  338  Q_ASSERT( hierarchyIt != mHierarchy.constEnd() );
 
  339  QList<IndexedPointCloudNode> lst;
 
  341  const int d = n.
d() + 1;
 
  342  const int x = n.
x() * 2;
 
  343  const int y = n.
y() * 2;
 
  344  const int z = n.
z() * 2;
 
  345  mHierarchyMutex.unlock();
 
  347  for ( 
int i = 0; i < 8; ++i )
 
  349    int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
 
  351    if ( fetchNodeHierarchy( n2 ) && mHierarchy[n] >= 0 )
 
  357void QgsCopcPointCloudIndex::copyCommonProperties( QgsCopcPointCloudIndex *destination )
 const 
  362  destination->mIsValid = mIsValid;
 
  363  destination->mFileName = mFileName;
 
  364  destination->mCopcFile.open( QgsLazDecoder::toNativePath( mFileName ), std::ios::binary );
 
  365  destination->mCopcInfoVlr = mCopcInfoVlr;
 
  366  destination->mHierarchyNodePos = mHierarchyNodePos;
 
  367  destination->mOriginalMetadata = mOriginalMetadata;
 
  368  destination->mLazInfo.reset( 
new QgsLazInfo( *mLazInfo ) );
 
  371QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData()
 
  373  uint64_t offset = mLazInfo->firstEvlrOffset();
 
  374  uint32_t evlrCount = mLazInfo->evlrCount();
 
  376  QByteArray statisticsEvlrData;
 
  378  for ( uint32_t i = 0; i < evlrCount; ++i )
 
  380    lazperf::evlr_header header;
 
  381    mCopcFile.seekg( offset );
 
  383    mCopcFile.read( buffer, 60 );
 
  384    header.fill( buffer, 60 );
 
  387    if ( header.user_id == 
"qgis" && header.record_id == 0 )
 
  389      statisticsEvlrData = QByteArray( header.data_length, Qt::Initialization::Uninitialized );
 
  390      mCopcFile.read( statisticsEvlrData.data(), header.data_length );
 
  394    offset += 60 + header.data_length;
 
  397  return statisticsEvlrData;
 
Represents a indexed point cloud node in octree.
 
IndexedPointCloudNode parentNode() const
Returns the parent of the node.
 
This class represents a coordinate reference system (CRS).
 
Class for extracting information contained in LAZ file such as the public header block and variable l...
 
QgsVector3D maxCoords() const
Returns the maximum coordinate across X, Y and Z axis.
 
QgsPointCloudAttributeCollection attributes() const
Returns the list of attributes contained in the LAZ file.
 
QByteArray vlrData(QString userId, int recordId)
Returns the binary data of the variable length record with the user identifier userId and record iden...
 
QVariantMap toMetadata() const
Returns a map containing various metadata extracted from the LAZ file.
 
QgsVector3D scale() const
Returns the scale of the points coordinates.
 
static QgsLazInfo fromFile(std::ifstream &file)
Static function to create a QgsLazInfo class from a file.
 
QgsVector3D minCoords() const
Returns the minimum coordinate across X, Y and Z axis.
 
QgsVector3D offset() const
Returns the offset of the points coordinates.
 
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
 
Collection of point cloud attributes.
 
void extend(const QgsPointCloudAttributeCollection &otherCollection, const QSet< QString > &matchingNames)
Adds specific missing attributes from another QgsPointCloudAttributeCollection.
 
Base class for handling loading QgsPointCloudBlock asynchronously.
 
Base class for storing raw data from point cloud nodes.
 
Represents packaged data bounds.
 
void copyCommonProperties(QgsPointCloudIndex *destination) const
Copies common properties to the destination index.
 
Point cloud data request.
 
QgsPointCloudAttributeCollection attributes() const
Returns attributes.
 
Class used to store statistics of a point cloud dataset.
 
static QgsPointCloudStatistics fromStatisticsJson(QByteArray stats)
Creates a statistics object from the JSON object stats.
 
QByteArray toStatisticsJson() const
Converts the current statistics object into JSON object.
 
double y() const
Returns Y coordinate.
 
double z() const
Returns Z coordinate.
 
double x() const
Returns X coordinate.
 
void set(double x, double y, double z)
Sets vector coordinates.
 
#define QgsDebugMsgLevel(str, level)
 
const QgsCoordinateReferenceSystem & crs