23 #include <QJsonDocument> 
   24 #include <QJsonObject> 
   37 #include "qgspointcloudexpression.h" 
   41 #define PROVIDER_KEY QStringLiteral( "ept" ) 
   42 #define PROVIDER_DESCRIPTION QStringLiteral( "EPT point cloud provider" ) 
   44 QgsEptPointCloudIndex::QgsEptPointCloudIndex() = 
default;
 
   46 QgsEptPointCloudIndex::~QgsEptPointCloudIndex() = 
default;
 
   48 std::unique_ptr<QgsPointCloudIndex> QgsEptPointCloudIndex::clone()
 const 
   50   QgsEptPointCloudIndex *clone = 
new QgsEptPointCloudIndex;
 
   51   QMutexLocker locker( &mHierarchyMutex );
 
   52   copyCommonProperties( clone );
 
   53   return std::unique_ptr<QgsPointCloudIndex>( clone );
 
   56 void 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();
 
   91 void 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();
 
  123 bool 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 ( type == QLatin1String( 
"float" ) && ( size == 4 ) )
 
  173     else if ( type == QLatin1String( 
"float" ) && ( size == 8 ) )
 
  177     else if ( size == 1 )
 
  181     else if ( type == QLatin1String( 
"unsigned" ) && size == 2 )
 
  185     else if ( size == 2 )
 
  189     else if ( size == 4 )
 
  200     if ( schemaObj.contains( QLatin1String( 
"scale" ) ) )
 
  201       scale = schemaObj.value( QLatin1String( 
"scale" ) ).toDouble();
 
  204     if ( schemaObj.contains( QLatin1String( 
"offset" ) ) )
 
  205       offset = schemaObj.value( QLatin1String( 
"offset" ) ).toDouble();
 
  207     if ( name == QLatin1String( 
"X" ) )
 
  209       mOffset.set( offset, mOffset.y(), mOffset.z() );
 
  210       mScale.set( scale, mScale.y(), mScale.z() );
 
  212     else if ( name == QLatin1String( 
"Y" ) )
 
  214       mOffset.set( mOffset.x(), offset, mOffset.z() );
 
  215       mScale.set( mScale.x(), scale, mScale.z() );
 
  217     else if ( name == QLatin1String( 
"Z" ) )
 
  219       mOffset.set( mOffset.x(), mOffset.y(), offset );
 
  220       mScale.set( mScale.x(), mScale.y(), scale );
 
  224     AttributeStatistics stats;
 
  225     bool foundStats = 
false;
 
  226     if ( schemaObj.contains( QLatin1String( 
"count" ) ) )
 
  228       stats.count = schemaObj.value( QLatin1String( 
"count" ) ).toInt();
 
  231     if ( schemaObj.contains( QLatin1String( 
"minimum" ) ) )
 
  233       stats.minimum = schemaObj.value( QLatin1String( 
"minimum" ) ).toDouble();
 
  236     if ( schemaObj.contains( QLatin1String( 
"maximum" ) ) )
 
  238       stats.maximum = schemaObj.value( QLatin1String( 
"maximum" ) ).toDouble();
 
  241     if ( schemaObj.contains( QLatin1String( 
"count" ) ) )
 
  243       stats.mean = schemaObj.value( QLatin1String( 
"mean" ) ).toDouble();
 
  246     if ( schemaObj.contains( QLatin1String( 
"stddev" ) ) )
 
  248       stats.stDev = schemaObj.value( QLatin1String( 
"stddev" ) ).toDouble();
 
  251     if ( schemaObj.contains( QLatin1String( 
"variance" ) ) )
 
  253       stats.variance = schemaObj.value( QLatin1String( 
"variance" ) ).toDouble();
 
  257       mMetadataStats.insert( name, stats );
 
  259     if ( schemaObj.contains( QLatin1String( 
"counts" ) ) )
 
  261       QMap< int, int >  classCounts;
 
  262       const QJsonArray counts = schemaObj.value( QLatin1String( 
"counts" ) ).toArray();
 
  263       for ( 
const QJsonValue &count : counts )
 
  265         const QJsonObject countObj = count.toObject();
 
  266         classCounts.insert( countObj.value( QLatin1String( 
"value" ) ).toInt(), countObj.value( QLatin1String( 
"count" ) ).toInt() );
 
  268       mAttributeClasses.insert( name, classCounts );
 
  271   setAttributes( attributes );
 
  276   const double xmin = bounds[0].toDouble();
 
  277   const double ymin = bounds[1].toDouble();
 
  278   const double zmin = bounds[2].toDouble();
 
  279   const double xmax = bounds[3].toDouble();
 
  280   const double ymax = bounds[4].toDouble();
 
  281   const double zmax = bounds[5].toDouble();
 
  284                   ( xmin - mOffset.x() ) / mScale.x(),
 
  285                   ( ymin - mOffset.y() ) / mScale.y(),
 
  286                   ( zmin - mOffset.z() ) / mScale.z(),
 
  287                   ( xmax - mOffset.x() ) / mScale.x(),
 
  288                   ( ymax - mOffset.y() ) / mScale.y(),
 
  289                   ( zmax - mOffset.z() ) / mScale.z()
 
  294   double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
 
  295   QgsDebugMsgLevel( QStringLiteral( 
"lvl0 node size in CRS units: %1 %2 %3" ).arg( dx ).arg( dy ).arg( dz ), 2 );    
 
  296   QgsDebugMsgLevel( QStringLiteral( 
"res at lvl0 %1" ).arg( dx / mSpan ), 2 );
 
  297   QgsDebugMsgLevel( QStringLiteral( 
"res at lvl1 %1" ).arg( dx / mSpan / 2 ), 2 );
 
  298   QgsDebugMsgLevel( QStringLiteral( 
"res at lvl2 %1 with node size %2" ).arg( dx / mSpan / 4 ).arg( dx / 4 ), 2 );
 
  306   mHierarchyMutex.lock();
 
  307   const bool found = mHierarchy.contains( n );
 
  308   mHierarchyMutex.unlock();
 
  315   QgsPointCloudExpression filterExpression = mFilterExpression;
 
  317   requestAttributes.
