23#include <lazperf/header.hpp> 
   24#include <lazperf/vlr.hpp> 
   25#include <lazperf/Extractor.hpp> 
   26#include <lazperf/filestream.hpp> 
   27#include <lazperf/readers.hpp> 
   28#include <lazperf/writers.hpp> 
   65  std::vector<char> buf( 32 );
 
   66  int numEntries = 
static_cast<int>( size / 32 );
 
   67  file.seekg( 
static_cast<int64_t
>( offset ) );
 
   68  while ( numEntries-- )
 
   70    file.read( buf.data(), 
static_cast<long>( buf.size() ) );
 
   71    lazperf::LeExtractor s( buf.data(), buf.size() );
 
   75    s >> d >> x >> y >> z;
 
 
   85bool QgsCopcUpdate::write( 
const QString &outputFilename, 
const QHash<QgsPointCloudNodeId, UpdatedChunk> &updatedChunks )
 
   88  m_f.open( QgsLazDecoder::toNativePath( outputFilename ), std::ios::out | std::ios::binary );
 
   93  std::vector<char> allHeaderData;
 
   94  allHeaderData.resize( mHeader.point_offset );
 
   95  mFile.read( allHeaderData.data(), 
static_cast<long>( allHeaderData.size() ) );
 
   96  m_f.write( allHeaderData.data(), 
static_cast<long>( allHeaderData.size() ) );
 
   98  m_f.write( 
"XXXXXXXX", 8 ); 
 
  100  uint64_t currentChunkOffset = mHeader.point_offset + 8;
 
  101  mFile.seekg( 
static_cast<long>( currentChunkOffset ) ); 
 
  109  QHash<QgsPointCloudNodeId, uint64_t> voxelToNewOffset;
 
  112  for ( lazperf::chunk ch : mChunks )
 
  114    Q_ASSERT( mOffsetToVoxel.contains( currentChunkOffset ) );
 
  117    uint64_t newOffset = m_f.tellp();
 
  118    voxelToNewOffset[n] = newOffset;
 
  121    if ( updatedChunks.contains( n ) )
 
  126      mFile.seekg( 
static_cast<long>( mFile.tellg() ) + 
static_cast<long>( ch.offset ) );
 
  131      mChunks[chIndex].offset = updatedChunk.
chunkData.size();
 
  136      std::vector<char> originalChunkData;
 
  137      originalChunkData.resize( ch.offset );
 
  138      mFile.read( originalChunkData.data(), 
static_cast<long>( originalChunkData.size() ) );
 
  139      m_f.write( originalChunkData.data(), 
static_cast<long>( originalChunkData.size() ) );
 
  142    currentChunkOffset += ch.offset;
 
  148  const uint64_t newChunkTableOffset = m_f.tellp();
 
  150  m_f.write( 
"\0\0\0\0", 4 ); 
 
  151  m_f.write( 
reinterpret_cast<const char *
>( &mChunkCount ), 
sizeof( mChunkCount ) );
 
  153  lazperf::OutFileStream outStream( m_f );
 
  154  lazperf::compress_chunk_table( outStream.cb(), mChunks, 
true );
 
  162  const long hierPositionShift = 
static_cast<long>( m_f.tellp() ) + 60 - 
static_cast<long>( mHierarchyOffset );
 
  165  const int nEntries = 
static_cast<int>( mHierarchyBlob.size() / 32 );
 
  166  for ( 
int i = 0; i < nEntries; ++i )
 
  172      Q_ASSERT( voxelToNewOffset.contains( e.
key ) );
 
  175      if ( updatedChunks.contains( e.
key ) )
 
  177        uint64_t newByteSize = updatedChunks[e.
key].chunkData.size();
 
  178        e.
byteSize = 
static_cast<int>( newByteSize );
 
  184      e.
offset += hierPositionShift;
 
  195  const uint64_t newEvlrOffset = m_f.tellp();
 
  197  lazperf::evlr_header outCopcHierEvlr;
 
  198  outCopcHierEvlr.reserved = 0;
 
  199  outCopcHierEvlr.user_id = 
"copc";
 
  200  outCopcHierEvlr.record_id = 1000;
 
  201  outCopcHierEvlr.data_length = mHierarchyBlob.size();
 
  202  outCopcHierEvlr.description = 
"EPT Hierarchy";
 
  204  outCopcHierEvlr.write( m_f );
 
  205  m_f.write( mHierarchyBlob.data(), 
static_cast<long>( mHierarchyBlob.size() ) );
 
  209  for ( 
size_t i = 0; i < mEvlrHeaders.size(); ++i )
 
  211    lazperf::evlr_header evlrHeader = mEvlrHeaders[i];
 
  212    std::vector<char> evlrBody = mEvlrData[i];
 
  214    evlrHeader.write( m_f );
 
  215    m_f.write( evlrBody.data(), 
static_cast<long>( evlrBody.size() ) );
 
  221  m_f.write( 
reinterpret_cast<const char *
>( &newEvlrOffset ), 8 );
 
  223  const uint64_t newRootHierOffset = mCopcVlr.root_hier_offset + hierPositionShift;
 
  225  m_f.write( 
reinterpret_cast<const char *
>( &newRootHierOffset ), 8 );
 
  227  m_f.seekp( mHeader.point_offset );
 
  228  m_f.write( 
reinterpret_cast<const char *
>( &newChunkTableOffset ), 8 );
 
 
  237  mInputFilename = inputFilename;
 
  239  mFile.open( QgsLazDecoder::toNativePath( inputFilename ), std::ios::binary | std::ios::in );
 
  242    mErrorMessage = QStringLiteral( 
"Could not open file for reading: %1" ).arg( inputFilename );
 
 
  256bool QgsCopcUpdate::readHeader()
 
  259  mHeader = lazperf::header14::create( mFile );
 
  262    mErrorMessage = QStringLiteral( 
"Error reading COPC header" );
 
  266  lazperf::vlr_header vh = lazperf::vlr_header::create( mFile );
 
  267  mCopcVlr = lazperf::copc_info_vlr::create( mFile );
 
  269  int baseCount = lazperf::baseCount( mHeader.point_format_id );
 
  270  if ( baseCount == 0 )
 
  272    mErrorMessage = QStringLiteral( 
"Bad point record format: %1" ).arg( mHeader.point_format_id );
 
  280void QgsCopcUpdate::readChunkTable()
 
  282  uint64_t chunkTableOffset;
 
  284  mFile.seekg( mHeader.point_offset );
 
  285  mFile.read( 
reinterpret_cast<char *
>( &chunkTableOffset ), 
sizeof( chunkTableOffset ) );
 
  286  mFile.seekg( 
static_cast<long>( chunkTableOffset ) + 4 ); 
 
  287  mFile.read( 
reinterpret_cast<char *
>( &mChunkCount ), 
sizeof( mChunkCount ) );
 
  293  bool variable = 
