23 #include <QJsonDocument>
24 #include <QJsonObject>
39 #define PROVIDER_KEY QStringLiteral( "ept" )
40 #define PROVIDER_DESCRIPTION QStringLiteral( "EPT point cloud provider" )
42 QgsEptPointCloudIndex::QgsEptPointCloudIndex() =
default;
44 QgsEptPointCloudIndex::~QgsEptPointCloudIndex() =
default;
46 void QgsEptPointCloudIndex::load(
const QString &fileName )
49 if ( !f.open( QIODevice::ReadOnly ) )
56 const QDir directory = QFileInfo( fileName ).absoluteDir();
57 mDirectory = directory.absolutePath();
59 const QByteArray dataJson = f.readAll();
60 bool success = loadSchema( dataJson );
65 QFile manifestFile( mDirectory + QStringLiteral(
"/ept-sources/manifest.json" ) );
66 if ( manifestFile.open( QIODevice::ReadOnly ) )
68 const QByteArray manifestJson = manifestFile.readAll();
69 loadManifest( manifestJson );
75 success = loadHierarchy();
81 void QgsEptPointCloudIndex::loadManifest(
const QByteArray &manifestJson )
85 const QJsonDocument manifestDoc = QJsonDocument::fromJson( manifestJson, &err );
86 if ( err.error == QJsonParseError::NoError )
88 const QJsonArray manifestArray = manifestDoc.array();
90 if ( ! manifestArray.empty() )
92 const QJsonObject sourceObject = manifestArray.at( 0 ).toObject();
93 const QString metadataPath = sourceObject.value( QStringLiteral(
"metadataPath" ) ).toString();
94 QFile metadataFile( mDirectory + QStringLiteral(
"/ept-sources/" ) + metadataPath );
95 if ( metadataFile.open( QIODevice::ReadOnly ) )
97 const QByteArray metadataJson = metadataFile.readAll();
98 const QJsonDocument metadataDoc = QJsonDocument::fromJson( metadataJson, &err );
99 if ( err.error == QJsonParseError::NoError )
101 const QJsonObject metadataObject = metadataDoc.object().value( QStringLiteral(
"metadata" ) ).toObject();
102 if ( !metadataObject.empty() )
104 const QJsonObject sourceMetadata = metadataObject.constBegin().value().toObject();
105 mOriginalMetadata = sourceMetadata.toVariantMap();
113 bool QgsEptPointCloudIndex::loadSchema(
const QByteArray &dataJson )
116 const QJsonDocument doc = QJsonDocument::fromJson( dataJson, &err );
117 if ( err.error != QJsonParseError::NoError )
119 const QJsonObject result = doc.object();
120 mDataType = result.value( QLatin1String(
"dataType" ) ).toString();
121 if ( mDataType != QLatin1String(
"laszip" ) && mDataType != QLatin1String(
"binary" ) && mDataType != QLatin1String(
"zstandard" ) )
124 const QString hierarchyType = result.value( QLatin1String(
"hierarchyType" ) ).toString();
125 if ( hierarchyType != QLatin1String(
"json" ) )
128 mSpan = result.value( QLatin1String(
"span" ) ).toInt();
129 mPointCount = result.value( QLatin1String(
"points" ) ).toDouble();
132 const QJsonObject srs = result.value( QLatin1String(
"srs" ) ).toObject();
133 mWkt = srs.value( QLatin1String(
"wkt" ) ).toString();
136 const QJsonArray bounds = result.value( QLatin1String(
"bounds" ) ).toArray();
137 if ( bounds.size() != 6 )
140 const QJsonArray boundsConforming = result.value( QLatin1String(
"boundsConforming" ) ).toArray();
141 if ( boundsConforming.size() != 6 )
143 mExtent.set( boundsConforming[0].toDouble(), boundsConforming[1].toDouble(),
144 boundsConforming[3].toDouble(), boundsConforming[4].toDouble() );
145 mZMin = boundsConforming[2].toDouble();
146 mZMax = boundsConforming[5].toDouble();
148 const QJsonArray schemaArray = result.value( QLatin1String(
"schema" ) ).toArray();
151 for (
const QJsonValue &schemaItem : schemaArray )
153 const QJsonObject schemaObj = schemaItem.toObject();
154 const QString name = schemaObj.value( QLatin1String(
"name" ) ).toString();
155 const QString type = schemaObj.value( QLatin1String(
"type" ) ).toString();
157 const int size = schemaObj.value( QLatin1String(
"size" ) ).toInt();
159 if ( type == QLatin1String(
"float" ) && ( size == 4 ) )
163 else if ( type == QLatin1String(
"float" ) && ( size == 8 ) )
167 else if ( size == 1 )
171 else if ( type == QLatin1String(
"unsigned" ) && size == 2 )
175 else if ( size == 2 )
179 else if ( size == 4 )
190 if ( schemaObj.contains( QLatin1String(
"scale" ) ) )
191 scale = schemaObj.value( QLatin1String(
"scale" ) ).toDouble();
194 if ( schemaObj.contains( QLatin1String(
"offset" ) ) )
195 offset = schemaObj.value( QLatin1String(
"offset" ) ).toDouble();
197 if ( name == QLatin1String(
"X" ) )
199 mOffset.set( offset, mOffset.y(), mOffset.z() );
200 mScale.set( scale, mScale.y(), mScale.z() );
202 else if ( name == QLatin1String(
"Y" ) )
204 mOffset.set( mOffset.x(), offset, mOffset.z() );
205 mScale.set( mScale.x(), scale, mScale.z() );
207 else if ( name == QLatin1String(
"Z" ) )
209 mOffset.set( mOffset.x(), mOffset.y(), offset );
210 mScale.set( mScale.x(), mScale.y(), scale );
214 AttributeStatistics stats;
215 if ( schemaObj.contains( QLatin1String(
"count" ) ) )
216 stats.count = schemaObj.value( QLatin1String(
"count" ) ).toInt();
217 if ( schemaObj.contains( QLatin1String(
"minimum" ) ) )
218 stats.minimum = schemaObj.value( QLatin1String(
"minimum" ) ).toDouble();
219 if ( schemaObj.contains( QLatin1String(
"maximum" ) ) )
220 stats.maximum = schemaObj.value( QLatin1String(
"maximum" ) ).toDouble();
221 if ( schemaObj.contains( QLatin1String(
"count" ) ) )
222 stats.mean = schemaObj.value( QLatin1String(
"mean" ) ).toDouble();
223 if ( schemaObj.contains( QLatin1String(
"stddev" ) ) )
224 stats.stDev = schemaObj.value( QLatin1String(
"stddev" ) ).toDouble();
225 if ( schemaObj.contains( QLatin1String(
"variance" ) ) )
226 stats.variance = schemaObj.value( QLatin1String(
"variance" ) ).toDouble();
227 mMetadataStats.insert( name, stats );
229 if ( schemaObj.contains( QLatin1String(
"counts" ) ) )
231 QMap< int, int > classCounts;
232 const QJsonArray counts = schemaObj.value( QLatin1String(
"counts" ) ).toArray();
233 for (
const QJsonValue &count : counts )
235 const QJsonObject countObj = count.toObject();
236 classCounts.insert( countObj.value( QLatin1String(
"value" ) ).toInt(), countObj.value( QLatin1String(
"count" ) ).toInt() );
238 mAttributeClasses.insert( name, classCounts );
241 setAttributes( attributes );
246 const double xmin = bounds[0].toDouble();
247 const double ymin = bounds[1].toDouble();
248 const double zmin = bounds[2].toDouble();
249 const double xmax = bounds[3].toDouble();
250 const double ymax = bounds[4].toDouble();
251 const double zmax = bounds[5].toDouble();
254 ( xmin - mOffset.x() ) / mScale.x(),
255 ( ymin - mOffset.y() ) / mScale.y(),
256 ( zmin - mOffset.z() ) / mScale.z(),
257 ( xmax - mOffset.x() ) / mScale.x(),
258 ( ymax - mOffset.y() ) / mScale.y(),
259 ( zmax - mOffset.z() ) / mScale.z()
264 double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
265 QgsDebugMsgLevel( QStringLiteral(
"lvl0 node size in CRS units: %1 %2 %3" ).arg( dx ).arg( dy ).arg( dz ), 2 );
266 QgsDebugMsgLevel( QStringLiteral(
"res at lvl0 %1" ).arg( dx / mSpan ), 2 );
267 QgsDebugMsgLevel( QStringLiteral(
"res at lvl1 %1" ).arg( dx / mSpan / 2 ), 2 );
268 QgsDebugMsgLevel( QStringLiteral(
"res at lvl2 %1 with node size %2" ).arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
276 mHierarchyMutex.lock();
277 const bool found = mHierarchy.contains( n );
278 mHierarchyMutex.unlock();
282 if ( mDataType == QLatin1String(
"binary" ) )
284 const QString filename = QStringLiteral(
"%1/ept-data/%2.bin" ).arg( mDirectory, n.
toString() );
285 return QgsEptDecoder::decompressBinary( filename, attributes(), request.
attributes(), scale(), offset() );
287 else if ( mDataType == QLatin1String(
"zstandard" ) )
289 const QString filename = QStringLiteral(
"%1/ept-data/%2.zst" ).arg( mDirectory, n.
toString() );
290 return QgsEptDecoder::decompressZStandard( filename, attributes(), request.
attributes(), scale(), offset() );
292 else if ( mDataType == QLatin1String(
"laszip" ) )
294 const QString filename = QStringLiteral(
"%1/ept-data/%2.laz" ).arg( mDirectory, n.
toString() );
295 return QgsEptDecoder::decompressLaz( filename, attributes(), request.
attributes(), scale(), offset() );
316 qint64 QgsEptPointCloudIndex::pointCount()
const
323 if ( !mMetadataStats.contains( attribute ) )
326 const AttributeStatistics &stats = mMetadataStats[ attribute ];
330 return stats.count >= 0 ? QVariant( stats.count ) : QVariant();
333 return std::isnan( stats.mean ) ? QVariant() : QVariant( stats.mean );
336 return std::isnan( stats.stDev ) ? QVariant() : QVariant( stats.stDev );
339 return stats.minimum;
342 return stats.maximum;
345 return stats.minimum.isValid() && stats.maximum.isValid() ? QVariant( stats.maximum.toDouble() - stats.minimum.toDouble() ) : QVariant();
365 QVariantList QgsEptPointCloudIndex::metadataClasses(
const QString &attribute )
const
367 QVariantList classes;
368 const QMap< int, int > values = mAttributeClasses.value( attribute );
369 for (
auto it = values.constBegin(); it != values.constEnd(); ++it )
381 const QMap< int, int > values = mAttributeClasses.value( attribute );
382 if ( !values.contains( value.toInt() ) )
384 return values.value( value.toInt() );
387 bool QgsEptPointCloudIndex::loadHierarchy()
389 QQueue<QString> queue;
390 queue.enqueue( QStringLiteral(
"0-0-0-0" ) );
391 while ( !queue.isEmpty() )
393 const QString filename = QStringLiteral(
"%1/ept-hierarchy/%2.json" ).arg( mDirectory, queue.dequeue() );
394 QFile fH( filename );
395 if ( !fH.open( QIODevice::ReadOnly ) )
397 QgsDebugMsgLevel( QStringLiteral(
"unable to read hierarchy from file %1" ).arg( filename ), 2 );
401 const QByteArray dataJsonH = fH.readAll();
402 QJsonParseError errH;
403 const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH );
404 if ( errH.error != QJsonParseError::NoError )
406 QgsDebugMsgLevel( QStringLiteral(
"QJsonParseError when reading hierarchy from file %1" ).arg( filename ), 2 );
410 const QJsonObject rootHObj = docH.object();
411 for (
auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it )
413 const QString nodeIdStr = it.key();
414 const int nodePointCount = it.value().toInt();
415 if ( nodePointCount < 0 )
417 queue.enqueue( nodeIdStr );
422 mHierarchyMutex.lock();
423 mHierarchy[nodeId] = nodePointCount;
424 mHierarchyMutex.unlock();
431 bool QgsEptPointCloudIndex::isValid()
const
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.
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.
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 push_back(const QgsPointCloudAttribute &attribute)
Adds extra attribute.
Attribute for point cloud data pair of name and size in bytes.
@ UShort
Unsigned short int 2 bytes.
@ Short
Short int 2 bytes.
Base class for handling loading QgsPointCloudBlock asynchronously.
Base class for storing raw data from point cloud nodes.
Represents packaged data bounds.
Point cloud data request.
QgsPointCloudAttributeCollection attributes() const
Returns attributes.
Statistic
Enumeration of flags that specify statistics to be calculated.
@ InterQuartileRange
Inter quartile range (IQR)
@ Median
Median of values.
@ Variety
Variety (count of distinct) values.
@ First
First value (since QGIS 3.6)
@ Last
Last value (since QGIS 3.6)
@ Minority
Minority of values.
@ ThirdQuartile
Third quartile.
@ Majority
Majority of values.
@ CountMissing
Number of missing (null) values.
@ Range
Range of values (max - min)
@ StDevSample
Sample standard deviation of values.
@ FirstQuartile
First quartile.
@ StDev
Standard deviation of values.
#define QgsDebugMsgLevel(str, level)
const QgsCoordinateReferenceSystem & crs