From f61d8cd91812869720abc9e6cfb92ac22c805b21 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 13 Jan 2025 16:46:47 +0100 Subject: [PATCH 1/5] Point cloud editing: write COPC when committing changes --- .../pointcloud/qgspointcloudindex.sip.in | 5 +- .../pointcloud/qgspointcloudindex.sip.in | 5 +- src/3d/qgs3dmapscene.cpp | 2 + src/core/CMakeLists.txt | 3 + .../pointcloud/qgscopcpointcloudindex.cpp | 26 ++ src/core/pointcloud/qgscopcpointcloudindex.h | 5 + src/core/pointcloud/qgscopcupdate.cpp | 392 ++++++++++++++++++ src/core/pointcloud/qgscopcupdate.h | 88 ++++ .../pointcloud/qgspointcloudeditingindex.cpp | 100 ++++- .../pointcloud/qgspointcloudeditingindex.h | 6 +- src/core/pointcloud/qgspointcloudindex.cpp | 4 +- src/core/pointcloud/qgspointcloudindex.h | 9 +- src/core/pointcloud/qgspointcloudlayer.cpp | 12 +- .../qgspointcloudlayereditutils.cpp | 173 +++++++- .../pointcloud/qgspointcloudlayereditutils.h | 5 + tests/src/core/testqgspointcloudediting.cpp | 59 +++ 16 files changed, 860 insertions(+), 34 deletions(-) create mode 100644 src/core/pointcloud/qgscopcupdate.cpp create mode 100644 src/core/pointcloud/qgscopcupdate.h diff --git a/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudindex.sip.in b/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudindex.sip.in index 18cbe8b42976..b451576fbb42 100644 --- a/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudindex.sip.in +++ b/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudindex.sip.in @@ -169,6 +169,7 @@ index is memory safe. operator bool() const; + void load( const QString &fileName ); %Docstring Loads the index from the file @@ -355,9 +356,11 @@ in an implementation-specific dynamic structure. .. seealso:: :py:func:`QgsAbstractPointCloudIndex.extraMetadata` %End - bool commitChanges(); + bool commitChanges( QString *errorMessage /Out/ = 0 ); %Docstring Tries to store pending changes to the data provider. +If errorMessage is not a null pointer, it will receive +an error message in case the call failed. :return: ``True`` on success, otherwise ``False`` %End diff --git a/python/core/auto_generated/pointcloud/qgspointcloudindex.sip.in b/python/core/auto_generated/pointcloud/qgspointcloudindex.sip.in index 18cbe8b42976..b451576fbb42 100644 --- a/python/core/auto_generated/pointcloud/qgspointcloudindex.sip.in +++ b/python/core/auto_generated/pointcloud/qgspointcloudindex.sip.in @@ -169,6 +169,7 @@ index is memory safe. operator bool() const; + void load( const QString &fileName ); %Docstring Loads the index from the file @@ -355,9 +356,11 @@ in an implementation-specific dynamic structure. .. seealso:: :py:func:`QgsAbstractPointCloudIndex.extraMetadata` %End - bool commitChanges(); + bool commitChanges( QString *errorMessage /Out/ = 0 ); %Docstring Tries to store pending changes to the data provider. +If errorMessage is not a null pointer, it will receive +an error message in case the call failed. :return: ``True`` on success, otherwise ``False`` %End diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 9f36f7bc9f0f..97ee693ad503 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -713,6 +713,7 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) QgsPointCloudLayer *pclayer = qobject_cast( layer ); connect( pclayer, &QgsPointCloudLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); connect( pclayer, &QgsPointCloudLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); + connect( pclayer, &QgsPointCloudLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); } } @@ -748,6 +749,7 @@ void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer ) QgsPointCloudLayer *pclayer = qobject_cast( layer ); disconnect( pclayer, &QgsPointCloudLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); disconnect( pclayer, &QgsPointCloudLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); + disconnect( pclayer, &QgsPointCloudLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); } } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c9d7fa662a2d..9e0d477f3d5e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2280,6 +2280,7 @@ if (WITH_EPT OR WITH_COPC) ${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/writers.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 @@ -2296,11 +2297,13 @@ if (WITH_EPT OR WITH_COPC) pointcloud/qgseptdecoder.cpp pointcloud/qgslazdecoder.cpp pointcloud/qgslazinfo.cpp + pointcloud/qgscopcupdate.cpp ) set(QGIS_CORE_HDRS ${QGIS_CORE_HDRS} pointcloud/qgseptdecoder.h pointcloud/qgslazdecoder.h pointcloud/qgslazinfo.h + pointcloud/qgscopcupdate.h ) endif() diff --git a/src/core/pointcloud/qgscopcpointcloudindex.cpp b/src/core/pointcloud/qgscopcpointcloudindex.cpp index 3cd5f3dfe8fe..fcddce4c66f9 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.cpp +++ b/src/core/pointcloud/qgscopcpointcloudindex.cpp @@ -512,6 +512,32 @@ QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData() const return statisticsEvlrData; } +void QgsCopcPointCloudIndex::reset() +{ + // QgsAbstractPointCloudIndex + mExtent = QgsRectangle(); + mZMin = 0; + mZMax = 0; + mHierarchy.clear(); + mScale = QgsVector3D(); + mOffset = QgsVector3D(); + mRootBounds = QgsBox3D(); + mAttributes = QgsPointCloudAttributeCollection(); + mSpan = 0; + //mFilterExpression + mError.clear(); + //mUri + + // QgsCopcPointCloudIndex + mIsValid = false; + mAccessType = Qgis::PointCloudAccessType::Local; + mCopcFile.close(); + mOriginalMetadata.clear(); + mStatistics.reset(); + mLazInfo.reset(); + mHierarchyNodePos.clear(); +} + QVariantMap QgsCopcPointCloudIndex::extraMetadata() const { return diff --git a/src/core/pointcloud/qgscopcpointcloudindex.h b/src/core/pointcloud/qgscopcpointcloudindex.h index 8d02b0f1622e..1ca11367d1eb 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.h +++ b/src/core/pointcloud/qgscopcpointcloudindex.h @@ -109,6 +109,8 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsAbstractPointCloudIndex QByteArray fetchCopcStatisticsEvlrData() const; + void reset(); + bool mIsValid = false; Qgis::PointCloudAccessType mAccessType = Qgis::PointCloudAccessType::Local; mutable std::ifstream mCopcFile; @@ -119,6 +121,9 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsAbstractPointCloudIndex mutable std::optional mStatistics; std::unique_ptr mLazInfo = nullptr; + + friend class QgsPointCloudLayerEditUtils; + friend class QgsPointCloudEditingIndex; }; ///@endcond diff --git a/src/core/pointcloud/qgscopcupdate.cpp b/src/core/pointcloud/qgscopcupdate.cpp new file mode 100644 index 000000000000..113eaf17f5aa --- /dev/null +++ b/src/core/pointcloud/qgscopcupdate.cpp @@ -0,0 +1,392 @@ +/*************************************************************************** + qgscopcupdate.cpp + --------------------- + begin : January 2025 + copyright : (C) 2025 by Martin Dobias + email : wonder dot sk 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 "qgscopcupdate.h" + +#include "qgslazdecoder.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + + +//! Keeps one entry of COPC hierarchy +struct HierarchyEntry +{ + //! Key of the data to which this entry corresponds + QgsPointCloudNodeId key; + + /** + * Absolute offset to the data chunk if the pointCount > 0. + * Absolute offset to a child hierarchy page if the pointCount is -1. + * 0 if the pointCount is 0. + */ + uint64_t offset; + + /** + * Size of the data chunk in bytes (compressed size) if the pointCount > 0. + * Size of the hierarchy page if the pointCount is -1. + * 0 if the pointCount is 0. + */ + int32_t byteSize; + + /** + * If > 0, represents the number of points in the data chunk. + * If -1, indicates the information for this octree node is found in another hierarchy page. + * If 0, no point data exists for this key, though may exist for child entries. + */ + int32_t pointCount; +}; + +typedef QVector HierarchyEntries; + + +HierarchyEntries getHierarchyPage( std::ifstream &file, uint64_t offset, uint64_t size ) +{ + HierarchyEntries page; + std::vector buf( 32 ); + int numEntries = size / 32; + file.seekg( offset ); + while ( numEntries-- ) + { + file.read( buf.data(), buf.size() ); + lazperf::LeExtractor s( buf.data(), buf.size() ); + + HierarchyEntry e; + int d, x, y, z; + s >> d >> x >> y >> z; + s >> e.offset >> e.byteSize >> e.pointCount; + e.key = QgsPointCloudNodeId( d, x, y, z ); + + page.push_back( e ); + } + return page; +} + + +bool QgsCopcUpdate::write( QString outputFilename, const QHash &updatedChunks ) +{ + + std::ofstream m_f; + m_f.open( QgsLazDecoder::toNativePath( outputFilename ), std::ios::out | std::ios::binary ); + + // write header and all VLRs all the way to point offset + // (then we patch what we need) + mFile.seekg( 0 ); + std::vector allHeaderData; + allHeaderData.resize( mHeader.point_offset ); + mFile.read( allHeaderData.data(), allHeaderData.size() ); + m_f.write( allHeaderData.data(), allHeaderData.size() ); + + m_f.write( "XXXXXXXX", 8 ); // placeholder for chunk table offset + + uint64_t currentChunkOffset = mHeader.point_offset + 8; + mFile.seekg( currentChunkOffset ); // this is where first chunk starts + + // now, let's write chunks: + // - iterate through original chunk table, write out chunks + // - if chunk is updated, use that instead + // - keep updating hierarchy as we go + // - keep updating chunk table as we go + + QHash voxelToNewOffset; + + int chIndex = 0; + for ( lazperf::chunk ch : mChunks ) + { + Q_ASSERT( mOffsetToVoxel.contains( currentChunkOffset ) ); + QgsPointCloudNodeId k = mOffsetToVoxel[currentChunkOffset]; + + uint64_t newOffset = m_f.tellp(); + voxelToNewOffset[k] = newOffset; + + // check whether the chunk is modified + if ( updatedChunks.contains( k ) ) + { + const UpdatedChunk &updatedChunk = updatedChunks[k]; + + // use updated one and skip in the original file + mFile.seekg( ( uint64_t )mFile.tellg() + ch.offset ); + + m_f.write( updatedChunk.chunkData.constData(), updatedChunk.chunkData.size() ); + + // update sizes + mChunks[chIndex].offset = updatedChunk.chunkData.size(); + } + else + { + // use as is + std::vector chunkDataX; + chunkDataX.resize( ch.offset ); + mFile.read( chunkDataX.data(), chunkDataX.size() ); + m_f.write( chunkDataX.data(), chunkDataX.size() ); + } + + currentChunkOffset += ch.offset; + ++chIndex; + } + + // write chunk table: size in bytes + point count of each chunk + + uint64_t newChunkTableOffset = m_f.tellp(); + + m_f.write( "\0\0\0\0", 4 ); // chunk table version + m_f.write( ( char * )&mChunkCount, sizeof( mChunkCount ) ); + + lazperf::OutFileStream outStream( m_f ); + lazperf::compress_chunk_table( outStream.cb(), mChunks, true ); + + // update hierarchy + + // NOTE: one big assumption we're doing here is that existing hierarchy pages + // are packed one after another, with no gaps. if that's not the case, things + // will break apart + + int hierPositionShift = ( uint64_t )m_f.tellp() + 60 - mHierarchyOffset; + + HierarchyEntry *oldCopcHierarchyBlobEntries = ( HierarchyEntry * ) mHierarchyBlob.data(); + int nEntries = mHierarchyBlob.size() / 32; + for ( int i = 0; i < nEntries; ++i ) + { + HierarchyEntry &e = oldCopcHierarchyBlobEntries[i]; + if ( e.pointCount > 0 ) + { + // update entry to new offset + Q_ASSERT( voxelToNewOffset.contains( e.key ) ); + e.offset = voxelToNewOffset[e.key]; + + if ( updatedChunks.contains( e.key ) ) + { + uint64_t newByteSize = updatedChunks[e.key].chunkData.size(); + e.byteSize = newByteSize; + } + } + else if ( e.pointCount < 0 ) + { + // move hierarchy pages to new offset + e.offset += hierPositionShift; + } + else // pointCount == 0 + { + // nothing to do - byte size and offset should be zero + } + + } + + // write hierarchy eVLR + + uint64_t newEvlrOffset = m_f.tellp(); + + lazperf::evlr_header outCopcHierEvlr; + outCopcHierEvlr.reserved = 0; + outCopcHierEvlr.user_id = "copc"; + outCopcHierEvlr.record_id = 1000; + outCopcHierEvlr.data_length = mHierarchyBlob.size(); + outCopcHierEvlr.description = "EPT Hierarchy"; + + outCopcHierEvlr.write( m_f ); + m_f.write( mHierarchyBlob.data(), mHierarchyBlob.size() ); + + // write other eVLRs + + for ( size_t i = 0; i < mEvlrHeaders.size(); ++i ) + { + lazperf::evlr_header evlrHeader = mEvlrHeaders[i]; + std::vector evlrBody = mEvlrData[i]; + + evlrHeader.write( m_f ); + m_f.write( evlrBody.data(), evlrBody.size() ); + } + + // patch header + + m_f.seekp( 235 ); + m_f.write( ( const char * )&newEvlrOffset, 8 ); + + uint64_t newRootHierOffset = mCopcVlr.root_hier_offset + hierPositionShift; + m_f.seekp( 469 ); + m_f.write( ( const char * )&newRootHierOffset, 8 ); + + m_f.seekp( mHeader.point_offset ); + m_f.write( ( const char * )&newChunkTableOffset, 8 ); + + return true; +} + + + +bool QgsCopcUpdate::read( QString inputFilename ) +{ + mInputFilename = inputFilename; + + mFile.open( QgsLazDecoder::toNativePath( inputFilename ), std::ios::binary | std::ios::in ); + if ( mFile.fail() ) + { + mErrorMessage = "Could not open file for reading: " + inputFilename; + return false; + } + + if ( !readHeader() ) + return false; + + readChunkTable(); + readHierarchy(); + + return true; +} + + +bool QgsCopcUpdate::readHeader() +{ + // read header and COPC VLR + mHeader = lazperf::header14::create( mFile ); + if ( !mFile ) + { + mErrorMessage = "Error reading COPC header"; + return false; + } + + lazperf::vlr_header vh = lazperf::vlr_header::create( mFile ); + mCopcVlr = lazperf::copc_info_vlr::create( mFile ); + + int baseCount = lazperf::baseCount( mHeader.point_format_id ); + if ( baseCount == 0 ) + { + mErrorMessage = QString( "Bad point record format: %1" ).arg( mHeader.point_format_id ); + return false; + } + + return true; +} + + +void QgsCopcUpdate::readChunkTable() +{ + uint64_t chunkTableOffset; + + mFile.seekg( mHeader.point_offset ); + mFile.read( ( char * )&chunkTableOffset, sizeof( chunkTableOffset ) ); + mFile.seekg( chunkTableOffset + 4 ); // The first 4 bytes are the version, then the chunk count. + mFile.read( ( char * )&mChunkCount, sizeof( mChunkCount ) ); + + // + // read chunk table + // + + bool variable = true; + + // TODO: not sure why, but after decompress_chunk_table() the input stream seems to be dead, so we create a temporary one + std::ifstream copcFileTmp; + copcFileTmp.open( QgsLazDecoder::toNativePath( mInputFilename ), std::ios::binary | std::ios::in ); + copcFileTmp.seekg( mFile.tellg() ); + lazperf::InFileStream copcInFileStream( copcFileTmp ); + + mChunks = lazperf::decompress_chunk_table( copcInFileStream.cb(), mChunkCount, variable ); + std::vector chunksWithAbsoluteOffsets; + uint64_t nextChunkOffset = mHeader.point_offset + 8; + for ( lazperf::chunk ch : mChunks ) + { + chunksWithAbsoluteOffsets.push_back( {nextChunkOffset, ch.count} ); + nextChunkOffset += ch.offset; + } +} + + +void QgsCopcUpdate::readHierarchy() +{ + + // get all hierarchy pages + + HierarchyEntries childEntriesToProcess; + childEntriesToProcess.push_back( HierarchyEntry{ QgsPointCloudNodeId( 0, 0, 0, 0 ), mCopcVlr.root_hier_offset, ( int32_t )mCopcVlr.root_hier_size, -1 } ); + + while ( !childEntriesToProcess.empty() ) + { + HierarchyEntry childEntry = childEntriesToProcess.back(); + childEntriesToProcess.pop_back(); + + HierarchyEntries page = getHierarchyPage( mFile, childEntry.offset, childEntry.byteSize ); + + for ( const HierarchyEntry &e : page ) + { + if ( e.pointCount > 0 ) // it's a non-empty node + { + Q_ASSERT( !mOffsetToVoxel.contains( e.offset ) ); + mOffsetToVoxel[e.offset] = e.key; + } + else if ( e.pointCount < 0 ) // referring to a child page + { + childEntriesToProcess.push_back( e ); + } + } + } + + lazperf::evlr_header evlr1; + mFile.seekg( mHeader.evlr_offset ); + + mHierarchyOffset = 0; // where the hierarchy eVLR payload starts + + for ( uint32_t i = 0; i < mHeader.evlr_count; ++i ) + { + evlr1.read( mFile ); + if ( evlr1.user_id == "copc" && evlr1.record_id == 1000 ) + { + mHierarchyBlob.resize( evlr1.data_length ); + mHierarchyOffset = mFile.tellg(); + mFile.read( mHierarchyBlob.data(), evlr1.data_length ); + } + else + { + // keep for later + mEvlrHeaders.push_back( evlr1 ); + std::vector evlrBlob; + evlrBlob.resize( evlr1.data_length ); + mFile.read( evlrBlob.data(), evlrBlob.size() ); + mEvlrData.push_back( evlrBlob ); + } + } + + Q_ASSERT( !mHierarchyBlob.empty() ); +} + + +bool QgsCopcUpdate::writeUpdatedFile( const QString &inputFilename, + const QString &outputFilename, + const QHash &updatedChunks, + QString *errorMessage ) +{ + QgsCopcUpdate copcUpdate; + if ( !copcUpdate.read( inputFilename ) ) + { + if ( errorMessage ) + *errorMessage = copcUpdate.errorMessage(); + return false; + } + + if ( !copcUpdate.write( outputFilename, updatedChunks ) ) + { + if ( errorMessage ) + *errorMessage = copcUpdate.errorMessage(); + return false; + } + + return true; +} diff --git a/src/core/pointcloud/qgscopcupdate.h b/src/core/pointcloud/qgscopcupdate.h new file mode 100644 index 000000000000..a60da4e0b4ae --- /dev/null +++ b/src/core/pointcloud/qgscopcupdate.h @@ -0,0 +1,88 @@ +/*************************************************************************** + qgscopcupdate.h + --------------------- + begin : January 2025 + copyright : (C) 2025 by Martin Dobias + email : wonder dot sk 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 QGSCOPCUPDATE_H +#define QGSCOPCUPDATE_H + +#include "qgis_core.h" + +#include +#include + +#include "qgspointcloudindex.h" + + +/** + * This class takes an existing COPC file and a list of chunks that should be modified, + * and outputs an updated COPC file where the modified chunks replace the original chunks. + * + * \since QGIS 3.42 + */ +class CORE_EXPORT QgsCopcUpdate +{ + public: + + //! Keeps information how points of a single chunk has been modified + struct UpdatedChunk + { + //! Number of points in the updated chunk + int32_t pointCount; + //! Data of the chunk (compressed already with LAZ compressor) + QByteArray chunkData; + }; + + //! Reads input COPC file and initializes all the members + bool read( QString inputFilename ); + + //! Writes a COPC file with updated chunks + bool write( QString outputFilename, const QHash &updatedChunks ); + + //! Returns error message + QString errorMessage() const { return mErrorMessage; } + + /** + * Convenience function to do the whole process in one go: + * load a COPC file, then write a new COPC file with updated + * chunks. Returns TRUE on success. If errorMessage is not + * a null pointer, it will be set to an error message in case + * of failure (i.e. FALSE is returned). + */ + static bool writeUpdatedFile( const QString &inputFilename, + const QString &outputFilename, + const QHash &updatedChunks, + QString *errorMessage = nullptr ); + + private: + bool readHeader(); + void readChunkTable(); + void readHierarchy(); + + private: + QString mInputFilename; + std::ifstream mFile; + lazperf::header14 mHeader; + lazperf::copc_info_vlr mCopcVlr; + std::vector mChunks; + uint32_t mChunkCount; + uint64_t mHierarchyOffset = 0; + std::vector mHierarchyBlob; + std::vector mEvlrHeaders; + std::vector> mEvlrData; + QHash mOffsetToVoxel; + + QString mErrorMessage; +}; + +#endif // QGSCOPCUPDATE_H diff --git a/src/core/pointcloud/qgspointcloudeditingindex.cpp b/src/core/pointcloud/qgspointcloudeditingindex.cpp index b1fe6fd29051..73b4144fa6a9 100644 --- a/src/core/pointcloud/qgspointcloudeditingindex.cpp +++ b/src/core/pointcloud/qgspointcloudeditingindex.cpp @@ -17,6 +17,11 @@ #include "qgspointcloudlayer.h" #include "qgspointcloudlayereditutils.h" #include "qgscoordinatereferencesystem.h" +#include "qgscopcpointcloudindex.h" +#include "qgscopcupdate.h" +#include "qgslazdecoder.h" + +#include QgsPointCloudEditingIndex::QgsPointCloudEditingIndex( QgsPointCloudLayer *layer ) @@ -27,6 +32,7 @@ QgsPointCloudEditingIndex::QgsPointCloudEditingIndex( QgsPointCloudLayer *layer !( layer->dataProvider()->capabilities() & QgsPointCloudDataProvider::Capability::ChangeAttributeValues ) ) return; + mUri = layer->source(); mIndex = layer->dataProvider()->index(); mAttributes = mIndex.attributes(); @@ -89,18 +95,22 @@ std::unique_ptr< QgsPointCloudBlock > QgsPointCloudEditingIndex::nodeData( const { if ( mEditedNodeData.contains( n ) ) { - const QByteArray data = mEditedNodeData.value( n ); - int nPoints = data.size() / mIndex.attributes().pointRecordSize(); - - const QByteArray requestedData = QgsPointCloudLayerEditUtils::dataForAttributes( mIndex.attributes(), data, request ); - - std::unique_ptr block = std::make_unique< QgsPointCloudBlock >( - nPoints, - request.attributes(), - requestedData, - mIndex.scale(), - mIndex.offset() ); - return block; + // 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() ); + + QgsRectangle filterRect = request.filterRect(); + + QByteArray rawBlockData = mEditedNodeData[n]; + + QgsCopcPointCloudIndex *copcIndex = static_cast( mIndex.get() ); + + int pointCount = copcIndex->mHierarchy.value( n ); + + return QgsLazDecoder::decompressCopc( rawBlockData, *copcIndex->mLazInfo.get(), pointCount, requestAttributes, filterExpression, filterRect ); } else { @@ -114,15 +124,63 @@ QgsPointCloudBlockRequest *QgsPointCloudEditingIndex::asyncNodeData( const QgsPo return nullptr; } -bool QgsPointCloudEditingIndex::commitChanges() +bool QgsPointCloudEditingIndex::commitChanges( QString *errorMessage ) { if ( !isModified() ) return true; - if ( !mIndex.updateNodeData( mEditedNodeData ) ) + QHash updatedChunks; + for ( auto it = mEditedNodeData.constBegin(); it != mEditedNodeData.constEnd(); ++it ) + { + QgsPointCloudNodeId n = it.key(); + // right now we're assuming there's no change of point count + qint32 nodePointCount = static_cast( getNode( n ).pointCount() ); + updatedChunks[n] = QgsCopcUpdate::UpdatedChunk{ nodePointCount, it.value() }; + } + + QFileInfo fileInfo( mUri ); + QString outputFilename = fileInfo.dir().filePath( fileInfo.baseName() + "-update.copc.laz" ); + + if ( !QgsCopcUpdate::writeUpdatedFile( mUri, outputFilename, updatedChunks, errorMessage ) ) + { return false; + } + + // reset the underlying index - we will reload it at the end + QgsCopcPointCloudIndex *copcIndex = static_cast( mIndex.get() ); + copcIndex->reset(); + + QString originalFilename = fileInfo.dir().filePath( fileInfo.baseName() + "-original.copc.laz" ); + if ( !QFile::rename( mUri, originalFilename ) ) + { + if ( errorMessage ) + *errorMessage = "Rename of the old COPC failed!"; + QFile::remove( outputFilename ); + return false; + } + + if ( !QFile::rename( outputFilename, mUri ) ) + { + if ( errorMessage ) + *errorMessage = "Rename of the new COPC failed!"; + QFile::rename( originalFilename, mUri ); + QFile::remove( outputFilename ); + return false; + } + + if ( !QFile::remove( originalFilename ) ) + { + if ( errorMessage ) + *errorMessage = "Removal of the old COPC failed!"; + // TODO: cleanup here as well? + return false; + } mEditedNodeData.clear(); + + // now let's reload + copcIndex->load( mUri ); + return true; } @@ -138,5 +196,19 @@ bool QgsPointCloudEditingIndex::updateNodeData( const QHash modifiedNodesIds = data.keys(); + QSet modifiedNodesSet( modifiedNodesIds.constBegin(), modifiedNodesIds.constEnd() ); + + // get rid of cached keys that got modified + { + QMutexLocker locker( &sBlockCacheMutex ); + const QList cacheKeys = sBlockCache.keys(); + for ( QgsPointCloudCacheKey cacheKey : cacheKeys ) + { + if ( cacheKey.uri() == mUri && modifiedNodesSet.contains( cacheKey.node() ) ) + sBlockCache.remove( cacheKey ); + } + } + return true; } diff --git a/src/core/pointcloud/qgspointcloudeditingindex.h b/src/core/pointcloud/qgspointcloudeditingindex.h index 70a892de201b..0517cb9474e5 100644 --- a/src/core/pointcloud/qgspointcloudeditingindex.h +++ b/src/core/pointcloud/qgspointcloudeditingindex.h @@ -57,9 +57,11 @@ class CORE_EXPORT QgsPointCloudEditingIndex : public QgsAbstractPointCloudIndex /** * Tries to store pending changes to the data provider. + * If errorMessage is not a null pointer, it will receive + * an error message in case the call failed. * \return TRUE on success, otherwise FALSE */ - bool commitChanges(); + bool commitChanges( QString *errorMessage = nullptr ); //! Returns TRUE if there are uncommitted changes, FALSE otherwise bool isModified() const; @@ -69,6 +71,8 @@ class CORE_EXPORT QgsPointCloudEditingIndex : public QgsAbstractPointCloudIndex QgsPointCloudIndex mIndex; bool mIsValid = false; QHash mEditedNodeData; + + friend class QgsPointCloudLayerEditUtils; }; #endif // QGSPOINTCLOUDEDITINGINDEX_H diff --git a/src/core/pointcloud/qgspointcloudindex.cpp b/src/core/pointcloud/qgspointcloudindex.cpp index af5aa064b656..184f13838a73 100644 --- a/src/core/pointcloud/qgspointcloudindex.cpp +++ b/src/core/pointcloud/qgspointcloudindex.cpp @@ -504,11 +504,11 @@ QVariantMap QgsPointCloudIndex::extraMetadata() const return mIndex->extraMetadata(); } -bool QgsPointCloudIndex::commitChanges() +bool QgsPointCloudIndex::commitChanges( QString *errorMessage ) { Q_ASSERT( mIndex ); if ( QgsPointCloudEditingIndex *index = dynamic_cast( mIndex.get() ) ) - return index->commitChanges(); + return index->commitChanges( errorMessage ); return false; } diff --git a/src/core/pointcloud/qgspointcloudindex.h b/src/core/pointcloud/qgspointcloudindex.h index 430cff42d678..c44d4960961b 100644 --- a/src/core/pointcloud/qgspointcloudindex.h +++ b/src/core/pointcloud/qgspointcloudindex.h @@ -422,6 +422,9 @@ class CORE_EXPORT QgsPointCloudIndex SIP_NODEFAULTCTORS //! Checks if index is non-null operator bool() const; + //! Returns pointer to the implementation class + QgsAbstractPointCloudIndex *get() SIP_SKIP { return mIndex.get(); } + /** * Loads the index from the file * @@ -643,9 +646,11 @@ class CORE_EXPORT QgsPointCloudIndex SIP_NODEFAULTCTORS /** * Tries to store pending changes to the data provider. + * If errorMessage is not a null pointer, it will receive + * an error message in case the call failed. * \return TRUE on success, otherwise FALSE */ - bool commitChanges(); + bool commitChanges( QString *errorMessage SIP_OUT = nullptr ); //! Returns TRUE if there are uncommitted changes, FALSE otherwise bool isModified() const; @@ -654,6 +659,8 @@ class CORE_EXPORT QgsPointCloudIndex SIP_NODEFAULTCTORS std::shared_ptr mIndex; friend class TestQgsPointCloudEditing; + friend class QgsPointCloudLayerEditUtils; + friend class QgsPointCloudEditingIndex; }; diff --git a/src/core/pointcloud/qgspointcloudlayer.cpp b/src/core/pointcloud/qgspointcloudlayer.cpp index 74da1df3688a..a506a5a19786 100644 --- a/src/core/pointcloud/qgspointcloudlayer.cpp +++ b/src/core/pointcloud/qgspointcloudlayer.cpp @@ -1001,10 +1001,18 @@ bool QgsPointCloudLayer::startEditing() bool QgsPointCloudLayer::commitChanges( bool stopEditing ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS - if ( !mEditIndex || - !mEditIndex.commitChanges() ) + if ( !mEditIndex ) return false; + if ( mEditIndex.isModified() ) + { + if ( !mEditIndex.commitChanges( &mCommitError ) ) + return false; + + emit layerModified(); + triggerRepaint(); + } + if ( stopEditing ) { mEditIndex = QgsPointCloudIndex(); diff --git a/src/core/pointcloud/qgspointcloudlayereditutils.cpp b/src/core/pointcloud/qgspointcloudlayereditutils.cpp index 28085388aa7a..251b7a1580ed 100644 --- a/src/core/pointcloud/qgspointcloudlayereditutils.cpp +++ b/src/core/pointcloud/qgspointcloudlayereditutils.cpp @@ -17,6 +17,11 @@ #include "qgspointcloudlayer.h" #include "qgslazdecoder.h" +#include "qgscopcpointcloudindex.h" +#include +#include +#include "qgspointcloudeditingindex.h" + QgsPointCloudLayerEditUtils::QgsPointCloudLayerEditUtils( QgsPointCloudLayer *layer ) : mIndex( layer->index() ) @@ -59,27 +64,171 @@ bool QgsPointCloudLayerEditUtils::changeAttributeValue( const QgsPointCloudNodeI sortedPoints.constLast() > mIndex.getNode( n ).pointCount() ) return false; - QgsPointCloudRequest req; - req.setAttributes( attributeCollection ); + QgsPointCloudEditingIndex *editIndex = static_cast( mIndex.get() ); + QgsCopcPointCloudIndex *copcIndex = static_cast( editIndex->mIndex.get() ); + + QByteArray chunkData; + if ( editIndex->mEditedNodeData.contains( n ) ) + { + chunkData = editIndex->mEditedNodeData[n]; + } + else + { + QPair offsetSizePair = copcIndex->mHierarchyNodePos[n]; + chunkData = copcIndex->readRange( offsetSizePair.first, offsetSizePair.second ); + } + + QByteArray data = updateChunkValues( copcIndex, chunkData, *at, value, n, pts ); + + return mIndex.updateNodeData( {{n, data}} ); +} + + +static void updatePoint( char *pointBuffer, int pointFormat, const QString &attributeName, double newValue ) +{ + if ( attributeName == QLatin1String( "Intensity" ) ) // unsigned short + { + quint16 newValueShort = ( quint16 ) newValue; + memcpy( pointBuffer + 12, &newValueShort, sizeof( qint16 ) ); + } + else if ( attributeName == QLatin1String( "ReturnNumber" ) ) // bits 0-3 + { + uchar newByteValue = ( ( uchar )newValue ) & 0xf; + pointBuffer[14] = ( char )( ( pointBuffer[14] & 0xf0 ) | newByteValue ); + } + else if ( attributeName == QLatin1String( "NumberOfReturns" ) ) // bits 4-7 + { + uchar newByteValue = ( ( ( uchar )newValue ) & 0xf ) << 4; + pointBuffer[14] = ( char )( ( pointBuffer[14] & 0xf ) | newByteValue ); + } + else if ( attributeName == QLatin1String( "Synthetic" ) ) // bit 0 + { + uchar newByteValue = ( ( uchar )newValue & 0x1 ); + pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xfe ) | newByteValue ); + } + else if ( attributeName == QLatin1String( "KeyPoint" ) ) // bit 1 + { + uchar newByteValue = ( ( uchar )newValue & 0x1 ) << 1; + pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xfd ) | newByteValue ); + } + else if ( attributeName == QLatin1String( "Withheld" ) ) // bit 2 + { + uchar newByteValue = ( ( uchar )newValue & 0x1 ) << 2; + pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xfb ) | newByteValue ); + } + else if ( attributeName == QLatin1String( "Overlap" ) ) // bit 3 + { + uchar newByteValue = ( ( uchar )newValue & 0x1 ) << 3; + pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xf7 ) | newByteValue ); + } + else if ( attributeName == QLatin1String( "ScannerChannel" ) ) // bits 4-5 + { + uchar newByteValue = ( ( uchar )newValue & 0x3 ) << 4; + pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xcf ) | newByteValue ); + } + else if ( attributeName == QLatin1String( "ScanDirectionFlag" ) ) // bit 6 + { + uchar newByteValue = ( ( uchar )newValue & 0x1 ) << 6; + pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xbf ) | newByteValue ); + } + else if ( attributeName == QLatin1String( "EdgeOfFlightLine" ) ) // bit 7 + { + uchar newByteValue = ( ( uchar )newValue & 0x1 ) << 7; + pointBuffer[15] = ( char )( ( pointBuffer[15] & 0x7f ) | newByteValue ); + } + else if ( attributeName == QLatin1String( "Classification" ) ) // unsigned char + { + pointBuffer[16] = ( char )( uchar )newValue; + } + else if ( attributeName == QLatin1String( "UserData" ) ) // unsigned char + { + pointBuffer[17] = ( char )( uchar )newValue; + } + else if ( attributeName == QLatin1String( "ScanAngleRank" ) ) // short + { + qint16 newValueShort = ( qint16 ) newValue; + memcpy( pointBuffer + 18, &newValueShort, sizeof( qint16 ) ); + } + else if ( attributeName == QLatin1String( "PointSourceId" ) ) // unsigned short + { + quint16 newValueShort = ( quint16 ) newValue; + memcpy( pointBuffer + 20, &newValueShort, sizeof( quint16 ) ); + } + else if ( attributeName == QLatin1String( "GpsTime" ) ) // double + { + memcpy( pointBuffer + 22, &newValue, sizeof( double ) ); + } + else if ( pointFormat == 7 || pointFormat == 8 ) + { + if ( attributeName == QLatin1String( "Red" ) ) // unsigned short + { + quint16 newValueShort = ( quint16 ) newValue; + memcpy( pointBuffer + 30, &newValueShort, sizeof( quint16 ) ); + } + else if ( attributeName == QLatin1String( "Green" ) ) // unsigned short + { + quint16 newValueShort = ( quint16 ) newValue; + memcpy( pointBuffer + 32, &newValueShort, sizeof( quint16 ) ); + } + else if ( attributeName == QLatin1String( "Blue" ) ) // unsigned short + { + quint16 newValueShort = ( quint16 ) newValue; + memcpy( pointBuffer + 34, &newValueShort, sizeof( quint16 ) ); + } + else if ( pointFormat == 8 ) + { + if ( attributeName == QLatin1String( "Infrared" ) ) // unsigned short + { + quint16 newValueShort = ( quint16 ) newValue; + memcpy( pointBuffer + 36, &newValueShort, sizeof( quint16 ) ); + } + } + } +} + + +QByteArray QgsPointCloudLayerEditUtils::updateChunkValues( QgsCopcPointCloudIndex *copcIndex, const QByteArray &chunkData, const QgsPointCloudAttribute &attribute, double newValue, QgsPointCloudNodeId k, QVector pointIndices ) +{ + // set new classification value for the given points in voxel and return updated chunk data + + Q_ASSERT( copcIndex->mHierarchy.contains( k ) ); + Q_ASSERT( copcIndex->mHierarchyNodePos.contains( k ) ); + + int pointCount = copcIndex->mHierarchy[k]; - std::unique_ptr block = mIndex.nodeData( n, req ); - const int count = block->pointCount(); - const int recordSize = attributeCollection.pointRecordSize(); + lazperf::header14 header = copcIndex->mLazInfo->header(); - // copy data - QByteArray data( block->data(), count * recordSize ); + lazperf::reader::chunk_decompressor decompressor( header.pointFormat(), header.ebCount(), chunkData.constData() ); + lazperf::writer::chunk_compressor compressor( header.pointFormat(), header.ebCount() ); - char *ptr = data.data(); + std::unique_ptr decodedData( new char[ header.point_record_length ] ); - for ( int i : sortedPoints ) + // only PDRF 6/7/8 is allowed by COPC + Q_ASSERT( header.pointFormat() == 6 || header.pointFormat() == 7 || header.pointFormat() == 8 ); + + QSet pointIndicesSet( pointIndices.constBegin(), pointIndices.constEnd() ); + + QString attributeName = attribute.name(); + + for ( int i = 0 ; i < pointCount; ++i ) { - // replace attribute for selected point - lazStoreDoubleToStream( ptr, i * recordSize + attributeOffset, attribute.type(), value ); + decompressor.decompress( decodedData.get() ); + char *buf = decodedData.get(); + + if ( pointIndicesSet.contains( i ) ) + { + // TODO: support for extrabytes attributes + updatePoint( buf, header.point_format_id, attributeName, newValue ); + } + + compressor.compress( decodedData.get() ); } - return mIndex.updateNodeData( {{n, data}} );; + std::vector data = compressor.done(); + return QByteArray( ( const char * ) data.data(), ( int ) data.size() ); // QByteArray makes a deep copy } + QByteArray QgsPointCloudLayerEditUtils::dataForAttributes( const QgsPointCloudAttributeCollection &allAttributes, const QByteArray &data, const QgsPointCloudRequest &request ) { const QVector attributes = allAttributes.attributes(); diff --git a/src/core/pointcloud/qgspointcloudlayereditutils.h b/src/core/pointcloud/qgspointcloudlayereditutils.h index 4aabb116f727..bd2b158b2e1f 100644 --- a/src/core/pointcloud/qgspointcloudlayereditutils.h +++ b/src/core/pointcloud/qgspointcloudlayereditutils.h @@ -30,6 +30,8 @@ class QgsPointCloudAttribute; class QgsPointCloudAttributeCollection; class QgsPointCloudRequest; +class QgsCopcPointCloudIndex; + /** * \ingroup core * @@ -63,6 +65,9 @@ class CORE_EXPORT QgsPointCloudLayerEditUtils static bool isAttributeValueValid( const QgsPointCloudAttribute &attribute, double value ); private: + + QByteArray updateChunkValues( QgsCopcPointCloudIndex *copcIndex, const QByteArray &chunkData, const QgsPointCloudAttribute &attribute, double newClassValue, QgsPointCloudNodeId k, QVector pointIndices ); + QgsPointCloudIndex mIndex; }; diff --git a/tests/src/core/testqgspointcloudediting.cpp b/tests/src/core/testqgspointcloudediting.cpp index c160c9951ace..b5db39b2eaa5 100644 --- a/tests/src/core/testqgspointcloudediting.cpp +++ b/tests/src/core/testqgspointcloudediting.cpp @@ -48,6 +48,7 @@ class TestQgsPointCloudEditing : public QgsTest void testStartStopEditing(); void testModifyAttributeValue(); void testModifyAttributeValueInvalid(); + void testCommitChanges(); }; //runs before all tests @@ -409,5 +410,63 @@ void TestQgsPointCloudEditing::testModifyAttributeValueInvalid() QCOMPARE( spy.size(), 0 ); } +void TestQgsPointCloudEditing::testCommitChanges() +{ + const QString dataPath = copyTestData( QStringLiteral( "point_clouds/copc/sunshine-coast.copc.laz" ) ); + + std::unique_ptr layer = std::make_unique( dataPath, QStringLiteral( "layer" ), QStringLiteral( "copc" ) ); + QVERIFY( layer->isValid() ); + QVERIFY( layer->startEditing() ); + QVERIFY( layer->isEditable() ); + + QgsPointCloudNodeId n( 0, 0, 0, 0 ); + QgsPointCloudAttribute at( QStringLiteral( "Classification" ), QgsPointCloudAttribute::UChar ); + + QgsPointCloudRequest request; + request.setAttributes( QgsPointCloudAttributeCollection( QVector() << at ) ); + + // check values before any changes + std::unique_ptr block0 = layer->index().nodeData( n, request ); + const char *block0Data = block0->data(); + QCOMPARE( block0Data[0], 2 ); + QCOMPARE( block0Data[6], 2 ); + QCOMPARE( block0Data[11], 3 ); + QCOMPARE( block0Data[14], 3 ); + + // Change some points, point order should not matter + QVERIFY( layer->changeAttributeValue( n, { 4, 2, 0, 1, 3, 16, 5, 13, 15, 14 }, at, 1 ) ); + QVERIFY( layer->isModified() ); + + // check values after change, before committing + std::unique_ptr block1 = layer->index().nodeData( n, request ); + const char *block1Data = block1->data(); + QCOMPARE( block1Data[0], 1 ); + QCOMPARE( block1Data[6], 2 ); // unchanged + QCOMPARE( block1Data[11], 3 ); // unchanged + QCOMPARE( block1Data[14], 1 ); + + QVERIFY( layer->commitChanges() ); + QVERIFY( !layer->isModified() ); + + // check values after committing changes + std::unique_ptr block2 = layer->index().nodeData( n, request ); + const char *block2Data = block2->data(); + QCOMPARE( block2Data[0], 1 ); + QCOMPARE( block2Data[6], 2 ); // unchanged + QCOMPARE( block2Data[11], 3 ); // unchanged + QCOMPARE( block2Data[14], 1 ); + + // try to open the file as a new layer and check saved values + std::unique_ptr layerNew = std::make_unique( dataPath, QStringLiteral( "layer" ), QStringLiteral( "copc" ) ); + + // check values in the new layer + std::unique_ptr block3 = layerNew->index().nodeData( n, request ); + const char *block3Data = block3->data(); + QCOMPARE( block3Data[0], 1 ); + QCOMPARE( block3Data[6], 2 ); // unchanged + QCOMPARE( block3Data[11], 3 ); // unchanged + QCOMPARE( block3Data[14], 1 ); +} + QGSTEST_MAIN( TestQgsPointCloudEditing ) #include "testqgspointcloudediting.moc" From 01878884f3f988e55a91d33341ef92998166baa4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 16:04:59 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/src/core/testqgspointcloudediting.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/src/core/testqgspointcloudediting.cpp b/tests/src/core/testqgspointcloudediting.cpp index b5db39b2eaa5..73100e59654f 100644 --- a/tests/src/core/testqgspointcloudediting.cpp +++ b/tests/src/core/testqgspointcloudediting.cpp @@ -427,7 +427,7 @@ void TestQgsPointCloudEditing::testCommitChanges() // check values before any changes std::unique_ptr block0 = layer->index().nodeData( n, request ); - const char *block0Data = block0->data(); + const char *block0Data = block0->data(); QCOMPARE( block0Data[0], 2 ); QCOMPARE( block0Data[6], 2 ); QCOMPARE( block0Data[11], 3 ); @@ -439,10 +439,10 @@ void TestQgsPointCloudEditing::testCommitChanges() // check values after change, before committing std::unique_ptr block1 = layer->index().nodeData( n, request ); - const char *block1Data = block1->data(); + const char *block1Data = block1->data(); QCOMPARE( block1Data[0], 1 ); QCOMPARE( block1Data[6], 2 ); // unchanged - QCOMPARE( block1Data[11], 3 ); // unchanged + QCOMPARE( block1Data[11], 3 ); // unchanged QCOMPARE( block1Data[14], 1 ); QVERIFY( layer->commitChanges() ); @@ -450,10 +450,10 @@ void TestQgsPointCloudEditing::testCommitChanges() // check values after committing changes std::unique_ptr block2 = layer->index().nodeData( n, request ); - const char *block2Data = block2->data(); + const char *block2Data = block2->data(); QCOMPARE( block2Data[0], 1 ); QCOMPARE( block2Data[6], 2 ); // unchanged - QCOMPARE( block2Data[11], 3 ); // unchanged + QCOMPARE( block2Data[11], 3 ); // unchanged QCOMPARE( block2Data[14], 1 ); // try to open the file as a new layer and check saved values @@ -461,10 +461,10 @@ void TestQgsPointCloudEditing::testCommitChanges() // check values in the new layer std::unique_ptr block3 = layerNew->index().nodeData( n, request ); - const char *block3Data = block3->data(); + const char *block3Data = block3->data(); QCOMPARE( block3Data[0], 1 ); QCOMPARE( block3Data[6], 2 ); // unchanged - QCOMPARE( block3Data[11], 3 ); // unchanged + QCOMPARE( block3Data[11], 3 ); // unchanged QCOMPARE( block3Data[14], 1 ); } From 3cddf34c8696da965b4ef021b50fc3e668532d46 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Tue, 14 Jan 2025 15:14:40 +0100 Subject: [PATCH 3/5] ci fixes --- src/core/pointcloud/qgscopcupdate.cpp | 40 +++++++++++++-------------- src/core/pointcloud/qgscopcupdate.h | 6 ++++ 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/core/pointcloud/qgscopcupdate.cpp b/src/core/pointcloud/qgscopcupdate.cpp index 113eaf17f5aa..b6fdd2c30bf4 100644 --- a/src/core/pointcloud/qgscopcupdate.cpp +++ b/src/core/pointcloud/qgscopcupdate.cpp @@ -63,11 +63,11 @@ HierarchyEntries getHierarchyPage( std::ifstream &file, uint64_t offset, uint64_ { HierarchyEntries page; std::vector buf( 32 ); - int numEntries = size / 32; - file.seekg( offset ); + int numEntries = static_cast( size / 32 ); + file.seekg( static_cast( offset ) ); while ( numEntries-- ) { - file.read( buf.data(), buf.size() ); + file.read( buf.data(), static_cast( buf.size() ) ); lazperf::LeExtractor s( buf.data(), buf.size() ); HierarchyEntry e; @@ -93,13 +93,13 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash allHeaderData; allHeaderData.resize( mHeader.point_offset ); - mFile.read( allHeaderData.data(), allHeaderData.size() ); - m_f.write( allHeaderData.data(), allHeaderData.size() ); + mFile.read( allHeaderData.data(), static_cast( allHeaderData.size() ) ); + m_f.write( allHeaderData.data(), static_cast( allHeaderData.size() ) ); m_f.write( "XXXXXXXX", 8 ); // placeholder for chunk table offset uint64_t currentChunkOffset = mHeader.point_offset + 8; - mFile.seekg( currentChunkOffset ); // this is where first chunk starts + mFile.seekg( static_cast( currentChunkOffset ) ); // this is where first chunk starts // now, let's write chunks: // - iterate through original chunk table, write out chunks @@ -124,7 +124,7 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash( ch.offset ) ); m_f.write( updatedChunk.chunkData.constData(), updatedChunk.chunkData.size() ); @@ -134,10 +134,10 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash chunkDataX; - chunkDataX.resize( ch.offset ); - mFile.read( chunkDataX.data(), chunkDataX.size() ); - m_f.write( chunkDataX.data(), chunkDataX.size() ); + std::vector originalChunkData; + originalChunkData.resize( ch.offset ); + mFile.read( originalChunkData.data(), static_cast( originalChunkData.size() ) ); + m_f.write( originalChunkData.data(), static_cast( originalChunkData.size() ) ); } currentChunkOffset += ch.offset; @@ -160,10 +160,10 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash( m_f.tellp() ) + 60 - static_cast( mHierarchyOffset ); HierarchyEntry *oldCopcHierarchyBlobEntries = ( HierarchyEntry * ) mHierarchyBlob.data(); - int nEntries = mHierarchyBlob.size() / 32; + int nEntries = static_cast( mHierarchyBlob.size() / 32 ); for ( int i = 0; i < nEntries; ++i ) { HierarchyEntry &e = oldCopcHierarchyBlobEntries[i]; @@ -176,7 +176,7 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash( newByteSize ); } } else if ( e.pointCount < 0 ) @@ -203,7 +203,7 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash( mHierarchyBlob.size() ) ); // write other eVLRs @@ -213,7 +213,7 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash evlrBody = mEvlrData[i]; evlrHeader.write( m_f ); - m_f.write( evlrBody.data(), evlrBody.size() ); + m_f.write( evlrBody.data(), static_cast( evlrBody.size() ) ); } // patch header @@ -284,7 +284,7 @@ void QgsCopcUpdate::readChunkTable() mFile.seekg( mHeader.point_offset ); mFile.read( ( char * )&chunkTableOffset, sizeof( chunkTableOffset ) ); - mFile.seekg( chunkTableOffset + 4 ); // The first 4 bytes are the version, then the chunk count. + mFile.seekg( static_cast( chunkTableOffset ) + 4 ); // The first 4 bytes are the version, then the chunk count. mFile.read( ( char * )&mChunkCount, sizeof( mChunkCount ) ); // @@ -340,7 +340,7 @@ void QgsCopcUpdate::readHierarchy() } lazperf::evlr_header evlr1; - mFile.seekg( mHeader.evlr_offset ); + mFile.seekg( static_cast( mHeader.evlr_offset ) ); mHierarchyOffset = 0; // where the hierarchy eVLR payload starts @@ -351,7 +351,7 @@ void QgsCopcUpdate::readHierarchy() { mHierarchyBlob.resize( evlr1.data_length ); mHierarchyOffset = mFile.tellg(); - mFile.read( mHierarchyBlob.data(), evlr1.data_length ); + mFile.read( mHierarchyBlob.data(), static_cast( evlr1.data_length ) ); } else { @@ -359,7 +359,7 @@ void QgsCopcUpdate::readHierarchy() mEvlrHeaders.push_back( evlr1 ); std::vector evlrBlob; evlrBlob.resize( evlr1.data_length ); - mFile.read( evlrBlob.data(), evlrBlob.size() ); + mFile.read( evlrBlob.data(), static_cast( evlrBlob.size() ) ); mEvlrData.push_back( evlrBlob ); } } diff --git a/src/core/pointcloud/qgscopcupdate.h b/src/core/pointcloud/qgscopcupdate.h index a60da4e0b4ae..059fcafb69e7 100644 --- a/src/core/pointcloud/qgscopcupdate.h +++ b/src/core/pointcloud/qgscopcupdate.h @@ -23,11 +23,17 @@ #include "qgspointcloudindex.h" +#define SIP_NO_FILE + /** + * \ingroup core + * * This class takes an existing COPC file and a list of chunks that should be modified, * and outputs an updated COPC file where the modified chunks replace the original chunks. * + * \note The API is considered EXPERIMENTAL and can be changed without a notice + * * \since QGIS 3.42 */ class CORE_EXPORT QgsCopcUpdate From 1ef6b5497124d353beba70d69b43397804dcbe88 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Tue, 14 Jan 2025 17:09:53 +0100 Subject: [PATCH 4/5] fix build --- src/core/pointcloud/qgscopcupdate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/pointcloud/qgscopcupdate.cpp b/src/core/pointcloud/qgscopcupdate.cpp index b6fdd2c30bf4..70b6e2eb2fbf 100644 --- a/src/core/pointcloud/qgscopcupdate.cpp +++ b/src/core/pointcloud/qgscopcupdate.cpp @@ -124,7 +124,7 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash( ch.offset ) ); + mFile.seekg( static_cast( mFile.tellg() ) + static_cast( ch.offset ) ); m_f.write( updatedChunk.chunkData.constData(), updatedChunk.chunkData.size() ); From 89e7d98ce435e50b675555281bea2055dcc6a282 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Thu, 16 Jan 2025 12:17:17 +0100 Subject: [PATCH 5/5] review fixes --- .../pointcloud/qgscopcpointcloudindex.cpp | 2 - src/core/pointcloud/qgscopcupdate.cpp | 51 +++++++------- src/core/pointcloud/qgscopcupdate.h | 9 ++- .../pointcloud/qgspointcloudeditingindex.cpp | 20 +++--- src/core/pointcloud/qgspointcloudindex.h | 2 - .../qgspointcloudlayereditutils.cpp | 68 +++++++++---------- .../pointcloud/qgspointcloudlayereditutils.h | 3 +- 7 files changed, 75 insertions(+), 80 deletions(-) diff --git a/src/core/pointcloud/qgscopcpointcloudindex.cpp b/src/core/pointcloud/qgscopcpointcloudindex.cpp index fcddce4c66f9..24bd1bd60244 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.cpp +++ b/src/core/pointcloud/qgscopcpointcloudindex.cpp @@ -524,9 +524,7 @@ void QgsCopcPointCloudIndex::reset() mRootBounds = QgsBox3D(); mAttributes = QgsPointCloudAttributeCollection(); mSpan = 0; - //mFilterExpression mError.clear(); - //mUri // QgsCopcPointCloudIndex mIsValid = false; diff --git a/src/core/pointcloud/qgscopcupdate.cpp b/src/core/pointcloud/qgscopcupdate.cpp index 70b6e2eb2fbf..047880aaef75 100644 --- a/src/core/pointcloud/qgscopcupdate.cpp +++ b/src/core/pointcloud/qgscopcupdate.cpp @@ -82,9 +82,8 @@ HierarchyEntries getHierarchyPage( std::ifstream &file, uint64_t offset, uint64_ } -bool QgsCopcUpdate::write( QString outputFilename, const QHash &updatedChunks ) +bool QgsCopcUpdate::write( const QString &outputFilename, const QHash &updatedChunks ) { - std::ofstream m_f; m_f.open( QgsLazDecoder::toNativePath( outputFilename ), std::ios::out | std::ios::binary ); @@ -113,15 +112,15 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash( mFile.tellg() ) + static_cast( ch.offset ) ); @@ -146,10 +145,10 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash( &mChunkCount ), sizeof( mChunkCount ) ); lazperf::OutFileStream outStream( m_f ); lazperf::compress_chunk_table( outStream.cb(), mChunks, true ); @@ -160,10 +159,10 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash( m_f.tellp() ) + 60 - static_cast( mHierarchyOffset ); + const long hierPositionShift = static_cast( m_f.tellp() ) + 60 - static_cast( mHierarchyOffset ); - HierarchyEntry *oldCopcHierarchyBlobEntries = ( HierarchyEntry * ) mHierarchyBlob.data(); - int nEntries = static_cast( mHierarchyBlob.size() / 32 ); + HierarchyEntry *oldCopcHierarchyBlobEntries = reinterpret_cast( mHierarchyBlob.data() ); + const int nEntries = static_cast( mHierarchyBlob.size() / 32 ); for ( int i = 0; i < nEntries; ++i ) { HierarchyEntry &e = oldCopcHierarchyBlobEntries[i]; @@ -193,7 +192,7 @@ bool QgsCopcUpdate::write( QString outputFilename, const QHash( &newEvlrOffset ), 8 ); - uint64_t newRootHierOffset = mCopcVlr.root_hier_offset + hierPositionShift; + const uint64_t newRootHierOffset = mCopcVlr.root_hier_offset + hierPositionShift; m_f.seekp( 469 ); - m_f.write( ( const char * )&newRootHierOffset, 8 ); + m_f.write( reinterpret_cast( &newRootHierOffset ), 8 ); m_f.seekp( mHeader.point_offset ); - m_f.write( ( const char * )&newChunkTableOffset, 8 ); + m_f.write( reinterpret_cast( &newChunkTableOffset ), 8 ); return true; } -bool QgsCopcUpdate::read( QString inputFilename ) +bool QgsCopcUpdate::read( const QString &inputFilename ) { mInputFilename = inputFilename; mFile.open( QgsLazDecoder::toNativePath( inputFilename ), std::ios::binary | std::ios::in ); if ( mFile.fail() ) { - mErrorMessage = "Could not open file for reading: " + inputFilename; + mErrorMessage = QStringLiteral( "Could not open file for reading: %1" ).arg( inputFilename ); return false; } @@ -260,7 +259,7 @@ bool QgsCopcUpdate::readHeader() mHeader = lazperf::header14::create( mFile ); if ( !mFile ) { - mErrorMessage = "Error reading COPC header"; + mErrorMessage = QStringLiteral( "Error reading COPC header" ); return false; } @@ -270,7 +269,7 @@ bool QgsCopcUpdate::readHeader() int baseCount = lazperf::baseCount( mHeader.point_format_id ); if ( baseCount == 0 ) { - mErrorMessage = QString( "Bad point record format: %1" ).arg( mHeader.point_format_id ); + mErrorMessage = QStringLiteral( "Bad point record format: %1" ).arg( mHeader.point_format_id ); return false; } @@ -283,9 +282,9 @@ void QgsCopcUpdate::readChunkTable() uint64_t chunkTableOffset; mFile.seekg( mHeader.point_offset ); - mFile.read( ( char * )&chunkTableOffset, sizeof( chunkTableOffset ) ); + mFile.read( reinterpret_cast( &chunkTableOffset ), sizeof( chunkTableOffset ) ); mFile.seekg( static_cast( chunkTableOffset ) + 4 ); // The first 4 bytes are the version, then the chunk count. - mFile.read( ( char * )&mChunkCount, sizeof( mChunkCount ) ); + mFile.read( reinterpret_cast( &mChunkCount ), sizeof( mChunkCount ) ); // // read chunk table @@ -312,11 +311,15 @@ void QgsCopcUpdate::readChunkTable() void QgsCopcUpdate::readHierarchy() { - // get all hierarchy pages HierarchyEntries childEntriesToProcess; - childEntriesToProcess.push_back( HierarchyEntry{ QgsPointCloudNodeId( 0, 0, 0, 0 ), mCopcVlr.root_hier_offset, ( int32_t )mCopcVlr.root_hier_size, -1 } ); + childEntriesToProcess.push_back( HierarchyEntry + { + QgsPointCloudNodeId( 0, 0, 0, 0 ), + mCopcVlr.root_hier_offset, + static_cast( mCopcVlr.root_hier_size ), + -1 } ); while ( !childEntriesToProcess.empty() ) { diff --git a/src/core/pointcloud/qgscopcupdate.h b/src/core/pointcloud/qgscopcupdate.h index 059fcafb69e7..e0a853022953 100644 --- a/src/core/pointcloud/qgscopcupdate.h +++ b/src/core/pointcloud/qgscopcupdate.h @@ -17,12 +17,11 @@ #define QGSCOPCUPDATE_H #include "qgis_core.h" +#include "qgspointcloudindex.h" #include #include -#include "qgspointcloudindex.h" - #define SIP_NO_FILE @@ -50,10 +49,10 @@ class CORE_EXPORT QgsCopcUpdate }; //! Reads input COPC file and initializes all the members - bool read( QString inputFilename ); + bool read( const QString &inputFilename ); //! Writes a COPC file with updated chunks - bool write( QString outputFilename, const QHash &updatedChunks ); + bool write( const QString &outputFilename, const QHash &updatedChunks ); //! Returns error message QString errorMessage() const { return mErrorMessage; } @@ -81,7 +80,7 @@ class CORE_EXPORT QgsCopcUpdate lazperf::header14 mHeader; lazperf::copc_info_vlr mCopcVlr; std::vector mChunks; - uint32_t mChunkCount; + uint32_t mChunkCount = 0; uint64_t mHierarchyOffset = 0; std::vector mHierarchyBlob; std::vector mEvlrHeaders; diff --git a/src/core/pointcloud/qgspointcloudeditingindex.cpp b/src/core/pointcloud/qgspointcloudeditingindex.cpp index 73b4144fa6a9..562d8d0c1119 100644 --- a/src/core/pointcloud/qgspointcloudeditingindex.cpp +++ b/src/core/pointcloud/qgspointcloudeditingindex.cpp @@ -21,7 +21,8 @@ #include "qgscopcupdate.h" #include "qgslazdecoder.h" -#include +#include +#include QgsPointCloudEditingIndex::QgsPointCloudEditingIndex( QgsPointCloudLayer *layer ) @@ -139,7 +140,7 @@ bool QgsPointCloudEditingIndex::commitChanges( QString *errorMessage ) } QFileInfo fileInfo( mUri ); - QString outputFilename = fileInfo.dir().filePath( fileInfo.baseName() + "-update.copc.laz" ); + const QString outputFilename = fileInfo.dir().filePath( fileInfo.baseName() + QStringLiteral( "-update.copc.laz" ) ); if ( !QgsCopcUpdate::writeUpdatedFile( mUri, outputFilename, updatedChunks, errorMessage ) ) { @@ -150,11 +151,11 @@ bool QgsPointCloudEditingIndex::commitChanges( QString *errorMessage ) QgsCopcPointCloudIndex *copcIndex = static_cast( mIndex.get() ); copcIndex->reset(); - QString originalFilename = fileInfo.dir().filePath( fileInfo.baseName() + "-original.copc.laz" ); + const QString originalFilename = fileInfo.dir().filePath( fileInfo.baseName() + QStringLiteral( "-original.copc.laz" ) ); if ( !QFile::rename( mUri, originalFilename ) ) { if ( errorMessage ) - *errorMessage = "Rename of the old COPC failed!"; + *errorMessage = QStringLiteral( "Rename of the old COPC failed!" ); QFile::remove( outputFilename ); return false; } @@ -162,7 +163,7 @@ bool QgsPointCloudEditingIndex::commitChanges( QString *errorMessage ) if ( !QFile::rename( outputFilename, mUri ) ) { if ( errorMessage ) - *errorMessage = "Rename of the new COPC failed!"; + *errorMessage = QStringLiteral( "Rename of the new COPC failed!" ); QFile::rename( originalFilename, mUri ); QFile::remove( outputFilename ); return false; @@ -171,7 +172,7 @@ bool QgsPointCloudEditingIndex::commitChanges( QString *errorMessage ) if ( !QFile::remove( originalFilename ) ) { if ( errorMessage ) - *errorMessage = "Removal of the old COPC failed!"; + *errorMessage = QStringLiteral( "Removal of the old COPC failed!" ); // TODO: cleanup here as well? return false; } @@ -196,16 +197,13 @@ bool QgsPointCloudEditingIndex::updateNodeData( const QHash modifiedNodesIds = data.keys(); - QSet modifiedNodesSet( modifiedNodesIds.constBegin(), modifiedNodesIds.constEnd() ); - // get rid of cached keys that got modified { QMutexLocker locker( &sBlockCacheMutex ); const QList cacheKeys = sBlockCache.keys(); - for ( QgsPointCloudCacheKey cacheKey : cacheKeys ) + for ( const QgsPointCloudCacheKey &cacheKey : cacheKeys ) { - if ( cacheKey.uri() == mUri && modifiedNodesSet.contains( cacheKey.node() ) ) + if ( cacheKey.uri() == mUri && data.contains( cacheKey.node() ) ) sBlockCache.remove( cacheKey ); } } diff --git a/src/core/pointcloud/qgspointcloudindex.h b/src/core/pointcloud/qgspointcloudindex.h index c44d4960961b..531504380e7c 100644 --- a/src/core/pointcloud/qgspointcloudindex.h +++ b/src/core/pointcloud/qgspointcloudindex.h @@ -659,8 +659,6 @@ class CORE_EXPORT QgsPointCloudIndex SIP_NODEFAULTCTORS std::shared_ptr mIndex; friend class TestQgsPointCloudEditing; - friend class QgsPointCloudLayerEditUtils; - friend class QgsPointCloudEditingIndex; }; diff --git a/src/core/pointcloud/qgspointcloudlayereditutils.cpp b/src/core/pointcloud/qgspointcloudlayereditutils.cpp index 251b7a1580ed..e29c19a8db5d 100644 --- a/src/core/pointcloud/qgspointcloudlayereditutils.cpp +++ b/src/core/pointcloud/qgspointcloudlayereditutils.cpp @@ -16,11 +16,11 @@ #include "qgspointcloudlayereditutils.h" #include "qgspointcloudlayer.h" #include "qgslazdecoder.h" - #include "qgscopcpointcloudindex.h" +#include "qgspointcloudeditingindex.h" + #include #include -#include "qgspointcloudeditingindex.h" QgsPointCloudLayerEditUtils::QgsPointCloudLayerEditUtils( QgsPointCloudLayer *layer ) @@ -88,70 +88,70 @@ static void updatePoint( char *pointBuffer, int pointFormat, const QString &attr { if ( attributeName == QLatin1String( "Intensity" ) ) // unsigned short { - quint16 newValueShort = ( quint16 ) newValue; + quint16 newValueShort = static_cast( newValue ); memcpy( pointBuffer + 12, &newValueShort, sizeof( qint16 ) ); } else if ( attributeName == QLatin1String( "ReturnNumber" ) ) // bits 0-3 { - uchar newByteValue = ( ( uchar )newValue ) & 0xf; - pointBuffer[14] = ( char )( ( pointBuffer[14] & 0xf0 ) | newByteValue ); + uchar newByteValue = static_cast( newValue ) & 0xf; + pointBuffer[14] = static_cast( ( pointBuffer[14] & 0xf0 ) | newByteValue ); } else if ( attributeName == QLatin1String( "NumberOfReturns" ) ) // bits 4-7 { - uchar newByteValue = ( ( ( uchar )newValue ) & 0xf ) << 4; - pointBuffer[14] = ( char )( ( pointBuffer[14] & 0xf ) | newByteValue ); + uchar newByteValue = ( static_cast( newValue ) & 0xf ) << 4; + pointBuffer[14] = static_cast( ( pointBuffer[14] & 0xf ) | newByteValue ); } else if ( attributeName == QLatin1String( "Synthetic" ) ) // bit 0 { - uchar newByteValue = ( ( uchar )newValue & 0x1 ); - pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xfe ) | newByteValue ); + uchar newByteValue = ( static_cast( newValue ) & 0x1 ); + pointBuffer[15] = static_cast( ( pointBuffer[15] & 0xfe ) | newByteValue ); } else if ( attributeName == QLatin1String( "KeyPoint" ) ) // bit 1 { - uchar newByteValue = ( ( uchar )newValue & 0x1 ) << 1; - pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xfd ) | newByteValue ); + uchar newByteValue = ( static_cast( newValue ) & 0x1 ) << 1; + pointBuffer[15] = static_cast( ( pointBuffer[15] & 0xfd ) | newByteValue ); } else if ( attributeName == QLatin1String( "Withheld" ) ) // bit 2 { - uchar newByteValue = ( ( uchar )newValue & 0x1 ) << 2; - pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xfb ) | newByteValue ); + uchar newByteValue = ( static_cast( newValue ) & 0x1 ) << 2; + pointBuffer[15] = static_cast( ( pointBuffer[15] & 0xfb ) | newByteValue ); } else if ( attributeName == QLatin1String( "Overlap" ) ) // bit 3 { - uchar newByteValue = ( ( uchar )newValue & 0x1 ) << 3; - pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xf7 ) | newByteValue ); + uchar newByteValue = ( static_cast( newValue ) & 0x1 ) << 3; + pointBuffer[15] = static_cast( ( pointBuffer[15] & 0xf7 ) | newByteValue ); } else if ( attributeName == QLatin1String( "ScannerChannel" ) ) // bits 4-5 { - uchar newByteValue = ( ( uchar )newValue & 0x3 ) << 4; - pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xcf ) | newByteValue ); + uchar newByteValue = ( static_cast( newValue ) & 0x3 ) << 4; + pointBuffer[15] = static_cast( ( pointBuffer[15] & 0xcf ) | newByteValue ); } else if ( attributeName == QLatin1String( "ScanDirectionFlag" ) ) // bit 6 { - uchar newByteValue = ( ( uchar )newValue & 0x1 ) << 6; - pointBuffer[15] = ( char )( ( pointBuffer[15] & 0xbf ) | newByteValue ); + uchar newByteValue = ( static_cast( newValue ) & 0x1 ) << 6; + pointBuffer[15] = static_cast( ( pointBuffer[15] & 0xbf ) | newByteValue ); } else if ( attributeName == QLatin1String( "EdgeOfFlightLine" ) ) // bit 7 { - uchar newByteValue = ( ( uchar )newValue & 0x1 ) << 7; - pointBuffer[15] = ( char )( ( pointBuffer[15] & 0x7f ) | newByteValue ); + uchar newByteValue = ( static_cast( newValue ) & 0x1 ) << 7; + pointBuffer[15] = static_cast( ( pointBuffer[15] & 0x7f ) | newByteValue ); } else if ( attributeName == QLatin1String( "Classification" ) ) // unsigned char { - pointBuffer[16] = ( char )( uchar )newValue; + pointBuffer[16] = static_cast( static_cast( newValue ) ); } else if ( attributeName == QLatin1String( "UserData" ) ) // unsigned char { - pointBuffer[17] = ( char )( uchar )newValue; + pointBuffer[17] = static_cast( static_cast( newValue ) ); } else if ( attributeName == QLatin1String( "ScanAngleRank" ) ) // short { - qint16 newValueShort = ( qint16 ) newValue; + qint16 newValueShort = static_cast( newValue ); memcpy( pointBuffer + 18, &newValueShort, sizeof( qint16 ) ); } else if ( attributeName == QLatin1String( "PointSourceId" ) ) // unsigned short { - quint16 newValueShort = ( quint16 ) newValue; + quint16 newValueShort = static_cast( newValue ); memcpy( pointBuffer + 20, &newValueShort, sizeof( quint16 ) ); } else if ( attributeName == QLatin1String( "GpsTime" ) ) // double @@ -162,24 +162,24 @@ static void updatePoint( char *pointBuffer, int pointFormat, const QString &attr { if ( attributeName == QLatin1String( "Red" ) ) // unsigned short { - quint16 newValueShort = ( quint16 ) newValue; + quint16 newValueShort = static_cast( newValue ); memcpy( pointBuffer + 30, &newValueShort, sizeof( quint16 ) ); } else if ( attributeName == QLatin1String( "Green" ) ) // unsigned short { - quint16 newValueShort = ( quint16 ) newValue; + quint16 newValueShort = static_cast( newValue ); memcpy( pointBuffer + 32, &newValueShort, sizeof( quint16 ) ); } else if ( attributeName == QLatin1String( "Blue" ) ) // unsigned short { - quint16 newValueShort = ( quint16 ) newValue; + quint16 newValueShort = static_cast( newValue ); memcpy( pointBuffer + 34, &newValueShort, sizeof( quint16 ) ); } else if ( pointFormat == 8 ) { if ( attributeName == QLatin1String( "Infrared" ) ) // unsigned short { - quint16 newValueShort = ( quint16 ) newValue; + quint16 newValueShort = static_cast( newValue ); memcpy( pointBuffer + 36, &newValueShort, sizeof( quint16 ) ); } } @@ -187,14 +187,12 @@ static void updatePoint( char *pointBuffer, int pointFormat, const QString &attr } -QByteArray QgsPointCloudLayerEditUtils::updateChunkValues( QgsCopcPointCloudIndex *copcIndex, const QByteArray &chunkData, const QgsPointCloudAttribute &attribute, double newValue, QgsPointCloudNodeId k, QVector pointIndices ) +QByteArray QgsPointCloudLayerEditUtils::updateChunkValues( QgsCopcPointCloudIndex *copcIndex, const QByteArray &chunkData, const QgsPointCloudAttribute &attribute, double newValue, const QgsPointCloudNodeId &n, const QVector &pointIndices ) { - // set new classification value for the given points in voxel and return updated chunk data - - Q_ASSERT( copcIndex->mHierarchy.contains( k ) ); - Q_ASSERT( copcIndex->mHierarchyNodePos.contains( k ) ); + Q_ASSERT( copcIndex->mHierarchy.contains( n ) ); + Q_ASSERT( copcIndex->mHierarchyNodePos.contains( n ) ); - int pointCount = copcIndex->mHierarchy[k]; + int pointCount = copcIndex->mHierarchy[n]; lazperf::header14 header = copcIndex->mLazInfo->header(); diff --git a/src/core/pointcloud/qgspointcloudlayereditutils.h b/src/core/pointcloud/qgspointcloudlayereditutils.h index bd2b158b2e1f..f94ba1720d9d 100644 --- a/src/core/pointcloud/qgspointcloudlayereditutils.h +++ b/src/core/pointcloud/qgspointcloudlayereditutils.h @@ -66,7 +66,8 @@ class CORE_EXPORT QgsPointCloudLayerEditUtils private: - QByteArray updateChunkValues( QgsCopcPointCloudIndex *copcIndex, const QByteArray &chunkData, const QgsPointCloudAttribute &attribute, double newClassValue, QgsPointCloudNodeId k, QVector pointIndices ); + //! Sets new classification value for the given points in voxel and return updated chunk data + QByteArray updateChunkValues( QgsCopcPointCloudIndex *copcIndex, const QByteArray &chunkData, const QgsPointCloudAttribute &attribute, double newClassValue, const QgsPointCloudNodeId &n, const QVector &pointIndices ); QgsPointCloudIndex mIndex; };