Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,12 @@ not.
.. versionadded:: 4.0
%End

QgsPointCloudIndex overview() const;
QVector<QgsPointCloudIndex> overviews() const;
%Docstring
Returns the overview point cloud index associated with the layer (only
if the layer has a virtual point cloud data provider).
Returns a list of all overview point cloud indexes associated with the
layer (only if the layer has a virtual point cloud data provider).

.. versionadded:: 4.0
.. versionadded:: 4.2
%End

signals:
Expand Down
7 changes: 5 additions & 2 deletions src/3d/qgspointcloudlayerchunkloader_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,11 @@ QgsPointCloudIndex QgsPointCloudLayerChunkedEntity::resolveIndex( const QgsPoint
return pcl->subIndexes().at( indexPosition ).index();
else if ( indexPosition == -1 && !pcl->isVpc() )
return pcl->index();
else if ( indexPosition == -2 && pcl->isVpc() )
return pcl->overview();
else if ( indexPosition < -1 && pcl->isVpc() )
{
const int ovId = -indexPosition - 2;
return pcl->overviews().at( ovId );
}
else
return QgsPointCloudIndex();
}
Expand Down
7 changes: 7 additions & 0 deletions src/3d/qgspointcloudlayerchunkloader_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ class QgsPointCloudLayerChunkedEntity : public QgsChunkedEntity
void updateIndex();

private:
/**
* Get the point cloud index from a layer using the index position, according to the following scheme:
* Zero or positive \a indexPosition will return the nth zero-based sub index of a VPC layer.
* -1 will return the point cloud index of a standard non VPC layer.
* -2 will return the first VPC overview index.
* -3 will return the second VPC overview index, and so on.
*/
static QgsPointCloudIndex resolveIndex( const QgsPointCloudLayer *pcl, int indexPosition );

QgsPointCloudLayer *mLayer = nullptr;
Expand Down
41 changes: 24 additions & 17 deletions src/3d/qgsvirtualpointcloudentity_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,19 @@ QgsVirtualPointCloudEntity::QgsVirtualPointCloudEntity(
createChunkedEntityForSubIndex( i );
}

if ( provider()->overview() )
const QVector<QgsPointCloudIndex> overviews( provider()->overviews() );
for ( int i = 0; i < overviews.size(); ++i )
{
// use -2 as a special identifier for overview files in chunked entity
mOverviewEntity
= new QgsPointCloudLayerChunkedEntity( mapSettings(), mLayer, -2, mCoordinateTransform, dynamic_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ), mMaximumScreenSpaceError, false, mZValueScale, mZValueOffset, mPointBudget );
mOverviewEntity->setParent( this );
connect( mOverviewEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
connect( mOverviewEntity, &QgsChunkedEntity::newEntityCreated, this, &Qgs3DMapSceneEntity::newEntityCreated );
emit newEntityCreated( mOverviewEntity );
// Overview indexes start at -2 and decrease, see QgsPointCloudLayerChunkedEntity::resolveIndex()
const int ovId = -i - 2;
QgsPointCloudLayerChunkedEntity *ovEnt(
new QgsPointCloudLayerChunkedEntity( mapSettings(), mLayer, ovId, mCoordinateTransform, dynamic_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ), mMaximumScreenSpaceError, false, mZValueScale, mZValueOffset, mPointBudget )
);
ovEnt->setParent( this );
connect( ovEnt, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
connect( ovEnt, &QgsChunkedEntity::newEntityCreated, this, &Qgs3DMapSceneEntity::newEntityCreated );
mOverviewEntities.append( ovEnt );
emit newEntityCreated( ovEnt );
}

// this is a rather arbitrary point, it could be somewhere else, ideally near the actual data
Expand All @@ -84,8 +88,8 @@ QgsVirtualPointCloudEntity::~QgsVirtualPointCloudEntity()
qDeleteAll( mChunkedEntitiesMap );
mChunkedEntitiesMap.clear();

delete mOverviewEntity;
mOverviewEntity = nullptr;
qDeleteAll( mOverviewEntities );
mOverviewEntities.clear();
}

