Implement virtual point cloud overviews in 2D

Load overview if it exists either by ref from vpc file or look for
overview file.
Render overview instead of extends by default when zoomed out.
This commit is contained in:
Withalion 2024-12-10 21:13:49 +01:00 committed by Martin Dobias
parent c45551b26e
commit a0fb608f69
5 changed files with 102 additions and 28 deletions

View File

@ -45,6 +45,9 @@
#include "qgscopcpointcloudindex.h"
#endif
#include "qgsvirtualpointcloudprovider.h"
#include <QUrl>
QgsPointCloudLayer::QgsPointCloudLayer( const QString &uri,
@ -956,6 +959,7 @@ void QgsPointCloudLayer::loadIndexesForRenderContext( QgsRenderContext &renderer
}
const QVector<QgsPointCloudSubIndex> subIndex = mDataProvider->subIndexes();
const QgsVirtualPointCloudProvider &vpcProvider = dynamic_cast<QgsVirtualPointCloudProvider &>( *mDataProvider );
for ( int i = 0; i < subIndex.size(); ++i )
{
// no need to load as it's there
@ -963,7 +967,8 @@ void QgsPointCloudLayer::loadIndexesForRenderContext( QgsRenderContext &renderer
continue;
if ( subIndex.at( i ).extent().intersects( renderExtent ) &&
renderExtent.width() < subIndex.at( i ).extent().width() )
renderExtent.width() < vpcProvider.averageSubIndexWidth() &&
renderExtent.height() < vpcProvider.averageSubIndexHeight() )
{
mDataProvider->loadSubIndex( i );
}

View File

@ -300,4 +300,4 @@ class CORE_EXPORT QgsPointCloudLayer : public QgsMapLayer, public QgsAbstractPro
};
#endif // QGSPOINTCLOUDPLAYER_H
#endif // QGSPOINTCLOUDLAYER_H

View File

@ -36,6 +36,8 @@
#include "qgspointcloudrequest.h"
#include "qgsrendercontext.h"
#include "qgsruntimeprofiler.h"
#include "qgsapplication.h"
#include "qgsvirtualpointcloudprovider.h"
#include <delaunator.hpp>
@ -198,37 +200,52 @@ bool QgsPointCloudLayerRenderer::render()
}
else
{
mSubIndexExtentRenderer->startRender( context );
const QgsVirtualPointCloudProvider &vpcProvider = dynamic_cast<QgsVirtualPointCloudProvider &>( *mLayer->dataProvider() );
QVector< QgsPointCloudSubIndex > visibleIndexes;
for ( const auto &si : mSubIndexes )
{
if ( canceled )
break;
if ( renderExtent.intersects( si.extent() ) )
{
visibleIndexes.append( si );
}
}
// if the overview of virtual point cloud exists we render it when we are zoomed out
if ( vpcProvider.overview() != nullptr &&
renderExtent.width() > vpcProvider.averageSubIndexWidth() &&
renderExtent.height() > vpcProvider.averageSubIndexHeight() )
{
renderIndex( vpcProvider.overview() );
}
else
{
mSubIndexExtentRenderer->startRender( context );
for ( const auto &si : visibleIndexes )
{
if ( canceled )
break;
QgsPointCloudIndex pc = si.index();
if ( !renderExtent.intersects( si.extent() ) )
continue;
if ( !pc || !pc.isValid() || renderExtent.width() > si.extent().width() )
{
// when dealing with virtual point clouds, we want to render the individual extents when zoomed out
// and only use the selected renderer when zoomed in
mSubIndexExtentRenderer->renderExtent( si.polygonBounds(), context );
// render the label of point cloud tile
if ( mSubIndexExtentRenderer->showLabels() )
if ( !pc || !pc.isValid() )
{
mSubIndexExtentRenderer->renderLabel(
context.renderContext().mapToPixel().transformBounds( si.extent().toRectF() ),
si.uri().section( "/", -1 ).section( ".", 0, 0 ),
context );
// TODO: render the individual extents when zoomed out and users requests them
mSubIndexExtentRenderer->renderExtent( si.polygonBounds(), context );
// render the label of point cloud tile
if ( mSubIndexExtentRenderer->showLabels() )
{
mSubIndexExtentRenderer->renderLabel(
context.renderContext().mapToPixel().transformBounds( si.extent().toRectF() ),
si.uri().section( "/", -1 ).section( ".", 0, 0 ),
context );
}
}
else
{
canceled = !renderIndex( pc );
}
}
else
{
canceled = !renderIndex( pc );
}
mSubIndexExtentRenderer->stopRender( context );
}
mSubIndexExtentRenderer->stopRender( context );
}
mRenderer->stopRender( context );

View File

@ -177,6 +177,8 @@ void QgsVirtualPointCloudProvider::parseFile()
}
QSet<QString> attributeNames;
double subIndexesWidth = 0.0;
double subIndexesHeight = 0.0;
for ( const auto &f : data["features"] )
{
@ -202,12 +204,35 @@ void QgsVirtualPointCloudProvider::parseFile()
QgsGeometry geometry;
QgsDoubleRange zRange;
for ( const auto &asset : f["assets"] )
// look directly for link to data file
if ( f["assets"].find( "data" ) != f["assets"].end() )
{
if ( asset.contains( "href" ) )
if ( f["assets"]["data"].contains( "href" ) )
{
uri = QString::fromStdString( asset["href"] );
break;
uri = QString::fromStdString( f["assets"]["data"]["href"] );
}
}
// look for vpc overview reference
if ( mOverview == nullptr && f["assets"].find( "overview" ) != f["assets"].end() )
{
if ( f["assets"]["overview"].contains( "href" ) )
{
mOverview = std::make_unique<QgsCopcPointCloudIndex>();
mOverview->load( fInfo.absoluteDir().absoluteFilePath( QString::fromStdString( f["assets"]["overview"]["href"] ) ) );
}
}
// if it doesn't exist look for overview file in the directory
else if ( mOverview == nullptr )
{
QDir vpcDir = fInfo.absoluteDir();
QStringList nameFilter = { "*overview.*laz" };
vpcDir.setNameFilters( nameFilter );
vpcDir.setFilter( QDir::Files );
if ( !vpcDir.entryList().empty() )
{
mOverview = std::make_unique<QgsCopcPointCloudIndex>();
mOverview->load( vpcDir.absoluteFilePath( vpcDir.entryList().first() ) );
}
}
@ -364,12 +389,16 @@ void QgsVirtualPointCloudProvider::parseFile()
}
}
subIndexesWidth += extent.width();
subIndexesHeight += extent.height();
mPolygonBounds->addPart( geometry );
mPointCount += count;
QgsPointCloudSubIndex si( uri, geometry, extent, zRange, count );
mSubLayers.push_back( si );
}
mExtent = mPolygonBounds->boundingBox();
mAverageSubIndexWidth = subIndexesWidth / mSubLayers.size();
mAverageSubIndexHeight = subIndexesHeight / mSubLayers.size();
populateAttributeCollection( attributeNames );
}

