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 ) );
167 return QgsLazDecoder::decompressCopc( rawBlockData, *mLazInfo.get(), pointCount, requestAttributes, filterExpression, filterRect );
180 return mLazInfo->crs();
183qint64 QgsCopcPointCloudIndex::pointCount()
const
185 return mLazInfo->pointCount();
188bool QgsCopcPointCloudIndex::loadHierarchy()
190 QMutexLocker locker( &mHierarchyMutex );
191 fetchHierarchyPage( mCopcInfoVlr.root_hier_offset, mCopcInfoVlr.root_hier_size );
197 if ( mLazInfo->version() != qMakePair<uint8_t, uint8_t>( 1, 4 ) )
204 QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
205 if ( !statisticsEvlrData.isEmpty() )
207 QgsMessageLog::logMessage( tr(
"Can't write statistics to \"%1\": file already contains COPC statistics!" ).arg( mFileName ) );
211 lazperf::evlr_header statsEvlrHeader;
212 statsEvlrHeader.user_id =
"qgis";
213 statsEvlrHeader.record_id = 0;
214 statsEvlrHeader.description =
"Contains calculated statistics";
216 statsEvlrHeader.data_length = statsJson.size();
220 std::fstream copcFile;
221 copcFile.open( QgsLazDecoder::toNativePath( mFileName ), std::ios_base::binary | std::iostream::in | std::iostream::out );
222 if ( copcFile.is_open() && copcFile.good() )
225 lazperf::header14 header = mLazInfo->header();
226 header.evlr_count = header.evlr_count + 1;
228 header.write( copcFile );
231 copcFile.seekg( 0, std::ios::end );
233 statsEvlrHeader.write( copcFile );
234 copcFile.write( statsJson.data(), statsEvlrHeader.data_length );
242 mCopcFile.open( QgsLazDecoder::toNativePath( mFileName ), std::ios::binary );
248 QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData();
250 if ( statisticsEvlrData.isEmpty() )
258bool QgsCopcPointCloudIndex::isValid()
const
265 QMutexLocker locker( &mHierarchyMutex );
267 QVector<IndexedPointCloudNode> ancestors;
269 while ( !mHierarchy.contains( foundRoot ) )
271 ancestors.push_front( foundRoot );
274 ancestors.push_front( foundRoot );
277 auto hierarchyIt = mHierarchy.constFind( n );
278 if ( hierarchyIt == mHierarchy.constEnd() )
280 int nodesCount = *hierarchyIt;
281 if ( nodesCount < 0 )
283 auto hierarchyNodePos = mHierarchyNodePos.constFind( n );
284 fetchHierarchyPage( hierarchyNodePos->first, hierarchyNodePos->second );
290void QgsCopcPointCloudIndex::fetchHierarchyPage( uint64_t offset, uint64_t byteSize )
const
292 mCopcFile.seekg( offset );
293 std::unique_ptr<char []> data(
new char[ byteSize ] );
294 mCopcFile.read( data.get(), byteSize );
312 for ( uint64_t i = 0; i < byteSize; i +=
sizeof( CopcEntry ) )
314 CopcEntry *entry =
reinterpret_cast<CopcEntry *
>( data.get() + i );
316 mHierarchy[nodeId] = entry->pointCount;
317 mHierarchyNodePos.insert( nodeId, QPair<uint64_t, int32_t>( entry->offset, entry->byteSize ) );
323 fetchNodeHierarchy( n );
324 mHierarchyMutex.lock();
326 auto it = mHierarchy.constFind( n );
327 const bool found = it != mHierarchy.constEnd() && ( *it ) >= 0;
328 mHierarchyMutex.unlock();
332QList<IndexedPointCloudNode> QgsCopcPointCloudIndex::nodeChildren(
const IndexedPointCloudNode &n )
const
334 fetchNodeHierarchy( n );
336 mHierarchyMutex.lock();
338 auto hierarchyIt = mHierarchy.constFind( n );
339 Q_ASSERT( hierarchyIt != mHierarchy.constEnd() );
340 QList<IndexedPointCloudNode> lst;
342 const int d = n.
d() + 1;
343 const int x = n.
x() * 2;
344 const int y = n.
y() * 2;
345 const int z = n.
z() * 2;
346 mHierarchyMutex.unlock();
348 for (
int i = 0; i < 8; ++i )
350 int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
352 if ( fetchNodeHierarchy( n2 ) && mHierarchy[n] >= 0 )
358void QgsCopcPointCloudIndex::copyCommonProperties( QgsCopcPointCloudIndex *destination )
const
363 destination->mIsValid = mIsValid;
364 destination->mFileName = mFileName;
365 destination->mCopcFile.open( QgsLazDecoder::toNativePath( mFileName ), std::ios::binary );
366 destination->mCopcInfoVlr = mCopcInfoVlr;
367 destination->mHierarchyNodePos = mHierarchyNodePos;
368 destination->mOriginalMetadata = mOriginalMetadata;
369 destination->mLazInfo.reset(
new QgsLazInfo( *mLazInfo ) );
372QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData()
374 uint64_t offset = mLazInfo->firstEvlrOffset();
375 uint32_t evlrCount = mLazInfo->evlrCount();
377 QByteArray statisticsEvlrData;
379 for ( uint32_t i = 0; i < evlrCount; ++i )
381 lazperf::evlr_header header;
382 mCopcFile.seekg( offset );
384 mCopcFile.read( buffer, 60 );
385 header.fill( buffer, 60 );
388 if ( header.user_id ==
"qgis" && header.record_id == 0 )
390 statisticsEvlrData = QByteArray( header.data_length, Qt::Initialization::Uninitialized );
391 mCopcFile.read( statisticsEvlrData.data(), header.data_length );
395 offset += 60 + header.data_length;
398 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.
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