From 322b4c0eef3fb609b4fa32f65c0ebaad747e9ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 21 Nov 2024 13:59:36 +0100 Subject: [PATCH 1/9] Refactor QgsPointCloudIndex statistics methods --- .../qgspointclouddataprovider.sip.in | 74 ------------ .../qgspointclouddataprovider.sip.in | 74 ------------ .../pointcloud/qgscopcpointcloudindex.cpp | 16 +-- src/core/pointcloud/qgscopcpointcloudindex.h | 9 +- src/core/pointcloud/qgseptpointcloudindex.cpp | 85 ++++---------- src/core/pointcloud/qgseptpointcloudindex.h | 5 +- .../pointcloud/qgspointclouddataprovider.cpp | 43 ------- .../pointcloud/qgspointclouddataprovider.h | 105 ------------------ src/core/pointcloud/qgspointcloudindex.cpp | 64 ++--------- src/core/pointcloud/qgspointcloudindex.h | 9 +- src/core/pointcloud/qgspointcloudlayer.cpp | 49 ++------ tests/src/providers/testqgscopcprovider.cpp | 2 +- 12 files changed, 53 insertions(+), 482 deletions(-) diff --git a/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in b/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in index fa0bf644f596..c1bd9092a626 100644 --- a/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in +++ b/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in @@ -170,80 +170,6 @@ Only providers which report the CreateRenderer capability will return a 2D rende providers will return ``None``. %End - virtual bool hasStatisticsMetadata() const; -%Docstring -Returns whether the dataset contains statistics metadata - -.. versionadded:: 3.26 -%End - - - SIP_PYOBJECT metadataStatistic( const QString &attribute, Qgis::Statistic statistic ) const; -%Docstring -Returns a statistic for the specified ``attribute``, taken only from the metadata of the point cloud -data source. - -This method will not perform any statistical calculations, rather it will return only precomputed attribute -statistics which are included in the data source's metadata. Not all data sources include this information -in the metadata, and even for sources with statistical metadata only some ``statistic`` values may be available. - -:raises ValueError: if no matching precalculated statistic is available for the attribute. -%End -%MethodCode - { - const QVariant res = sipCpp->metadataStatistic( *a0, a1 ); - if ( !res.isValid() ) - { - PyErr_SetString( PyExc_ValueError, QStringLiteral( "Statistic is not available" ).toUtf8().constData() ); - sipIsErr = 1; - } - else - { - QVariant *v = new QVariant( res ); - sipRes = sipConvertFromNewType( v, sipType_QVariant, Py_None ); - } - } -%End - - virtual QVariantList metadataClasses( const QString &attribute ) const; -%Docstring -Returns a list of existing classes which are present for the specified ``attribute``, taken only from the -metadata of the point cloud data source. - -This method will not perform any classification or scan for available classes, rather it will return only -precomputed classes which are included in the data source's metadata. Not all data sources include this information -in the metadata. -%End - - - - SIP_PYOBJECT metadataClassStatistic( const QString &attribute, const QVariant &value, Qgis::Statistic statistic ) const; -%Docstring -Returns a statistic for one class ``value`` from the specified ``attribute``, taken only from the metadata of the point cloud -data source. -This method will not perform any statistical calculations, rather it will return only precomputed class -statistics which are included in the data source's metadata. Not all data sources include this information -in the metadata, and even for sources with statistical metadata only some ``statistic`` values may be available. - -:raises ValueError: if no matching precalculated statistic is available for the attribute. -%End -%MethodCode - { - const QVariant res = sipCpp->metadataClassStatistic( *a0, *a1, a2 ); - if ( !res.isValid() ) - { - PyErr_SetString( PyExc_ValueError, QStringLiteral( "Statistic is not available" ).toUtf8().constData() ); - sipIsErr = 1; - } - else - { - QVariant *v = new QVariant( res ); - sipRes = sipConvertFromNewType( v, sipType_QVariant, Py_None ); - } - } -%End - - QgsPointCloudStatistics metadataStatistics(); %Docstring Returns the object containing the statistics metadata extracted from the dataset diff --git a/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in b/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in index 3c5fd6b753ed..4155ab4d36f6 100644 --- a/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in +++ b/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in @@ -170,80 +170,6 @@ Only providers which report the CreateRenderer capability will return a 2D rende providers will return ``None``. %End - virtual bool hasStatisticsMetadata() const; -%Docstring -Returns whether the dataset contains statistics metadata - -.. versionadded:: 3.26 -%End - - - SIP_PYOBJECT metadataStatistic( const QString &attribute, Qgis::Statistic statistic ) const; -%Docstring -Returns a statistic for the specified ``attribute``, taken only from the metadata of the point cloud -data source. - -This method will not perform any statistical calculations, rather it will return only precomputed attribute -statistics which are included in the data source's metadata. Not all data sources include this information -in the metadata, and even for sources with statistical metadata only some ``statistic`` values may be available. - -:raises ValueError: if no matching precalculated statistic is available for the attribute. -%End -%MethodCode - { - const QVariant res = sipCpp->metadataStatistic( *a0, a1 ); - if ( !res.isValid() ) - { - PyErr_SetString( PyExc_ValueError, QStringLiteral( "Statistic is not available" ).toUtf8().constData() ); - sipIsErr = 1; - } - else - { - QVariant *v = new QVariant( res ); - sipRes = sipConvertFromNewType( v, sipType_QVariant, Py_None ); - } - } -%End - - virtual QVariantList metadataClasses( const QString &attribute ) const; -%Docstring -Returns a list of existing classes which are present for the specified ``attribute``, taken only from the -metadata of the point cloud data source. - -This method will not perform any classification or scan for available classes, rather it will return only -precomputed classes which are included in the data source's metadata. Not all data sources include this information -in the metadata. -%End - - - - SIP_PYOBJECT metadataClassStatistic( const QString &attribute, const QVariant &value, Qgis::Statistic statistic ) const; -%Docstring -Returns a statistic for one class ``value`` from the specified ``attribute``, taken only from the metadata of the point cloud -data source. -This method will not perform any statistical calculations, rather it will return only precomputed class -statistics which are included in the data source's metadata. Not all data sources include this information -in the metadata, and even for sources with statistical metadata only some ``statistic`` values may be available. - -:raises ValueError: if no matching precalculated statistic is available for the attribute. -%End -%MethodCode - { - const QVariant res = sipCpp->metadataClassStatistic( *a0, *a1, a2 ); - if ( !res.isValid() ) - { - PyErr_SetString( PyExc_ValueError, QStringLiteral( "Statistic is not available" ).toUtf8().constData() ); - sipIsErr = 1; - } - else - { - QVariant *v = new QVariant( res ); - sipRes = sipConvertFromNewType( v, sipType_QVariant, Py_None ); - } - } -%End - - QgsPointCloudStatistics metadataStatistics(); %Docstring Returns the object containing the statistics metadata extracted from the dataset diff --git a/src/core/pointcloud/qgscopcpointcloudindex.cpp b/src/core/pointcloud/qgscopcpointcloudindex.cpp index 518f022662ae..2c876afb81ae 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.cpp +++ b/src/core/pointcloud/qgscopcpointcloudindex.cpp @@ -310,16 +310,18 @@ bool QgsCopcPointCloudIndex::writeStatistics( QgsPointCloudStatistics &stats ) return true; } -QgsPointCloudStatistics QgsCopcPointCloudIndex::readStatistics() +QgsPointCloudStatistics QgsCopcPointCloudIndex::metadataStatistics() const { - QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData(); - - if ( statisticsEvlrData.isEmpty() ) + if ( ! mStatistics ) { - return QgsPointCloudStatistics(); + QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData(); + if ( statisticsEvlrData.isEmpty() ) + mStatistics = QgsPointCloudIndex::metadataStatistics(); + else + mStatistics = QgsPointCloudStatistics::fromStatisticsJson( statisticsEvlrData ); } - return QgsPointCloudStatistics::fromStatisticsJson( statisticsEvlrData ); + return *mStatistics; } bool QgsCopcPointCloudIndex::isValid() const @@ -476,7 +478,7 @@ void QgsCopcPointCloudIndex::copyCommonProperties( QgsCopcPointCloudIndex *desti destination->mLazInfo.reset( new QgsLazInfo( *mLazInfo ) ); } -QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData() +QByteArray QgsCopcPointCloudIndex::fetchCopcStatisticsEvlrData() const { Q_ASSERT( mAccessType == Local ); // TODO: Remote uint64_t offset = mLazInfo->firstEvlrOffset(); diff --git a/src/core/pointcloud/qgscopcpointcloudindex.h b/src/core/pointcloud/qgscopcpointcloudindex.h index 4eab75261d65..32e0f52fab50 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.h +++ b/src/core/pointcloud/qgscopcpointcloudindex.h @@ -27,6 +27,7 @@ #include #include +#include #include "qgspointcloudindex.h" #include "qgspointcloudstatistics.h" @@ -59,7 +60,6 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex QgsCoordinateReferenceSystem crs() const override; qint64 pointCount() const override; - bool hasStatisticsMetadata() const override { return false; }; QVariantMap originalMetadata() const override { return mOriginalMetadata; } bool isValid() const override; @@ -75,9 +75,9 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex /** * Returns the statistics object contained in the COPC dataset. * If the dataset doesn't contain statistics EVLR, an object with 0 samples will be returned. - * \since QGIS 3.26 + * \since QGIS 3.42 */ - QgsPointCloudStatistics readStatistics(); + virtual QgsPointCloudStatistics metadataStatistics() const override; /** * Copies common properties to the \a destination index @@ -106,7 +106,7 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex void populateHierarchy( const char *hierarchyPageData, uint64_t byteSize ) const; - QByteArray fetchCopcStatisticsEvlrData(); + QByteArray fetchCopcStatisticsEvlrData() const; bool mIsValid = false; QgsPointCloudIndex::AccessType mAccessType = Local; @@ -115,6 +115,7 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex mutable QHash> mHierarchyNodePos; //!< Additional data hierarchy for COPC QVariantMap mOriginalMetadata; + mutable std::optional mStatistics; std::unique_ptr mLazInfo = nullptr; }; diff --git a/src/core/pointcloud/qgseptpointcloudindex.cpp b/src/core/pointcloud/qgseptpointcloudindex.cpp index 726438553cba..27da4cbf5735 100644 --- a/src/core/pointcloud/qgseptpointcloudindex.cpp +++ b/src/core/pointcloud/qgseptpointcloudindex.cpp @@ -41,6 +41,7 @@ #include "qgslogger.h" #include "qgspointcloudexpression.h" #include "qgssetrequestinitiator_p.h" +#include "qgspointcloudstatistics.h" ///@cond PRIVATE @@ -513,75 +514,27 @@ qint64 QgsEptPointCloudIndex::nodePointCount( const IndexedPointCloudNode &nodeI return -1; } -bool QgsEptPointCloudIndex::hasStatisticsMetadata() const +QgsPointCloudStatistics QgsEptPointCloudIndex::metadataStatistics() const { - return !mMetadataStats.isEmpty(); -} - -QVariant QgsEptPointCloudIndex::metadataStatistic( const QString &attribute, Qgis::Statistic statistic ) const -{ - if ( !mMetadataStats.contains( attribute ) ) - return QVariant(); - - const AttributeStatistics &stats = mMetadataStats[ attribute ]; - switch ( statistic ) + QMap statsMap; + for ( QgsPointCloudAttribute attribute : attributes().attributes() ) { - case Qgis::Statistic::Count: - return stats.count >= 0 ? QVariant( stats.count ) : QVariant(); - - case Qgis::Statistic::Mean: - return std::isnan( stats.mean ) ? QVariant() : QVariant( stats.mean ); - - case Qgis::Statistic::StDev: - return std::isnan( stats.stDev ) ? QVariant() : QVariant( stats.stDev ); - - case Qgis::Statistic::Min: - return stats.minimum; - - case Qgis::Statistic::Max: - return stats.maximum; - - case Qgis::Statistic::Range: - return stats.minimum.isValid() && stats.maximum.isValid() ? QVariant( stats.maximum.toDouble() - stats.minimum.toDouble() ) : QVariant(); - - case Qgis::Statistic::CountMissing: - case Qgis::Statistic::Sum: - case Qgis::Statistic::Median: - case Qgis::Statistic::StDevSample: - case Qgis::Statistic::Minority: - case Qgis::Statistic::Majority: - case Qgis::Statistic::Variety: - case Qgis::Statistic::FirstQuartile: - case Qgis::Statistic::ThirdQuartile: - case Qgis::Statistic::InterQuartileRange: - case Qgis::Statistic::First: - case Qgis::Statistic::Last: - case Qgis::Statistic::All: - return QVariant(); + QString name = attribute.name(); + const AttributeStatistics &stats = mMetadataStats[ name ]; + if ( !stats.minimum.isValid() ) + continue; + QgsPointCloudAttributeStatistics s; + s.minimum = stats.minimum.toDouble(); + s.maximum = stats.maximum.toDouble(); + s.mean = stats.mean; + s.stDev = stats.stDev; + s.count = stats.count; + + s.classCount = mAttributeClasses[ name ]; + + statsMap[ name ] = s; } - return QVariant(); -} - -QVariantList QgsEptPointCloudIndex::metadataClasses( const QString &attribute ) const -{ - QVariantList classes; - const QMap< int, int > values = mAttributeClasses.value( attribute ); - for ( auto it = values.constBegin(); it != values.constEnd(); ++it ) - { - classes << it.key(); - } - return classes; -} - -QVariant QgsEptPointCloudIndex::metadataClassStatistic( const QString &attribute, const QVariant &value, Qgis::Statistic statistic ) const -{ - if ( statistic != Qgis::Statistic::Count ) - return QVariant(); - - const QMap< int, int > values = mAttributeClasses.value( attribute ); - if ( !values.contains( value.toInt() ) ) - return QVariant(); - return values.value( value.toInt() ); + return QgsPointCloudStatistics( pointCount(), statsMap ); } bool QgsEptPointCloudIndex::loadSingleNodeHierarchy( const IndexedPointCloudNode &nodeId ) const diff --git a/src/core/pointcloud/qgseptpointcloudindex.h b/src/core/pointcloud/qgseptpointcloudindex.h index 3a7259c1f68f..097894b6caab 100644 --- a/src/core/pointcloud/qgseptpointcloudindex.h +++ b/src/core/pointcloud/qgseptpointcloudindex.h @@ -53,11 +53,8 @@ class CORE_EXPORT QgsEptPointCloudIndex: public QgsPointCloudIndex QgsCoordinateReferenceSystem crs() const override; qint64 pointCount() const override; virtual qint64 nodePointCount( const IndexedPointCloudNode &n ) const override; - bool hasStatisticsMetadata() const override; - QVariant metadataStatistic( const QString &attribute, Qgis::Statistic statistic ) const override; - QVariantList metadataClasses( const QString &attribute ) const override; - QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, Qgis::Statistic statistic ) const override; QVariantMap originalMetadata() const override { return mOriginalMetadata; } + virtual QgsPointCloudStatistics metadataStatistics() const override; bool isValid() const override; QgsPointCloudIndex::AccessType accessType() const override; diff --git a/src/core/pointcloud/qgspointclouddataprovider.cpp b/src/core/pointcloud/qgspointclouddataprovider.cpp index 9729a06173af..f4ed0d6b2762 100644 --- a/src/core/pointcloud/qgspointclouddataprovider.cpp +++ b/src/core/pointcloud/qgspointclouddataprovider.cpp @@ -184,49 +184,6 @@ QMap QgsPointCloudDataProvider::translatedDataFormatIds() return sCodes; } -bool QgsPointCloudDataProvider::hasStatisticsMetadata() const -{ - QGIS_PROTECT_QOBJECT_THREAD_ACCESS - - return index() && index()->hasStatisticsMetadata(); -} - -QVariant QgsPointCloudDataProvider::metadataStatistic( const QString &attribute, Qgis::Statistic statistic ) const -{ - QGIS_PROTECT_QOBJECT_THREAD_ACCESS - - QgsPointCloudIndex *pcIndex = index(); - if ( pcIndex ) - { - return pcIndex->metadataStatistic( attribute, statistic ); - } - return QVariant(); -} - -QVariantList QgsPointCloudDataProvider::metadataClasses( const QString &attribute ) const -{ - QGIS_PROTECT_QOBJECT_THREAD_ACCESS - - QgsPointCloudIndex *pcIndex = index(); - if ( pcIndex ) - { - return pcIndex->metadataClasses( attribute ); - } - return QVariantList(); -} - -QVariant QgsPointCloudDataProvider::metadataClassStatistic( const QString &attribute, const QVariant &value, Qgis::Statistic statistic ) const -{ - QGIS_PROTECT_QOBJECT_THREAD_ACCESS - - QgsPointCloudIndex *pcIndex = index(); - if ( pcIndex ) - { - return pcIndex->metadataClassStatistic( attribute, value, statistic ); - } - return QVariant(); -} - QgsPointCloudStatistics QgsPointCloudDataProvider::metadataStatistics() { QGIS_PROTECT_QOBJECT_THREAD_ACCESS diff --git a/src/core/pointcloud/qgspointclouddataprovider.h b/src/core/pointcloud/qgspointclouddataprovider.h index 94fdf6f6ca03..58a750730350 100644 --- a/src/core/pointcloud/qgspointclouddataprovider.h +++ b/src/core/pointcloud/qgspointclouddataprovider.h @@ -227,111 +227,6 @@ class CORE_EXPORT QgsPointCloudDataProvider: public QgsDataProvider */ virtual QgsPointCloudRenderer *createRenderer( const QVariantMap &configuration = QVariantMap() ) const SIP_FACTORY; - /** - * Returns whether the dataset contains statistics metadata - * - * \since QGIS 3.26 - */ - virtual bool hasStatisticsMetadata() const; - -#ifndef SIP_RUN - - /** - * Returns a statistic for the specified \a attribute, taken only from the metadata of the point cloud - * data source. - * - * This method will not perform any statistical calculations, rather it will return only precomputed attribute - * statistics which are included in the data source's metadata. Not all data sources include this information - * in the metadata, and even for sources with statistical metadata only some \a statistic values may be available. - * - * If no matching precalculated statistic is available then an invalid variant will be returned. - */ - virtual QVariant metadataStatistic( const QString &attribute, Qgis::Statistic statistic ) const; -#else - - /** - * Returns a statistic for the specified \a attribute, taken only from the metadata of the point cloud - * data source. - * - * This method will not perform any statistical calculations, rather it will return only precomputed attribute - * statistics which are included in the data source's metadata. Not all data sources include this information - * in the metadata, and even for sources with statistical metadata only some \a statistic values may be available. - * - * \throws ValueError if no matching precalculated statistic is available for the attribute. - */ - SIP_PYOBJECT metadataStatistic( const QString &attribute, Qgis::Statistic statistic ) const; - % MethodCode - { - const QVariant res = sipCpp->metadataStatistic( *a0, a1 ); - if ( !res.isValid() ) - { - PyErr_SetString( PyExc_ValueError, QStringLiteral( "Statistic is not available" ).toUtf8().constData() ); - sipIsErr = 1; - } - else - { - QVariant *v = new QVariant( res ); - sipRes = sipConvertFromNewType( v, sipType_QVariant, Py_None ); - } - } - % End -#endif - - /** - * Returns a list of existing classes which are present for the specified \a attribute, taken only from the - * metadata of the point cloud data source. - * - * This method will not perform any classification or scan for available classes, rather it will return only - * precomputed classes which are included in the data source's metadata. Not all data sources include this information - * in the metadata. - */ - virtual QVariantList metadataClasses( const QString &attribute ) const; - - -#ifndef SIP_RUN - - /** - * Returns a statistic for one class \a value from the specified \a attribute, taken only from the metadata of the point cloud - * data source. - * - * This method will not perform any statistical calculations, rather it will return only precomputed class - * statistics which are included in the data source's metadata. Not all data sources include this information - * in the metadata, and even for sources with statistical metadata only some \a statistic values may be available. - * - * If no matching precalculated statistic is available then an invalid variant will be returned. - */ - virtual QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, Qgis::Statistic statistic ) const; - -#else - - /** - * Returns a statistic for one class \a value from the specified \a attribute, taken only from the metadata of the point cloud - * data source. - * This method will not perform any statistical calculations, rather it will return only precomputed class - * statistics which are included in the data source's metadata. Not all data sources include this information - * in the metadata, and even for sources with statistical metadata only some \a statistic values may be available. - * - * \throws ValueError if no matching precalculated statistic is available for the attribute. - */ - SIP_PYOBJECT metadataClassStatistic( const QString &attribute, const QVariant &value, Qgis::Statistic statistic ) const; - % MethodCode - { - const QVariant res = sipCpp->metadataClassStatistic( *a0, *a1, a2 ); - if ( !res.isValid() ) - { - PyErr_SetString( PyExc_ValueError, QStringLiteral( "Statistic is not available" ).toUtf8().constData() ); - sipIsErr = 1; - } - else - { - QVariant *v = new QVariant( res ); - sipRes = sipConvertFromNewType( v, sipType_QVariant, Py_None ); - } - } - % End -#endif - - /** * Returns the object containing the statistics metadata extracted from the dataset * \since QGIS 3.26 diff --git a/src/core/pointcloud/qgspointcloudindex.cpp b/src/core/pointcloud/qgspointcloudindex.cpp index 12f0e4f8955c..e4e72833277f 100644 --- a/src/core/pointcloud/qgspointcloudindex.cpp +++ b/src/core/pointcloud/qgspointcloudindex.cpp @@ -306,66 +306,16 @@ QString QgsPointCloudIndex::subsetString() const return mFilterExpression; } -QVariant QgsPointCloudIndex::metadataStatistic( const QString &attribute, Qgis::Statistic statistic ) const -{ - if ( attribute == QLatin1String( "X" ) && statistic == Qgis::Statistic::Min ) - return mExtent.xMinimum(); - if ( attribute == QLatin1String( "X" ) && statistic == Qgis::Statistic::Max ) - return mExtent.xMaximum(); - - if ( attribute == QLatin1String( "Y" ) && statistic == Qgis::Statistic::Min ) - return mExtent.yMinimum(); - if ( attribute == QLatin1String( "Y" ) && statistic == Qgis::Statistic::Max ) - return mExtent.yMaximum(); - - if ( attribute == QLatin1String( "Z" ) && statistic == Qgis::Statistic::Min ) - return mZMin; - if ( attribute == QLatin1String( "Z" ) && statistic == Qgis::Statistic::Max ) - return mZMax; - - return QVariant(); -} - -QVariantList QgsPointCloudIndex::metadataClasses( const QString &attribute ) const -{ - Q_UNUSED( attribute ); - return QVariantList(); -} - -QVariant QgsPointCloudIndex::metadataClassStatistic( const QString &attribute, const QVariant &value, Qgis::Statistic statistic ) const -{ - Q_UNUSED( attribute ); - Q_UNUSED( value ); - Q_UNUSED( statistic ); - return QVariant(); -} - QgsPointCloudStatistics QgsPointCloudIndex::metadataStatistics() const { QMap statsMap; - for ( QgsPointCloudAttribute attribute : attributes().attributes() ) - { - QString name = attribute.name(); - QgsPointCloudAttributeStatistics s; - QVariant min = metadataStatistic( name, Qgis::Statistic::Min ); - QVariant max = metadataStatistic( name, Qgis::Statistic::Max ); - QVariant mean = metadataStatistic( name, Qgis::Statistic::Mean ); - QVariant stDev = metadataStatistic( name, Qgis::Statistic::StDev ); - if ( !min.isValid() ) - continue; - - s.minimum = min.toDouble(); - s.maximum = max.toDouble(); - s.mean = mean.toDouble(); - s.stDev = stDev.toDouble(); - s.count = metadataStatistic( name, Qgis::Statistic::Count ).toInt(); - QVariantList classes = metadataClasses( name ); - for ( QVariant c : classes ) - { - s.classCount[ c.toInt() ] = metadataClassStatistic( name, c, Qgis::Statistic::Count ).toInt(); - } - statsMap[ name ] = s; - } + statsMap[ "X" ].minimum = mExtent.xMinimum(); + statsMap[ "X" ].maximum = mExtent.xMaximum(); + statsMap[ "Y" ].minimum = mExtent.yMinimum(); + statsMap[ "Y" ].maximum = mExtent.yMinimum(); + statsMap[ "Z" ].minimum = mZMin; + statsMap[ "Z" ].maximum = mZMax; + return QgsPointCloudStatistics( pointCount(), statsMap ); } diff --git a/src/core/pointcloud/qgspointcloudindex.h b/src/core/pointcloud/qgspointcloudindex.h index e889e4c2ef85..4887795a3d8e 100644 --- a/src/core/pointcloud/qgspointcloudindex.h +++ b/src/core/pointcloud/qgspointcloudindex.h @@ -28,6 +28,7 @@ #include #include "qgis_core.h" +#include "qgspointcloudstatistics.h" #include "qgsrectangle.h" #include "qgsvector3d.h" #include "qgis_sip.h" @@ -251,14 +252,6 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject virtual QgsCoordinateReferenceSystem crs() const = 0; //! Returns the number of points in the point cloud virtual qint64 pointCount() const = 0; - //! Returns whether the dataset contains metadata of statistics - virtual bool hasStatisticsMetadata() const = 0; - //! Returns the statistic \a statistic of \a attribute - virtual QVariant metadataStatistic( const QString &attribute, Qgis::Statistic statistic ) const; - //! Returns the classes of \a attribute - virtual QVariantList metadataClasses( const QString &attribute ) const; - //! Returns the statistic \a statistic of the class \a value of the attribute \a attribute - virtual QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, Qgis::Statistic statistic ) const; //! Returns the original metadata map virtual QVariantMap originalMetadata() const = 0; diff --git a/src/core/pointcloud/qgspointcloudlayer.cpp b/src/core/pointcloud/qgspointcloudlayer.cpp index cd6eaaf4a428..9a807d0b8025 100644 --- a/src/core/pointcloud/qgspointcloudlayer.cpp +++ b/src/core/pointcloud/qgspointcloudlayer.cpp @@ -19,6 +19,7 @@ #include "moc_qgspointcloudlayer.cpp" #include "qgspointcloudlayerrenderer.h" #include "qgspointcloudindex.h" +#include "qgspointcloudstatistics.h" #include "qgspointcloudsubindex.h" #include "qgsrectangle.h" #include "qgspointclouddataprovider.h" @@ -449,7 +450,6 @@ void QgsPointCloudLayer::setDataSourcePrivate( const QString &dataSource, const if ( !mLayerOptions.skipStatisticsCalculation && mDataProvider && - !mDataProvider->hasStatisticsMetadata() && mDataProvider->indexingState() == QgsPointCloudDataProvider::PointCloudIndexGenerationState::Indexed && mDataProvider->pointCount() > 0 ) { @@ -864,15 +864,6 @@ void QgsPointCloudLayer::calculateStatistics() QgsMessageLog::logMessage( QObject::tr( "A statistics calculation task for the point cloud %1 is already in progress" ).arg( this->name() ) ); return; } -#ifdef HAVE_COPC - if ( mDataProvider && mDataProvider->index() && mDataProvider->index()->isValid() ) - { - if ( QgsCopcPointCloudIndex *index = qobject_cast( mDataProvider->index() ) ) - { - mStatistics = index->readStatistics(); - } - } -#endif if ( mStatistics.sampledPointsCount() != 0 ) { mStatisticsCalculationState = QgsPointCloudLayer::PointCloudStatisticsCalculationState::Calculated; @@ -881,11 +872,13 @@ void QgsPointCloudLayer::calculateStatistics() return; } + QgsPointCloudStatistics indexStats = mDataProvider->metadataStatistics(); + QList indexStatsAttributes = indexStats.statisticsMap().keys(); QVector attributes = mDataProvider->attributes().attributes(); - // Do not calculate stats for X, Y & Z since the point cloud index contains that + // Do not calculate stats for attributes that the index gives us stats for for ( int i = 0; i < attributes.size(); ++i ) { - if ( attributes[i].name() == QLatin1String( "X" ) || attributes[i].name() == QLatin1String( "Y" ) || attributes[i].name() == QLatin1String( "Z" ) ) + if ( indexStatsAttributes.contains( attributes[i].name() ) ) { attributes.remove( i ); --i; @@ -893,39 +886,17 @@ void QgsPointCloudLayer::calculateStatistics() } QgsPointCloudStatsCalculationTask *task = new QgsPointCloudStatsCalculationTask( mDataProvider->index(), attributes, 1000000 ); - connect( task, &QgsTask::taskCompleted, this, [this, task]() + connect( task, &QgsTask::taskCompleted, this, [this, task, indexStats, indexStatsAttributes]() { mStatistics = task->calculationResults(); - // fetch X, Y & Z stats directly from the index - QVector coordinateAttributes; - coordinateAttributes.push_back( QStringLiteral( "X" ) ); - coordinateAttributes.push_back( QStringLiteral( "Y" ) ); - coordinateAttributes.push_back( QStringLiteral( "Z" ) ); - + // Fetch what we can directly from the index QMap statsMap = mStatistics.statisticsMap(); - QgsPointCloudIndex *index = mDataProvider->index(); - for ( const QString &attribute : coordinateAttributes ) + for ( const QString &attribute : indexStatsAttributes ) { - QgsPointCloudAttributeStatistics s; - QVariant min = index->metadataStatistic( attribute, Qgis::Statistic::Min ); - QVariant max = index->metadataStatistic( attribute, Qgis::Statistic::Max ); - if ( !min.isValid() ) - continue; - s.minimum = min.toDouble(); - s.maximum = max.toDouble(); - s.count = index->metadataStatistic( attribute, Qgis::Statistic::Count ).toInt(); - s.mean = index->metadataStatistic( attribute, Qgis::Statistic::Mean ).toInt(); - s.stDev = index->metadataStatistic( attribute, Qgis::Statistic::StDev ).toInt(); - QVariantList classes = index->metadataClasses( attribute ); - for ( const QVariant &c : classes ) - { - s.classCount[ c.toInt() ] = index->metadataClassStatistic( attribute, c, Qgis::Statistic::Count ).toInt(); - } - statsMap[ attribute ] = s; + statsMap[ attribute ] = indexStats.statisticsOf( attribute ); } mStatistics = QgsPointCloudStatistics( mStatistics.sampledPointsCount(), statsMap ); - // mStatisticsCalculationState = QgsPointCloudLayer::PointCloudStatisticsCalculationState::Calculated; emit statisticsCalculationStateChanged( mStatisticsCalculationState ); @@ -963,7 +934,7 @@ void QgsPointCloudLayer::resetRenderer() QGIS_PROTECT_QOBJECT_THREAD_ACCESS mDataProvider->loadIndex(); - if ( !mLayerOptions.skipStatisticsCalculation && !mDataProvider->hasStatisticsMetadata() && statisticsCalculationState() == QgsPointCloudLayer::PointCloudStatisticsCalculationState::NotStarted ) + if ( !mLayerOptions.skipStatisticsCalculation && statisticsCalculationState() == QgsPointCloudLayer::PointCloudStatisticsCalculationState::NotStarted ) { calculateStatistics(); } diff --git a/tests/src/providers/testqgscopcprovider.cpp b/tests/src/providers/testqgscopcprovider.cpp index 392195c0fe57..1cfa745736b7 100644 --- a/tests/src/providers/testqgscopcprovider.cpp +++ b/tests/src/providers/testqgscopcprovider.cpp @@ -1144,7 +1144,7 @@ void TestQgsCopcProvider::testSaveLoadStats() QVERIFY( layer->dataProvider() && layer->dataProvider()->isValid() && layer->dataProvider()->index() ); QgsCopcPointCloudIndex *index = qobject_cast( layer->dataProvider()->index() ); - readStats = index->readStatistics(); + readStats = index->metadataStatistics(); } QVERIFY( calculatedStats.sampledPointsCount() == readStats.sampledPointsCount() ); From 634f73b73ff181b822cacef90a97ef5ee7c6565c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Fri, 22 Nov 2024 16:32:03 +0100 Subject: [PATCH 2/9] Refactor QgsPointCloudIndex - Rename IndexedPointCloudNode to QgsPointCloudNodeId. - Introduce QgsPointCloudNode and move methods to it from QgsPointCloudIndex. - Report point cloud node bounds directly in CRS coordinates, without extra scaling and offset. --- src/3d/qgspointcloudlayerchunkloader_p.cpp | 45 ++--- src/3d/symbols/qgspointcloud3dsymbol_p.cpp | 29 +-- src/3d/symbols/qgspointcloud3dsymbol_p.h | 18 +- .../qgscachedpointcloudblockrequest.cpp | 2 +- .../qgscachedpointcloudblockrequest.h | 2 +- .../qgscopcpointcloudblockrequest.cpp | 2 +- .../qgscopcpointcloudblockrequest.h | 2 +- .../pointcloud/qgscopcpointcloudindex.cpp | 93 ++++----- src/core/pointcloud/qgscopcpointcloudindex.h | 13 +- .../qgseptpointcloudblockrequest.cpp | 2 +- .../pointcloud/qgseptpointcloudblockrequest.h | 2 +- src/core/pointcloud/qgseptpointcloudindex.cpp | 80 ++++---- src/core/pointcloud/qgseptpointcloudindex.h | 18 +- .../pointcloud/qgspointcloudblockrequest.cpp | 2 +- .../pointcloud/qgspointcloudblockrequest.h | 4 +- .../pointcloud/qgspointclouddataprovider.cpp | 31 ++- .../pointcloud/qgspointclouddataprovider.h | 4 +- src/core/pointcloud/qgspointcloudindex.cpp | 177 ++++++------------ src/core/pointcloud/qgspointcloudindex.h | 139 +++++--------- src/core/pointcloud/qgspointcloudlayer.cpp | 2 +- .../pointcloud/qgspointcloudlayerexporter.cpp | 23 +-- .../qgspointcloudlayerprofilegenerator.cpp | 35 ++-- .../qgspointcloudlayerprofilegenerator.h | 8 +- .../pointcloud/qgspointcloudlayerrenderer.cpp | 37 ++-- .../pointcloud/qgspointcloudlayerrenderer.h | 8 +- src/core/pointcloud/qgspointcloudstatistics.h | 2 +- .../qgspointcloudstatscalculator.cpp | 31 +-- .../pointcloud/qgspointcloudstatscalculator.h | 5 +- tests/src/providers/testqgscopcprovider.cpp | 84 ++++----- tests/src/providers/testqgseptprovider.cpp | 76 ++++---- 30 files changed, 438 insertions(+), 538 deletions(-) diff --git a/src/3d/qgspointcloudlayerchunkloader_p.cpp b/src/3d/qgspointcloudlayerchunkloader_p.cpp index b3ab8ba1e273..e94aa6909e7c 100644 --- a/src/3d/qgspointcloudlayerchunkloader_p.cpp +++ b/src/3d/qgspointcloudlayerchunkloader_p.cpp @@ -17,6 +17,7 @@ #include "moc_qgspointcloudlayerchunkloader_p.cpp" #include "qgs3dutils.h" +#include "qgsbox3d.h" #include "qgspointcloudlayer3drenderer.h" #include "qgschunknode.h" #include "qgslogger.h" @@ -57,7 +58,7 @@ QgsPointCloudLayerChunkLoader::QgsPointCloudLayerChunkLoader( const QgsPointClou mContext.setAttributes( pc->attributes() ); const QgsChunkNodeId nodeId = node->tileId(); - const IndexedPointCloudNode pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z ); + const QgsPointCloudNodeId pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z ); Q_ASSERT( pc->hasNode( pcNode ) ); @@ -128,7 +129,7 @@ Qt3DCore::QEntity *QgsPointCloudLayerChunkLoader::createEntity( Qt3DCore::QEntit { QgsPointCloudIndex *pc = mFactory->mPointCloudIndex; const QgsChunkNodeId nodeId = mNode->tileId(); - const IndexedPointCloudNode pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z ); + const QgsPointCloudNodeId pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z ); Q_ASSERT( pc->hasNode( pcNode ) ); Qt3DCore::QEntity *entity = new Qt3DCore::QEntity( parent ); @@ -165,7 +166,7 @@ QgsChunkLoader *QgsPointCloudLayerChunkLoaderFactory::createChunkLoader( QgsChun { const QgsChunkNodeId id = node->tileId(); - Q_ASSERT( mPointCloudIndex->hasNode( IndexedPointCloudNode( id.d, id.x, id.y, id.z ) ) ); + Q_ASSERT( mPointCloudIndex->hasNode( QgsPointCloudNodeId( id.d, id.x, id.y, id.z ) ) ); QgsPointCloud3DSymbol *symbol = static_cast< QgsPointCloud3DSymbol * >( mSymbol->clone() ); return new QgsPointCloudLayerChunkLoader( this, node, std::unique_ptr< QgsPointCloud3DSymbol >( symbol ), mCoordinateTransform, mZValueScale, mZValueOffset ); } @@ -173,20 +174,20 @@ QgsChunkLoader *QgsPointCloudLayerChunkLoaderFactory::createChunkLoader( QgsChun int QgsPointCloudLayerChunkLoaderFactory::primitivesCount( QgsChunkNode *node ) const { const QgsChunkNodeId id = node->tileId(); - const IndexedPointCloudNode n( id.d, id.x, id.y, id.z ); + const QgsPointCloudNodeId n( id.d, id.x, id.y, id.z ); Q_ASSERT( mPointCloudIndex->hasNode( n ) ); - return mPointCloudIndex->nodePointCount( n ); + return mPointCloudIndex->getNode( n ).pointCount(); } -QgsBox3D nodeBoundsToBox3D( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const QgsCoordinateTransform &coordinateTransform, double zValueOffset, double zValueScale ) +static QgsBox3D nodeBoundsToBox3D( QgsBox3D nodeBounds, const QgsCoordinateTransform &coordinateTransform, double zValueOffset, double zValueScale ) { - QgsVector3D extentMin3D( static_cast( nodeBounds.xMin() ) * scale.x() + offset.x(), - static_cast( nodeBounds.yMin() ) * scale.y() + offset.y(), - ( static_cast( nodeBounds.zMin() ) * scale.z() + offset.z() ) * zValueScale + zValueOffset ); - QgsVector3D extentMax3D( static_cast( nodeBounds.xMax() ) * scale.x() + offset.x(), - static_cast( nodeBounds.yMax() ) * scale.y() + offset.y(), - ( static_cast( nodeBounds.zMax() ) * scale.z() + offset.z() ) * zValueScale + zValueOffset ); + QgsVector3D extentMin3D( static_cast( nodeBounds.xMinimum() ), + static_cast( nodeBounds.yMinimum() ), + static_cast( nodeBounds.zMinimum() ) * zValueScale + zValueOffset ); + QgsVector3D extentMax3D( static_cast( nodeBounds.xMaximum() ), + static_cast( nodeBounds.yMaximum() ), + static_cast( nodeBounds.zMaximum() ) * zValueScale + zValueOffset ); QgsCoordinateTransform extentTransform = coordinateTransform; extentTransform.setBallparkTransformsAreAppropriate( true ); try @@ -205,10 +206,11 @@ QgsBox3D nodeBoundsToBox3D( QgsPointCloudDataBounds nodeBounds, QgsVector3D offs QgsChunkNode *QgsPointCloudLayerChunkLoaderFactory::createRootNode() const { - const QgsPointCloudDataBounds rootNodeBounds = mPointCloudIndex->nodeBounds( IndexedPointCloudNode( 0, 0, 0, 0 ) ); - QgsBox3D rootNodeBox3D = nodeBoundsToBox3D( rootNodeBounds, mPointCloudIndex->offset(), mPointCloudIndex->scale(), mCoordinateTransform, mZValueOffset, mZValueScale ); + const QgsPointCloudNode pcNode = mPointCloudIndex->getNode( mPointCloudIndex->root() ); + const QgsBox3D rootNodeBounds = pcNode.bounds(); + QgsBox3D rootNodeBox3D = nodeBoundsToBox3D( rootNodeBounds, mCoordinateTransform, mZValueOffset, mZValueScale ); - const float error = mPointCloudIndex->nodeError( IndexedPointCloudNode( 0, 0, 0, 0 ) ); + const float error = pcNode.error(); QgsChunkNode *node = new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, error ); node->setRefinementProcess( mSymbol->renderAsTriangles() ? Qgis::TileRefinementProcess::Replacement : Qgis::TileRefinementProcess::Additive ); return node; @@ -224,15 +226,16 @@ QVector QgsPointCloudLayerChunkLoaderFactory::createChildren( Qg { int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 ); const QgsChunkNodeId childId( nodeId.d + 1, nodeId.x * 2 + dx, nodeId.y * 2 + dy, nodeId.z * 2 + dz ); - - if ( !mPointCloudIndex->hasNode( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ) ) + const QgsPointCloudNodeId childPcId( childId.d, childId.x, childId.y, childId.z ); + if ( !mPointCloudIndex->hasNode( childPcId ) ) continue; + const QgsPointCloudNode childNode = mPointCloudIndex->getNode( childPcId ); + const QgsBox3D childBounds = childNode.bounds(); if ( !mExtent.isEmpty() && - !mPointCloudIndex->nodeMapExtent( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ).intersects( mExtent ) ) + !childBounds.intersects( mExtent ) ) continue; - const QgsPointCloudDataBounds childBounds = mPointCloudIndex->nodeBounds( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ); - QgsBox3D childBox3D = nodeBoundsToBox3D( childBounds, mPointCloudIndex->offset(), mPointCloudIndex->scale(), mCoordinateTransform, mZValueOffset, mZValueScale ); + QgsBox3D childBox3D = nodeBoundsToBox3D( childBounds, mCoordinateTransform, mZValueOffset, mZValueScale ); QgsChunkNode *child = new QgsChunkNode( childId, childBox3D, childError, node ); child->setRefinementProcess( mSymbol->renderAsTriangles() ? Qgis::TileRefinementProcess::Replacement : Qgis::TileRefinementProcess::Additive ); @@ -302,7 +305,7 @@ QVector QgsPointCloudLayerChunkedEntity::rayIntersec for ( QgsChunkNode *node : activeNodes ) { const QgsChunkNodeId id = node->tileId(); - const IndexedPointCloudNode n( id.d, id.x, id.y, id.z ); + const QgsPointCloudNodeId n( id.d, id.x, id.y, id.z ); if ( !index->hasNode( n ) ) continue; diff --git a/src/3d/symbols/qgspointcloud3dsymbol_p.cpp b/src/3d/symbols/qgspointcloud3dsymbol_p.cpp index f1ce9895e71c..4d2a21768244 100644 --- a/src/3d/symbols/qgspointcloud3dsymbol_p.cpp +++ b/src/3d/symbols/qgspointcloud3dsymbol_p.cpp @@ -57,15 +57,15 @@ typedef Qt3DCore::QGeometry Qt3DQGeometry; #include // pick a point that we'll use as origin for coordinates for this node's points -static QgsVector3D originFromNodeBounds( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsPointCloudBlock *block ) +static QgsVector3D originFromNodeBounds( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, const QgsPointCloudBlock *block ) { const QgsVector3D blockScale = block->scale(); const QgsVector3D blockOffset = block->offset(); - QgsPointCloudDataBounds bounds = pc->nodeBounds( n ); - double nodeOriginX = bounds.xMin() * blockScale.x() + blockOffset.x(); - double nodeOriginY = bounds.yMin() * blockScale.y() + blockOffset.y(); - double nodeOriginZ = ( bounds.zMin() * blockScale.z() + blockOffset.z() ) * context.zValueScale() + context.zValueFixedOffset(); + QgsBox3D bounds = pc->getNode( n ).bounds(); + double nodeOriginX = bounds.xMinimum() * blockScale.x() + blockOffset.x(); + double nodeOriginY = bounds.yMinimum() * blockScale.y() + blockOffset.y(); + double nodeOriginZ = ( bounds.zMinimum() * blockScale.z() + blockOffset.z() ) * context.zValueScale() + context.zValueFixedOffset(); try { context.coordinateTransform().transformInPlace( nodeOriginX, nodeOriginY, nodeOriginZ ); @@ -359,7 +359,7 @@ void QgsPointCloud3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const } -std::vector QgsPointCloud3DSymbolHandler::getVertices( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ) +std::vector QgsPointCloud3DSymbolHandler::getVertices( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ) { bool hasColorData = !outNormal.colors.empty(); @@ -376,7 +376,7 @@ std::vector QgsPointCloud3DSymbolHandler::getVertices( QgsPointCloudInde } // next, we also need all points of all parents nodes to make the triangulation (also external points) - IndexedPointCloudNode parentNode = n.parentNode(); + QgsPointCloudNodeId parentNode = n.parentNode(); double span = pc->span(); //factor to take account of the density of the point to calculate extension of the bounding box @@ -520,7 +520,7 @@ void QgsPointCloud3DSymbolHandler::filterTriangles( const std::vector &t } } -void QgsPointCloud3DSymbolHandler::triangulate( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ) +void QgsPointCloud3DSymbolHandler::triangulate( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ) { if ( outNormal.positions.isEmpty() ) return; @@ -547,7 +547,7 @@ void QgsPointCloud3DSymbolHandler::triangulate( QgsPointCloudIndex *pc, const In filterTriangles( triangleIndexes, context, box3D ); } -std::unique_ptr QgsPointCloud3DSymbolHandler::pointCloudBlock( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloudRequest &request, const QgsPointCloud3DRenderContext &context ) +std::unique_ptr QgsPointCloud3DSymbolHandler::pointCloudBlock( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request, const QgsPointCloud3DRenderContext &context ) { std::unique_ptr block; if ( pc->accessType() == QgsPointCloudIndex::AccessType::Local ) @@ -556,7 +556,8 @@ std::unique_ptr QgsPointCloud3DSymbolHandler::pointCloudBloc } else if ( pc->accessType() == QgsPointCloudIndex::AccessType::Remote ) { - if ( pc->nodePointCount( n ) < 1 ) + QgsPointCloudNode node = pc->getNode( n ); + if ( node.pointCount() < 1 ) return block; bool loopAborted = false; @@ -590,7 +591,7 @@ bool QgsSingleColorPointCloud3DSymbolHandler::prepare( const QgsPointCloud3DRend return true; } -void QgsSingleColorPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, PointData *output ) +void QgsSingleColorPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, PointData *output ) { QgsPointCloudAttributeCollection attributes; attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "X" ), QgsPointCloudAttribute::Int32 ) ); @@ -670,7 +671,7 @@ bool QgsColorRampPointCloud3DSymbolHandler::prepare( const QgsPointCloud3DRender return true; } -void QgsColorRampPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, PointData *output ) +void QgsColorRampPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, PointData *output ) { QgsPointCloudAttributeCollection attributes; const int xOffset = 0; @@ -808,7 +809,7 @@ bool QgsRGBPointCloud3DSymbolHandler::prepare( const QgsPointCloud3DRenderContex return true; } -void QgsRGBPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, PointData *output ) +void QgsRGBPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, PointData *output ) { QgsPointCloudAttributeCollection attributes; attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "X" ), QgsPointCloudAttribute::Int32 ) ); @@ -953,7 +954,7 @@ bool QgsClassificationPointCloud3DSymbolHandler::prepare( const QgsPointCloud3DR return true; } -void QgsClassificationPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, PointData *output ) +void QgsClassificationPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, PointData *output ) { QgsPointCloudAttributeCollection attributes; const int xOffset = 0; diff --git a/src/3d/symbols/qgspointcloud3dsymbol_p.h b/src/3d/symbols/qgspointcloud3dsymbol_p.h index acda5656ecfc..579d2a3ca5b8 100644 --- a/src/3d/symbols/qgspointcloud3dsymbol_p.h +++ b/src/3d/symbols/qgspointcloud3dsymbol_p.h @@ -32,7 +32,7 @@ #define SIP_NO_FILE -class IndexedPointCloudNode; +class QgsPointCloudNodeId; class QgsAABB; class QgsPointCloud3DSymbolHandler @@ -45,10 +45,10 @@ class QgsPointCloud3DSymbolHandler struct PointData; virtual bool prepare( const QgsPointCloud3DRenderContext &context ) = 0;// override; - virtual void processNode( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) = 0; // override; + virtual void processNode( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) = 0; // override; virtual void finalize( Qt3DCore::QEntity *parent, const QgsPointCloud3DRenderContext &context ) = 0;// override; - void triangulate( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ); + void triangulate( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ); float zMinimum() const { return mZMin; } float zMaximum() const { return mZMax; } @@ -76,14 +76,14 @@ class QgsPointCloud3DSymbolHandler #else virtual Qt3DCore::QGeometry *makeGeometry( Qt3DCore::QNode *parent, const QgsPointCloud3DSymbolHandler::PointData &data, unsigned int byteStride ) = 0; #endif - std::unique_ptr pointCloudBlock( QgsPointCloudIndex *pc, const IndexedPointCloudNode &node, const QgsPointCloudRequest &request, const QgsPointCloud3DRenderContext &context ); + std::unique_ptr pointCloudBlock( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &node, const QgsPointCloudRequest &request, const QgsPointCloud3DRenderContext &context ); // outputs PointData outNormal; //!< Features that are not selected private: //! Returns all vertices of the node \a n, and of its parents contained in \a bbox and in an extension of this box depending of the density of the points - std::vector getVertices( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ); + std::vector getVertices( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ); //! Calculates the normals of triangles dedined by index contained in \a triangles. Must be used only in the method triangulate(). void calculateNormals( const std::vector &triangles ); @@ -105,7 +105,7 @@ class QgsSingleColorPointCloud3DSymbolHandler : public QgsPointCloud3DSymbolHand QgsSingleColorPointCloud3DSymbolHandler(); bool prepare( const QgsPointCloud3DRenderContext &context ) override; - void processNode( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) override; + void processNode( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) override; void finalize( Qt3DCore::QEntity *parent, const QgsPointCloud3DRenderContext &context ) override; private: @@ -122,7 +122,7 @@ class QgsColorRampPointCloud3DSymbolHandler : public QgsPointCloud3DSymbolHandle QgsColorRampPointCloud3DSymbolHandler(); bool prepare( const QgsPointCloud3DRenderContext &context ) override; - void processNode( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) override; + void processNode( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) override; void finalize( Qt3DCore::QEntity *parent, const QgsPointCloud3DRenderContext &context ) override; private: @@ -139,7 +139,7 @@ class QgsRGBPointCloud3DSymbolHandler : public QgsPointCloud3DSymbolHandler QgsRGBPointCloud3DSymbolHandler(); bool prepare( const QgsPointCloud3DRenderContext &context ) override; - void processNode( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) override; + void processNode( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) override; void finalize( Qt3DCore::QEntity *parent, const QgsPointCloud3DRenderContext &context ) override; private: @@ -156,7 +156,7 @@ class QgsClassificationPointCloud3DSymbolHandler : public QgsPointCloud3DSymbolH QgsClassificationPointCloud3DSymbolHandler(); bool prepare( const QgsPointCloud3DRenderContext &context ) override; - void processNode( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) override; + void processNode( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) override; void finalize( Qt3DCore::QEntity *parent, const QgsPointCloud3DRenderContext &context ) override; private: diff --git a/src/core/pointcloud/qgscachedpointcloudblockrequest.cpp b/src/core/pointcloud/qgscachedpointcloudblockrequest.cpp index 1acd1df58792..aed6418d9db9 100644 --- a/src/core/pointcloud/qgscachedpointcloudblockrequest.cpp +++ b/src/core/pointcloud/qgscachedpointcloudblockrequest.cpp @@ -20,7 +20,7 @@ ///@cond PRIVATE -QgsCachedPointCloudBlockRequest::QgsCachedPointCloudBlockRequest( QgsPointCloudBlock *block, const IndexedPointCloudNode &node, const QString &uri, +QgsCachedPointCloudBlockRequest::QgsCachedPointCloudBlockRequest( QgsPointCloudBlock *block, const QgsPointCloudNodeId &node, const QString &uri, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, const QgsPointCloudExpression &filterExpression, const QgsRectangle &filterRect ) : QgsPointCloudBlockRequest( node, uri, attributes, requestedAttributes, scale, offset, filterExpression, filterRect ) diff --git a/src/core/pointcloud/qgscachedpointcloudblockrequest.h b/src/core/pointcloud/qgscachedpointcloudblockrequest.h index 324292701f0c..b500f60852a7 100644 --- a/src/core/pointcloud/qgscachedpointcloudblockrequest.h +++ b/src/core/pointcloud/qgscachedpointcloudblockrequest.h @@ -44,7 +44,7 @@ class CORE_EXPORT QgsCachedPointCloudBlockRequest : public QgsPointCloudBlockReq * QgsCachedPointCloudBlockRequest constructor using an existing \a block * Note: Ownership of \a block is transferred */ - QgsCachedPointCloudBlockRequest( QgsPointCloudBlock *block, const IndexedPointCloudNode &node, const QString &uri, + QgsCachedPointCloudBlockRequest( QgsPointCloudBlock *block, const QgsPointCloudNodeId &node, const QString &uri, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, const QgsPointCloudExpression &filterExpression, const QgsRectangle &filterRect ); diff --git a/src/core/pointcloud/qgscopcpointcloudblockrequest.cpp b/src/core/pointcloud/qgscopcpointcloudblockrequest.cpp index 148fc3ee1545..8a93ea33c877 100644 --- a/src/core/pointcloud/qgscopcpointcloudblockrequest.cpp +++ b/src/core/pointcloud/qgscopcpointcloudblockrequest.cpp @@ -30,7 +30,7 @@ ///@cond PRIVATE -QgsCopcPointCloudBlockRequest::QgsCopcPointCloudBlockRequest( const IndexedPointCloudNode &node, const QString &uri, +QgsCopcPointCloudBlockRequest::QgsCopcPointCloudBlockRequest( const QgsPointCloudNodeId &node, const QString &uri, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, const QgsPointCloudExpression &filterExpression, const QgsRectangle &filterRect, uint64_t blockOffset, int32_t blockSize, int pointCount, const QgsLazInfo &lazInfo ) diff --git a/src/core/pointcloud/qgscopcpointcloudblockrequest.h b/src/core/pointcloud/qgscopcpointcloudblockrequest.h index 8c68ec61fafe..32cc9aa2685d 100644 --- a/src/core/pointcloud/qgscopcpointcloudblockrequest.h +++ b/src/core/pointcloud/qgscopcpointcloudblockrequest.h @@ -46,7 +46,7 @@ class CORE_EXPORT QgsCopcPointCloudBlockRequest : public QgsPointCloudBlockReque * Requests the block data of size \a blockSize at offset blockOffset * Note: It is the responsablitiy of the caller to delete the block if it was loaded correctly */ - QgsCopcPointCloudBlockRequest( const IndexedPointCloudNode &node, const QString &Uri, + QgsCopcPointCloudBlockRequest( const QgsPointCloudNodeId &node, const QString &Uri, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, const QgsPointCloudExpression &filterExpression, const QgsRectangle &filterRect, uint64_t blockOffset, int32_t blockSize, int pointCount, const QgsLazInfo &lazInfo ); diff --git a/src/core/pointcloud/qgscopcpointcloudindex.cpp b/src/core/pointcloud/qgscopcpointcloudindex.cpp index 2c876afb81ae..2c1ad603a2cd 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.cpp +++ b/src/core/pointcloud/qgscopcpointcloudindex.cpp @@ -27,6 +27,7 @@ #include #include "qgsapplication.h" +#include "qgsbox3d.h" #include "qgscachedpointcloudblockrequest.h" #include "qgscopcpointcloudblockrequest.h" #include "qgseptdecoder.h" @@ -71,7 +72,7 @@ void QgsCopcPointCloudIndex::load( const QString &urlString ) mCopcFile.open( QgsLazDecoder::toNativePath( urlString ), std::ios::binary ); if ( mCopcFile.fail() ) { - mError = tr( "Unable to open %1 for reading" ).arg( urlString ); + mError = QObject::tr( "Unable to open %1 for reading" ).arg( urlString ); mIsValid = false; return; } @@ -93,7 +94,7 @@ void QgsCopcPointCloudIndex::load( const QString &urlString ) } if ( !mIsValid ) { - mError = tr( "Unable to recognize %1 as a LAZ file: \"%2\"" ).arg( urlString, mLazInfo->error() ); + mError = QObject::tr( "Unable to recognize %1 as a LAZ file: \"%2\"" ).arg( urlString, mLazInfo->error() ); } } @@ -102,7 +103,7 @@ bool QgsCopcPointCloudIndex::loadSchema( QgsLazInfo &lazInfo ) QByteArray copcInfoVlrData = lazInfo.vlrData( QStringLiteral( "copc" ), 1 ); if ( copcInfoVlrData.isEmpty() ) { - mError = tr( "Invalid COPC file" ); + mError = QObject::tr( "Invalid COPC file" ); return false; } mCopcInfoVlr.fill( copcInfoVlrData.data(), copcInfoVlrData.size() ); @@ -127,17 +128,10 @@ bool QgsCopcPointCloudIndex::loadSchema( QgsLazInfo &lazInfo ) const double ymax = mCopcInfoVlr.center_y + mCopcInfoVlr.halfsize; const double zmax = mCopcInfoVlr.center_z + mCopcInfoVlr.halfsize; - mRootBounds = QgsPointCloudDataBounds( - ( xmin - mOffset.x() ) / mScale.x(), - ( ymin - mOffset.y() ) / mScale.y(), - ( zmin - mOffset.z() ) / mScale.z(), - ( xmax - mOffset.x() ) / mScale.x(), - ( ymax - mOffset.y() ) / mScale.y(), - ( zmax - mOffset.z() ) / mScale.z() - ); + mRootBounds = QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax ); - double calculatedSpan = nodeMapExtent( root() ).width() / mCopcInfoVlr.spacing; - mSpan = calculatedSpan; + // TODO: Rounding? + mSpan = mRootBounds.width() / mCopcInfoVlr.spacing; #ifdef QGIS_DEBUG double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin; @@ -150,7 +144,7 @@ bool QgsCopcPointCloudIndex::loadSchema( QgsLazInfo &lazInfo ) return true; } -std::unique_ptr QgsCopcPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) +std::unique_ptr QgsCopcPointCloudIndex::nodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) { if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) ) { @@ -196,7 +190,7 @@ std::unique_ptr QgsCopcPointCloudIndex::nodeData( const Inde return nullptr; QEventLoop loop; - connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit ); + QObject::connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit ); loop.exec(); block = blockRequest->takeBlock(); @@ -209,7 +203,7 @@ std::unique_ptr QgsCopcPointCloudIndex::nodeData( const Inde return block; } -QgsPointCloudBlockRequest *QgsCopcPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) +QgsPointCloudBlockRequest *QgsCopcPointCloudIndex::asyncNodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) { if ( mAccessType == Local ) return nullptr; // TODO @@ -257,21 +251,21 @@ bool QgsCopcPointCloudIndex::writeStatistics( QgsPointCloudStatistics &stats ) { if ( mAccessType == Remote ) { - QgsMessageLog::logMessage( tr( "Can't write statistics to remote file \"%1\"" ).arg( mUri ) ); + QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to remote file \"%1\"" ).arg( mUri ) ); return false; } if ( mLazInfo->version() != qMakePair( 1, 4 ) ) { // EVLR isn't supported in the first place - QgsMessageLog::logMessage( tr( "Can't write statistics to \"%1\": laz version != 1.4" ).arg( mUri ) ); + QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to \"%1\": laz version != 1.4" ).arg( mUri ) ); return false; } QByteArray statisticsEvlrData = fetchCopcStatisticsEvlrData(); if ( !statisticsEvlrData.isEmpty() ) { - QgsMessageLog::logMessage( tr( "Can't write statistics to \"%1\": file already contains COPC statistics!" ).arg( mUri ) ); + QgsMessageLog::logMessage( QObject::tr( "Can't write statistics to \"%1\": file already contains COPC statistics!" ).arg( mUri ) ); return false; } @@ -302,7 +296,7 @@ bool QgsCopcPointCloudIndex::writeStatistics( QgsPointCloudStatistics &stats ) } else { - QgsMessageLog::logMessage( tr( "Couldn't open COPC file \"%1\" to write statistics" ).arg( mUri ) ); + QgsMessageLog::logMessage( QObject::tr( "Couldn't open COPC file \"%1\" to write statistics" ).arg( mUri ) ); return false; } copcFile.close(); @@ -329,19 +323,19 @@ bool QgsCopcPointCloudIndex::isValid() const return mIsValid; } -bool QgsCopcPointCloudIndex::fetchNodeHierarchy( const IndexedPointCloudNode &n ) const +bool QgsCopcPointCloudIndex::fetchNodeHierarchy( const QgsPointCloudNodeId &n ) const { QMutexLocker locker( &mHierarchyMutex ); - QVector ancestors; - IndexedPointCloudNode foundRoot = n; + QVector ancestors; + QgsPointCloudNodeId foundRoot = n; while ( !mHierarchy.contains( foundRoot ) ) { ancestors.push_front( foundRoot ); foundRoot = foundRoot.parentNode(); } ancestors.push_front( foundRoot ); - for ( IndexedPointCloudNode n : ancestors ) + for ( QgsPointCloudNodeId n : ancestors ) { auto hierarchyIt = mHierarchy.constFind( n ); if ( hierarchyIt == mHierarchy.constEnd() ) @@ -385,7 +379,7 @@ void QgsCopcPointCloudIndex::fetchHierarchyPage( uint64_t offset, uint64_t byteS std::unique_ptr reply( QgsApplication::tileDownloadManager()->get( nr ) ); QEventLoop loop; - connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit ); + QObject::connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit ); loop.exec(); if ( reply->error() != QNetworkReply::NoError ) @@ -425,41 +419,48 @@ void QgsCopcPointCloudIndex::populateHierarchy( const char *hierarchyPageData, u for ( uint64_t i = 0; i < byteSize; i += sizeof( CopcEntry ) ) { const CopcEntry *entry = reinterpret_cast( hierarchyPageData + i ); - const IndexedPointCloudNode nodeId( entry->key.level, entry->key.x, entry->key.y, entry->key.z ); + const QgsPointCloudNodeId nodeId( entry->key.level, entry->key.x, entry->key.y, entry->key.z ); mHierarchy[nodeId] = entry->pointCount; mHierarchyNodePos.insert( nodeId, QPair( entry->offset, entry->byteSize ) ); } } -bool QgsCopcPointCloudIndex::hasNode( const IndexedPointCloudNode &n ) const +bool QgsCopcPointCloudIndex::hasNode( const QgsPointCloudNodeId &n ) const { return fetchNodeHierarchy( n ); } -QList QgsCopcPointCloudIndex::nodeChildren( const IndexedPointCloudNode &n ) const +QgsPointCloudNode QgsCopcPointCloudIndex::getNode( const QgsPointCloudNodeId &id ) const { - fetchNodeHierarchy( n ); + fetchNodeHierarchy( id ); - mHierarchyMutex.lock(); + qint64 pointCount; - auto hierarchyIt = mHierarchy.constFind( n ); - Q_ASSERT( hierarchyIt != mHierarchy.constEnd() ); - QList lst; - lst.reserve( 8 ); - const int d = n.d() + 1; - const int x = n.x() * 2; - const int y = n.y() * 2; - const int z = n.z() * 2; - mHierarchyMutex.unlock(); - - for ( int i = 0; i < 8; ++i ) + QList children; { - int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 ); - const IndexedPointCloudNode n2( d, x + dx, y + dy, z + dz ); - if ( fetchNodeHierarchy( n2 ) && mHierarchy[n] >= 0 ) - lst.append( n2 ); + QMutexLocker locker( &mHierarchyMutex ); + + pointCount = mHierarchy.value( id, -1 ); + + auto hierarchyIt = mHierarchy.constFind( id ); + Q_ASSERT( hierarchyIt != mHierarchy.constEnd() ); + children.reserve( 8 ); + const int d = id.d() + 1; + const int x = id.x() * 2; + const int y = id.y() * 2; + const int z = id.z() * 2; + mHierarchyMutex.unlock(); + + for ( int i = 0; i < 8; ++i ) + { + int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 ); + const QgsPointCloudNodeId n2( d, x + dx, y + dy, z + dz ); + if ( fetchNodeHierarchy( n2 ) && mHierarchy[id] >= 0 ) + children.append( n2 ); + } } - return lst; + + return QgsPointCloudNode( *this, id, pointCount, children ); } void QgsCopcPointCloudIndex::copyCommonProperties( QgsCopcPointCloudIndex *destination ) const diff --git a/src/core/pointcloud/qgscopcpointcloudindex.h b/src/core/pointcloud/qgscopcpointcloudindex.h index 32e0f52fab50..b54bf474f61f 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.h +++ b/src/core/pointcloud/qgscopcpointcloudindex.h @@ -42,7 +42,6 @@ class QgsCoordinateReferenceSystem; class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex { - Q_OBJECT public: explicit QgsCopcPointCloudIndex(); @@ -52,11 +51,11 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex void load( const QString &fileName ) override; - bool hasNode( const IndexedPointCloudNode &n ) const override; - QList nodeChildren( const IndexedPointCloudNode &n ) const override; + bool hasNode( const QgsPointCloudNodeId &n ) const override; + virtual QgsPointCloudNode getNode( const QgsPointCloudNodeId &id ) const override; - std::unique_ptr< QgsPointCloudBlock> nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override; - QgsPointCloudBlockRequest *asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override; + std::unique_ptr< QgsPointCloudBlock> nodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) override; + QgsPointCloudBlockRequest *asyncNodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) override; QgsCoordinateReferenceSystem crs() const override; qint64 pointCount() const override; @@ -97,7 +96,7 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex bool loadHierarchy(); //! Fetches all nodes leading to node \a node into memory - bool fetchNodeHierarchy( const IndexedPointCloudNode &n ) const; + bool fetchNodeHierarchy( const QgsPointCloudNodeId &n ) const; /** * Fetches the COPC hierarchy page at offset \a offset and of size \a byteSize into memory @@ -112,7 +111,7 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex QgsPointCloudIndex::AccessType mAccessType = Local; mutable std::ifstream mCopcFile; mutable lazperf::copc_info_vlr mCopcInfoVlr; - mutable QHash> mHierarchyNodePos; //!< Additional data hierarchy for COPC + mutable QHash> mHierarchyNodePos; //!< Additional data hierarchy for COPC QVariantMap mOriginalMetadata; mutable std::optional mStatistics; diff --git a/src/core/pointcloud/qgseptpointcloudblockrequest.cpp b/src/core/pointcloud/qgseptpointcloudblockrequest.cpp index 6cb0d6263165..18029e72e7b0 100644 --- a/src/core/pointcloud/qgseptpointcloudblockrequest.cpp +++ b/src/core/pointcloud/qgseptpointcloudblockrequest.cpp @@ -31,7 +31,7 @@ ///@cond PRIVATE -QgsEptPointCloudBlockRequest::QgsEptPointCloudBlockRequest( const IndexedPointCloudNode &node, const QString &uri, const QString &dataType, +QgsEptPointCloudBlockRequest::QgsEptPointCloudBlockRequest( const QgsPointCloudNodeId &node, const QString &uri, const QString &dataType, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, const QgsPointCloudExpression &filterExpression, const QgsRectangle &filterRect ) : QgsPointCloudBlockRequest( node, uri, attributes, requestedAttributes, scale, offset, filterExpression, filterRect ), diff --git a/src/core/pointcloud/qgseptpointcloudblockrequest.h b/src/core/pointcloud/qgseptpointcloudblockrequest.h index 38321ab85434..64afbfc1a3b6 100644 --- a/src/core/pointcloud/qgseptpointcloudblockrequest.h +++ b/src/core/pointcloud/qgseptpointcloudblockrequest.h @@ -45,7 +45,7 @@ class CORE_EXPORT QgsEptPointCloudBlockRequest : public QgsPointCloudBlockReques * Requests the block data of size \a blockSize at offset blockOffset * Note: It is the responsablitiy of the caller to delete the block if it was loaded correctly */ - QgsEptPointCloudBlockRequest( const IndexedPointCloudNode &node, const QString &Uri, const QString &dataType, + QgsEptPointCloudBlockRequest( const QgsPointCloudNodeId &node, const QString &Uri, const QString &dataType, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, const QgsPointCloudExpression &filterExpression, const QgsRectangle &filterRect ); diff --git a/src/core/pointcloud/qgseptpointcloudindex.cpp b/src/core/pointcloud/qgseptpointcloudindex.cpp index 27da4cbf5735..4542279cf7b9 100644 --- a/src/core/pointcloud/qgseptpointcloudindex.cpp +++ b/src/core/pointcloud/qgseptpointcloudindex.cpp @@ -50,7 +50,7 @@ QgsEptPointCloudIndex::QgsEptPointCloudIndex() { - mHierarchyNodes.insert( IndexedPointCloudNode( 0, 0, 0, 0 ) ); + mHierarchyNodes.insert( QgsPointCloudNodeId( 0, 0, 0, 0 ) ); } QgsEptPointCloudIndex::~QgsEptPointCloudIndex() = default; @@ -98,7 +98,7 @@ void QgsEptPointCloudIndex::load( const QString &urlString ) QFile f( mUri ); if ( !f.open( QIODevice::ReadOnly ) ) { - mError = tr( "Unable to open %1 for reading" ).arg( mUri ); + mError = QObject::tr( "Unable to open %1 for reading" ).arg( mUri ); mIsValid = false; return; } @@ -132,7 +132,7 @@ void QgsEptPointCloudIndex::load( const QString &urlString ) loadManifest( manifestJson ); } - if ( !loadNodeHierarchy( IndexedPointCloudNode( 0, 0, 0, 0 ) ) ) + if ( !loadNodeHierarchy( QgsPointCloudNodeId( 0, 0, 0, 0 ) ) ) { QgsDebugError( QStringLiteral( "Failed to load root EPT node" ) ); success = false; @@ -356,15 +356,7 @@ bool QgsEptPointCloudIndex::loadSchema( const QByteArray &dataJson ) const double ymax = bounds[4].toDouble(); const double zmax = bounds[5].toDouble(); - mRootBounds = QgsPointCloudDataBounds( - ( xmin - mOffset.x() ) / mScale.x(), - ( ymin - mOffset.y() ) / mScale.y(), - ( zmin - mOffset.z() ) / mScale.z(), - ( xmax - mOffset.x() ) / mScale.x(), - ( ymax - mOffset.y() ) / mScale.y(), - ( zmax - mOffset.z() ) / mScale.z() - ); - + mRootBounds = QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax ); #ifdef QGIS_DEBUG double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin; @@ -377,7 +369,7 @@ bool QgsEptPointCloudIndex::loadSchema( const QByteArray &dataJson ) return true; } -std::unique_ptr QgsEptPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) +std::unique_ptr QgsEptPointCloudIndex::nodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) { if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) ) { @@ -392,7 +384,7 @@ std::unique_ptr QgsEptPointCloudIndex::nodeData( const Index return nullptr; QEventLoop loop; - connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit ); + QObject::connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit ); loop.exec(); block = blockRequest->takeBlock(); @@ -432,7 +424,7 @@ std::unique_ptr QgsEptPointCloudIndex::nodeData( const Index return block; } -QgsPointCloudBlockRequest *QgsEptPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) +QgsPointCloudBlockRequest *QgsEptPointCloudIndex::asyncNodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) { if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) ) { @@ -473,7 +465,7 @@ QgsPointCloudBlockRequest *QgsEptPointCloudIndex::asyncNodeData( const IndexedPo return new QgsEptPointCloudBlockRequest( n, fileUrl, mDataType, attributes(), requestAttributes, scale(), offset(), filterExpression, request.filterRect() ); } -bool QgsEptPointCloudIndex::hasNode( const IndexedPointCloudNode &n ) const +bool QgsEptPointCloudIndex::hasNode( const QgsPointCloudNodeId &n ) const { return loadNodeHierarchy( n ); } @@ -488,30 +480,29 @@ qint64 QgsEptPointCloudIndex::pointCount() const return mPointCount; } -qint64 QgsEptPointCloudIndex::nodePointCount( const IndexedPointCloudNode &nodeId ) const +QgsPointCloudNode QgsEptPointCloudIndex::getNode( const QgsPointCloudNodeId &id ) const { - // First try loading our cached value - { - QMutexLocker locker( &mHierarchyMutex ); - qint64 pointCount = mHierarchy.value( nodeId, -1 ); - if ( pointCount != -1 ) - return pointCount; - } + QgsPointCloudNode node = QgsPointCloudIndex::getNode( id ); + + // First try cached value + if ( node.pointCount() != -1 ) + return node; // Try loading all nodes' hierarchy files on the path from root and stop when // one contains the point count for nodeId - QVector pathToRoot = nodePathToRoot( nodeId ); + QVector pathToRoot = nodePathToRoot( id ); for ( int i = pathToRoot.size() - 1; i >= 0; --i ) { loadSingleNodeHierarchy( pathToRoot[i] ); QMutexLocker locker( &mHierarchyMutex ); - qint64 pointCount = mHierarchy.value( nodeId, -1 ); + qint64 pointCount = mHierarchy.value( id, -1 ); if ( pointCount != -1 ) - return pointCount; + return QgsPointCloudNode( *this, id, pointCount, node.children() ); } - return -1; + // If we fail, return with pointCount = -1 anyway + return node; } QgsPointCloudStatistics QgsEptPointCloudIndex::metadataStatistics() const @@ -537,7 +528,7 @@ QgsPointCloudStatistics QgsEptPointCloudIndex::metadataStatistics() const return QgsPointCloudStatistics( pointCount(), statsMap ); } -bool QgsEptPointCloudIndex::loadSingleNodeHierarchy( const IndexedPointCloudNode &nodeId ) const +bool QgsEptPointCloudIndex::loadSingleNodeHierarchy( const QgsPointCloudNodeId &nodeId ) const { mHierarchyMutex.lock(); const bool foundInHierarchy = mHierarchy.contains( nodeId ); @@ -563,7 +554,7 @@ bool QgsEptPointCloudIndex::loadSingleNodeHierarchy( const IndexedPointCloudNode std::unique_ptr reply( QgsApplication::tileDownloadManager()->get( nr ) ); QEventLoop loop; - connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit ); + QObject::connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit ); loop.exec(); if ( reply->error() != QNetworkReply::NoError ) @@ -599,7 +590,7 @@ bool QgsEptPointCloudIndex::loadSingleNodeHierarchy( const IndexedPointCloudNode { const QString nodeIdStr = it.key(); const int nodePointCount = it.value().toInt(); - const IndexedPointCloudNode nodeId = IndexedPointCloudNode::fromString( nodeIdStr ); + const QgsPointCloudNodeId nodeId = QgsPointCloudNodeId::fromString( nodeIdStr ); if ( nodePointCount >= 0 ) mHierarchy[nodeId] = nodePointCount; else if ( nodePointCount == -1 ) @@ -609,10 +600,10 @@ bool QgsEptPointCloudIndex::loadSingleNodeHierarchy( const IndexedPointCloudNode return true; } -QVector QgsEptPointCloudIndex::nodePathToRoot( const IndexedPointCloudNode &nodeId ) const +QVector QgsEptPointCloudIndex::nodePathToRoot( const QgsPointCloudNodeId &nodeId ) const { - QVector path; - IndexedPointCloudNode currentNode = nodeId; + QVector path; + QgsPointCloudNodeId currentNode = nodeId; do { path.push_back( currentNode ); @@ -623,25 +614,28 @@ QVector QgsEptPointCloudIndex::nodePathToRoot( const Inde return path; } -bool QgsEptPointCloudIndex::loadNodeHierarchy( const IndexedPointCloudNode &nodeId ) const +bool QgsEptPointCloudIndex::loadNodeHierarchy( const QgsPointCloudNodeId &nodeId ) const { - mHierarchyMutex.lock(); - bool found = mHierarchy.contains( nodeId ); - mHierarchyMutex.unlock(); + bool found; + { + QMutexLocker lock( &mHierarchyMutex ); + found = mHierarchy.contains( nodeId ); + } if ( found ) return true; - QVector pathToRoot = nodePathToRoot( nodeId ); + QVector pathToRoot = nodePathToRoot( nodeId ); for ( int i = pathToRoot.size() - 1; i >= 0 && !mHierarchy.contains( nodeId ); --i ) { - const IndexedPointCloudNode node = pathToRoot[i]; + const QgsPointCloudNodeId node = pathToRoot[i]; if ( !loadSingleNodeHierarchy( node ) ) return false; } - mHierarchyMutex.lock(); - found = mHierarchy.contains( nodeId ); - mHierarchyMutex.unlock(); + { + QMutexLocker lock( &mHierarchyMutex ); + found = mHierarchy.contains( nodeId ); + } return found; } diff --git a/src/core/pointcloud/qgseptpointcloudindex.h b/src/core/pointcloud/qgseptpointcloudindex.h index 097894b6caab..382e269cb843 100644 --- a/src/core/pointcloud/qgseptpointcloudindex.h +++ b/src/core/pointcloud/qgseptpointcloudindex.h @@ -28,6 +28,7 @@ #include "qgspointcloudindex.h" #include "qgis_sip.h" +#include "qgsvector3d.h" ///@cond PRIVATE #define SIP_NO_FILE @@ -36,7 +37,6 @@ class QgsCoordinateReferenceSystem; class CORE_EXPORT QgsEptPointCloudIndex: public QgsPointCloudIndex { - Q_OBJECT public: explicit QgsEptPointCloudIndex(); @@ -46,13 +46,13 @@ class CORE_EXPORT QgsEptPointCloudIndex: public QgsPointCloudIndex void load( const QString &fileName ) override; - std::unique_ptr nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override; - QgsPointCloudBlockRequest *asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) override; - bool hasNode( const IndexedPointCloudNode &n ) const override; + std::unique_ptr nodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) override; + QgsPointCloudBlockRequest *asyncNodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) override; + bool hasNode( const QgsPointCloudNodeId &n ) const override; QgsCoordinateReferenceSystem crs() const override; qint64 pointCount() const override; - virtual qint64 nodePointCount( const IndexedPointCloudNode &n ) const override; + virtual QgsPointCloudNode getNode( const QgsPointCloudNodeId &id ) const override; QVariantMap originalMetadata() const override { return mOriginalMetadata; } virtual QgsPointCloudStatistics metadataStatistics() const override; @@ -69,9 +69,9 @@ class CORE_EXPORT QgsEptPointCloudIndex: public QgsPointCloudIndex bool loadSchema( const QByteArray &dataJson ); void loadManifest( const QByteArray &manifestJson ); bool loadSchema( QFile &f ); - bool loadSingleNodeHierarchy( const IndexedPointCloudNode &nodeId ) const; - QVector nodePathToRoot( const IndexedPointCloudNode &nodeId ) const; - bool loadNodeHierarchy( const IndexedPointCloudNode &nodeId ) const; + bool loadSingleNodeHierarchy( const QgsPointCloudNodeId &nodeId ) const; + QVector nodePathToRoot( const QgsPointCloudNodeId &nodeId ) const; + bool loadNodeHierarchy( const QgsPointCloudNodeId &nodeId ) const; bool mIsValid = false; QgsPointCloudIndex::AccessType mAccessType = Local; @@ -81,7 +81,7 @@ class CORE_EXPORT QgsEptPointCloudIndex: public QgsPointCloudIndex QString mUrlDirectoryPart; //! Contains the nodes that will have */ept-hierarchy/d-x-y-z.json file - mutable QSet mHierarchyNodes; + mutable QSet mHierarchyNodes; qint64 mPointCount = 0; diff --git a/src/core/pointcloud/qgspointcloudblockrequest.cpp b/src/core/pointcloud/qgspointcloudblockrequest.cpp index 9cb8a3b0185c..c898ecff6490 100644 --- a/src/core/pointcloud/qgspointcloudblockrequest.cpp +++ b/src/core/pointcloud/qgspointcloudblockrequest.cpp @@ -24,7 +24,7 @@ ///@cond PRIVATE -QgsPointCloudBlockRequest::QgsPointCloudBlockRequest( const IndexedPointCloudNode &node, const QString &uri, +QgsPointCloudBlockRequest::QgsPointCloudBlockRequest( const QgsPointCloudNodeId &node, const QString &uri, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, const QgsPointCloudExpression &filterExpression, const QgsRectangle &filterRect ) : mNode( node ) diff --git a/src/core/pointcloud/qgspointcloudblockrequest.h b/src/core/pointcloud/qgspointcloudblockrequest.h index ce6e438e360b..1e0ebb555d3b 100644 --- a/src/core/pointcloud/qgspointcloudblockrequest.h +++ b/src/core/pointcloud/qgspointcloudblockrequest.h @@ -42,7 +42,7 @@ class CORE_EXPORT QgsPointCloudBlockRequest : public QObject * QgsPointCloudBlockRequest constructor * Note: It is the responsablitiy of the caller to delete the block if it was loaded correctly */ - QgsPointCloudBlockRequest( const IndexedPointCloudNode &node, const QString &Uri, + QgsPointCloudBlockRequest( const QgsPointCloudNodeId &node, const QString &Uri, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, const QgsPointCloudExpression &filterExpression, const QgsRectangle &filterRect ); @@ -62,7 +62,7 @@ class CORE_EXPORT QgsPointCloudBlockRequest : public QObject void finished(); protected: - IndexedPointCloudNode mNode; + QgsPointCloudNodeId mNode; QString mUri; QgsPointCloudAttributeCollection mAttributes; QgsPointCloudAttributeCollection mRequestedAttributes; diff --git a/src/core/pointcloud/qgspointclouddataprovider.cpp b/src/core/pointcloud/qgspointclouddataprovider.cpp index f4ed0d6b2762..916c7f0d80c0 100644 --- a/src/core/pointcloud/qgspointclouddataprovider.cpp +++ b/src/core/pointcloud/qgspointclouddataprovider.cpp @@ -221,7 +221,7 @@ struct MapIndexedPointCloudNode : mRequest( request ), mIndexScale( indexScale ), mIndexOffset( indexOffset ), mExtentGeometry( extentGeometry ), mZRange( zRange ), mIndex( index ), mPointsLimit( pointsLimit ) { } - QVector operator()( IndexedPointCloudNode n ) + QVector operator()( QgsPointCloudNodeId n ) { QVector acceptedPoints; std::unique_ptr block( mIndex->nodeData( n, mRequest ) ); @@ -325,12 +325,8 @@ QVector QgsPointCloudDataProvider::identify( if ( !index || !index->isValid() ) return acceptedPoints; - const IndexedPointCloudNode root = index->root(); - - const QgsRectangle rootNodeExtent = index->nodeMapExtent( root ); - const double rootError = rootNodeExtent.width() / index->span(); - - const QVector nodes = traverseTree( index, root, maxError, rootError, extentGeometry, extentZRange ); + const QgsPointCloudNode root = index->getNode( index->root() ); + const QVector nodes = traverseTree( index, root, maxError, root.error(), extentGeometry, extentZRange ); const QgsPointCloudAttributeCollection attributeCollection = index->attributes(); QgsPointCloudRequest request; @@ -344,9 +340,9 @@ QVector QgsPointCloudDataProvider::identify( return acceptedPoints; } -QVector QgsPointCloudDataProvider::traverseTree( +QVector QgsPointCloudDataProvider::traverseTree( const QgsPointCloudIndex *pc, - IndexedPointCloudNode n, + QgsPointCloudNode node, double maxError, double nodeError, const QgsGeometry &extentGeometry, @@ -354,26 +350,27 @@ QVector QgsPointCloudDataProvider::traverseTree( { QGIS_PROTECT_QOBJECT_THREAD_ACCESS - QVector nodes; + QVector nodes; - const QgsDoubleRange nodeZRange = pc->nodeZRange( n ); + const QgsBox3D nodeBounds = node.bounds(); + const QgsDoubleRange nodeZRange( nodeBounds.zMinimum(), nodeBounds.zMaximum() ); if ( !extentZRange.overlaps( nodeZRange ) ) return nodes; - if ( !extentGeometry.intersects( pc->nodeMapExtent( n ) ) ) + if ( !extentGeometry.intersects( nodeBounds.toRectangle() ) ) return nodes; - nodes.append( n ); + nodes.append( node.id() ); const double childrenError = nodeError / 2.0; if ( childrenError < maxError ) return nodes; - const QList children = pc->nodeChildren( n ); - for ( const IndexedPointCloudNode &nn : children ) + for ( const QgsPointCloudNodeId &nn : node.children() ) { - if ( extentGeometry.intersects( pc->nodeMapExtent( nn ) ) ) - nodes += traverseTree( pc, nn, maxError, childrenError, extentGeometry, extentZRange ); + const QgsPointCloudNode childNode = pc->getNode( nn ); + if ( extentGeometry.intersects( childNode.bounds().toRectangle() ) ) + nodes += traverseTree( pc, childNode, maxError, childrenError, extentGeometry, extentZRange ); } return nodes; diff --git a/src/core/pointcloud/qgspointclouddataprovider.h b/src/core/pointcloud/qgspointclouddataprovider.h index 58a750730350..0df928b8267b 100644 --- a/src/core/pointcloud/qgspointclouddataprovider.h +++ b/src/core/pointcloud/qgspointclouddataprovider.h @@ -24,8 +24,6 @@ #include "qgspointcloudindex.h" #include "qgspointcloudsubindex.h" -class IndexedPointCloudNode; -class QgsPointCloudIndex; class QgsPointCloudRenderer; class QgsGeometry; class QgsPointCloudStatistics; @@ -284,7 +282,7 @@ class CORE_EXPORT QgsPointCloudDataProvider: public QgsDataProvider QVector identify( QgsPointCloudIndex *index, double maxError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange, int pointsLimit ) SIP_SKIP ; private: - QVector traverseTree( const QgsPointCloudIndex *pc, IndexedPointCloudNode n, double maxError, double nodeError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange ); + QVector traverseTree( const QgsPointCloudIndex *pc, QgsPointCloudNode node, double maxError, double nodeError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange ); }; diff --git a/src/core/pointcloud/qgspointcloudindex.cpp b/src/core/pointcloud/qgspointcloudindex.cpp index e4e72833277f..58e2916bbfc2 100644 --- a/src/core/pointcloud/qgspointcloudindex.cpp +++ b/src/core/pointcloud/qgspointcloudindex.cpp @@ -25,64 +25,66 @@ #include #include #include +#include +#include "qgsbox3d.h" #include "qgstiledownloadmanager.h" #include "qgspointcloudstatistics.h" #include "qgslogger.h" -IndexedPointCloudNode::IndexedPointCloudNode(): +QgsPointCloudNodeId::QgsPointCloudNodeId(): mD( -1 ), mX( 0 ), mY( 0 ), mZ( 0 ) {} -IndexedPointCloudNode::IndexedPointCloudNode( int _d, int _x, int _y, int _z ): +QgsPointCloudNodeId::QgsPointCloudNodeId( int _d, int _x, int _y, int _z ): mD( _d ), mX( _x ), mY( _y ), mZ( _z ) {} -IndexedPointCloudNode IndexedPointCloudNode::parentNode() const +QgsPointCloudNodeId QgsPointCloudNodeId::parentNode() const { - return IndexedPointCloudNode( mD - 1, mX / 2, mY / 2, mZ / 2 ); + return QgsPointCloudNodeId( mD - 1, mX / 2, mY / 2, mZ / 2 ); } -IndexedPointCloudNode IndexedPointCloudNode::fromString( const QString &str ) +QgsPointCloudNodeId QgsPointCloudNodeId::fromString( const QString &str ) { QStringList lst = str.split( '-' ); if ( lst.count() != 4 ) - return IndexedPointCloudNode(); - return IndexedPointCloudNode( lst[0].toInt(), lst[1].toInt(), lst[2].toInt(), lst[3].toInt() ); + return QgsPointCloudNodeId(); + return QgsPointCloudNodeId( lst[0].toInt(), lst[1].toInt(), lst[2].toInt(), lst[3].toInt() ); } -QString IndexedPointCloudNode::toString() const +QString QgsPointCloudNodeId::toString() const { return QStringLiteral( "%1-%2-%3-%4" ).arg( mD ).arg( mX ).arg( mY ).arg( mZ ); } -int IndexedPointCloudNode::d() const +int QgsPointCloudNodeId::d() const { return mD; } -int IndexedPointCloudNode::x() const +int QgsPointCloudNodeId::x() const { return mX; } -int IndexedPointCloudNode::y() const +int QgsPointCloudNodeId::y() const { return mY; } -int IndexedPointCloudNode::z() const +int QgsPointCloudNodeId::z() const { return mZ; } -uint qHash( IndexedPointCloudNode id ) +uint qHash( QgsPointCloudNodeId id ) { return id.d() + id.x() + id.y() + id.z(); } @@ -93,7 +95,7 @@ uint qHash( IndexedPointCloudNode id ) // QgsPointCloudCacheKey // -QgsPointCloudCacheKey::QgsPointCloudCacheKey( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request, const QgsPointCloudExpression &expression, const QString &uri ) +QgsPointCloudCacheKey::QgsPointCloudCacheKey( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request, const QgsPointCloudExpression &expression, const QString &uri ) : mNode( n ) , mUri( uri ) , mRequest( request ) @@ -115,63 +117,28 @@ uint qHash( const QgsPointCloudCacheKey &key ) } // -// QgsPointCloudDataBounds +// QgsPointCloudNode // -QgsPointCloudDataBounds::QgsPointCloudDataBounds() = default; - -QgsPointCloudDataBounds::QgsPointCloudDataBounds( qint64 xmin, qint64 ymin, qint64 zmin, qint64 xmax, qint64 ymax, qint64 zmax ) - : mXMin( xmin ) - , mYMin( ymin ) - , mZMin( zmin ) - , mXMax( xmax ) - , mYMax( ymax ) - , mZMax( zmax ) -{ - -} - -qint64 QgsPointCloudDataBounds::xMin() const -{ - return mXMin; -} - -qint64 QgsPointCloudDataBounds::yMin() const -{ - return mYMin; -} - -qint64 QgsPointCloudDataBounds::xMax() const +QgsBox3D QgsPointCloudNode::bounds() const { - return mXMax; -} + const QgsBox3D rootBounds = mIndex.rootNodeBounds(); + const double d = rootBounds.xMaximum() - rootBounds.xMinimum(); + const double dLevel = ( double )d / pow( 2, mId.d() ); -qint64 QgsPointCloudDataBounds::yMax() const -{ - return mYMax; -} + const double xMin = rootBounds.xMinimum() + dLevel * mId.x(); + const double xMax = rootBounds.xMinimum() + dLevel * ( mId.x() + 1 ); + const double yMin = rootBounds.yMinimum() + dLevel * mId.y(); + const double yMax = rootBounds.yMinimum() + dLevel * ( mId.y() + 1 ); + const double zMin = rootBounds.zMinimum() + dLevel * mId.z(); + const double zMax = rootBounds.zMinimum() + dLevel * ( mId.z() + 1 ); -qint64 QgsPointCloudDataBounds::zMin() const -{ - return mZMin; + return QgsBox3D( xMin, yMin, zMin, xMax, yMax, zMax ); } -qint64 QgsPointCloudDataBounds::zMax() const +float QgsPointCloudNode::error() const { - return mZMax; -} - -QgsRectangle QgsPointCloudDataBounds::mapExtent( const QgsVector3D &offset, const QgsVector3D &scale ) const -{ - return QgsRectangle( - mXMin * scale.x() + offset.x(), mYMin * scale.y() + offset.y(), - mXMax * scale.x() + offset.x(), mYMax * scale.y() + offset.y() - ); -} - -QgsDoubleRange QgsPointCloudDataBounds::zRange( const QgsVector3D &offset, const QgsVector3D &scale ) const -{ - return QgsDoubleRange( mZMin * scale.z() + offset.z(), mZMax * scale.z() + offset.z() ); + return bounds().width() / mIndex.span(); } ///@endcond @@ -187,74 +154,44 @@ QgsPointCloudIndex::QgsPointCloudIndex() = default; QgsPointCloudIndex::~QgsPointCloudIndex() = default; -bool QgsPointCloudIndex::hasNode( const IndexedPointCloudNode &n ) const +bool QgsPointCloudIndex::hasNode( const QgsPointCloudNodeId &n ) const { QMutexLocker locker( &mHierarchyMutex ); return mHierarchy.contains( n ); } -qint64 QgsPointCloudIndex::nodePointCount( const IndexedPointCloudNode &n ) const +QgsPointCloudNode QgsPointCloudIndex::getNode( const QgsPointCloudNodeId &id ) const { - QMutexLocker locker( &mHierarchyMutex ); - return mHierarchy.value( n, -1 ); -} + Q_ASSERT( hasNode( id ) ); -QList QgsPointCloudIndex::nodeChildren( const IndexedPointCloudNode &n ) const -{ - Q_ASSERT( hasNode( n ) ); - QList lst; - const int d = n.d() + 1; - const int x = n.x() * 2; - const int y = n.y() * 2; - const int z = n.z() * 2; - - for ( int i = 0; i < 8; ++i ) + qint64 pointCount; { - int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 ); - const IndexedPointCloudNode n2( d, x + dx, y + dy, z + dz ); - if ( hasNode( n2 ) ) - lst.append( n2 ); + QMutexLocker locker( &mHierarchyMutex ); + pointCount = mHierarchy.value( id, -1 ); } - return lst; -} - -QgsPointCloudAttributeCollection QgsPointCloudIndex::attributes() const -{ - return mAttributes; -} - -QgsPointCloudDataBounds QgsPointCloudIndex::nodeBounds( const IndexedPointCloudNode &n ) const -{ - qint64 xMin, yMin, zMin, xMax, yMax, zMax; - - const qint64 d = mRootBounds.xMax() - mRootBounds.xMin(); - const double dLevel = ( double )d / pow( 2, n.d() ); - xMin = round( mRootBounds.xMin() + dLevel * n.x() ); - xMax = round( mRootBounds.xMin() + dLevel * ( n.x() + 1 ) ); - yMin = round( mRootBounds.yMin() + dLevel * n.y() ); - yMax = round( mRootBounds.yMin() + dLevel * ( n.y() + 1 ) ); - zMin = round( mRootBounds.zMin() + dLevel * n.z() ); - zMax = round( mRootBounds.zMin() + dLevel * ( n.z() + 1 ) ); - - QgsPointCloudDataBounds db( xMin, yMin, zMin, xMax, yMax, zMax ); - return db; -} + QList children; + { + const int d = id.d() + 1; + const int x = id.x() * 2; + const int y = id.y() * 2; + const int z = id.z() * 2; -QgsRectangle QgsPointCloudIndex::nodeMapExtent( const IndexedPointCloudNode &node ) const -{ - return nodeBounds( node ).mapExtent( mOffset, mScale ); -} + for ( int i = 0; i < 8; ++i ) + { + int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 ); + const QgsPointCloudNodeId n2( d, x + dx, y + dy, z + dz ); + if ( hasNode( n2 ) ) + children.append( n2 ); + } + } -QgsDoubleRange QgsPointCloudIndex::nodeZRange( const IndexedPointCloudNode &node ) const -{ - return nodeBounds( node ).zRange( mOffset, mScale ); + return QgsPointCloudNode( *this, id, pointCount, children ); } -float QgsPointCloudIndex::nodeError( const IndexedPointCloudNode &n ) const +QgsPointCloudAttributeCollection QgsPointCloudIndex::attributes() const { - const double w = nodeMapExtent( n ).width(); - return w / mSpan; + return mAttributes; } QgsVector3D QgsPointCloudIndex::scale() const @@ -327,15 +264,13 @@ void QgsPointCloudIndex::copyCommonProperties( QgsPointCloudIndex *destination ) destination->mZMin = mZMin; destination->mZMax = mZMax; destination->mHierarchy = mHierarchy; - destination->mScale = mScale; - destination->mOffset = mOffset; destination->mRootBounds = mRootBounds; destination->mAttributes = mAttributes; destination->mSpan = mSpan; destination->mFilterExpression = mFilterExpression; } -QgsPointCloudBlock *QgsPointCloudIndex::getNodeDataFromCache( const IndexedPointCloudNode &node, const QgsPointCloudRequest &request ) +QgsPointCloudBlock *QgsPointCloudIndex::getNodeDataFromCache( const QgsPointCloudNodeId &node, const QgsPointCloudRequest &request ) { QgsPointCloudCacheKey key( node, request, mFilterExpression, mUri ); @@ -344,12 +279,12 @@ QgsPointCloudBlock *QgsPointCloudIndex::getNodeDataFromCache( const IndexedPoint return cached ? cached->clone() : nullptr; } -void QgsPointCloudIndex::storeNodeDataToCache( QgsPointCloudBlock *data, const IndexedPointCloudNode &node, const QgsPointCloudRequest &request ) +void QgsPointCloudIndex::storeNodeDataToCache( QgsPointCloudBlock *data, const QgsPointCloudNodeId &node, const QgsPointCloudRequest &request ) { storeNodeDataToCacheStatic( data, node, request, mFilterExpression, mUri ); } -void QgsPointCloudIndex::storeNodeDataToCacheStatic( QgsPointCloudBlock *data, const IndexedPointCloudNode &node, const QgsPointCloudRequest &request, const QgsPointCloudExpression &expression, const QString &uri ) +void QgsPointCloudIndex::storeNodeDataToCacheStatic( QgsPointCloudBlock *data, const QgsPointCloudNodeId &node, const QgsPointCloudRequest &request, const QgsPointCloudExpression &expression, const QString &uri ) { if ( !data ) return; diff --git a/src/core/pointcloud/qgspointcloudindex.h b/src/core/pointcloud/qgspointcloudindex.h index 4887795a3d8e..858728d6fb30 100644 --- a/src/core/pointcloud/qgspointcloudindex.h +++ b/src/core/pointcloud/qgspointcloudindex.h @@ -30,10 +30,9 @@ #include "qgis_core.h" #include "qgspointcloudstatistics.h" #include "qgsrectangle.h" -#include "qgsvector3d.h" +#include "qgsbox3d.h" #include "qgis_sip.h" #include "qgspointcloudblock.h" -#include "qgsrange.h" #include "qgspointcloudattribute.h" #include "qgspointcloudexpression.h" #include "qgspointcloudrequest.h" @@ -51,26 +50,26 @@ class QgsPointCloudIndex; /** * \ingroup core * - * \brief Represents a indexed point cloud node in octree + * \brief Represents a indexed point cloud node's position in octree * * \note The API is considered EXPERIMENTAL and can be changed without a notice * * \since QGIS 3.18 */ -class CORE_EXPORT IndexedPointCloudNode +class CORE_EXPORT QgsPointCloudNodeId { public: //! Constructs invalid node - IndexedPointCloudNode(); + QgsPointCloudNodeId(); //! Constructs valid node - IndexedPointCloudNode( int _d, int _x, int _y, int _z ); + QgsPointCloudNodeId( int _d, int _x, int _y, int _z ); //! Returns whether node is valid bool isValid() const { return mD >= 0; } // TODO c++20 - replace with = default - bool operator==( IndexedPointCloudNode other ) const + bool operator==( QgsPointCloudNodeId other ) const { return mD == other.d() && mX == other.x() && mY == other.y() && mZ == other.z(); } @@ -79,10 +78,10 @@ class CORE_EXPORT IndexedPointCloudNode * Returns the parent of the node * \since QGIS 3.20 */ - IndexedPointCloudNode parentNode() const; + QgsPointCloudNodeId parentNode() const; //! Creates node from string - static IndexedPointCloudNode fromString( const QString &str ); + static QgsPointCloudNodeId fromString( const QString &str ); //! Encode node to string QString toString() const; @@ -103,10 +102,10 @@ class CORE_EXPORT IndexedPointCloudNode int mD = -1, mX = -1, mY = -1, mZ = -1; }; -Q_DECLARE_TYPEINFO( IndexedPointCloudNode, Q_PRIMITIVE_TYPE ); +Q_DECLARE_TYPEINFO( QgsPointCloudNodeId, Q_PRIMITIVE_TYPE ); //! Hash function for indexed nodes -CORE_EXPORT uint qHash( IndexedPointCloudNode id ); +CORE_EXPORT uint qHash( QgsPointCloudNodeId id ); /** * \ingroup core @@ -121,12 +120,12 @@ class CORE_EXPORT QgsPointCloudCacheKey { public: //! Ctor - QgsPointCloudCacheKey( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request, const QgsPointCloudExpression &expression, const QString &uri ); + QgsPointCloudCacheKey( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request, const QgsPointCloudExpression &expression, const QString &uri ); bool operator==( const QgsPointCloudCacheKey &other ) const; - //! Returns the key's IndexedPointCloudNode - IndexedPointCloudNode node() const { return mNode; } + //! Returns the key's QgsPointCloudNodeId + QgsPointCloudNodeId node() const { return mNode; } //! Returns the key's uri QString uri() const { return mUri; } @@ -138,7 +137,7 @@ class CORE_EXPORT QgsPointCloudCacheKey QgsPointCloudExpression filterExpression() const { return mFilterExpression; } private: - IndexedPointCloudNode mNode; + QgsPointCloudNodeId mNode; QString mUri; QgsPointCloudRequest mRequest; QgsPointCloudExpression mFilterExpression; @@ -150,51 +149,33 @@ uint qHash( const QgsPointCloudCacheKey &key ); /** * \ingroup core * - * \brief Represents packaged data bounds + * \brief Keeps metadata for indexed point cloud node * * \note The API is considered EXPERIMENTAL and can be changed without a notice * - * \since QGIS 3.18 + * \since QGIS 3.42 */ -class CORE_EXPORT QgsPointCloudDataBounds +class CORE_EXPORT QgsPointCloudNode { public: - //! Constructs invalid bounds - QgsPointCloudDataBounds(); - //! Constructs bounds - QgsPointCloudDataBounds( qint64 xmin, qint64 ymin, qint64 zmin, qint64 xmax, qint64 ymax, qint64 zmax ); - - //! Returns x min - qint64 xMin() const; - - //! Returns y min - qint64 yMin() const; - - //! Returns z min - qint64 zMin() const; - - //! Returns x max - qint64 xMax() const; - - //! Returns y max - qint64 yMax() const; - - //! Returns z max - qint64 zMax() const; - - //! Returns 2D rectangle in map coordinates - QgsRectangle mapExtent( const QgsVector3D &offset, const QgsVector3D &scale ) const; - - //! Returns the z range, applying the specified \a offset and \a scale. - QgsDoubleRange zRange( const QgsVector3D &offset, const QgsVector3D &scale ) const; + QgsPointCloudNode( const QgsPointCloudIndex &index, QgsPointCloudNodeId id, qint64 pointCount, + QList childIds ) + : mIndex( index ), mId( id ), mPointCount( pointCount ), mChildIds( childIds ) + { + } + QgsPointCloudNodeId id() const { return mId; } + qint64 pointCount() const { return mPointCount; } + QList children() const { return mChildIds; } + //! Returns node's error in map units (used to determine in whether the node has enough detail for the current view) + float error() const; + QgsBox3D bounds() const; private: - qint64 mXMin = 0; - qint64 mYMin = 0; - qint64 mZMin = 0; - qint64 mXMax = 0; - qint64 mYMax = 0; - qint64 mZMax = 0; + const QgsPointCloudIndex &mIndex; + // Specific node metadata: + QgsPointCloudNodeId mId; + qint64 mPointCount; + QList mChildIds; }; /** @@ -206,9 +187,8 @@ class CORE_EXPORT QgsPointCloudDataBounds * * \since QGIS 3.18 */ -class CORE_EXPORT QgsPointCloudIndex: public QObject +class CORE_EXPORT QgsPointCloudIndex { - Q_OBJECT public: //! The access type of the data, local is for local files and remote for remote files (over HTTP) enum AccessType @@ -219,7 +199,7 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject //! Constructs index explicit QgsPointCloudIndex(); - ~QgsPointCloudIndex(); + virtual ~QgsPointCloudIndex(); /** * Returns a clone of the current point cloud index object @@ -262,16 +242,12 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject virtual QgsPointCloudStatistics metadataStatistics() const; //! Returns root node of the index - IndexedPointCloudNode root() { return IndexedPointCloudNode( 0, 0, 0, 0 ); } + QgsPointCloudNodeId root() { return QgsPointCloudNodeId( 0, 0, 0, 0 ); } //! Returns whether the octree contain given node - virtual bool hasNode( const IndexedPointCloudNode &n ) const; - - //! Returns the number of points of a given node \a n - virtual qint64 nodePointCount( const IndexedPointCloudNode &n ) const; + virtual bool hasNode( const QgsPointCloudNodeId &n ) const; - //! Returns all children of node - virtual QList nodeChildren( const IndexedPointCloudNode &n ) const; + virtual QgsPointCloudNode getNode( const QgsPointCloudNodeId &id ) const; //! Returns all attributes that are stored in the file QgsPointCloudAttributeCollection attributes() const; @@ -284,7 +260,7 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject * * May return nullptr in case the node is not present or any other problem with loading */ - virtual std::unique_ptr< QgsPointCloudBlock > nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) = 0; + virtual std::unique_ptr< QgsPointCloudBlock > nodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) = 0; /** * Returns a handle responsible for loading a node data block @@ -296,7 +272,7 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject * * May return nullptr in case the node is not present or any other problem with loading */ - virtual QgsPointCloudBlockRequest *asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request ) = 0; + virtual QgsPointCloudBlockRequest *asyncNodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) = 0; //! Returns extent of the data QgsRectangle extent() const { return mExtent; } @@ -306,30 +282,13 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject //! Returns z max double zMax() const { return mZMax; } - //! Returns bounds of particular \a node - QgsPointCloudDataBounds nodeBounds( const IndexedPointCloudNode &node ) const; - - /** - * Returns the extent of a \a node in map coordinates. - * - * \see nodeZRange() - */ - QgsRectangle nodeMapExtent( const IndexedPointCloudNode &node ) const; - - /** - * Returns the z range of a \a node. - * - * \see nodeMapExtent() - */ - QgsDoubleRange nodeZRange( const IndexedPointCloudNode &node ) const; - - //! Returns node's error in map units (used to determine in whether the node has enough detail for the current view) - float nodeError( const IndexedPointCloudNode &n ) const; + //! Returns bounding box of root node in CRS coords + QgsBox3D rootNodeBounds() const { return mRootBounds; } - //! Returns scale + //! Returns scale of data relative to CRS QgsVector3D scale() const; - //! Returns offset + //! Returns offset of data from CRS QgsVector3D offset() const; /** @@ -364,17 +323,17 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject * If not found in the cache, nullptr is returned. * Caller takes ownership of the returned object. */ - QgsPointCloudBlock *getNodeDataFromCache( const IndexedPointCloudNode &node, const QgsPointCloudRequest &request ); + QgsPointCloudBlock *getNodeDataFromCache( const QgsPointCloudNodeId &node, const QgsPointCloudRequest &request ); /** * Stores existing \a data to the cache for the specified \a node and \a request. Ownership is not transferred, block gets cloned in the cache. */ - void storeNodeDataToCache( QgsPointCloudBlock *data, const IndexedPointCloudNode &node, const QgsPointCloudRequest &request ); + void storeNodeDataToCache( QgsPointCloudBlock *data, const QgsPointCloudNodeId &node, const QgsPointCloudRequest &request ); /** * Stores existing \a data to the cache for the specified \a node, \a request, \a expression and \a uri. Ownership is not transferred, block gets cloned in the cache. */ - static void storeNodeDataToCacheStatic( QgsPointCloudBlock *data, const IndexedPointCloudNode &node, const QgsPointCloudRequest &request, + static void storeNodeDataToCacheStatic( QgsPointCloudBlock *data, const QgsPointCloudNodeId &node, const QgsPointCloudRequest &request, const QgsPointCloudExpression &expression, const QString &uri ); protected: //TODO private @@ -385,10 +344,10 @@ class CORE_EXPORT QgsPointCloudIndex: public QObject double mZMin = 0, mZMax = 0; //!< Vertical extent of data mutable QMutex mHierarchyMutex; - mutable QHash mHierarchy; //!< Data hierarchy + mutable QHash mHierarchy; //!< Data hierarchy QgsVector3D mScale; //!< Scale of our int32 coordinates compared to CRS coords QgsVector3D mOffset; //!< Offset of our int32 coordinates compared to CRS coords - QgsPointCloudDataBounds mRootBounds; //!< Bounds of the root node's cube (in int32 coordinates) + QgsBox3D mRootBounds; //!< Bounds of the root node's cube (in int32 coordinates) QgsPointCloudAttributeCollection mAttributes; //! All native attributes stored in the file int mSpan = 0; //!< Number of points in one direction in a single node QgsPointCloudExpression mFilterExpression; //!< The filter expression to be evaluated when fetching node data diff --git a/src/core/pointcloud/qgspointcloudlayer.cpp b/src/core/pointcloud/qgspointcloudlayer.cpp index 9a807d0b8025..058006a834ec 100644 --- a/src/core/pointcloud/qgspointcloudlayer.cpp +++ b/src/core/pointcloud/qgspointcloudlayer.cpp @@ -905,7 +905,7 @@ void QgsPointCloudLayer::calculateStatistics() #ifdef HAVE_COPC if ( mDataProvider && mDataProvider->index() && mDataProvider->index()->isValid() && mDataProvider->name() == QLatin1String( "pdal" ) && mStatistics.sampledPointsCount() != 0 ) { - if ( QgsCopcPointCloudIndex *index = qobject_cast( mDataProvider->index() ) ) + if ( QgsCopcPointCloudIndex *index = dynamic_cast( mDataProvider->index() ) ) { index->writeStatistics( mStatistics ); } diff --git a/src/core/pointcloud/qgspointcloudlayerexporter.cpp b/src/core/pointcloud/qgspointcloudlayerexporter.cpp index b3c13f2f41c4..e97a42ae935a 100644 --- a/src/core/pointcloud/qgspointcloudlayerexporter.cpp +++ b/src/core/pointcloud/qgspointcloudlayerexporter.cpp @@ -24,6 +24,7 @@ #include "moc_qgspointcloudlayerexporter.cpp" #include "qgsmemoryproviderutils.h" #include "qgspointcloudrequest.h" +#include "qgsrectangle.h" #include "qgsvectorfilewriter.h" #include "qgsgeos.h" @@ -329,23 +330,23 @@ void QgsPointCloudLayerExporter::ExporterBase::run() geometryFilterRectangle = envelope->boundingBox(); } - QVector nodes; + QVector nodes; qint64 pointCount = 0; - QQueue queue; + QQueue queue; queue.push_back( mParent->mIndex->root() ); while ( !queue.empty() ) { - IndexedPointCloudNode node = queue.front(); + QgsPointCloudNode node = mParent->mIndex->getNode( queue.front() ); queue.pop_front(); - const QgsRectangle nodeExtent = mParent->mIndex->nodeMapExtent( node ); - if ( mParent->mExtent.intersects( nodeExtent ) && - mParent->mZRange.overlaps( mParent->mIndex->nodeZRange( node ) ) && - geometryFilterRectangle.intersects( nodeExtent ) ) + const QgsBox3D nodeBounds = node.bounds(); + if ( mParent->mExtent.intersects( nodeBounds.toRectangle() ) && + mParent->mZRange.overlaps( { nodeBounds.zMinimum(), nodeBounds.zMaximum() } ) && + geometryFilterRectangle.intersects( nodeBounds.toRectangle() ) ) { - pointCount += mParent->mIndex->nodePointCount( node ); - nodes.push_back( node ); + pointCount += node.pointCount(); + nodes.push_back( node.id() ); } - for ( const IndexedPointCloudNode &child : mParent->mIndex->nodeChildren( node ) ) + for ( const QgsPointCloudNodeId &child : node.children() ) { queue.push_back( child ); } @@ -356,7 +357,7 @@ void QgsPointCloudLayerExporter::ExporterBase::run() request.setAttributes( mParent->requestedAttributeCollection() ); std::unique_ptr block = nullptr; qint64 pointsExported = 0; - for ( const IndexedPointCloudNode &node : nodes ) + for ( const QgsPointCloudNodeId &node : nodes ) { block = mParent->mIndex->nodeData( node, request ); const QgsPointCloudAttributeCollection attributesCollection = block->attributes(); diff --git a/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp b/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp index 9982c4f1d325..a8d706b0e14e 100644 --- a/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp +++ b/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp @@ -510,8 +510,8 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera for ( QgsPointCloudIndex *pc : std::as_const( indexes ) ) { - const IndexedPointCloudNode root = pc->root(); - const QgsRectangle rootNodeExtentLayerCoords = pc->nodeMapExtent( root ); + const QgsPointCloudNode root = pc->getNode( pc->root() ); + const QgsRectangle rootNodeExtentLayerCoords = root.bounds().toRectangle(); QgsRectangle rootNodeExtentInCurveCrs; try { @@ -531,7 +531,7 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera } double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels - const QVector nodes = traverseTree( pc, pc->root(), maximumErrorPixels, rootErrorPixels, context.elevationRange() ); + const QVector nodes = traverseTree( pc, pc->root(), maximumErrorPixels, rootErrorPixels, context.elevationRange() ); const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / pc->span(); const double maxErrorInMapCoordinates = maximumErrorPixels * mapUnitsPerPixel; @@ -599,37 +599,36 @@ QgsFeedback *QgsPointCloudLayerProfileGenerator::feedback() const return mFeedback.get(); } -QVector QgsPointCloudLayerProfileGenerator::traverseTree( const QgsPointCloudIndex *pc, IndexedPointCloudNode n, double maxErrorPixels, double nodeErrorPixels, const QgsDoubleRange &zRange ) +QVector QgsPointCloudLayerProfileGenerator::traverseTree( const QgsPointCloudIndex *pc, QgsPointCloudNodeId n, double maxErrorPixels, double nodeErrorPixels, const QgsDoubleRange &zRange ) { - QVector nodes; + QVector nodes; if ( mFeedback->isCanceled() ) { return nodes; } - const QgsDoubleRange nodeZRange = pc->nodeZRange( n ); - const QgsDoubleRange adjustedNodeZRange = QgsDoubleRange( nodeZRange.lower() * mZScale + mZOffset, nodeZRange.upper() * mZScale + mZOffset ); - if ( !zRange.isInfinite() && !zRange.overlaps( adjustedNodeZRange ) ) + QgsPointCloudNode node = pc->getNode( n ); + QgsBox3D nodeBounds = node.bounds(); + const QgsDoubleRange nodeZRange( nodeBounds.zMinimum(), nodeBounds.zMaximum() ); + if ( !zRange.isInfinite() && !zRange.overlaps( nodeZRange ) ) return nodes; - const QgsRectangle nodeMapExtent = pc->nodeMapExtent( n ); - if ( !mMaxSearchExtentInLayerCrs.intersects( nodeMapExtent ) ) + if ( !mMaxSearchExtentInLayerCrs.intersects( nodeBounds.toRectangle() ) ) return nodes; - const QgsGeometry nodeMapGeometry = QgsGeometry::fromRect( nodeMapExtent ); + const QgsGeometry nodeMapGeometry = QgsGeometry::fromRect( nodeBounds.toRectangle() ); if ( !mSearchGeometryInLayerCrsGeometryEngine->intersects( nodeMapGeometry.constGet() ) ) return nodes; - if ( pc->nodePointCount( n ) > 0 ) + if ( node.pointCount() > 0 ) nodes.append( n ); double childrenErrorPixels = nodeErrorPixels / 2.0; if ( childrenErrorPixels < maxErrorPixels ) return nodes; - const QList children = pc->nodeChildren( n ); - for ( const IndexedPointCloudNode &nn : children ) + for ( const QgsPointCloudNodeId &nn : node.children() ) { nodes += traverseTree( pc, nn, maxErrorPixels, childrenErrorPixels, zRange ); } @@ -637,10 +636,10 @@ QVector QgsPointCloudLayerProfileGenerator::traverseTree( return nodes; } -int QgsPointCloudLayerProfileGenerator::visitNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange ) +int QgsPointCloudLayerProfileGenerator::visitNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange ) { int nodesDrawn = 0; - for ( const IndexedPointCloudNode &n : nodes ) + for ( const QgsPointCloudNodeId &n : nodes ) { if ( mFeedback->isCanceled() ) break; @@ -657,7 +656,7 @@ int QgsPointCloudLayerProfileGenerator::visitNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange ) +int QgsPointCloudLayerProfileGenerator::visitNodesAsync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange ) { int nodesDrawn = 0; @@ -670,7 +669,7 @@ int QgsPointCloudLayerProfileGenerator::visitNodesAsync( const QVectorasyncNodeData( n, request ); blockRequests.append( blockRequest ); diff --git a/src/core/pointcloud/qgspointcloudlayerprofilegenerator.h b/src/core/pointcloud/qgspointcloudlayerprofilegenerator.h index 6b082cc23b94..370b786c4362 100644 --- a/src/core/pointcloud/qgspointcloudlayerprofilegenerator.h +++ b/src/core/pointcloud/qgspointcloudlayerprofilegenerator.h @@ -37,7 +37,7 @@ class QgsPointCloudLayer; class QgsAbstractTerrainProvider; class QgsProfileSnapContext; class QgsPointCloudRenderer; -class IndexedPointCloudNode; +class QgsPointCloudNodeId; class QgsPointCloudIndex; class QgsPointCloudRequest; class QgsPointCloudBlock; @@ -144,9 +144,9 @@ class CORE_EXPORT QgsPointCloudLayerProfileGenerator : public QgsAbstractProfile QgsFeedback *feedback() const override; private: - QVector traverseTree( const QgsPointCloudIndex *pc, IndexedPointCloudNode n, double maxErrorPixels, double nodeErrorPixels, const QgsDoubleRange &zRange ); - int visitNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange ); - int visitNodesAsync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange ); + QVector traverseTree( const QgsPointCloudIndex *pc, QgsPointCloudNodeId n, double maxErrorPixels, double nodeErrorPixels, const QgsDoubleRange &zRange ); + int visitNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange ); + int visitNodesAsync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRequest &request, const QgsDoubleRange &zRange ); void visitBlock( const QgsPointCloudBlock *block, const QgsDoubleRange &zRange ); QPointer< QgsPointCloudLayer > mLayer; diff --git a/src/core/pointcloud/qgspointcloudlayerrenderer.cpp b/src/core/pointcloud/qgspointcloudlayerrenderer.cpp index f127228aaa27..d4baafe8083c 100644 --- a/src/core/pointcloud/qgspointcloudlayerrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudlayerrenderer.cpp @@ -240,11 +240,12 @@ bool QgsPointCloudLayerRenderer::renderIndex( QgsPointCloudIndex *pc ) t.start(); #endif - const IndexedPointCloudNode root = pc->root(); + const QgsPointCloudNodeId root = pc->root(); const double maximumError = context.renderContext().convertToPainterUnits( mRenderer->maximumScreenError(), mRenderer->maximumScreenErrorUnit() );// in pixels - const QgsRectangle rootNodeExtentLayerCoords = pc->nodeMapExtent( root ); + const QgsPointCloudNode rootNode = pc->getNode( root ); + const QgsRectangle rootNodeExtentLayerCoords = pc->extent(); QgsRectangle rootNodeExtentMapCoords; if ( !context.renderContext().coordinateTransform().isShortCircuited() ) { @@ -274,7 +275,7 @@ bool QgsPointCloudLayerRenderer::renderIndex( QgsPointCloudIndex *pc ) return false; } double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels - const QVector nodes = traverseTree( pc, context.renderContext(), pc->root(), maximumError, rootErrorPixels ); + const QVector nodes = traverseTree( pc, context.renderContext(), pc->root(), maximumError, rootErrorPixels ); QgsPointCloudRequest request; request.setAttributes( mAttributes ); @@ -328,7 +329,7 @@ bool QgsPointCloudLayerRenderer::renderIndex( QgsPointCloudIndex *pc ) return !canceled; } -int QgsPointCloudLayerRenderer::renderNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled ) +int QgsPointCloudLayerRenderer::renderNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled ) { QPainter *finalPainter = context.renderContext().painter(); if ( mRenderer->renderAsTriangles() && context.renderContext().previewRenderPainter() ) @@ -339,7 +340,7 @@ int QgsPointCloudLayerRenderer::renderNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled ) +int QgsPointCloudLayerRenderer::renderNodesAsync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled ) { if ( context.feedback() && context.feedback()->isCanceled() ) return 0; @@ -409,7 +410,7 @@ int QgsPointCloudLayerRenderer::renderNodesAsync( const QVectorasyncNodeData( n, request ); blockRequests.append( blockRequest ); @@ -486,7 +487,7 @@ int QgsPointCloudLayerRenderer::renderNodesAsync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled, Qgis::PointCloudDrawOrder order ) +int QgsPointCloudLayerRenderer::renderNodesSorted( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled, Qgis::PointCloudDrawOrder order ) { int blockCount = 0; int pointCount = 0; @@ -501,7 +502,7 @@ int QgsPointCloudLayerRenderer::renderNodesSorted( const QVector> allPairs; - for ( const IndexedPointCloudNode &n : nodes ) + for ( const QgsPointCloudNodeId &n : nodes ) { if ( context.renderContext().renderingStopped() ) { @@ -758,13 +759,13 @@ void QgsPointCloudLayerRenderer::setLayerRenderingTimeHint( int time ) mRenderTimeHint = time; } -QVector QgsPointCloudLayerRenderer::traverseTree( const QgsPointCloudIndex *pc, +QVector QgsPointCloudLayerRenderer::traverseTree( const QgsPointCloudIndex *pc, const QgsRenderContext &context, - IndexedPointCloudNode n, + QgsPointCloudNodeId n, double maxErrorPixels, double nodeErrorPixels ) { - QVector nodes; + QVector nodes; if ( context.renderingStopped() ) { @@ -772,23 +773,25 @@ QVector QgsPointCloudLayerRenderer::traverseTree( const Q return nodes; } - if ( !context.extent().intersects( pc->nodeMapExtent( n ) ) ) + QgsPointCloudNode node = pc->getNode( n ); + QgsBox3D nodeExtent = node.bounds(); + + if ( !context.extent().intersects( nodeExtent.toRectangle() ) ) return nodes; - const QgsDoubleRange nodeZRange = pc->nodeZRange( n ); + const QgsDoubleRange nodeZRange( nodeExtent.zMinimum(), nodeExtent.zMaximum() ); const QgsDoubleRange adjustedNodeZRange = QgsDoubleRange( nodeZRange.lower() + mZOffset, nodeZRange.upper() + mZOffset ); if ( !context.zRange().isInfinite() && !context.zRange().overlaps( adjustedNodeZRange ) ) return nodes; - if ( pc->nodePointCount( n ) > 0 ) + if ( node.pointCount() > 0 ) nodes.append( n ); double childrenErrorPixels = nodeErrorPixels / 2.0; if ( childrenErrorPixels < maxErrorPixels ) return nodes; - const QList children = pc->nodeChildren( n ); - for ( const IndexedPointCloudNode &nn : children ) + for ( const QgsPointCloudNodeId &nn : node.children() ) { nodes += traverseTree( pc, context, nn, maxErrorPixels, childrenErrorPixels ); } diff --git a/src/core/pointcloud/qgspointcloudlayerrenderer.h b/src/core/pointcloud/qgspointcloudlayerrenderer.h index 1fe90fadf93c..23be4090291d 100644 --- a/src/core/pointcloud/qgspointcloudlayerrenderer.h +++ b/src/core/pointcloud/qgspointcloudlayerrenderer.h @@ -72,10 +72,10 @@ class CORE_EXPORT QgsPointCloudLayerRenderer: public QgsMapLayerRenderer QgsFeedback *feedback() const override { return mFeedback.get(); } private: - QVector traverseTree( const QgsPointCloudIndex *pc, const QgsRenderContext &context, IndexedPointCloudNode n, double maxErrorPixels, double nodeErrorPixels ); - int renderNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled ); - int renderNodesAsync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled ); - int renderNodesSorted( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled, Qgis::PointCloudDrawOrder order ); + QVector traverseTree( const QgsPointCloudIndex *pc, const QgsRenderContext &context, QgsPointCloudNodeId n, double maxErrorPixels, double nodeErrorPixels ); + int renderNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled ); + int renderNodesAsync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled ); + int renderNodesSorted( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled, Qgis::PointCloudDrawOrder order ); void renderTriangulatedSurface( QgsPointCloudRenderContext &context ); bool renderIndex( QgsPointCloudIndex *pc ); diff --git a/src/core/pointcloud/qgspointcloudstatistics.h b/src/core/pointcloud/qgspointcloudstatistics.h index 299bccea226f..6da262cba6b4 100644 --- a/src/core/pointcloud/qgspointcloudstatistics.h +++ b/src/core/pointcloud/qgspointcloudstatistics.h @@ -26,7 +26,7 @@ #include class QgsPointCloudAttribute; -class IndexedPointCloudNode; +class QgsPointCloudNodeId; /** * \ingroup core diff --git a/src/core/pointcloud/qgspointcloudstatscalculator.cpp b/src/core/pointcloud/qgspointcloudstatscalculator.cpp index 4cdf9388b1f0..5748dd2412a5 100644 --- a/src/core/pointcloud/qgspointcloudstatscalculator.cpp +++ b/src/core/pointcloud/qgspointcloudstatscalculator.cpp @@ -57,22 +57,23 @@ struct StatsProcessor return *this; } - QgsPointCloudStatistics operator()( IndexedPointCloudNode node ) + QgsPointCloudStatistics operator()( QgsPointCloudNodeId nodeId ) { - if ( mIndex->nodePointCount( node ) < 1 ) + QgsPointCloudNode node = mIndex->getNode( nodeId ); + if ( node.pointCount() < 1 ) return QgsPointCloudStatistics(); std::unique_ptr block = nullptr; if ( mIndex->accessType() == QgsPointCloudIndex::Local ) { - block = mIndex->nodeData( node, mRequest ); + block = mIndex->nodeData( nodeId, mRequest ); } else { - QgsPointCloudBlockRequest *request = mIndex->asyncNodeData( node, mRequest ); + QgsPointCloudBlockRequest *request = mIndex->asyncNodeData( nodeId, mRequest ); if ( request == nullptr ) { - QgsDebugError( QStringLiteral( "Unable to calculate statistics for node %1: Got nullptr async request" ).arg( node.toString() ) ); + QgsDebugError( QStringLiteral( "Unable to calculate statistics for node %1: Got nullptr async request" ).arg( nodeId.toString() ) ); return QgsPointCloudStatistics(); } QEventLoop loop; @@ -84,7 +85,7 @@ struct StatsProcessor block = request->takeBlock(); if ( !block ) { - QgsMessageLog::logMessage( QObject::tr( "Unable to calculate statistics for node %1, error: \"%2\"" ).arg( node.toString(), request->errorStr() ) ); + QgsMessageLog::logMessage( QObject::tr( "Unable to calculate statistics for node %1, error: \"%2\"" ).arg( nodeId.toString(), request->errorStr() ) ); } } } @@ -198,23 +199,23 @@ bool QgsPointCloudStatsCalculator::calculateStats( QgsFeedback *feedback, const mRequest.setAttributes( attributes ); qint64 pointCount = 0; - QVector nodes; - QQueue queue; + QVector nodes; + QQueue queue; queue.push_back( mIndex->root() ); while ( !queue.empty() ) { - IndexedPointCloudNode node = queue.front(); + QgsPointCloudNode node = mIndex->getNode( queue.front() ); queue.pop_front(); - if ( !mProcessedNodes.contains( node ) ) - pointCount += mIndex->nodePointCount( node ); + if ( !mProcessedNodes.contains( node.id() ) ) + pointCount += node.pointCount(); if ( pointsLimit != -1 && pointCount > pointsLimit ) break; - if ( !mProcessedNodes.contains( node ) ) + if ( !mProcessedNodes.contains( node.id() ) ) { - nodes.push_back( node ); - mProcessedNodes.insert( node ); + nodes.push_back( node.id() ); + mProcessedNodes.insert( node.id() ); } - for ( const IndexedPointCloudNode &child : mIndex->nodeChildren( node ) ) + for ( const QgsPointCloudNodeId &child : node.children() ) { queue.push_back( child ); } diff --git a/src/core/pointcloud/qgspointcloudstatscalculator.h b/src/core/pointcloud/qgspointcloudstatscalculator.h index a9ec7e755f01..d7fff2a6953a 100644 --- a/src/core/pointcloud/qgspointcloudstatscalculator.h +++ b/src/core/pointcloud/qgspointcloudstatscalculator.h @@ -27,7 +27,6 @@ #include #include "qgspointcloudrequest.h" -#include "qgsstatisticalsummary.h" #include "qgspointcloudstatistics.h" #define SIP_NO_FILE @@ -35,7 +34,7 @@ class QgsPointCloudIndex; class QgsPointCloudBlock; class QgsPointCloudAttribute; -class IndexedPointCloudNode; +class QgsPointCloudNodeId; class QgsFeedback; /** @@ -66,7 +65,7 @@ class CORE_EXPORT QgsPointCloudStatsCalculator : public QObject std::unique_ptr mIndex; QgsPointCloudStatistics mStats; - QSet mProcessedNodes; + QSet mProcessedNodes; QgsPointCloudRequest mRequest; }; diff --git a/tests/src/providers/testqgscopcprovider.cpp b/tests/src/providers/testqgscopcprovider.cpp index 1cfa745736b7..be1c34dcaf55 100644 --- a/tests/src/providers/testqgscopcprovider.cpp +++ b/tests/src/providers/testqgscopcprovider.cpp @@ -265,8 +265,8 @@ void TestQgsCopcProvider::validLayer() QVERIFY( layer->dataProvider()->index() ); // all hierarchy is stored in a single node - QVERIFY( layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "0-0-0-0" ) ) ); - QVERIFY( !layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "1-0-0-0" ) ) ); + QVERIFY( layer->dataProvider()->index()->hasNode( QgsPointCloudNodeId::fromString( "0-0-0-0" ) ) ); + QVERIFY( !layer->dataProvider()->index()->hasNode( QgsPointCloudNodeId::fromString( "1-0-0-0" ) ) ); } void TestQgsCopcProvider::validLayerWithCopcHierarchy() @@ -283,8 +283,8 @@ void TestQgsCopcProvider::validLayerWithCopcHierarchy() QVERIFY( layer->dataProvider()->index() ); // all hierarchy is stored in multiple nodes - QVERIFY( layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "1-1-1-0" ) ) ); - QVERIFY( layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "2-3-3-1" ) ) ); + QVERIFY( layer->dataProvider()->index()->hasNode( QgsPointCloudNodeId::fromString( "1-1-1-0" ) ) ); + QVERIFY( layer->dataProvider()->index()->hasNode( QgsPointCloudNodeId::fromString( "2-3-3-1" ) ) ); } void TestQgsCopcProvider::attributes() @@ -827,10 +827,10 @@ void TestQgsCopcProvider::testPointCloudIndex() QgsPointCloudIndex *index = layer->dataProvider()->index(); QVERIFY( index->isValid() ); - QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "0-0-0-0" ) ) ), 56721 ); - QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "1-1-1-1" ) ) ), -1 ); - QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "2-3-3-1" ) ) ), 446 ); - QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "9-9-9-9" ) ) ), -1 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "0-0-0-0" ) ) ).pointCount(), 56721 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ).pointCount(), -1 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "2-3-3-1" ) ) ).pointCount(), 446 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "9-9-9-9" ) ) ).pointCount(), -1 ); QCOMPARE( index->pointCount(), 518862 ); QCOMPARE( index->zMin(), 2322.89625 ); @@ -839,38 +839,38 @@ void TestQgsCopcProvider::testPointCloudIndex() QCOMPARE( index->offset().toVector3D(), QVector3D( 515385, 4918361, 2330.5 ) ); QCOMPARE( index->span(), 128 ); - QCOMPARE( index->nodeError( IndexedPointCloudNode::fromString( QStringLiteral( "0-0-0-0" ) ) ), 0.328125 ); - QCOMPARE( index->nodeError( IndexedPointCloudNode::fromString( QStringLiteral( "1-1-1-1" ) ) ), 0.1640625 ); - QCOMPARE( index->nodeError( IndexedPointCloudNode::fromString( QStringLiteral( "2-3-3-1" ) ) ), 0.08203125 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "0-0-0-0" ) ) ).error(), 0.328125 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ).error(), 0.1640625 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "2-3-3-1" ) ) ).error(), 0.08203125 ); { - QgsPointCloudDataBounds bounds = index->nodeBounds( IndexedPointCloudNode::fromString( QStringLiteral( "0-0-0-0" ) ) ); - QCOMPARE( bounds.xMin(), -170000 ); - QCOMPARE( bounds.yMin(), -210000 ); - QCOMPARE( bounds.zMin(), -85000 ); - QCOMPARE( bounds.xMax(), 250000 ); - QCOMPARE( bounds.yMax(), 210000 ); - QCOMPARE( bounds.zMax(), 335000 ); + QgsBox3D bounds = index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "0-0-0-0" ) ) ).bounds(); + QCOMPARE( bounds.xMinimum(), 515368 ); + QCOMPARE( bounds.yMinimum(), 4918340 ); + QCOMPARE( bounds.zMinimum(), 2322 ); + QCOMPARE( bounds.xMaximum(), 515410 ); + QCOMPARE( bounds.yMaximum(), 4918382 ); + QCOMPARE( bounds.zMaximum(), 2364 ); } { - QgsPointCloudDataBounds bounds = index->nodeBounds( IndexedPointCloudNode::fromString( QStringLiteral( "1-1-1-1" ) ) ); - QCOMPARE( bounds.xMin(), 40000 ); - QCOMPARE( bounds.yMin(), 0 ); - QCOMPARE( bounds.zMin(), 125000 ); - QCOMPARE( bounds.xMax(), 250000 ); - QCOMPARE( bounds.yMax(), 210000 ); - QCOMPARE( bounds.zMax(), 335000 ); + QgsBox3D bounds = index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ).bounds(); + QCOMPARE( bounds.xMinimum(), 515389 ); + QCOMPARE( bounds.yMinimum(), 4918361 ); + QCOMPARE( bounds.zMinimum(), 2343 ); + QCOMPARE( bounds.xMaximum(), 515410 ); + QCOMPARE( bounds.yMaximum(), 4918382 ); + QCOMPARE( bounds.zMaximum(), 2364 ); } { - QgsPointCloudDataBounds bounds = index->nodeBounds( IndexedPointCloudNode::fromString( QStringLiteral( "2-3-3-1" ) ) ); - QCOMPARE( bounds.xMin(), 145000 ); - QCOMPARE( bounds.yMin(), 105000 ); - QCOMPARE( bounds.zMin(), 20000 ); - QCOMPARE( bounds.xMax(), 250000 ); - QCOMPARE( bounds.yMax(), 210000 ); - QCOMPARE( bounds.zMax(), 125000 ); + QgsBox3D bounds = index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "2-3-3-1" ) ) ).bounds(); + QCOMPARE( bounds.xMinimum(), 515399.5 ); + QCOMPARE( bounds.yMinimum(), 4918371.5 ); + QCOMPARE( bounds.zMinimum(), 2332.5 ); + QCOMPARE( bounds.xMaximum(), 515410 ); + QCOMPARE( bounds.yMaximum(), 4918382 ); + QCOMPARE( bounds.zMaximum(), 2343 ); } } @@ -1131,7 +1131,7 @@ void TestQgsCopcProvider::testSaveLoadStats() QVERIFY( layer->isValid() ); QVERIFY( layer->dataProvider() && layer->dataProvider()->isValid() && layer->dataProvider()->index() ); - QgsCopcPointCloudIndex *index = qobject_cast( layer->dataProvider()->index() ); + QgsCopcPointCloudIndex *index = dynamic_cast( layer->dataProvider()->index() ); calculatedStats = layer->statistics(); index->writeStatistics( calculatedStats ); @@ -1143,7 +1143,7 @@ void TestQgsCopcProvider::testSaveLoadStats() QVERIFY( layer->dataProvider() && layer->dataProvider()->isValid() && layer->dataProvider()->index() ); - QgsCopcPointCloudIndex *index = qobject_cast( layer->dataProvider()->index() ); + QgsCopcPointCloudIndex *index = dynamic_cast( layer->dataProvider()->index() ); readStats = index->metadataStatistics(); } @@ -1161,16 +1161,16 @@ void TestQgsCopcProvider::testPointCloudRequest() QgsPointCloudIndex *index = layer->dataProvider()->index(); QVERIFY( index->isValid() ); - QVector nodes; - QQueue queue; + QVector nodes; + QQueue queue; queue.push_back( index->root() ); while ( !queue.empty() ) { - IndexedPointCloudNode node = queue.front(); + QgsPointCloudNodeId node = queue.front(); queue.pop_front(); nodes.push_back( node ); - for ( const IndexedPointCloudNode &child : index->nodeChildren( node ) ) + for ( const QgsPointCloudNodeId &child : index->getNode( node ).children() ) { queue.push_back( child ); } @@ -1180,7 +1180,7 @@ void TestQgsCopcProvider::testPointCloudRequest() request.setAttributes( layer->attributes() ); // If request.setFilterRect() is not called, no filter should be applied int count = 0; - for ( IndexedPointCloudNode node : nodes ) + for ( QgsPointCloudNodeId node : nodes ) { auto block = index->nodeData( node, request ); count += block->pointCount(); @@ -1191,7 +1191,7 @@ void TestQgsCopcProvider::testPointCloudRequest() QgsRectangle extent( 515390, 4918360, 515400, 4918370 ); request.setFilterRect( extent ); count = 0; - for ( IndexedPointCloudNode node : nodes ) + for ( QgsPointCloudNodeId node : nodes ) { auto block = index->nodeData( node, request ); count += block->pointCount(); @@ -1202,7 +1202,7 @@ void TestQgsCopcProvider::testPointCloudRequest() extent = QgsRectangle( 0, 0, 1, 1 ); request.setFilterRect( extent ); count = 0; - for ( IndexedPointCloudNode node : nodes ) + for ( QgsPointCloudNodeId node : nodes ) { auto block = index->nodeData( node, request ); count += block->pointCount(); @@ -1213,7 +1213,7 @@ void TestQgsCopcProvider::testPointCloudRequest() count = 0; extent = QgsRectangle(); request.setFilterRect( extent ); - for ( IndexedPointCloudNode node : nodes ) + for ( QgsPointCloudNodeId node : nodes ) { auto block = index->nodeData( node, request ); count += block->pointCount(); diff --git a/tests/src/providers/testqgseptprovider.cpp b/tests/src/providers/testqgseptprovider.cpp index b237ca7911ab..e312fc8a8419 100644 --- a/tests/src/providers/testqgseptprovider.cpp +++ b/tests/src/providers/testqgseptprovider.cpp @@ -265,8 +265,8 @@ void TestQgsEptProvider::validLayer() QVERIFY( layer->dataProvider()->index() ); // all hierarchy is stored in a single node - QVERIFY( layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "0-0-0-0" ) ) ); - QVERIFY( !layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "1-0-0-0" ) ) ); + QVERIFY( layer->dataProvider()->index()->hasNode( QgsPointCloudNodeId::fromString( "0-0-0-0" ) ) ); + QVERIFY( !layer->dataProvider()->index()->hasNode( QgsPointCloudNodeId::fromString( "1-0-0-0" ) ) ); } void TestQgsEptProvider::validLayerWithEptHierarchy() @@ -283,8 +283,8 @@ void TestQgsEptProvider::validLayerWithEptHierarchy() QVERIFY( layer->dataProvider()->index() ); // all hierarchy is stored in multiple nodes - QVERIFY( layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "1-1-1-1" ) ) ); - QVERIFY( layer->dataProvider()->index()->hasNode( IndexedPointCloudNode::fromString( "2-3-3-1" ) ) ); + QVERIFY( layer->dataProvider()->index()->hasNode( QgsPointCloudNodeId::fromString( "1-1-1-1" ) ) ); + QVERIFY( layer->dataProvider()->index()->hasNode( QgsPointCloudNodeId::fromString( "2-3-3-1" ) ) ); } void TestQgsEptProvider::attributes() @@ -630,10 +630,10 @@ void TestQgsEptProvider::testPointCloudIndex() QgsPointCloudIndex *index = layer->dataProvider()->index(); QVERIFY( index->isValid() ); - QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "0-0-0-0" ) ) ), 41998 ); - QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "1-1-1-1" ) ) ), 48879 ); - QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "2-3-3-1" ) ) ), 41734 ); - QCOMPARE( index->nodePointCount( IndexedPointCloudNode::fromString( QStringLiteral( "9-9-9-9" ) ) ), -1 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "0-0-0-0" ) ) ).pointCount(), 41998 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ).pointCount(), 48879 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "2-3-3-1" ) ) ).pointCount(), 41734 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "9-9-9-9" ) ) ).pointCount(), -1 ); QCOMPARE( index->pointCount(), 518862 ); QCOMPARE( index->zMin(), 2322 ); @@ -642,11 +642,12 @@ void TestQgsEptProvider::testPointCloudIndex() QCOMPARE( index->offset().toVector3D(), QVector3D( 515385, 4918361, 2331 ) ); QCOMPARE( index->span(), 128 ); - QCOMPARE( index->nodeError( IndexedPointCloudNode::fromString( QStringLiteral( "0-0-0-0" ) ) ), 0.34375 ); - QCOMPARE( index->nodeError( IndexedPointCloudNode::fromString( QStringLiteral( "1-1-1-1" ) ) ), 0.171875 ); - QCOMPARE( index->nodeError( IndexedPointCloudNode::fromString( QStringLiteral( "2-3-3-1" ) ) ), 0.0859375 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "0-0-0-0" ) ) ).error(), 0.34375 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ).error(), 0.171875 ); + QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "2-3-3-1" ) ) ).error(), 0.0859375 ); { +<<<<<<< HEAD QgsPointCloudDataBounds bounds = index->nodeBounds( IndexedPointCloudNode::fromString( QStringLiteral( "0-0-0-0" ) ) ); QCOMPARE( bounds.xMin(), -88000 ); QCOMPARE( bounds.yMin(), -88000 ); @@ -654,26 +655,35 @@ void TestQgsEptProvider::testPointCloudIndex() QCOMPARE( bounds.xMax(), 88000 ); QCOMPARE( bounds.yMax(), 88000 ); QCOMPARE( bounds.zMax(), 88000 ); +======= + QgsBox3D bounds = index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "0-0-0-0" ) ) ).bounds(); + QCOMPARE( bounds.xMinimum(), 515363 ); + QCOMPARE( bounds.yMinimum(), 4918339 ); + QCOMPARE( bounds.zMinimum(), 2309 ); + QCOMPARE( bounds.xMaximum(), 515407 ); + QCOMPARE( bounds.yMaximum(), 4918383 ); + QCOMPARE( bounds.zMaximum(), 2353 ); +>>>>>>> 4bac513354a (Refactor QgsPointCloudIndex) } { - QgsPointCloudDataBounds bounds = index->nodeBounds( IndexedPointCloudNode::fromString( QStringLiteral( "1-1-1-1" ) ) ); - QCOMPARE( bounds.xMin(), 0 ); - QCOMPARE( bounds.yMin(), 0 ); - QCOMPARE( bounds.zMin(), 0 ); - QCOMPARE( bounds.xMax(), 88000 ); - QCOMPARE( bounds.yMax(), 88000 ); - QCOMPARE( bounds.zMax(), 88000 ); + QgsBox3D bounds = index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ).bounds(); + QCOMPARE( bounds.xMinimum(), 515385 ); + QCOMPARE( bounds.yMinimum(), 4918361 ); + QCOMPARE( bounds.zMinimum(), 2331 ); + QCOMPARE( bounds.xMaximum(), 515407 ); + QCOMPARE( bounds.yMaximum(), 4918383 ); + QCOMPARE( bounds.zMaximum(), 2353 ); } { - QgsPointCloudDataBounds bounds = index->nodeBounds( IndexedPointCloudNode::fromString( QStringLiteral( "2-3-3-1" ) ) ); - QCOMPARE( bounds.xMin(), 44000 ); - QCOMPARE( bounds.yMin(), 44000 ); - QCOMPARE( bounds.zMin(), -44000 ); - QCOMPARE( bounds.xMax(), 88000 ); - QCOMPARE( bounds.yMax(), 88000 ); - QCOMPARE( bounds.zMax(), 0 ); + QgsBox3D bounds = index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "2-3-3-1" ) ) ).bounds(); + QCOMPARE( bounds.xMinimum(), 515396 ); + QCOMPARE( bounds.yMinimum(), 4918372 ); + QCOMPARE( bounds.zMinimum(), 2320 ); + QCOMPARE( bounds.xMaximum(), 515407 ); + QCOMPARE( bounds.yMaximum(), 4918383 ); + QCOMPARE( bounds.zMaximum(), 2331 ); } } @@ -687,16 +697,16 @@ void TestQgsEptProvider::testPointCloudRequest() QgsPointCloudIndex *index = layer->dataProvider()->index(); QVERIFY( index->isValid() ); - QVector nodes; - QQueue queue; + QVector nodes; + QQueue queue; queue.push_back( index->root() ); while ( !queue.empty() ) { - IndexedPointCloudNode node = queue.front(); + QgsPointCloudNodeId node = queue.front(); queue.pop_front(); nodes.push_back( node ); - for ( const IndexedPointCloudNode &child : index->nodeChildren( node ) ) + for ( const QgsPointCloudNodeId &child : index->getNode( node ).children() ) { queue.push_back( child ); } @@ -706,7 +716,7 @@ void TestQgsEptProvider::testPointCloudRequest() request.setAttributes( layer->attributes() ); // If request.setFilterRect() is not called, no filter should be applied int count = 0; - for ( IndexedPointCloudNode node : nodes ) + for ( QgsPointCloudNodeId node : nodes ) { std::unique_ptr block( index->nodeData( node, request ) ); count += block->pointCount(); @@ -717,7 +727,7 @@ void TestQgsEptProvider::testPointCloudRequest() QgsRectangle extent( 515390, 4918360, 515400, 4918370 ); request.setFilterRect( extent ); count = 0; - for ( IndexedPointCloudNode node : nodes ) + for ( QgsPointCloudNodeId node : nodes ) { std::unique_ptr block( index->nodeData( node, request ) ); count += block->pointCount(); @@ -728,7 +738,7 @@ void TestQgsEptProvider::testPointCloudRequest() extent = QgsRectangle( 0, 0, 1, 1 ); request.setFilterRect( extent ); count = 0; - for ( IndexedPointCloudNode node : nodes ) + for ( QgsPointCloudNodeId node : nodes ) { std::unique_ptr block( index->nodeData( node, request ) ); count += block->pointCount(); @@ -739,7 +749,7 @@ void TestQgsEptProvider::testPointCloudRequest() count = 0; extent = QgsRectangle(); request.setFilterRect( extent ); - for ( IndexedPointCloudNode node : nodes ) + for ( QgsPointCloudNodeId node : nodes ) { std::unique_ptr block( index->nodeData( node, request ) ); count += block->pointCount(); From e44e1a9c28dc947838316589d5ad3313ae3c3fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Mon, 25 Nov 2024 12:38:18 +0100 Subject: [PATCH 3/9] Clean up after refactor --- src/3d/qgspointcloudlayerchunkloader_p.cpp | 12 ++++++------ src/3d/symbols/qgspointcloud3dsymbol_p.cpp | 19 ++++++++----------- src/core/pointcloud/qgspointcloudindex.cpp | 2 +- src/core/pointcloud/qgspointcloudindex.h | 6 ++++++ tests/src/3d/testqgspointcloud3drendering.cpp | 4 ++-- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/3d/qgspointcloudlayerchunkloader_p.cpp b/src/3d/qgspointcloudlayerchunkloader_p.cpp index e94aa6909e7c..f7e8a4519c26 100644 --- a/src/3d/qgspointcloudlayerchunkloader_p.cpp +++ b/src/3d/qgspointcloudlayerchunkloader_p.cpp @@ -182,12 +182,12 @@ int QgsPointCloudLayerChunkLoaderFactory::primitivesCount( QgsChunkNode *node ) static QgsBox3D nodeBoundsToBox3D( QgsBox3D nodeBounds, const QgsCoordinateTransform &coordinateTransform, double zValueOffset, double zValueScale ) { - QgsVector3D extentMin3D( static_cast( nodeBounds.xMinimum() ), - static_cast( nodeBounds.yMinimum() ), - static_cast( nodeBounds.zMinimum() ) * zValueScale + zValueOffset ); - QgsVector3D extentMax3D( static_cast( nodeBounds.xMaximum() ), - static_cast( nodeBounds.yMaximum() ), - static_cast( nodeBounds.zMaximum() ) * zValueScale + zValueOffset ); + QgsVector3D extentMin3D( nodeBounds.xMinimum(), + nodeBounds.yMinimum(), + nodeBounds.zMinimum() * zValueScale + zValueOffset ); + QgsVector3D extentMax3D( nodeBounds.xMaximum(), + nodeBounds.yMaximum(), + nodeBounds.zMaximum() * zValueScale + zValueOffset ); QgsCoordinateTransform extentTransform = coordinateTransform; extentTransform.setBallparkTransformsAreAppropriate( true ); try diff --git a/src/3d/symbols/qgspointcloud3dsymbol_p.cpp b/src/3d/symbols/qgspointcloud3dsymbol_p.cpp index 4d2a21768244..22d704825dc5 100644 --- a/src/3d/symbols/qgspointcloud3dsymbol_p.cpp +++ b/src/3d/symbols/qgspointcloud3dsymbol_p.cpp @@ -57,15 +57,12 @@ typedef Qt3DCore::QGeometry Qt3DQGeometry; #include // pick a point that we'll use as origin for coordinates for this node's points -static QgsVector3D originFromNodeBounds( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context, const QgsPointCloudBlock *block ) +static QgsVector3D originFromNodeBounds( QgsPointCloudIndex *pc, const QgsPointCloudNodeId &n, const QgsPointCloud3DRenderContext &context ) { - const QgsVector3D blockScale = block->scale(); - const QgsVector3D blockOffset = block->offset(); - QgsBox3D bounds = pc->getNode( n ).bounds(); - double nodeOriginX = bounds.xMinimum() * blockScale.x() + blockOffset.x(); - double nodeOriginY = bounds.yMinimum() * blockScale.y() + blockOffset.y(); - double nodeOriginZ = ( bounds.zMinimum() * blockScale.z() + blockOffset.z() ) * context.zValueScale() + context.zValueFixedOffset(); + double nodeOriginX = bounds.xMinimum(); + double nodeOriginY = bounds.yMinimum(); + double nodeOriginZ = bounds.zMinimum() * context.zValueScale() + context.zValueFixedOffset(); try { context.coordinateTransform().transformInPlace( nodeOriginX, nodeOriginY, nodeOriginZ ); @@ -618,7 +615,7 @@ void QgsSingleColorPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *p if ( !output ) output = &outNormal; - output->positionsOrigin = originFromNodeBounds( pc, n, context, block.get() ); + output->positionsOrigin = originFromNodeBounds( pc, n, context ); for ( int i = 0; i < count; ++i ) { @@ -743,7 +740,7 @@ void QgsColorRampPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, if ( !output ) output = &outNormal; - output->positionsOrigin = originFromNodeBounds( pc, n, context, block.get() ); + output->positionsOrigin = originFromNodeBounds( pc, n, context ); for ( int i = 0; i < count; ++i ) { @@ -865,7 +862,7 @@ void QgsRGBPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const if ( !output ) output = &outNormal; - output->positionsOrigin = originFromNodeBounds( pc, n, context, block.get() ); + output->positionsOrigin = originFromNodeBounds( pc, n, context ); int ir = 0; int ig = 0; @@ -1035,7 +1032,7 @@ void QgsClassificationPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex if ( !output ) output = &outNormal; - output->positionsOrigin = originFromNodeBounds( pc, n, context, block.get() ); + output->positionsOrigin = originFromNodeBounds( pc, n, context ); const QSet filteredOutValues = context.getFilteredOutValues(); for ( int i = 0; i < count; ++i ) diff --git a/src/core/pointcloud/qgspointcloudindex.cpp b/src/core/pointcloud/qgspointcloudindex.cpp index 58e2916bbfc2..724d1053e1ed 100644 --- a/src/core/pointcloud/qgspointcloudindex.cpp +++ b/src/core/pointcloud/qgspointcloudindex.cpp @@ -124,7 +124,7 @@ QgsBox3D QgsPointCloudNode::bounds() const { const QgsBox3D rootBounds = mIndex.rootNodeBounds(); const double d = rootBounds.xMaximum() - rootBounds.xMinimum(); - const double dLevel = ( double )d / pow( 2, mId.d() ); + const double dLevel = d / pow( 2, mId.d() ); const double xMin = rootBounds.xMinimum() + dLevel * mId.x(); const double xMax = rootBounds.xMinimum() + dLevel * ( mId.x() + 1 ); diff --git a/src/core/pointcloud/qgspointcloudindex.h b/src/core/pointcloud/qgspointcloudindex.h index 858728d6fb30..215d9f0ab8fe 100644 --- a/src/core/pointcloud/qgspointcloudindex.h +++ b/src/core/pointcloud/qgspointcloudindex.h @@ -158,16 +158,21 @@ uint qHash( const QgsPointCloudCacheKey &key ); class CORE_EXPORT QgsPointCloudNode { public: + //! Constructs new node object. Should only be called by QgsPointCloudIndex::getNode() QgsPointCloudNode( const QgsPointCloudIndex &index, QgsPointCloudNodeId id, qint64 pointCount, QList childIds ) : mIndex( index ), mId( id ), mPointCount( pointCount ), mChildIds( childIds ) { } + //! Returns node's ID (unique in index) QgsPointCloudNodeId id() const { return mId; } + //! Returns number of points contained in node data qint64 pointCount() const { return mPointCount; } + //! Returns IDs of child nodes QList children() const { return mChildIds; } //! Returns node's error in map units (used to determine in whether the node has enough detail for the current view) float error() const; + //! Returns node's bounding cube in CRS coords QgsBox3D bounds() const; private: @@ -247,6 +252,7 @@ class CORE_EXPORT QgsPointCloudIndex //! Returns whether the octree contain given node virtual bool hasNode( const QgsPointCloudNodeId &n ) const; + //! Returns object for a given node virtual QgsPointCloudNode getNode( const QgsPointCloudNodeId &id ) const; //! Returns all attributes that are stored in the file diff --git a/tests/src/3d/testqgspointcloud3drendering.cpp b/tests/src/3d/testqgspointcloud3drendering.cpp index 988f529ed7fd..df49e68c17ae 100644 --- a/tests/src/3d/testqgspointcloud3drendering.cpp +++ b/tests/src/3d/testqgspointcloud3drendering.cpp @@ -478,9 +478,9 @@ void TestQgsPointCloud3DRendering::testPointCloudFilteredClassification() // find a better fix in the future. QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "pointcloud_3d_filtered_classification", "pointcloud_3d_filtered_classification", img, QString(), 80, QSize( 0, 0 ), 15 ); - mLayer->setSubsetString( "" ); + + QGSVERIFYIMAGECHECK( "pointcloud_3d_filtered_classification", "pointcloud_3d_filtered_classification", img, QString(), 80, QSize( 0, 0 ), 15 ); } void TestQgsPointCloud3DRendering::testPointCloudFilteredSceneExtent() From f61e4113d26c31f8fc4277a001f490bdba98f9be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Mon, 25 Nov 2024 12:53:56 +0100 Subject: [PATCH 4/9] Remove index reference from QgsPointCloudNode --- .../pointcloud/qgscopcpointcloudindex.cpp | 3 ++- src/core/pointcloud/qgseptpointcloudindex.cpp | 2 +- src/core/pointcloud/qgspointcloudindex.cpp | 25 +++++++++++-------- src/core/pointcloud/qgspointcloudindex.h | 18 +++++++++---- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/core/pointcloud/qgscopcpointcloudindex.cpp b/src/core/pointcloud/qgscopcpointcloudindex.cpp index 2c1ad603a2cd..dd6b4987c03d 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.cpp +++ b/src/core/pointcloud/qgscopcpointcloudindex.cpp @@ -460,7 +460,8 @@ QgsPointCloudNode QgsCopcPointCloudIndex::getNode( const QgsPointCloudNodeId &id } } - return QgsPointCloudNode( *this, id, pointCount, children ); + QgsBox3D bounds = QgsPointCloudNode::bounds( mRootBounds, id ); + return QgsPointCloudNode( id, pointCount, children, bounds.width() / mSpan, bounds ); } void QgsCopcPointCloudIndex::copyCommonProperties( QgsCopcPointCloudIndex *destination ) const diff --git a/src/core/pointcloud/qgseptpointcloudindex.cpp b/src/core/pointcloud/qgseptpointcloudindex.cpp index 4542279cf7b9..7f51fb830f04 100644 --- a/src/core/pointcloud/qgseptpointcloudindex.cpp +++ b/src/core/pointcloud/qgseptpointcloudindex.cpp @@ -498,7 +498,7 @@ QgsPointCloudNode QgsEptPointCloudIndex::getNode( const QgsPointCloudNodeId &id QMutexLocker locker( &mHierarchyMutex ); qint64 pointCount = mHierarchy.value( id, -1 ); if ( pointCount != -1 ) - return QgsPointCloudNode( *this, id, pointCount, node.children() ); + return QgsPointCloudNode( id, pointCount, node.children(), node.error(), node.bounds() ); } // If we fail, return with pointCount = -1 anyway diff --git a/src/core/pointcloud/qgspointcloudindex.cpp b/src/core/pointcloud/qgspointcloudindex.cpp index 724d1053e1ed..e52021d27406 100644 --- a/src/core/pointcloud/qgspointcloudindex.cpp +++ b/src/core/pointcloud/qgspointcloudindex.cpp @@ -122,23 +122,27 @@ uint qHash( const QgsPointCloudCacheKey &key ) QgsBox3D QgsPointCloudNode::bounds() const { - const QgsBox3D rootBounds = mIndex.rootNodeBounds(); + return mBounds; +} + +QgsBox3D QgsPointCloudNode::bounds( QgsBox3D rootBounds, QgsPointCloudNodeId id ) +{ const double d = rootBounds.xMaximum() - rootBounds.xMinimum(); - const double dLevel = d / pow( 2, mId.d() ); + const double dLevel = d / pow( 2, id.d() ); - const double xMin = rootBounds.xMinimum() + dLevel * mId.x(); - const double xMax = rootBounds.xMinimum() + dLevel * ( mId.x() + 1 ); - const double yMin = rootBounds.yMinimum() + dLevel * mId.y(); - const double yMax = rootBounds.yMinimum() + dLevel * ( mId.y() + 1 ); - const double zMin = rootBounds.zMinimum() + dLevel * mId.z(); - const double zMax = rootBounds.zMinimum() + dLevel * ( mId.z() + 1 ); + const double xMin = rootBounds.xMinimum() + dLevel * id.x(); + const double xMax = rootBounds.xMinimum() + dLevel * ( id.x() + 1 ); + const double yMin = rootBounds.yMinimum() + dLevel * id.y(); + const double yMax = rootBounds.yMinimum() + dLevel * ( id.y() + 1 ); + const double zMin = rootBounds.zMinimum() + dLevel * id.z(); + const double zMax = rootBounds.zMinimum() + dLevel * ( id.z() + 1 ); return QgsBox3D( xMin, yMin, zMin, xMax, yMax, zMax ); } float QgsPointCloudNode::error() const { - return bounds().width() / mIndex.span(); + return mError; } ///@endcond @@ -186,7 +190,8 @@ QgsPointCloudNode QgsPointCloudIndex::getNode( const QgsPointCloudNodeId &id ) c } } - return QgsPointCloudNode( *this, id, pointCount, children ); + QgsBox3D bounds = QgsPointCloudNode::bounds( mRootBounds, id ); + return QgsPointCloudNode( id, pointCount, children, bounds.width() / mSpan, bounds ); } QgsPointCloudAttributeCollection QgsPointCloudIndex::attributes() const diff --git a/src/core/pointcloud/qgspointcloudindex.h b/src/core/pointcloud/qgspointcloudindex.h index 215d9f0ab8fe..1a4e62879874 100644 --- a/src/core/pointcloud/qgspointcloudindex.h +++ b/src/core/pointcloud/qgspointcloudindex.h @@ -158,10 +158,14 @@ uint qHash( const QgsPointCloudCacheKey &key ); class CORE_EXPORT QgsPointCloudNode { public: - //! Constructs new node object. Should only be called by QgsPointCloudIndex::getNode() - QgsPointCloudNode( const QgsPointCloudIndex &index, QgsPointCloudNodeId id, qint64 pointCount, - QList childIds ) - : mIndex( index ), mId( id ), mPointCount( pointCount ), mChildIds( childIds ) + + /** + * Constructs new node object. Should only be called by QgsPointCloudIndex::getNode(). + * Bounds should always be computed by QgsPointCloudNode::bounds(). + */ + QgsPointCloudNode( QgsPointCloudNodeId id, qint64 pointCount, + QList childIds, float error, QgsBox3D bounds ) + : mId( id ), mPointCount( pointCount ), mChildIds( childIds ), mError( error ), mBounds( bounds ) { } //! Returns node's ID (unique in index) @@ -175,12 +179,16 @@ class CORE_EXPORT QgsPointCloudNode //! Returns node's bounding cube in CRS coords QgsBox3D bounds() const; + //! Returns bounding box of specific node + static QgsBox3D bounds( QgsBox3D rootBounds, QgsPointCloudNodeId id ); + private: - const QgsPointCloudIndex &mIndex; // Specific node metadata: QgsPointCloudNodeId mId; qint64 mPointCount; QList mChildIds; + float mError; + QgsBox3D mBounds; }; /** From 95c8642b2e9ddee768a466578fb37f0ba850cc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 28 Nov 2024 11:01:04 +0100 Subject: [PATCH 5/9] Expose point cloud class statistics to Python --- .../auto_generated/pointcloud/qgspointcloudstatistics.sip.in | 4 ++++ .../auto_generated/pointcloud/qgspointcloudstatistics.sip.in | 4 ++++ src/core/pointcloud/qgspointcloudstatistics.cpp | 5 +++++ src/core/pointcloud/qgspointcloudstatistics.h | 2 ++ 4 files changed, 15 insertions(+) diff --git a/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in b/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in index aa172b6499e2..29f15a551c12 100644 --- a/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in +++ b/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in @@ -18,6 +18,10 @@ struct QgsPointCloudAttributeStatistics double mean; double stDev; int count; + int singleClassCount( int cls ) const; +%Docstring +Returns the count of points in given class or -1 on error +%End }; class QgsPointCloudStatistics diff --git a/python/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in b/python/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in index aa172b6499e2..29f15a551c12 100644 --- a/python/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in +++ b/python/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in @@ -18,6 +18,10 @@ struct QgsPointCloudAttributeStatistics double mean; double stDev; int count; + int singleClassCount( int cls ) const; +%Docstring +Returns the count of points in given class or -1 on error +%End }; class QgsPointCloudStatistics diff --git a/src/core/pointcloud/qgspointcloudstatistics.cpp b/src/core/pointcloud/qgspointcloudstatistics.cpp index ee33c1ec0e61..402384b2958e 100644 --- a/src/core/pointcloud/qgspointcloudstatistics.cpp +++ b/src/core/pointcloud/qgspointcloudstatistics.cpp @@ -50,6 +50,11 @@ void QgsPointCloudAttributeStatistics::cumulateStatistics( const QgsPointCloudAt } } +int QgsPointCloudAttributeStatistics::singleClassCount( int cls ) const +{ + return classCount.value( cls, -1 ); +} + // QgsPointCloudStatistics QgsPointCloudStatistics::QgsPointCloudStatistics() diff --git a/src/core/pointcloud/qgspointcloudstatistics.h b/src/core/pointcloud/qgspointcloudstatistics.h index 6da262cba6b4..faf2be9a274d 100644 --- a/src/core/pointcloud/qgspointcloudstatistics.h +++ b/src/core/pointcloud/qgspointcloudstatistics.h @@ -48,6 +48,8 @@ struct CORE_EXPORT QgsPointCloudAttributeStatistics //! Updates the current point cloud statistics to hold the cumulation of the current statistics and \a stats void cumulateStatistics( const QgsPointCloudAttributeStatistics &stats ); #endif + //! Returns the count of points in given class or -1 on error + int singleClassCount( int cls ) const; }; /** From cd61f2d80fe65b3fd7c569ced72df271effa75bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 28 Nov 2024 12:09:36 +0100 Subject: [PATCH 6/9] Fix tests and crash --- .../pointcloud/qgscopcpointcloudindex.cpp | 6 +- src/core/pointcloud/qgspointcloudindex.cpp | 2 + src/core/pointcloud/qgspointcloudlayer.cpp | 24 ++- .../src/python/test_qgspointcloudprovider.py | 162 +++--------------- 4 files changed, 43 insertions(+), 151 deletions(-) diff --git a/src/core/pointcloud/qgscopcpointcloudindex.cpp b/src/core/pointcloud/qgscopcpointcloudindex.cpp index dd6b4987c03d..ac2b0c06e11d 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.cpp +++ b/src/core/pointcloud/qgscopcpointcloudindex.cpp @@ -449,13 +449,15 @@ QgsPointCloudNode QgsCopcPointCloudIndex::getNode( const QgsPointCloudNodeId &id const int x = id.x() * 2; const int y = id.y() * 2; const int z = id.z() * 2; - mHierarchyMutex.unlock(); for ( int i = 0; i < 8; ++i ) { int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 ); const QgsPointCloudNodeId n2( d, x + dx, y + dy, z + dz ); - if ( fetchNodeHierarchy( n2 ) && mHierarchy[id] >= 0 ) + mHierarchyMutex.unlock(); + bool found = fetchNodeHierarchy( n2 ); + mHierarchyMutex.lock(); + if ( found && mHierarchy[id] >= 0 ) children.append( n2 ); } } diff --git a/src/core/pointcloud/qgspointcloudindex.cpp b/src/core/pointcloud/qgspointcloudindex.cpp index e52021d27406..2fd52bb88a19 100644 --- a/src/core/pointcloud/qgspointcloudindex.cpp +++ b/src/core/pointcloud/qgspointcloudindex.cpp @@ -269,6 +269,8 @@ void QgsPointCloudIndex::copyCommonProperties( QgsPointCloudIndex *destination ) destination->mZMin = mZMin; destination->mZMax = mZMax; destination->mHierarchy = mHierarchy; + destination->mScale = mScale; + destination->mOffset = mOffset; destination->mRootBounds = mRootBounds; destination->mAttributes = mAttributes; destination->mSpan = mSpan; diff --git a/src/core/pointcloud/qgspointcloudlayer.cpp b/src/core/pointcloud/qgspointcloudlayer.cpp index 058006a834ec..2bcf85332a0b 100644 --- a/src/core/pointcloud/qgspointcloudlayer.cpp +++ b/src/core/pointcloud/qgspointcloudlayer.cpp @@ -456,13 +456,6 @@ void QgsPointCloudLayer::setDataSourcePrivate( const QString &dataSource, const calculateStatistics(); } - // Note: we load the statistics from the data provider regardless of it being an existing metadata (do not check for hasStatisticsMetadata) - // since the X, Y & Z coordinates will be in the header of the dataset - if ( mDataProvider && mDataProvider->isValid() && mStatistics.sampledPointsCount() == 0 && mDataProvider->indexingState() == QgsPointCloudDataProvider::Indexed ) - { - mStatistics = mDataProvider->metadataStatistics(); - } - if ( !mRenderer || loadDefaultStyleFlag ) { std::unique_ptr< QgsScopedRuntimeProfile > profile; @@ -864,13 +857,6 @@ void QgsPointCloudLayer::calculateStatistics() QgsMessageLog::logMessage( QObject::tr( "A statistics calculation task for the point cloud %1 is already in progress" ).arg( this->name() ) ); return; } - if ( mStatistics.sampledPointsCount() != 0 ) - { - mStatisticsCalculationState = QgsPointCloudLayer::PointCloudStatisticsCalculationState::Calculated; - emit statisticsCalculationStateChanged( mStatisticsCalculationState ); - resetRenderer(); - return; - } QgsPointCloudStatistics indexStats = mDataProvider->metadataStatistics(); QList indexStatsAttributes = indexStats.statisticsMap().keys(); @@ -885,6 +871,16 @@ void QgsPointCloudLayer::calculateStatistics() } } + if ( attributes.empty() && indexStats.sampledPointsCount() > 0 ) + { + // All attributes are covered by the saved stats, use them directly + mStatistics = indexStats; + mStatisticsCalculationState = QgsPointCloudLayer::PointCloudStatisticsCalculationState::Calculated; + emit statisticsCalculationStateChanged( mStatisticsCalculationState ); + resetRenderer(); + return; + } + QgsPointCloudStatsCalculationTask *task = new QgsPointCloudStatsCalculationTask( mDataProvider->index(), attributes, 1000000 ); connect( task, &QgsTask::taskCompleted, this, [this, task, indexStats, indexStatsAttributes]() { diff --git a/tests/src/python/test_qgspointcloudprovider.py b/tests/src/python/test_qgspointcloudprovider.py index 9a1ab3ad0411..904ca3524c57 100644 --- a/tests/src/python/test_qgspointcloudprovider.py +++ b/tests/src/python/test_qgspointcloudprovider.py @@ -37,93 +37,25 @@ def testStatistics(self): ) self.assertTrue(layer.isValid()) - self.assertEqual( - layer.dataProvider().metadataStatistic( - "X", QgsStatisticalSummary.Statistic.Count - ), - 253, - ) - self.assertEqual( - layer.dataProvider().metadataStatistic( - "X", QgsStatisticalSummary.Statistic.Min - ), - 498062.0, - ) - self.assertEqual( - layer.dataProvider().metadataStatistic( - "X", QgsStatisticalSummary.Statistic.Max - ), - 498067.39, - ) - self.assertAlmostEqual( - layer.dataProvider().metadataStatistic( - "X", QgsStatisticalSummary.Statistic.Range - ), - 5.39000000001397, - 5, - ) - self.assertAlmostEqual( - layer.dataProvider().metadataStatistic( - "X", QgsStatisticalSummary.Statistic.Mean - ), - 498064.7342292491, - 5, - ) - self.assertAlmostEqual( - layer.dataProvider().metadataStatistic( - "X", QgsStatisticalSummary.Statistic.StDev - ), - 1.5636647117681046, - 5, - ) - with self.assertRaises(ValueError): - layer.dataProvider().metadataStatistic( - "X", QgsStatisticalSummary.Statistic.Majority - ) - - with self.assertRaises(ValueError): - layer.dataProvider().metadataStatistic( - "Xxxxx", QgsStatisticalSummary.Statistic.Count - ) - - self.assertEqual( - layer.dataProvider().metadataStatistic( - "Intensity", QgsStatisticalSummary.Statistic.Count - ), - 253, - ) - self.assertEqual( - layer.dataProvider().metadataStatistic( - "Intensity", QgsStatisticalSummary.Statistic.Min - ), - 199, - ) - self.assertEqual( - layer.dataProvider().metadataStatistic( - "Intensity", QgsStatisticalSummary.Statistic.Max - ), - 2086.0, - ) + stats = layer.dataProvider().metadataStatistics() + self.assertEqual(stats.statisticsOf("X").count, 253) + self.assertEqual(stats.statisticsOf("X").minimum, 498062.0) + self.assertEqual(stats.statisticsOf("X").maximum, 498067.39) + self.assertAlmostEqual(stats.statisticsOf("X").mean, 498064.7342292491, 5) + self.assertAlmostEqual(stats.statisticsOf("X").stDev, 1.5636647117681046, 5) + with self.assertRaises(AttributeError): + stats.statisticsOf("X").majority + + self.assertEqual(stats.statisticsOf("Xxxxx").count, 0) + + self.assertEqual(stats.statisticsOf("Intensity").count, 253) + self.assertEqual(stats.statisticsOf("Intensity").minimum, 199) + self.assertEqual(stats.statisticsOf("Intensity").maximum, 2086.0) self.assertAlmostEqual( - layer.dataProvider().metadataStatistic( - "Intensity", QgsStatisticalSummary.Statistic.Range - ), - 1887.0, - 5, + stats.statisticsOf("Intensity").mean, 728.521739130435, 5 ) self.assertAlmostEqual( - layer.dataProvider().metadataStatistic( - "Intensity", QgsStatisticalSummary.Statistic.Mean - ), - 728.521739130435, - 5, - ) - self.assertAlmostEqual( - layer.dataProvider().metadataStatistic( - "Intensity", QgsStatisticalSummary.Statistic.StDev - ), - 440.9652417017358, - 5, + stats.statisticsOf("Intensity").stDev, 440.9652417017358, 5 ) @unittest.skipIf( @@ -138,10 +70,9 @@ def testMetadataClasses(self): ) self.assertTrue(layer.isValid()) - self.assertEqual(layer.dataProvider().metadataClasses("X"), []) - self.assertCountEqual( - layer.dataProvider().metadataClasses("Classification"), [1, 2, 3, 5] - ) + stats = layer.dataProvider().metadataStatistics() + self.assertEqual(stats.classesOf("X"), []) + self.assertCountEqual(stats.classesOf("Classification"), [1, 2, 3, 5]) @unittest.skipIf( "ept" not in QgsProviderRegistry.instance().providerList(), @@ -155,54 +86,15 @@ def testMetadataClassStatistics(self): ) self.assertTrue(layer.isValid()) - with self.assertRaises(ValueError): - self.assertEqual( - layer.dataProvider().metadataClassStatistic( - "X", 0, QgsStatisticalSummary.Statistic.Count - ), - [], - ) - - with self.assertRaises(ValueError): - self.assertEqual( - layer.dataProvider().metadataClassStatistic( - "Classification", 0, QgsStatisticalSummary.Statistic.Count - ), - [], - ) - - with self.assertRaises(ValueError): - self.assertEqual( - layer.dataProvider().metadataClassStatistic( - "Classification", 1, QgsStatisticalSummary.Statistic.Sum - ), - [], - ) + stats = layer.dataProvider().metadataStatistics() - self.assertEqual( - layer.dataProvider().metadataClassStatistic( - "Classification", 1, QgsStatisticalSummary.Statistic.Count - ), - 1, - ) - self.assertEqual( - layer.dataProvider().metadataClassStatistic( - "Classification", 2, QgsStatisticalSummary.Statistic.Count - ), - 160, - ) - self.assertEqual( - layer.dataProvider().metadataClassStatistic( - "Classification", 3, QgsStatisticalSummary.Statistic.Count - ), - 89, - ) - self.assertEqual( - layer.dataProvider().metadataClassStatistic( - "Classification", 5, QgsStatisticalSummary.Statistic.Count - ), - 3, - ) + self.assertEqual(stats.statisticsOf("X").singleClassCount(0), -1) + self.assertEqual(stats.statisticsOf("Classification").singleClassCount(0), -1) + + self.assertEqual(stats.statisticsOf("Classification").singleClassCount(1), 1) + self.assertEqual(stats.statisticsOf("Classification").singleClassCount(2), 160) + self.assertEqual(stats.statisticsOf("Classification").singleClassCount(3), 89) + self.assertEqual(stats.statisticsOf("Classification").singleClassCount(5), 3) @unittest.skipIf( "ept" not in QgsProviderRegistry.instance().providerList(), From 16058f401c3a4f962ad25d2ce4b3fdb403f49887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 28 Nov 2024 17:45:18 +0100 Subject: [PATCH 7/9] Further fix tests --- .../pointcloud/qgscopcpointcloudindex.cpp | 31 +++++++++---------- src/core/pointcloud/qgspointcloudlayer.cpp | 5 +-- tests/src/providers/testqgscopcprovider.cpp | 7 ++--- tests/src/providers/testqgseptprovider.cpp | 12 +------ 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/src/core/pointcloud/qgscopcpointcloudindex.cpp b/src/core/pointcloud/qgscopcpointcloudindex.cpp index ac2b0c06e11d..2f130eb127fb 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.cpp +++ b/src/core/pointcloud/qgscopcpointcloudindex.cpp @@ -432,31 +432,28 @@ bool QgsCopcPointCloudIndex::hasNode( const QgsPointCloudNodeId &n ) const QgsPointCloudNode QgsCopcPointCloudIndex::getNode( const QgsPointCloudNodeId &id ) const { - fetchNodeHierarchy( id ); + Q_ASSERT( fetchNodeHierarchy( id ) ); qint64 pointCount; - - QList children; { QMutexLocker locker( &mHierarchyMutex ); - pointCount = mHierarchy.value( id, -1 ); + } - auto hierarchyIt = mHierarchy.constFind( id ); - Q_ASSERT( hierarchyIt != mHierarchy.constEnd() ); - children.reserve( 8 ); - const int d = id.d() + 1; - const int x = id.x() * 2; - const int y = id.y() * 2; - const int z = id.z() * 2; + QList children; + children.reserve( 8 ); + const int d = id.d() + 1; + const int x = id.x() * 2; + const int y = id.y() * 2; + const int z = id.z() * 2; - for ( int i = 0; i < 8; ++i ) + for ( int i = 0; i < 8; ++i ) + { + int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 ); + const QgsPointCloudNodeId n2( d, x + dx, y + dy, z + dz ); + bool found = fetchNodeHierarchy( n2 ); { - int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 ); - const QgsPointCloudNodeId n2( d, x + dx, y + dy, z + dz ); - mHierarchyMutex.unlock(); - bool found = fetchNodeHierarchy( n2 ); - mHierarchyMutex.lock(); + QMutexLocker locker( &mHierarchyMutex ); if ( found && mHierarchy[id] >= 0 ) children.append( n2 ); } diff --git a/src/core/pointcloud/qgspointcloudlayer.cpp b/src/core/pointcloud/qgspointcloudlayer.cpp index 2bcf85332a0b..e2288ad901d8 100644 --- a/src/core/pointcloud/qgspointcloudlayer.cpp +++ b/src/core/pointcloud/qgspointcloudlayer.cpp @@ -871,10 +871,11 @@ void QgsPointCloudLayer::calculateStatistics() } } + // Use the layer statistics for now, until we can calculate complete ones + mStatistics = indexStats; if ( attributes.empty() && indexStats.sampledPointsCount() > 0 ) { - // All attributes are covered by the saved stats, use them directly - mStatistics = indexStats; + // All attributes are covered by the saved stats, skip calculating anything mStatisticsCalculationState = QgsPointCloudLayer::PointCloudStatisticsCalculationState::Calculated; emit statisticsCalculationStateChanged( mStatisticsCalculationState ); resetRenderer(); diff --git a/tests/src/providers/testqgscopcprovider.cpp b/tests/src/providers/testqgscopcprovider.cpp index be1c34dcaf55..48d273b1c8be 100644 --- a/tests/src/providers/testqgscopcprovider.cpp +++ b/tests/src/providers/testqgscopcprovider.cpp @@ -828,9 +828,9 @@ void TestQgsCopcProvider::testPointCloudIndex() QVERIFY( index->isValid() ); QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "0-0-0-0" ) ) ).pointCount(), 56721 ); - QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ).pointCount(), -1 ); + QVERIFY( !index->hasNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ) ); QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "2-3-3-1" ) ) ).pointCount(), 446 ); - QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "9-9-9-9" ) ) ).pointCount(), -1 ); + QVERIFY( !index->hasNode( QgsPointCloudNodeId::fromString( QStringLiteral( "9-9-9-9" ) ) ) ); QCOMPARE( index->pointCount(), 518862 ); QCOMPARE( index->zMin(), 2322.89625 ); @@ -840,7 +840,6 @@ void TestQgsCopcProvider::testPointCloudIndex() QCOMPARE( index->span(), 128 ); QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "0-0-0-0" ) ) ).error(), 0.328125 ); - QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ).error(), 0.1640625 ); QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "2-3-3-1" ) ) ).error(), 0.08203125 ); { @@ -854,7 +853,7 @@ void TestQgsCopcProvider::testPointCloudIndex() } { - QgsBox3D bounds = index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ).bounds(); + QgsBox3D bounds = QgsPointCloudNode::bounds( index->rootNodeBounds(), QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ); QCOMPARE( bounds.xMinimum(), 515389 ); QCOMPARE( bounds.yMinimum(), 4918361 ); QCOMPARE( bounds.zMinimum(), 2343 ); diff --git a/tests/src/providers/testqgseptprovider.cpp b/tests/src/providers/testqgseptprovider.cpp index e312fc8a8419..2797ab7e6504 100644 --- a/tests/src/providers/testqgseptprovider.cpp +++ b/tests/src/providers/testqgseptprovider.cpp @@ -633,7 +633,7 @@ void TestQgsEptProvider::testPointCloudIndex() QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "0-0-0-0" ) ) ).pointCount(), 41998 ); QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "1-1-1-1" ) ) ).pointCount(), 48879 ); QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "2-3-3-1" ) ) ).pointCount(), 41734 ); - QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "9-9-9-9" ) ) ).pointCount(), -1 ); + QVERIFY( !index->hasNode( QgsPointCloudNodeId::fromString( QStringLiteral( "9-9-9-9" ) ) ) ); QCOMPARE( index->pointCount(), 518862 ); QCOMPARE( index->zMin(), 2322 ); @@ -647,15 +647,6 @@ void TestQgsEptProvider::testPointCloudIndex() QCOMPARE( index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "2-3-3-1" ) ) ).error(), 0.0859375 ); { -<<<<<<< HEAD - QgsPointCloudDataBounds bounds = index->nodeBounds( IndexedPointCloudNode::fromString( QStringLiteral( "0-0-0-0" ) ) ); - QCOMPARE( bounds.xMin(), -88000 ); - QCOMPARE( bounds.yMin(), -88000 ); - QCOMPARE( bounds.zMin(), -88000 ); - QCOMPARE( bounds.xMax(), 88000 ); - QCOMPARE( bounds.yMax(), 88000 ); - QCOMPARE( bounds.zMax(), 88000 ); -======= QgsBox3D bounds = index->getNode( QgsPointCloudNodeId::fromString( QStringLiteral( "0-0-0-0" ) ) ).bounds(); QCOMPARE( bounds.xMinimum(), 515363 ); QCOMPARE( bounds.yMinimum(), 4918339 ); @@ -663,7 +654,6 @@ void TestQgsEptProvider::testPointCloudIndex() QCOMPARE( bounds.xMaximum(), 515407 ); QCOMPARE( bounds.yMaximum(), 4918383 ); QCOMPARE( bounds.zMaximum(), 2353 ); ->>>>>>> 4bac513354a (Refactor QgsPointCloudIndex) } { From 44d5399ea5fc8f42622c83e881c3f0f9c3e4bd83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Fri, 29 Nov 2024 13:25:23 +0100 Subject: [PATCH 8/9] Minor changes per review --- .../pointcloud/qgspointcloudstatistics.sip.in | 3 +++ .../pointcloud/qgspointcloudstatistics.sip.in | 3 +++ src/core/pointcloud/qgscopcpointcloudindex.h | 4 ++-- src/core/pointcloud/qgseptpointcloudindex.h | 4 ++-- src/core/pointcloud/qgspointcloudstatistics.h | 6 +++++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in b/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in index 29f15a551c12..f2d213bd823c 100644 --- a/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in +++ b/python/PyQt6/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in @@ -18,9 +18,12 @@ struct QgsPointCloudAttributeStatistics double mean; double stDev; int count; + int singleClassCount( int cls ) const; %Docstring Returns the count of points in given class or -1 on error + +.. versionadded:: 3.42 %End }; diff --git a/python/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in b/python/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in index 29f15a551c12..f2d213bd823c 100644 --- a/python/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in +++ b/python/core/auto_generated/pointcloud/qgspointcloudstatistics.sip.in @@ -18,9 +18,12 @@ struct QgsPointCloudAttributeStatistics double mean; double stDev; int count; + int singleClassCount( int cls ) const; %Docstring Returns the count of points in given class or -1 on error + +.. versionadded:: 3.42 %End }; diff --git a/src/core/pointcloud/qgscopcpointcloudindex.h b/src/core/pointcloud/qgscopcpointcloudindex.h index b54bf474f61f..a5a9be56306a 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.h +++ b/src/core/pointcloud/qgscopcpointcloudindex.h @@ -52,7 +52,7 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex void load( const QString &fileName ) override; bool hasNode( const QgsPointCloudNodeId &n ) const override; - virtual QgsPointCloudNode getNode( const QgsPointCloudNodeId &id ) const override; + QgsPointCloudNode getNode( const QgsPointCloudNodeId &id ) const override; std::unique_ptr< QgsPointCloudBlock> nodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) override; QgsPointCloudBlockRequest *asyncNodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request ) override; @@ -76,7 +76,7 @@ class CORE_EXPORT QgsCopcPointCloudIndex: public QgsPointCloudIndex * If the dataset doesn't contain statistics EVLR, an object with 0 samples will be returned. * \since QGIS 3.42 */ - virtual QgsPointCloudStatistics metadataStatistics() const override; + QgsPointCloudStatistics metadataStatistics() const override; /** * Copies common properties to the \a destination index diff --git a/src/core/pointcloud/qgseptpointcloudindex.h b/src/core/pointcloud/qgseptpointcloudindex.h index 382e269cb843..d49a7c120f7f 100644 --- a/src/core/pointcloud/qgseptpointcloudindex.h +++ b/src/core/pointcloud/qgseptpointcloudindex.h @@ -52,9 +52,9 @@ class CORE_EXPORT QgsEptPointCloudIndex: public QgsPointCloudIndex QgsCoordinateReferenceSystem crs() const override; qint64 pointCount() const override; - virtual QgsPointCloudNode getNode( const QgsPointCloudNodeId &id ) const override; + QgsPointCloudNode getNode( const QgsPointCloudNodeId &id ) const override; QVariantMap originalMetadata() const override { return mOriginalMetadata; } - virtual QgsPointCloudStatistics metadataStatistics() const override; + QgsPointCloudStatistics metadataStatistics() const override; bool isValid() const override; QgsPointCloudIndex::AccessType accessType() const override; diff --git a/src/core/pointcloud/qgspointcloudstatistics.h b/src/core/pointcloud/qgspointcloudstatistics.h index faf2be9a274d..7517cf9baaed 100644 --- a/src/core/pointcloud/qgspointcloudstatistics.h +++ b/src/core/pointcloud/qgspointcloudstatistics.h @@ -48,7 +48,11 @@ struct CORE_EXPORT QgsPointCloudAttributeStatistics //! Updates the current point cloud statistics to hold the cumulation of the current statistics and \a stats void cumulateStatistics( const QgsPointCloudAttributeStatistics &stats ); #endif - //! Returns the count of points in given class or -1 on error + + /** + * Returns the count of points in given class or -1 on error + * \since QGIS 3.42 + */ int singleClassCount( int cls ) const; }; From 19f7b96b78f3e8d35dec9ed9432d809d14a72fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Mon, 2 Dec 2024 22:32:59 +0100 Subject: [PATCH 9/9] Don't skip call in build without asserts --- src/core/pointcloud/qgscopcpointcloudindex.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/pointcloud/qgscopcpointcloudindex.cpp b/src/core/pointcloud/qgscopcpointcloudindex.cpp index 2f130eb127fb..227c5d3e4555 100644 --- a/src/core/pointcloud/qgscopcpointcloudindex.cpp +++ b/src/core/pointcloud/qgscopcpointcloudindex.cpp @@ -432,7 +432,8 @@ bool QgsCopcPointCloudIndex::hasNode( const QgsPointCloudNodeId &n ) const QgsPointCloudNode QgsCopcPointCloudIndex::getNode( const QgsPointCloudNodeId &id ) const { - Q_ASSERT( fetchNodeHierarchy( id ) ); + bool nodeFound = fetchNodeHierarchy( id ); + Q_ASSERT( nodeFound ); qint64 pointCount; {