extend( attributes(), filterExpression.referencedAttributes() );
 
  319   if ( mDataType == QLatin1String( 
"binary" ) )
 
  321     const QString filename = QStringLiteral( 
"%1/ept-data/%2.bin" ).arg( mDirectory, n.
toString() );
 
  322     return QgsEptDecoder::decompressBinary( filename, attributes(), requestAttributes, scale(), offset(), filterExpression );
 
  324   else if ( mDataType == QLatin1String( 
"zstandard" ) )
 
  326     const QString filename = QStringLiteral( 
"%1/ept-data/%2.zst" ).arg( mDirectory, n.
toString() );
 
  327     return QgsEptDecoder::decompressZStandard( filename, attributes(), request.
attributes(), scale(), offset(), filterExpression );
 
  329   else if ( mDataType == QLatin1String( 
"laszip" ) )
 
  331     const QString filename = QStringLiteral( 
"%1/ept-data/%2.laz" ).arg( mDirectory, n.
toString() );
 
  332     return QgsLazDecoder::decompressLaz( filename, requestAttributes, filterExpression );
 
  353 qint64 QgsEptPointCloudIndex::pointCount()
 const 
  358 bool QgsEptPointCloudIndex::hasStatisticsMetadata()
 const 
  360   return !mMetadataStats.isEmpty();
 
  365   if ( !mMetadataStats.contains( attribute ) )
 
  368   const AttributeStatistics &stats = mMetadataStats[ attribute ];
 
  372       return stats.count >= 0 ? QVariant( stats.count ) : QVariant();
 
  375       return std::isnan( stats.mean ) ? QVariant() : QVariant( stats.mean );
 
  378       return std::isnan( stats.stDev ) ? QVariant() : QVariant( stats.stDev );
 
  381       return stats.minimum;
 
  384       return stats.maximum;
 
  387       return stats.minimum.isValid() && stats.maximum.isValid() ? QVariant( stats.maximum.toDouble() - stats.minimum.toDouble() ) : QVariant();
 
  407 QVariantList QgsEptPointCloudIndex::metadataClasses( 
const QString &attribute )
 const 
  409   QVariantList classes;
 
  410   const QMap< int, int > values =  mAttributeClasses.value( attribute );
 
  411   for ( 
auto it = values.constBegin(); it != values.constEnd(); ++it )
 
  423   const QMap< int, int > values =  mAttributeClasses.value( attribute );
 
  424   if ( !values.contains( value.toInt() ) )
 
  426   return values.value( value.toInt() );
 
  429 bool QgsEptPointCloudIndex::loadHierarchy()
 
  431   QQueue<QString> queue;
 
  432   queue.enqueue( QStringLiteral( 
"0-0-0-0" ) );
 
  433   while ( !queue.isEmpty() )
 
  435     const QString filename = QStringLiteral( 
"%1/ept-hierarchy/%2.json" ).arg( mDirectory, queue.dequeue() );
 
  436     QFile fH( filename );
 
  437     if ( !fH.open( QIODevice::ReadOnly ) )
 
  439       QgsDebugMsgLevel( QStringLiteral( 
"unable to read hierarchy from file %1" ).arg( filename ), 2 );
 
  440       mError = QStringLiteral( 
"unable to read hierarchy from file %1" ).arg( filename );
 
  444     const QByteArray dataJsonH = fH.readAll();
 
  445     QJsonParseError errH;
 
  446     const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH );
 
  447     if ( errH.error != QJsonParseError::NoError )
 
  449       QgsDebugMsgLevel( QStringLiteral( 
"QJsonParseError when reading hierarchy from file %1" ).arg( filename ), 2 );
 
  450       mError = QStringLiteral( 
"QJsonParseError when reading hierarchy from file %1" ).arg( filename );
 
  454     const QJsonObject rootHObj = docH.object();
 
  455     for ( 
auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it )
 
  457       const QString nodeIdStr = it.key();
 
  458       const int nodePointCount = it.value().toInt();
 
  459       if ( nodePointCount < 0 )
 
  461         queue.enqueue( nodeIdStr );
 
  466         mHierarchyMutex.lock();
 
  467         mHierarchy[nodeId] = nodePointCount;
 
  468         mHierarchyMutex.unlock();
 
  475 bool QgsEptPointCloudIndex::isValid()
 const 
  480 void QgsEptPointCloudIndex::copyCommonProperties( QgsEptPointCloudIndex *destination )
 const 
  485   destination->mIsValid = mIsValid;
 
  486   destination->mDataType = mDataType;
 
  487   destination->mDirectory = mDirectory;
 
  488   destination->mWkt = mWkt;
 
  489   destination->mPointCount = mPointCount;
 
  490   destination->mMetadataStats = mMetadataStats;
 
  491   destination->mAttributeClasses = mAttributeClasses;
 
  492   destination->mOriginalMetadata = mOriginalMetadata;