24 #include <QMutexLocker> 
   25 #include <QJsonDocument> 
   26 #include <QJsonObject> 
   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" ) 
   47 QgsCopcPointCloudIndex::QgsCopcPointCloudIndex() = 
default;
 
   49 QgsCopcPointCloudIndex::~QgsCopcPointCloudIndex() = 
default;
 
   51 std::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 );
 
   59 void 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() );
 
   86 bool 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();
 
  182 qint64 QgsCopcPointCloudIndex::pointCount()
 const 
  184   return mLazInfo->pointCount();
 
  187 bool 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() )
 
  257 bool 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 );
 
  289 void 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();
 
  331 QList<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 )
 
  357 void 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 ) );
 
  371 QByteArray 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;