19#include "moc_qgscopcpointcloudindex.cpp"
25#include <QMutexLocker>
26#include <QJsonDocument>
37#include "qgspointcloudexpression.h"
39#include "lazperf/lazperf.hpp"
40#include "lazperf/readers.hpp"
41#include "lazperf/vlr.hpp"
45#define PROVIDER_KEY QStringLiteral( "copc" )
46#define PROVIDER_DESCRIPTION QStringLiteral( "COPC point cloud provider" )
48QgsCopcPointCloudIndex::QgsCopcPointCloudIndex() =
default;
50QgsCopcPointCloudIndex::~QgsCopcPointCloudIndex() =
default;
52std::unique_ptr<QgsPointCloudIndex> QgsCopcPointCloudIndex::clone()
const
54 QgsCopcPointCloudIndex *clone =
new QgsCopcPointCloudIndex;
55 QMutexLocker locker( &mHierarchyMutex );
56 copyCommonProperties( clone );
57 return std::unique_ptr<QgsPointCloudIndex>( clone );
60void QgsCopcPointCloudIndex::load(
const QString &fileName )
63 mCopcFile.open( QgsLazDecoder::toNativePath( fileName ), std::ios::binary );
65 if ( !mCopcFile.is_open() || !mCopcFile.good() )
67 mError = tr(
"Unable to open %1 for reading" ).arg( fileName );
73 mIsValid = mLazInfo->isValid();
76 mIsValid = loadSchema( *mLazInfo.get() );
80 mError = tr(
"Unable to recognize %1 as a LAZ file: \"%2\"" ).arg( fileName, mLazInfo->error() );
87bool QgsCopcPointCloudIndex::loadSchema(
QgsLazInfo &lazInfo )
89 QByteArray copcInfoVlrData = lazInfo.
vlrData( QStringLiteral(
"copc" ), 1 );
90 if ( copcInfoVlrData.isEmpty() )
92 mError = tr(
"Invalid COPC file" );
95 mCopcInfoVlr.fill( copcInfoVlrData.data(), copcInfoVlrData.size() );
97 mScale = lazInfo.
scale();
98 mOffset = lazInfo.
offset();
104 mExtent.
set( minCoords.
x(), minCoords.
y(), maxCoords.
x(), maxCoords.
y() );
105 mZMin = minCoords.
z();
106 mZMax = maxCoords.
z();
110 const double xmin = mCopcInfoVlr.center_x - mCopcInfoVlr.halfsize;
111 const double ymin = mCopcInfoVlr.center_y - mCopcInfoVlr.halfsize;
112 const double zmin = mCopcInfoVlr.center_z - mCopcInfoVlr.halfsize;
113 const double xmax = mCopcInfoVlr.center_x + mCopcInfoVlr.halfsize;
114 const double ymax = mCopcInfoVlr.center_y + mCopcInfoVlr.halfsize;
115 const double zmax = mCopcInfoVlr.center_z + mCopcInfoVlr.halfsize;
118 ( xmin - mOffset.x() ) / mScale.x(),
119 ( ymin - mOffset.y() ) / mScale.y(),
120 ( zmin - mOffset.z() ) / mScale.z(),
121 ( xmax - mOffset.x() ) / mScale.x(),
122 ( ymax - mOffset.y() ) / mScale.y(),
123 ( zmax - mOffset.z() ) / mScale.z()
126 double calculatedSpan = nodeMapExtent( root() ).width() / mCopcInfoVlr.spacing;
127 mSpan = calculatedSpan;
130 double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
131 QgsDebugMsgLevel( QStringLiteral(
"lvl0 node size in CRS units: %1 %2 %3" ).arg( dx ).arg( dy ).arg( dz ), 2 );
132 QgsDebugMsgLevel( QStringLiteral(
"res at lvl0 %1" ).arg( dx / mSpan ), 2 );
133 QgsDebugMsgLevel( QStringLiteral(
"res at lvl1 %1" ).arg( dx / mSpan / 2 ), 2 );
134 QgsDebugMsgLevel( QStringLiteral(
"res at lvl2 %1 with node size %2" ).arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
144 return std::unique_ptr<QgsPointCloudBlock>( cached );
147 const bool found = fetchNodeHierarchy( n );
150 mHierarchyMutex.lock();
151 int pointCount = mHierarchy.value( n );
152 auto [blockOffset, blockSize] = mHierarchyNodePos.value( n );
153 mHierarchyMutex.unlock();
158 QgsPointCloudExpression filterExpression = mFilterExpression;
160 requestAttributes.
extend( attributes(), filterExpression.referencedAttributes() );
162 QByteArray rawBlockData( blockSize, Qt::Initialization::Uninitialized );
163 std::ifstream file( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
164 file.seekg( blockOffset );
165 file.read( rawBlockData.data(), blockSize );
168 QgsDebugError( QStringLiteral(
"Could not read file %1" ).arg( mUri ) );
173 std::unique_ptr<QgsPointCloudBlock> decoded = QgsLazDecoder::decompressCopc( rawBlockData, *mLazInfo.get(), pointCount, requestAttributes, filterExpression, filterRect );
174 storeNodeDataToCache( decoded.get(), n, request );
188 return mLazInfo->crs();
191qint64 QgsCopcPointCloudIndex::pointCount()
const
193 return mLazInfo->pointCount();
196bool QgsCopcPointCloudIndex::loadHierarchy()
198 fetchHierarchyPage( mCopcInfoVlr.root_hier_offset, mCopcInfoVlr.root_hier_size );
204 if ( mLazInfo->version() != qMakePair<uint8_t, uint8_t>( 1, 4 ) )
211 QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
212 if ( !statisticsEvlrData.isEmpty() )
214 QgsMessageLog::logMessage( tr(
"Can't write statistics to \"%1\": file already contains COPC statistics!" ).arg( mUri ) );
218 lazperf::evlr_header statsEvlrHeader;
219 statsEvlrHeader.user_id =
"qgis";
220 statsEvlrHeader.record_id = 0;
221 statsEvlrHeader.description =
"Contains calculated statistics";
223 statsEvlrHeader.data_length = statsJson.size();
227 std::fstream copcFile;
228 copcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios_base::binary | std::iostream::in | std::iostream::out );
229 if ( copcFile.is_open() && copcFile.good() )
232 lazperf::header14 header = mLazInfo->header();
233 header.evlr_count = header.evlr_count + 1;
235 header.write( copcFile );
238 copcFile.seekg( 0, std::ios::end );
240 statsEvlrHeader.write( copcFile );
241 copcFile.write( statsJson.data(), statsEvlrHeader.data_length );
249 mCopcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
255 QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
257 if ( statisticsEvlrData.isEmpty() )
265bool QgsCopcPointCloudIndex::isValid()
const
272 QMutexLocker locker( &mHierarchyMutex );
274 QVector<IndexedPointCloudNode> ancestors;
276 while ( !mHierarchy.contains( foundRoot ) )
278 ancestors.push_front( foundRoot );
281 ancestors.push_front( foundRoot );
284 auto hierarchyIt = mHierarchy.constFind( n );
285 if ( hierarchyIt == mHierarchy.constEnd() )
287 int nodesCount = *hierarchyIt;
288 if ( nodesCount < 0 )
290 auto hierarchyNodePos = mHierarchyNodePos.constFind( n );
291 mHierarchyMutex.unlock();
292 fetchHierarchyPage( hierarchyNodePos->first, hierarchyNodePos->second );
293 mHierarchyMutex.lock();
296 return mHierarchy.contains( n );
299void QgsCopcPointCloudIndex::fetchHierarchyPage( uint64_t offset, uint64_t byteSize )
const
301 mCopcFile.seekg( offset );
302 std::unique_ptr<char []> data(
new char[ byteSize ] );
303 mCopcFile.read( data.get(), byteSize );
305 populateHierarchy( data.get(), byteSize );
308void QgsCopcPointCloudIndex::populateHierarchy(
const char *hierarchyPageData, uint64_t byteSize )
const
326 QMutexLocker locker( &mHierarchyMutex );
328 for ( uint64_t i = 0; i < byteSize; i +=
sizeof( CopcEntry ) )
330 const CopcEntry *entry =
reinterpret_cast<const CopcEntry *
>( hierarchyPageData + i );
332 mHierarchy[nodeId] = entry->pointCount;
333 mHierarchyNodePos.insert( nodeId, QPair<uint64_t, int32_t>( entry->offset, entry->byteSize ) );
339 return fetchNodeHierarchy( n );
342QList<IndexedPointCloudNode> QgsCopcPointCloudIndex::nodeChildren(
const IndexedPointCloudNode &n )
const
344 fetchNodeHierarchy( n );
346 mHierarchyMutex.lock();
348 auto hierarchyIt = mHierarchy.constFind( n );
349 Q_ASSERT( hierarchyIt != mHierarchy.constEnd() );
350 QList<IndexedPointCloudNode> lst;
352 const int d = n.
d() + 1;
353 const int x = n.
x() * 2;
354 const int y = n.
y() * 2;
355 const int z = n.
z() * 2;
356 mHierarchyMutex.unlock();
358 for (
int i = 0; i < 8; ++i )
360 int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
362 if ( fetchNodeHierarchy( n2 ) && mHierarchy[n] >= 0 )
368void QgsCopcPointCloudIndex::copyCommonProperties( QgsCopcPointCloudIndex *destination )
const
373 destination->mIsValid = mIsValid;
374 destination->mUri = mUri;
375 destination->mCopcFile.open( QgsLazDecoder::toNativePath( mUri ), std::ios::binary );
376 destination->mCopcInfoVlr = mCopcInfoVlr;
377 destination->mHierarchyNodePos = mHierarchyNodePos;
378 destination->mOriginalMetadata = mOriginalMetadata;
379 destination->mLazInfo.reset(
new QgsLazInfo( *mLazInfo ) );
382QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData()
384 uint64_t offset = mLazInfo->firstEvlrOffset();
385 uint32_t evlrCount = mLazInfo->evlrCount();
387 QByteArray statisticsEvlrData;
389 for ( uint32_t i = 0; i < evlrCount; ++i )
391 lazperf::evlr_header header;
392 mCopcFile.seekg( offset );
394 mCopcFile.read( buffer, 60 );
395 header.fill( buffer, 60 );
398 if ( header.user_id ==
"qgis" && header.record_id == 0 )
400 statisticsEvlrData = QByteArray( header.data_length, Qt::Initialization::Uninitialized );
401 mCopcFile.read( statisticsEvlrData.data(), header.data_length );
405 offset += 60 + header.data_length;
408 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.
QgsRectangle filterRect() const
Returns the rectangle from which points will be taken, in point cloud's crs.
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.
A rectangle specified with double values.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
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)
#define QgsDebugError(str)