true;
 
  296  std::ifstream copcFileTmp;
 
  297  copcFileTmp.open( QgsLazDecoder::toNativePath( mInputFilename ), std::ios::binary | std::ios::in );
 
  298  copcFileTmp.seekg( mFile.tellg() );
 
  299  lazperf::InFileStream copcInFileStream( copcFileTmp );
 
  301  mChunks = lazperf::decompress_chunk_table( copcInFileStream.cb(), mChunkCount, variable );
 
  302  std::vector<lazperf::chunk> chunksWithAbsoluteOffsets;
 
  303  uint64_t nextChunkOffset = mHeader.point_offset + 8;
 
  304  for ( lazperf::chunk ch : mChunks )
 
  306    chunksWithAbsoluteOffsets.push_back( {nextChunkOffset, ch.count} );
 
  307    nextChunkOffset += ch.offset;
 
  312void QgsCopcUpdate::readHierarchy()
 
  320    mCopcVlr.root_hier_offset,
 
  321    static_cast<int32_t
>( mCopcVlr.root_hier_size ),
 
  324  while ( !childEntriesToProcess.empty() )
 
  327    childEntriesToProcess.pop_back();
 
  333      if ( e.pointCount > 0 ) 
 
  335        Q_ASSERT( !mOffsetToVoxel.contains( e.offset ) );
 
  336        mOffsetToVoxel[e.offset] = e.key;
 
  338      else if ( e.pointCount < 0 ) 
 
  340        childEntriesToProcess.push_back( e );
 
  345  lazperf::evlr_header evlr1;
 
  346  mFile.seekg( 
static_cast<long>( mHeader.evlr_offset ) );
 
  348  mHierarchyOffset = 0;  
 
  350  for ( uint32_t i = 0; i < mHeader.evlr_count; ++i )
 
  353    if ( evlr1.user_id == 
"copc" && evlr1.record_id == 1000 )
 
  355      mHierarchyBlob.resize( evlr1.data_length );
 
  356      mHierarchyOffset = mFile.tellg();
 
  357      mFile.read( mHierarchyBlob.data(), 
static_cast<long>( evlr1.data_length ) );
 
  362      mEvlrHeaders.push_back( evlr1 );
 
  363      std::vector<char> evlrBlob;
 
  364      evlrBlob.resize( evlr1.data_length );
 
  365      mFile.read( evlrBlob.data(), 
static_cast<long>( evlrBlob.size() ) );
 
  366      mEvlrData.push_back( evlrBlob );
 
  370  Q_ASSERT( !mHierarchyBlob.empty() );
 
  375                                      const QString &outputFilename,
 
  376                                      const QHash<QgsPointCloudNodeId, UpdatedChunk> &updatedChunks,
 
  377                                      QString *errorMessage )
 
  380  if ( !copcUpdate.
read( inputFilename ) )
 
  387  if ( !copcUpdate.
write( outputFilename, updatedChunks ) )
 
 
Handles update operations to a COPC file.
 
QString errorMessage() const
Returns error message.
 
static bool writeUpdatedFile(const QString &inputFilename, const QString &outputFilename, const QHash< QgsPointCloudNodeId, UpdatedChunk > &updatedChunks, QString *errorMessage=nullptr)
Convenience function to do the whole process in one go: load a COPC file, then write a new COPC file ...
 
bool read(const QString &inputFilename)
Reads input COPC file and initializes all the members.
 
bool write(const QString &outputFilename, const QHash< QgsPointCloudNodeId, UpdatedChunk > &updatedChunks)
Writes a COPC file with updated chunks.
 
Represents an indexed point cloud node's position in octree.
 
QVector< HierarchyEntry > HierarchyEntries
 
HierarchyEntries getHierarchyPage(std::ifstream &file, uint64_t offset, uint64_t size)
 
Keeps one entry of COPC hierarchy.
 
QgsPointCloudNodeId key
Key of the data to which this entry corresponds.
 
uint64_t offset
Absolute offset to the data chunk if the pointCount > 0.
 
int32_t pointCount
If > 0, represents the number of points in the data chunk.
 
int32_t byteSize
Size of the data chunk in bytes (compressed size) if the pointCount > 0.
 
Keeps information how points of a single chunk has been modified.
 
QByteArray chunkData
Data of the chunk (compressed already with LAZ compressor)