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