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();
58 bool success = loadSchema( f );
61 success = loadHierarchy();
67 bool QgsEptPointCloudIndex::loadSchema( QFile &f )
69 const QByteArray dataJson = f.readAll();
71 const QJsonDocument doc = QJsonDocument::fromJson( dataJson, &err );
72 if ( err.error != QJsonParseError::NoError )
74 const QJsonObject result = doc.object();
75 mDataType = result.value( QLatin1String(
"dataType" ) ).toString();
76 if ( mDataType != QLatin1String(
"laszip" ) && mDataType != QLatin1String(
"binary" ) && mDataType != QLatin1String(
"zstandard" ) )
79 const QString hierarchyType = result.value( QLatin1String(
"hierarchyType" ) ).toString();
80 if ( hierarchyType != QLatin1String(
"json" ) )
83 mSpan = result.value( QLatin1String(
"span" ) ).toInt();
84 mPointCount = result.value( QLatin1String(
"points" ) ).toInt();
87 const QJsonObject srs = result.value( QLatin1String(
"srs" ) ).toObject();
88 mWkt = srs.value( QLatin1String(
"wkt" ) ).toString();
91 const QJsonArray bounds = result.value( QLatin1String(
"bounds" ) ).toArray();
92 if ( bounds.size() != 6 )
95 const QJsonArray boundsConforming = result.value( QLatin1String(
"boundsConforming" ) ).toArray();
96 if ( boundsConforming.size() != 6 )
98 mExtent.set( boundsConforming[0].toDouble(), boundsConforming[1].toDouble(),
99 boundsConforming[3].toDouble(), boundsConforming[4].toDouble() );
100 mZMin = boundsConforming[2].toDouble();
101 mZMax = boundsConforming[5].toDouble();
103 const QJsonArray schemaArray = result.value( QLatin1String(
"schema" ) ).toArray();
106 for (
const QJsonValue &schemaItem : schemaArray )
108 const QJsonObject schemaObj = schemaItem.toObject();
109 const QString name = schemaObj.value( QLatin1String(
"name" ) ).toString();
110 const QString type = schemaObj.value( QLatin1String(
"type" ) ).toString();
112 int size = schemaObj.value( QLatin1String(
"size" ) ).toInt();
114 if ( type == QLatin1String(
"float" ) && ( size == 4 ) )
118 else if ( type == QLatin1String(
"float" ) && ( size == 8 ) )
122 else if ( size == 1 )
126 else if ( type == QLatin1String(
"unsigned" ) && size == 2 )
130 else if ( size == 2 )
134 else if ( size == 4 )
145 if ( schemaObj.contains( QLatin1String(
"scale" ) ) )
146 scale = schemaObj.value( QLatin1String(
"scale" ) ).toDouble();
149 if ( schemaObj.contains( QLatin1String(
"offset" ) ) )
150 offset = schemaObj.value( QLatin1String(
"offset" ) ).toDouble();
152 if ( name == QLatin1String(
"X" ) )
154 mOffset.set( offset, mOffset.y(), mOffset.z() );
155 mScale.set( scale, mScale.y(), mScale.z() );
157 else if ( name == QLatin1String(
"Y" ) )
159 mOffset.set( mOffset.x(), offset, mOffset.z() );
160 mScale.set( mScale.x(), scale, mScale.z() );
162 else if ( name == QLatin1String(
"Z" ) )
164 mOffset.set( mOffset.x(), mOffset.y(), offset );
165 mScale.set( mScale.x(), mScale.y(), scale );
169 AttributeStatistics stats;
170 if ( schemaObj.contains( QLatin1String(
"count" ) ) )
171 stats.count = schemaObj.value( QLatin1String(
"count" ) ).toInt();
172 if ( schemaObj.contains( QLatin1String(
"minimum" ) ) )
173 stats.minimum = schemaObj.value( QLatin1String(
"minimum" ) ).toDouble();
174 if ( schemaObj.contains( QLatin1String(
"maximum" ) ) )
175 stats.maximum = schemaObj.value( QLatin1String(
"maximum" ) ).toDouble();
176 if ( schemaObj.contains( QLatin1String(
"count" ) ) )
177 stats.mean = schemaObj.value( QLatin1String(
"mean" ) ).toDouble();
178 if ( schemaObj.contains( QLatin1String(
"stddev" ) ) )
179 stats.stDev = schemaObj.value( QLatin1String(
"stddev" ) ).toDouble();
180 if ( schemaObj.contains( QLatin1String(
"variance" ) ) )
181 stats.variance = schemaObj.value( QLatin1String(
"variance" ) ).toDouble();
182 mMetadataStats.insert( name, stats );
184 if ( schemaObj.contains( QLatin1String(
"counts" ) ) )
186 QMap< int, int > classCounts;
187 const QJsonArray counts = schemaObj.value( QLatin1String(
"counts" ) ).toArray();
188 for (
const QJsonValue &count : counts )
190 const QJsonObject countObj = count.toObject();
191 classCounts.insert( countObj.value( QLatin1String(
"value" ) ).toInt(), countObj.value( QLatin1String(
"count" ) ).toInt() );
193 mAttributeClasses.insert( name, classCounts );
196 setAttributes( attributes );
200 QFile manifestFile( mDirectory + QStringLiteral(
"/ept-sources/manifest.json" ) );
201 if ( manifestFile.open( QIODevice::ReadOnly ) )
203 const QByteArray manifestJson = manifestFile.readAll();
204 const QJsonDocument manifestDoc = QJsonDocument::fromJson( manifestJson, &err );
205 if ( err.error == QJsonParseError::NoError )
207 const QJsonArray manifestArray = manifestDoc.array();
209 if ( ! manifestArray.empty() )
211 const QJsonObject sourceObject = manifestArray.at( 0 ).toObject();
212 const QString metadataPath = sourceObject.value( QStringLiteral(
"metadataPath" ) ).toString();
213 QFile metadataFile( mDirectory + QStringLiteral(
"/ept-sources/" ) + metadataPath );
214 if ( metadataFile.open( QIODevice::ReadOnly ) )
216 const QByteArray metadataJson = metadataFile.readAll();
217 const QJsonDocument metadataDoc = QJsonDocument::fromJson( metadataJson, &err );
218 if ( err.error == QJsonParseError::NoError )
220 const QJsonObject metadataObject = metadataDoc.object().value( QStringLiteral(
"metadata" ) ).toObject();
221 if ( !metadataObject.empty() )
223 const QJsonObject sourceMetadata = metadataObject.constBegin().value().toObject();
224 mOriginalMetadata = sourceMetadata.toVariantMap();
235 double xmin = bounds[0].toDouble();
236 double ymin = bounds[1].toDouble();
237 double zmin = bounds[2].toDouble();
238 double xmax = bounds[3].toDouble();
239 double ymax = bounds[4].toDouble();
240 double zmax = bounds[5].toDouble();
243 ( xmin - mOffset.x() ) / mScale.x(),
244 ( ymin - mOffset.y() ) / mScale.y(),
245 ( zmin - mOffset.z() ) / mScale.z(),
246 ( xmax - mOffset.x() ) / mScale.x(),
247 ( ymax - mOffset.y() ) / mScale.y(),
248 ( zmax - mOffset.z() ) / mScale.z()
253 double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
254 QgsDebugMsgLevel( QStringLiteral(
"lvl0 node size in CRS units: %1 %2 %3" ).arg( dx ).arg( dy ).arg( dz ), 2 );
255 QgsDebugMsgLevel( QStringLiteral(
"res at lvl0 %1" ).arg( dx / mSpan ), 2 );
256 QgsDebugMsgLevel( QStringLiteral(
"res at lvl1 %1" ).arg( dx / mSpan / 2 ), 2 );
257 QgsDebugMsgLevel( QStringLiteral(
"res at lvl2 %1 with node size %2" ).arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
265 if ( !mHierarchy.contains( n ) )
268 if ( mDataType == QLatin1String(
"binary" ) )
270 QString filename = QStringLiteral(
"%1/ept-data/%2.bin" ).arg( mDirectory, n.
toString() );
271 return QgsEptDecoder::decompressBinary( filename, attributes(), request.
attributes() );
273 else if ( mDataType == QLatin1String(
"zstandard" ) )
275 QString filename = QStringLiteral(
"%1/ept-data/%2.zst" ).arg( mDirectory, n.
toString() );
276 return QgsEptDecoder::decompressZStandard( filename, attributes(), request.
attributes() );
278 else if ( mDataType == QLatin1String(
"laszip" ) )
280 QString filename = QStringLiteral(
"%1/ept-data/%2.laz" ).arg( mDirectory, n.
toString() );
281 return QgsEptDecoder::decompressLaz( filename, attributes(), request.
attributes() );
294 int QgsEptPointCloudIndex::pointCount()
const
301 if ( !mMetadataStats.contains( attribute ) )
304 const AttributeStatistics &stats = mMetadataStats[ attribute ];
308 return stats.count >= 0 ? QVariant( stats.count ) : QVariant();
311 return std::isnan( stats.mean ) ? QVariant() : QVariant( stats.mean );
314 return std::isnan( stats.stDev ) ? QVariant() : QVariant( stats.stDev );
317 return stats.minimum;
320 return stats.maximum;
323 return stats.minimum.isValid() && stats.maximum.isValid() ? QVariant( stats.maximum.toDouble() - stats.minimum.toDouble() ) : QVariant();
343 QVariantList QgsEptPointCloudIndex::metadataClasses(
const QString &attribute )
const
345 QVariantList classes;
346 const QMap< int, int > values = mAttributeClasses.value( attribute );
347 for (
auto it = values.constBegin(); it != values.constEnd(); ++it )
359 const QMap< int, int > values = mAttributeClasses.value( attribute );
360 if ( !values.contains( value.toInt() ) )
362 return values.value( value.toInt() );
365 bool QgsEptPointCloudIndex::loadHierarchy()
367 QQueue<QString> queue;
368 queue.enqueue( QStringLiteral(
"0-0-0-0" ) );
369 while ( !queue.isEmpty() )
371 const QString filename = QStringLiteral(
"%1/ept-hierarchy/%2.json" ).arg( mDirectory, queue.dequeue() );
372 QFile fH( filename );
373 if ( !fH.open( QIODevice::ReadOnly ) )
375 QgsDebugMsgLevel( QStringLiteral(
"unable to read hierarchy from file %1" ).arg( filename ), 2 );
379 QByteArray dataJsonH = fH.readAll();
380 QJsonParseError errH;
381 const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH );
382 if ( errH.error != QJsonParseError::NoError )
384 QgsDebugMsgLevel( QStringLiteral(
"QJsonParseError when reading hierarchy from file %1" ).arg( filename ), 2 );
388 const QJsonObject rootHObj = docH.object();
389 for (
auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it )
391 QString nodeIdStr = it.key();
392 int nodePointCount = it.value().toInt();
393 if ( nodePointCount < 0 )
395 queue.enqueue( nodeIdStr );
400 mHierarchy[nodeId] = nodePointCount;
407 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::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 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