View File

@ -61,6 +61,26 @@ class CORE_EXPORT QgsVirtualPointCloudProvider: public QgsPointCloudDataProvider
QgsPointCloudRenderer *createRenderer( const QVariantMap &configuration = QVariantMap() ) const override SIP_FACTORY;
bool renderInPreview( const QgsDataProvider::PreviewContext & ) override { return false; }
/**
* Returns pointer to the overview index. May be NULLPTR if it doesn't exist.
* \since QGIS 3.42
*/
QgsPointCloudIndex *overview() const { return mOverview.get(); }
/**
* Returns the calculated average width of point clouds.
* \note We use this value to calculate when to switch between overview and point clouds
* \since QGIS 3.42
*/
double averageSubIndexWidth() const { return mAverageSubIndexWidth; }
/**
* Returns the calculated average height of point clouds.
* \note We use this value to calculate when to switch between overview and point clouds
* \since QGIS 3.42
*/
double averageSubIndexHeight() const { return mAverageSubIndexHeight; }
signals:
void subIndexLoaded( int i );
@ -70,11 +90,14 @@ class CORE_EXPORT QgsVirtualPointCloudProvider: public QgsPointCloudDataProvider
QVector<QgsPointCloudSubIndex> mSubLayers;
std::unique_ptr<QgsGeometry> mPolygonBounds;
QgsPointCloudAttributeCollection mAttributes;
std::unique_ptr<QgsPointCloudIndex> mOverview;
QStringList mUriList;
QgsRectangle mExtent;
qint64 mPointCount = 0;
QgsCoordinateReferenceSystem mCrs;
double mAverageSubIndexWidth = 0;
double mAverageSubIndexHeight = 0;
};
class QgsVirtualPointCloudProviderMetadata : public QgsProviderMetadata