QList<QgsChunkedEntity *> QgsVirtualPointCloudEntity::chunkedEntities() const
Expand Down Expand Up @@ -164,16 +168,19 @@ void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneCon
}
updateBboxEntity();

if ( provider()->overview()
if ( !provider()->overviews().isEmpty()
&& rendererBehavior
&& ( rendererBehavior->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverview || rendererBehavior->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) )
{
// no need to render the overview if all sub indexes are shown
if ( !mChunkedEntitiesMap.isEmpty() && subIndexesRendered == mChunkedEntitiesMap.size() )
mOverviewEntity->setEnabled( false );
else
mOverviewEntity->setEnabled( true );
mOverviewEntity->handleSceneUpdate( sceneContext );
for ( const auto &ovEnt : std::as_const( mOverviewEntities ) )
{
// no need to render the overview if all sub indexes are shown
if ( !mChunkedEntitiesMap.isEmpty() && subIndexesRendered == mChunkedEntitiesMap.size() )
ovEnt->setEnabled( false );
else
ovEnt->setEnabled( true );
ovEnt->handleSceneUpdate( sceneContext );
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/3d/qgsvirtualpointcloudentity_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class QgsVirtualPointCloudEntity : public Qgs3DMapSceneEntity
QgsPointCloudLayer *mLayer = nullptr;
QMap<int, QgsChunkedEntity *> mChunkedEntitiesMap;
QgsChunkBoundsEntity *mBboxesEntity = nullptr;
QgsPointCloudLayerChunkedEntity *mOverviewEntity = nullptr;
QList<QgsPointCloudLayerChunkedEntity *> mOverviewEntities;
QList<QgsBox3D> mBboxes;
QgsCoordinateTransform mCoordinateTransform;
std::unique_ptr<QgsPointCloud3DSymbol> mSymbol;
Expand Down
2 changes: 1 addition & 1 deletion src/app/3d/qgspointcloud3dsymbolwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ QgsPointCloud3DSymbolWidget::QgsPointCloud3DSymbolWidget( QgsPointCloudLayer *la
mZoomOutOptions->addItem( tr( "Show Extents Only" ), QVariant::fromValue( Qgis::PointCloudZoomOutRenderBehavior::RenderExtents ) );
if ( const QgsVirtualPointCloudProvider *vpcProvider = dynamic_cast<QgsVirtualPointCloudProvider *>( mLayer->dataProvider() ) )
{
if ( vpcProvider->overview() )
if ( !vpcProvider->overviews().isEmpty() )
{
mZoomOutOptions->addItem( tr( "Show Overview Only" ), QVariant::fromValue( Qgis::PointCloudZoomOutRenderBehavior::RenderOverview ) );
mZoomOutOptions->addItem( tr( "Show Extents Over Overview" ), QVariant::fromValue( Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) );
Expand Down
2 changes: 1 addition & 1 deletion src/app/layers/qgsapplayerhandling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ void QgsAppLayerHandling::postProcessAddedLayer( QgsMapLayer *layer )
// if overview of the virtual point cloud exists set the zoom out behavior to show it
if ( const QgsVirtualPointCloudProvider *vpcProvider = dynamic_cast<QgsVirtualPointCloudProvider *>( pcLayer->dataProvider() ) )
{
renderer3D->setZoomOutBehavior( vpcProvider->overview() ? Qgis::PointCloudZoomOutRenderBehavior::RenderOverview : Qgis::PointCloudZoomOutRenderBehavior::RenderExtents );
renderer3D->setZoomOutBehavior( vpcProvider->overviews().isEmpty() ? Qgis::PointCloudZoomOutRenderBehavior::RenderExtents : Qgis::PointCloudZoomOutRenderBehavior::RenderOverview );
}
layer->setRenderer3D( renderer3D.release() );
}
Expand Down
6 changes: 3 additions & 3 deletions src/core/pointcloud/qgspointcloudlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1283,11 +1283,11 @@ QVector<QgsPointCloudSubIndex> QgsPointCloudLayer::subIndexes() const
return indexes;
}

QgsPointCloudIndex QgsPointCloudLayer::overview() const
QVector<QgsPointCloudIndex> QgsPointCloudLayer::overviews() const
{
if ( !mDataProvider || !mIsVpc )
return QgsPointCloudIndex();
return {};

const QgsVirtualPointCloudProvider *vpcProvider = dynamic_cast<QgsVirtualPointCloudProvider *>( mDataProvider.get() );
return vpcProvider->overview();
return vpcProvider->overviews();
}
6 changes: 3 additions & 3 deletions src/core/pointcloud/qgspointcloudlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,11 @@ class CORE_EXPORT QgsPointCloudLayer : public QgsMapLayer, public QgsAbstractPro
bool isVpc() const { return mIsVpc; }

/**
* Returns the overview point cloud index associated with the layer (only if the layer has a virtual point cloud data provider).
* Returns a list of all overview point cloud indexes associated with the layer (only if the layer has a virtual point cloud data provider).
*
* \since QGIS 4.0
* \since QGIS 4.2
*/
QgsPointCloudIndex overview() const;
QVector<QgsPointCloudIndex> overviews() const;

signals:

Expand Down
73 changes: 43 additions & 30 deletions src/core/pointcloud/qgspointcloudlayerrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ QgsPointCloudLayerRenderer::QgsPointCloudLayerRenderer( QgsPointCloudLayer *laye
mIsVpc = true;
mAverageSubIndexWidth = vpcProvider->averageSubIndexWidth();
mAverageSubIndexHeight = vpcProvider->averageSubIndexHeight();
mOverviewIndex = vpcProvider->overview();
mOverviewIndexes = vpcProvider->overviews();
}

mCloudExtent = layer->dataProvider()->polygonBounds();
Expand Down Expand Up @@ -218,47 +218,60 @@ bool QgsPointCloudLayerRenderer::render()
visibleIndexes.append( si );
}
}

const double overviewSwitchingScale = mRenderer->overviewSwitchingScale();
const bool zoomedOut = renderExtent.width() > mAverageSubIndexWidth * overviewSwitchingScale || renderExtent.height() > mAverageSubIndexHeight * overviewSwitchingScale;
// if the overview of virtual point cloud exists, and we are zoomed out, we render just overview
if ( mOverviewIndex && mOverviewIndex->isValid() && zoomedOut && mRenderer->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverview )

bool shouldRenderOverviews, shouldRenderExtents;
switch ( mRenderer->zoomOutBehavior() )
{
renderIndex( *mOverviewIndex );
case Qgis::PointCloudZoomOutRenderBehavior::RenderOverview:
shouldRenderOverviews = true;
shouldRenderExtents = false;
break;
case Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents:
shouldRenderOverviews = true;
shouldRenderExtents = true;
break;
case Qgis::PointCloudZoomOutRenderBehavior::RenderExtents:
shouldRenderOverviews = false;
shouldRenderExtents = true;
break;
}
else

if ( zoomedOut && shouldRenderOverviews )
{
// if the overview of virtual point cloud exists, and we are zoomed out, but we want both overview and extents,
// we render overview
if ( mOverviewIndex && mOverviewIndex->isValid() && zoomedOut && mRenderer->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents )
for ( QgsPointCloudIndex &ovIdx : mOverviewIndexes )
{
renderIndex( *mOverviewIndex );
if ( ovIdx.isValid() && renderExtent.intersects( ovIdx.extent() ) )
renderIndex( ovIdx );
}
mSubIndexExtentRenderer->startRender( context );
for ( const QgsPointCloudSubIndex &si : visibleIndexes )
{
if ( canceled )
break;
}

QgsPointCloudIndex pc = si.index();
// if the index of point cloud is invalid, or we are zoomed out and want extents, we render the point cloud extent
if ( !pc
|| !pc.isValid()
|| ( ( mRenderer->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderExtents || mRenderer->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) && zoomedOut ) )
{
mSubIndexExtentRenderer->renderExtent( si.polygonBounds(), context );
if ( mSubIndexExtentRenderer->showLabels() )
{
mSubIndexExtentRenderer->renderLabel( context.renderContext().mapToPixel().transformBounds( si.extent().toRectF() ), si.uri().section( "/", -1 ).section( ".", 0, 0 ), context );
}
}
// else we just render the visible point cloud
else
mSubIndexExtentRenderer->startRender( context );
for ( const QgsPointCloudSubIndex &si : visibleIndexes )
{
if ( canceled )
break;

QgsPointCloudIndex pc = si.index();
// if the index of point cloud is invalid, or we are zoomed out and want extents, we render the point cloud extent
if ( ( zoomedOut && shouldRenderExtents ) || ( !zoomedOut && ( !pc || !pc.isValid() ) ) )
{
mSubIndexExtentRenderer->renderExtent( si.polygonBounds(), context );
if ( mSubIndexExtentRenderer->showLabels() )
{
canceled = !renderIndex( pc );
mSubIndexExtentRenderer->renderLabel( context.renderContext().mapToPixel().transformBounds( si.extent().toRectF() ), si.uri().section( "/", -1 ).section( ".", 0, 0 ), context );
}
}
mSubIndexExtentRenderer->stopRender( context );

// When properly zoomed, render the visible point cloud
if ( pc && pc.isValid() && !zoomedOut )
{
canceled = !renderIndex( pc );
}
}
mSubIndexExtentRenderer->stopRender( context );
}

mRenderer->stopRender( context );
Expand Down
2 changes: 1 addition & 1 deletion src/core/pointcloud/qgspointcloudlayerrenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class CORE_EXPORT QgsPointCloudLayerRenderer : public QgsMapLayerRenderer

bool mIsVpc = false;
const QVector< QgsPointCloudSubIndex > mSubIndexes;
std::optional<QgsPointCloudIndex> mOverviewIndex;
QVector<QgsPointCloudIndex> mOverviewIndexes;
double mAverageSubIndexWidth = 0;
double mAverageSubIndexHeight = 0;

Expand Down
53 changes: 33 additions & 20 deletions src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "qgsvirtualpointcloudprovider.h"

#include <algorithm>
#include <memory>
#include <nlohmann/json.hpp>

Expand Down Expand Up @@ -218,9 +219,9 @@ void QgsVirtualPointCloudProvider::parseFile()
}

QSet<QString> attributeNames;
QSet<QString> overviewUris;
double subIndexesWidth = 0.0;
double subIndexesHeight = 0.0;
bool noOverviewFound = false;

for ( const auto &f : data["features"] )
{
Expand Down Expand Up @@ -261,25 +262,14 @@ void QgsVirtualPointCloudProvider::parseFile()
mAllLocalFiles = false;
}

// look for vpc overview reference
if ( !noOverviewFound && !mOverview && f["assets"].contains( "overview" ) && f["assets"]["overview"].contains( "href" ) )
for ( const nlohmann::json &ass : f["assets"] )
{
mOverview = QgsPointCloudIndex( new QgsCopcPointCloudIndex() );
const QUrl overviewUrl = url.resolved( QUrl( QString::fromStdString( f["assets"]["overview"]["href"] ) ) );
mOverview.load( overviewUrl.isLocalFile() ? overviewUrl.toLocalFile() : overviewUrl.toString(), authcfg );
}
// if it doesn't exist look for overview file in the directory
else if ( !noOverviewFound && !mOverview )
{
const QString baseName = QFileInfo( url.fileName() ).baseName();
const QUrl overviewUrl = url.resolved( QUrl( baseName + u"-overview.copc.laz"_s ) );
mOverview = QgsPointCloudIndex( new QgsCopcPointCloudIndex() );
mOverview.load( overviewUrl.isLocalFile() ? overviewUrl.toLocalFile() : overviewUrl.toString(), authcfg );
}
if ( !noOverviewFound && !mOverview.isValid() )
{
mOverview = QgsPointCloudIndex();
noOverviewFound = true;
if ( ass.contains( "roles" ) && ass.contains( "href" ) )
{
nlohmann::json roles = ass["roles"];
if ( std::find( roles.cbegin(), roles.cend(), "overview" ) != roles.cend() )
overviewUris.insert( QString::fromStdString( ass["href"] ) );
}
}

// Only COPC and EPT formats are currently supported. Other files will only have their bounds rendered
Expand Down Expand Up @@ -461,6 +451,29 @@ void QgsVirtualPointCloudProvider::parseFile()
QgsPointCloudSubIndex si( uri, geometry, extent, zRange, count );
mSubLayers.push_back( si );
}

