19#include "moc_qgseptpointcloudindex.cpp"
24#include <QJsonDocument>
36#include "qgspointcloudexpression.h"
40#define PROVIDER_KEY QStringLiteral( "ept" )
41#define PROVIDER_DESCRIPTION QStringLiteral( "EPT point cloud provider" )
43QgsEptPointCloudIndex::QgsEptPointCloudIndex() =
default;
45QgsEptPointCloudIndex::~QgsEptPointCloudIndex() =
default;
47std::unique_ptr<QgsPointCloudIndex> QgsEptPointCloudIndex::clone()
const
49 QgsEptPointCloudIndex *clone =
new QgsEptPointCloudIndex;
50 QMutexLocker locker( &mHierarchyMutex );
51 copyCommonProperties( clone );
52 return std::unique_ptr<QgsPointCloudIndex>( clone );
55void QgsEptPointCloudIndex::load(
const QString &fileName )
59 if ( !f.open( QIODevice::ReadOnly ) )
61 mError = tr(
"Unable to open %1 for reading" ).arg( fileName );
66 const QDir directory = QFileInfo( fileName ).absoluteDir();
67 mDirectory = directory.absolutePath();
69 const QByteArray dataJson = f.readAll();
70 bool success = loadSchema( dataJson );
75 QFile manifestFile( mDirectory + QStringLiteral(
"/ept-sources/manifest.json" ) );
76 if ( manifestFile.open( QIODevice::ReadOnly ) )
78 const QByteArray manifestJson = manifestFile.readAll();
79 loadManifest( manifestJson );
85 success = loadHierarchy();
91void QgsEptPointCloudIndex::loadManifest(
const QByteArray &manifestJson )
95 const QJsonDocument manifestDoc = QJsonDocument::fromJson( manifestJson, &err );
96 if ( err.error == QJsonParseError::NoError )
98 const QJsonArray manifestArray = manifestDoc.array();
100 if ( ! manifestArray.empty() )
102 const QJsonObject sourceObject = manifestArray.at( 0 ).toObject();
103 const QString metadataPath = sourceObject.value( QStringLiteral(
"metadataPath" ) ).toString();
104 QFile metadataFile( mDirectory + QStringLiteral(
"/ept-sources/" ) + metadataPath );
105 if ( metadataFile.open( QIODevice::ReadOnly ) )
107 const QByteArray metadataJson = metadataFile.readAll();
108 const QJsonDocument metadataDoc = QJsonDocument::fromJson( metadataJson, &err );
109 if ( err.error == QJsonParseError::NoError )
111 const QJsonObject metadataObject = metadataDoc.object().value( QStringLiteral(
"metadata" ) ).toObject();
112 if ( !metadataObject.empty() )
114 const QJsonObject sourceMetadata = metadataObject.constBegin().value().toObject();
115 mOriginalMetadata = sourceMetadata.toVariantMap();
123bool QgsEptPointCloudIndex::loadSchema(
const QByteArray &dataJson )
126 const QJsonDocument doc = QJsonDocument::fromJson( dataJson, &err );
127 if ( err.error != QJsonParseError::NoError )
129 const QJsonObject result = doc.object();
130 mDataType = result.value( QLatin1String(
"dataType" ) ).toString();
131 if ( mDataType != QLatin1String(
"laszip" ) && mDataType != QLatin1String(
"binary" ) && mDataType != QLatin1String(
"zstandard" ) )
134 const QString hierarchyType = result.value( QLatin1String(
"hierarchyType" ) ).toString();
135 if ( hierarchyType != QLatin1String(
"json" ) )
138 mSpan = result.value( QLatin1String(
"span" ) ).toInt();
139 mPointCount = result.value( QLatin1String(
"points" ) ).toDouble();
142 const QJsonObject srs = result.value( QLatin1String(
"srs" ) ).toObject();
143 mWkt = srs.value( QLatin1String(
"wkt" ) ).toString();
146 const QJsonArray bounds = result.value( QLatin1String(
"bounds" ) ).toArray();
147 if ( bounds.size() != 6 )
150 const QJsonArray boundsConforming = result.value( QLatin1String(
"boundsConforming" ) ).toArray();
151 if ( boundsConforming.size() != 6 )
153 mExtent.set( boundsConforming[0].toDouble(), boundsConforming[1].toDouble(),
154 boundsConforming[3].toDouble(), boundsConforming[4].toDouble() );
155 mZMin = boundsConforming[2].toDouble();
156 mZMax = boundsConforming[5].toDouble();
158 const QJsonArray schemaArray = result.value( QLatin1String(
"schema" ) ).toArray();
161 for (
const QJsonValue &schemaItem : schemaArray )
163 const QJsonObject schemaObj = schemaItem.toObject();
164 const QString name = schemaObj.value( QLatin1String(
"name" ) ).toString();
165 const QString type = schemaObj.value( QLatin1String(
"type" ) ).toString();
167 const int size = schemaObj.value( QLatin1String(
"size" ) ).toInt();
169 if ( name == QLatin1String(
"ClassFlags" ) && size == 1 )
176 else if ( type == QLatin1String(
"float" ) && ( size == 4 ) )
180 else if ( type == QLatin1String(
"float" ) && ( size == 8 ) )
184 else if ( size == 1 )
188 else if ( type == QLatin1String(
"unsigned" ) && size == 2 )
192 else if ( size == 2 )
196 else if ( size == 4 )
207 if ( schemaObj.contains( QLatin1String(
"scale" ) ) )
208 scale = schemaObj.value( QLatin1String(
"scale" ) ).toDouble();
211 if ( schemaObj.contains( QLatin1String(
"offset" ) ) )
212 offset = schemaObj.value( QLatin1String(
"offset" ) ).toDouble();
214 if ( name == QLatin1String(
"X" ) )
216 mOffset.set( offset, mOffset.y(), mOffset.z() );
217 mScale.set( scale, mScale.y(), mScale.z() );
219 else if ( name == QLatin1String(
"Y" ) )
221 mOffset.set( mOffset.x(), offset, mOffset.z() );
222 mScale.set( mScale.x(), scale, mScale.z() );
224 else if ( name == QLatin1String(
"Z" ) )
226 mOffset.set( mOffset.x(), mOffset.y(), offset );
227 mScale.set( mScale.x(), mScale.y(), scale );
231 AttributeStatistics stats;
232 bool foundStats =
false;
233 if ( schemaObj.contains( QLatin1String(
"count" ) ) )
235 stats.count = schemaObj.value( QLatin1String(
"count" ) ).toInt();
238 if ( schemaObj.contains( QLatin1String(
"minimum" ) ) )
240 stats.minimum = schemaObj.value( QLatin1String(
"minimum" ) ).toDouble();
243 if ( schemaObj.contains( QLatin1String(
"maximum" ) ) )
245 stats.maximum = schemaObj.value( QLatin1String(
"maximum" ) ).toDouble();
248 if ( schemaObj.contains( QLatin1String(
"count" ) ) )
250 stats.mean = schemaObj.value( QLatin1String(
"mean" ) ).toDouble();
253 if ( schemaObj.contains( QLatin1String(
"stddev" ) ) )
255 stats.stDev = schemaObj.value( QLatin1String(
"stddev" ) ).toDouble();
258 if ( schemaObj.contains( QLatin1String(
"variance" ) ) )
260 stats.variance = schemaObj.value( QLatin1String(
"variance" ) ).toDouble();
264 mMetadataStats.insert( name, stats );
266 if ( schemaObj.contains( QLatin1String(
"counts" ) ) )
268 QMap< int, int > classCounts;
269 const QJsonArray counts = schemaObj.value( QLatin1String(
"counts" ) ).toArray();
270 for (
const QJsonValue &count : counts )
272 const QJsonObject countObj = count.toObject();
273 classCounts.insert( countObj.value( QLatin1String(
"value" ) ).toInt(), countObj.value( QLatin1String(
"count" ) ).toInt() );
275 mAttributeClasses.insert( name, classCounts );
278 setAttributes( attributes );
283 const double xmin = bounds[0].toDouble();
284 const double ymin = bounds[1].toDouble();
285 const double zmin = bounds[2].toDouble();
286 const double xmax = bounds[3].toDouble();
287 const double ymax = bounds[4].toDouble();
288 const double zmax = bounds[5].toDouble();
291 ( xmin - mOffset.x() ) / mScale.x(),
292 ( ymin - mOffset.y() ) / mScale.y(),
293 ( zmin - mOffset.z() ) / mScale.z(),
294 ( xmax - mOffset.x() ) / mScale.x(),
295 ( ymax - mOffset.y() ) / mScale.y(),
296 ( zmax - mOffset.z() ) / mScale.z()
301 double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
302 QgsDebugMsgLevel( QStringLiteral(
"lvl0 node size in CRS units: %1 %2 %3" ).arg( dx ).arg( dy ).arg( dz ), 2 );
303 QgsDebugMsgLevel( QStringLiteral(
"res at lvl0 %1" ).arg( dx / mSpan ), 2 );
304 QgsDebugMsgLevel( QStringLiteral(
"res at lvl1 %1" ).arg( dx / mSpan / 2 ), 2 );
305 QgsDebugMsgLevel( QStringLiteral(
"res at lvl2 %1 with node size %2" ).arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
315 return std::unique_ptr<QgsPointCloudBlock>( cached );
318 mHierarchyMutex.lock();
319 const bool found = mHierarchy.contains( n );
320 mHierarchyMutex.unlock();
327 QgsPointCloudExpression filterExpression = mFilterExpression;
329 requestAttributes.
extend( attributes(), filterExpression.referencedAttributes() );
332 std::unique_ptr<QgsPointCloudBlock> decoded;
333 if ( mDataType == QLatin1String(
"binary" ) )
335 const QString filename = QStringLiteral(
"%1/ept-data/%2.bin" ).arg( mDirectory, n.
toString() );
336 decoded = QgsEptDecoder::decompressBinary( filename, attributes(), requestAttributes, scale(), offset(), filterExpression, filterRect );
338 else if ( mDataType == QLatin1String(
"zstandard" ) )
340 const QString filename = QStringLiteral(
"%1/ept-data/%2.zst" ).arg( mDirectory, n.
toString() );
341 decoded = QgsEptDecoder::decompressZStandard( filename, attributes(), request.
attributes(), scale(), offset(), filterExpression, filterRect );
343 else if ( mDataType == QLatin1String(
"laszip" ) )
345 const QString filename = QStringLiteral(
"%1/ept-data/%2.laz" ).arg( mDirectory, n.
toString() );
346 decoded = QgsLazDecoder::decompressLaz( filename, requestAttributes, filterExpression, filterRect );
349 storeNodeDataToCache( decoded.get(), n, request );
366qint64 QgsEptPointCloudIndex::pointCount()
const
371bool QgsEptPointCloudIndex::hasStatisticsMetadata()
const
373 return !mMetadataStats.isEmpty();
376QVariant QgsEptPointCloudIndex::metadataStatistic(
const QString &attribute,
Qgis::Statistic statistic )
const
378 if ( !mMetadataStats.contains( attribute ) )
381 const AttributeStatistics &stats = mMetadataStats[ attribute ];
385 return stats.count >= 0 ? QVariant( stats.count ) : QVariant();
388 return std::isnan( stats.mean ) ? QVariant() : QVariant( stats.mean );
391 return std::isnan( stats.stDev ) ? QVariant() : QVariant( stats.stDev );
394 return stats.minimum;
397 return stats.maximum;
400 return stats.minimum.isValid() && stats.maximum.isValid() ? QVariant( stats.maximum.toDouble() - stats.minimum.toDouble() ) : QVariant();
420QVariantList QgsEptPointCloudIndex::metadataClasses(
const QString &attribute )
const
422 QVariantList classes;
423 const QMap< int, int > values = mAttributeClasses.value( attribute );
424 for (
auto it = values.constBegin(); it != values.constEnd(); ++it )
431QVariant QgsEptPointCloudIndex::metadataClassStatistic(
const QString &attribute,
const QVariant &value,
Qgis::Statistic statistic )
const
436 const QMap< int, int > values = mAttributeClasses.value( attribute );
437 if ( !values.contains( value.toInt() ) )
439 return values.value( value.toInt() );
442bool QgsEptPointCloudIndex::loadHierarchy()
444 QQueue<QString> queue;
445 queue.enqueue( QStringLiteral(
"0-0-0-0" ) );
446 while ( !queue.isEmpty() )
448 const QString filename = QStringLiteral(
"%1/ept-hierarchy/%2.json" ).arg( mDirectory, queue.dequeue() );
449 QFile fH( filename );
450 if ( !fH.open( QIODevice::ReadOnly ) )
452 QgsDebugMsgLevel( QStringLiteral(
"unable to read hierarchy from file %1" ).arg( filename ), 2 );
453 mError = QStringLiteral(
"unable to read hierarchy from file %1" ).arg( filename );
457 const QByteArray dataJsonH = fH.readAll();
458 QJsonParseError errH;
459 const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH );
460 if ( errH.error != QJsonParseError::NoError )
462 QgsDebugMsgLevel( QStringLiteral(
"QJsonParseError when reading hierarchy from file %1" ).arg( filename ), 2 );
463 mError = QStringLiteral(
"QJsonParseError when reading hierarchy from file %1" ).arg( filename );
467 const QJsonObject rootHObj = docH.object();
468 for (
auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it )
470 const QString nodeIdStr = it.key();
471 const int nodePointCount = it.value().toInt();
472 if ( nodePointCount < 0 )
474 queue.enqueue( nodeIdStr );
479 mHierarchyMutex.lock();
480 mHierarchy[nodeId] = nodePointCount;
481 mHierarchyMutex.unlock();
488bool QgsEptPointCloudIndex::isValid()
const
493void QgsEptPointCloudIndex::copyCommonProperties( QgsEptPointCloudIndex *destination )
const
498 destination->mIsValid = mIsValid;
499 destination->mDataType = mDataType;
500 destination->mDirectory = mDirectory;
501 destination->mWkt = mWkt;
502 destination->mPointCount = mPointCount;
503 destination->mMetadataStats = mMetadataStats;
504 destination->mAttributeClasses = mAttributeClasses;
505 destination->mOriginalMetadata = mOriginalMetadata;
Represents a indexed point cloud node in octree.
static IndexedPointCloudNode fromString(const QString &str)
Creates node from string.
QString toString() const
Encode node to string.
Statistic
Available generic statistics.
@ StDev
Standard deviation of values.
@ FirstQuartile
First quartile.
@ Median
Median of values.
@ Range
Range of values (max - min)
@ Minority
Minority of values.
@ CountMissing
Number of missing (null) values.
@ Majority
Majority of values.
@ Variety
Variety (count of distinct) values.
@ StDevSample
Sample standard deviation of values.
@ ThirdQuartile
Third quartile.
@ InterQuartileRange
Inter quartile range (IQR)
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
Collection of point cloud attributes.
void push_back(const QgsPointCloudAttribute &attribute)
Adds extra attribute.
void extend(const QgsPointCloudAttributeCollection &otherCollection, const QSet< QString > &matchingNames)
Adds specific missing attributes from another QgsPointCloudAttributeCollection.
Attribute for point cloud data pair of name and size in bytes.
@ UShort
Unsigned short int 2 bytes.
@ Short
Short int 2 bytes.
@ UChar
Unsigned char 1 byte.
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.
A rectangle specified with double values.
#define QgsDebugMsgLevel(str, level)