// Load gathered overviews
for ( const QString &uri : std::as_const( overviewUris ) )
{
const QUrl overviewUrl = url.resolved( QUrl( uri ) );

QgsPointCloudIndex ovIdx( new QgsCopcPointCloudIndex() );
ovIdx.load( overviewUrl.isLocalFile() ? overviewUrl.toLocalFile() : overviewUrl.toString(), authcfg );
if ( ovIdx.isValid() )
mOverviews.append( std::move( ovIdx ) );
}

// if no overviews found, look for a file in the same directory
if ( mOverviews.isEmpty() )
{
const QString baseName = QFileInfo( url.fileName() ).baseName();
const QUrl overviewUrl = url.resolved( QUrl( baseName + u"-overview.copc.laz"_s ) );
QgsPointCloudIndex ovIdx( new QgsCopcPointCloudIndex() );
ovIdx.load( overviewUrl.isLocalFile() ? overviewUrl.toLocalFile() : overviewUrl.toString(), authcfg );
if ( ovIdx.isValid() )
mOverviews.append( std::move( ovIdx ) );
}

mExtent = mPolygonBounds->boundingBox();
mAverageSubIndexWidth = subIndexesWidth / mSubLayers.size();
mAverageSubIndexHeight = subIndexesHeight / mSubLayers.size();
Expand Down Expand Up @@ -632,7 +645,7 @@ QgsPointCloudRenderer *QgsVirtualPointCloudProvider::createRenderer( const QVari
if ( mAttributes.indexOf( "Classification"_L1 ) >= 0 )
{
QgsPointCloudClassifiedRenderer *newRenderer = new QgsPointCloudClassifiedRenderer( u"Classification"_s, QgsPointCloudClassifiedRenderer::defaultCategories() );
if ( mOverview )
if ( !mOverviews.isEmpty() )
{
newRenderer->setZoomOutBehavior( Qgis::PointCloudZoomOutRenderBehavior::RenderOverview );
}
Expand Down
7 changes: 4 additions & 3 deletions src/core/providers/vpc/qgsvirtualpointcloudprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ class CORE_EXPORT QgsVirtualPointCloudProvider : public QgsPointCloudDataProvide
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
* Returns a list of all overview indexes.
* \since QGIS 4.2
*/
QgsPointCloudIndex overview() const { return mOverview; }
QVector<QgsPointCloudIndex> overviews() const { return mOverviews; }

/**
* Returns the calculated average width of point clouds.
Expand Down Expand Up @@ -98,6 +98,7 @@ class CORE_EXPORT QgsVirtualPointCloudProvider : public QgsPointCloudDataProvide
std::unique_ptr<QgsGeometry> mPolygonBounds;
QgsPointCloudAttributeCollection mAttributes;
QgsPointCloudIndex mOverview = QgsPointCloudIndex( nullptr );
QVector<QgsPointCloudIndex> mOverviews;

double mRedMax = std::numeric_limits<double>::lowest();
double mGreenMax = std::numeric_limits<double>::lowest();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ QgsPointCloudRendererPropertiesWidget::QgsPointCloudRendererPropertiesWidget( Qg

if ( const QgsVirtualPointCloudProvider *vpcProvider = dynamic_cast<QgsVirtualPointCloudProvider *>( mLayer->dataProvider() ) )
{
if ( vpcProvider->overview() )
if ( !vpcProvider->overviews().isEmpty() )
{
mZoomOutOptions->addItem( tr( "Show Overview Only" ), QVariant::fromValue( Qgis::PointCloudZoomOutRenderBehavior::RenderOverview ) );
mZoomOutOptions->addItem( tr( "Show Extents Over Overview" ), QVariant::fromValue( Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) );
Expand Down
Loading