mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-31 00:04:24 -05:00
1093 lines
36 KiB
C++
1093 lines
36 KiB
C++
/***************************************************************************
|
|
qgspointcloudlayer.cpp
|
|
--------------------
|
|
begin : October 2020
|
|
copyright : (C) 2020 by Peter Petrik
|
|
email : zilolv at gmail dot com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "qgspointcloudlayer.h"
|
|
#include "moc_qgspointcloudlayer.cpp"
|
|
#include "qgspointcloudeditingindex.h"
|
|
#include "qgspointcloudlayereditutils.h"
|
|
#include "qgspointcloudlayerrenderer.h"
|
|
#include "qgspointcloudindex.h"
|
|
#include "qgspointcloudstatistics.h"
|
|
#include "qgspointcloudsubindex.h"
|
|
#include "qgsrectangle.h"
|
|
#include "qgspointclouddataprovider.h"
|
|
#include "qgsproviderregistry.h"
|
|
#include "qgslogger.h"
|
|
#include "qgslayermetadataformatter.h"
|
|
#include "qgspointcloudrenderer.h"
|
|
#include "qgsruntimeprofiler.h"
|
|
#include "qgsapplication.h"
|
|
#include "qgspainting.h"
|
|
#include "qgspointcloudrendererregistry.h"
|
|
#include "qgspointcloudlayerelevationproperties.h"
|
|
#include "qgsmaplayerlegend.h"
|
|
#include "qgsxmlutils.h"
|
|
#include "qgsmaplayerfactory.h"
|
|
#include "qgsabstractpointcloud3drenderer.h"
|
|
#include "qgspointcloudstatscalculationtask.h"
|
|
#include "qgsmessagelog.h"
|
|
#include "qgstaskmanager.h"
|
|
#include "qgsthreadingutils.h"
|
|
#include "qgspointcloudlayerprofilegenerator.h"
|
|
#ifdef HAVE_COPC
|
|
#include "qgscopcpointcloudindex.h"
|
|
#endif
|
|
|
|
#include "qgsvirtualpointcloudprovider.h"
|
|
|
|
|
|
#include <QUrl>
|
|
|
|
QgsPointCloudLayer::QgsPointCloudLayer( const QString &uri,
|
|
const QString &baseName,
|
|
const QString &providerLib,
|
|
const QgsPointCloudLayer::LayerOptions &options )
|
|
: QgsMapLayer( Qgis::LayerType::PointCloud, baseName, uri )
|
|
, mElevationProperties( new QgsPointCloudLayerElevationProperties( this ) )
|
|
, mLayerOptions( options )
|
|
{
|
|
if ( !uri.isEmpty() && !providerLib.isEmpty() )
|
|
{
|
|
const QgsDataProvider::ProviderOptions providerOptions { options.transformContext };
|
|
Qgis::DataProviderReadFlags providerFlags;
|
|
if ( options.loadDefaultStyle )
|
|
{
|
|
providerFlags |= Qgis::DataProviderReadFlag::LoadDefaultStyle;
|
|
}
|
|
setDataSource( uri, baseName, providerLib, providerOptions, providerFlags );
|
|
}
|
|
|
|
setLegend( QgsMapLayerLegend::defaultPointCloudLegend( this ) );
|
|
connect( this, &QgsPointCloudLayer::subsetStringChanged, this, &QgsMapLayer::configChanged );
|
|
}
|
|
|
|
QgsPointCloudLayer::~QgsPointCloudLayer()
|
|
{
|
|
if ( QgsTask *task = QgsApplication::taskManager()->task( mStatsCalculationTask ) )
|
|
{
|
|
mStatsCalculationTask = 0;
|
|
task->cancel();
|
|
task->waitForFinished();
|
|
}
|
|
}
|
|
|
|
QgsPointCloudLayer *QgsPointCloudLayer::clone() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QgsPointCloudLayer *layer = new QgsPointCloudLayer( source(), name(), mProviderKey, mLayerOptions );
|
|
QgsMapLayer::clone( layer );
|
|
|
|
if ( mRenderer )
|
|
layer->setRenderer( mRenderer->clone() );
|
|
|
|
layer->mElevationProperties = mElevationProperties->clone();
|
|
layer->mElevationProperties->setParent( layer );
|
|
|
|
layer->mLayerOptions = mLayerOptions;
|
|
layer->mSync3DRendererTo2DRenderer = mSync3DRendererTo2DRenderer;
|
|
|
|
return layer;
|
|
}
|
|
|
|
QgsRectangle QgsPointCloudLayer::extent() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( !mDataProvider )
|
|
return QgsRectangle();
|
|
|
|
return mDataProvider->extent();
|
|
}
|
|
|
|
QgsMapLayerRenderer *QgsPointCloudLayer::createMapRenderer( QgsRenderContext &rendererContext )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mRenderer->type() != QLatin1String( "extent" ) )
|
|
loadIndexesForRenderContext( rendererContext );
|
|
|
|
return new QgsPointCloudLayerRenderer( this, rendererContext );
|
|
}
|
|
|
|
QgsAbstractProfileGenerator *QgsPointCloudLayer::createProfileGenerator( const QgsProfileRequest &request )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return new QgsPointCloudLayerProfileGenerator( this, request );
|
|
}
|
|
|
|
QgsPointCloudDataProvider *QgsPointCloudLayer::dataProvider()
|
|
{
|
|
// BAD! 2D rendering of point clouds is NOT thread safe
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mDataProvider.get();
|
|
}
|
|
|
|
const QgsPointCloudDataProvider *QgsPointCloudLayer::dataProvider() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mDataProvider.get();
|
|
}
|
|
|
|
bool QgsPointCloudLayer::readXml( const QDomNode &layerNode, QgsReadWriteContext &context )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
// create provider
|
|
const QDomNode pkeyNode = layerNode.namedItem( QStringLiteral( "provider" ) );
|
|
mProviderKey = pkeyNode.toElement().text();
|
|
|
|
if ( !( mReadFlags & QgsMapLayer::FlagDontResolveLayers ) )
|
|
{
|
|
const QgsDataProvider::ProviderOptions providerOptions { context.transformContext() };
|
|
Qgis::DataProviderReadFlags flags = providerReadFlags( layerNode, mReadFlags );
|
|
// read extent
|
|
if ( mReadFlags & QgsMapLayer::FlagReadExtentFromXml )
|
|
{
|
|
const QDomNode extentNode = layerNode.namedItem( QStringLiteral( "extent" ) );
|
|
if ( !extentNode.isNull() )
|
|
{
|
|
// get the extent
|
|
const QgsRectangle mbr = QgsXmlUtils::readRectangle( extentNode.toElement() );
|
|
|
|
// store the extent
|
|
setExtent( mbr );
|
|
}
|
|
}
|
|
|
|
setDataSource( mDataSource, mLayerName, mProviderKey, providerOptions, flags );
|
|
const QDomNode subset = layerNode.namedItem( QStringLiteral( "subset" ) );
|
|
const QString subsetText = subset.toElement().text();
|
|
if ( !subsetText.isEmpty() )
|
|
setSubsetString( subsetText );
|
|
}
|
|
|
|
if ( !isValid() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QString errorMsg;
|
|
if ( !readSymbology( layerNode, errorMsg, context ) )
|
|
return false;
|
|
|
|
readStyleManager( layerNode );
|
|
return true;
|
|
}
|
|
|
|
bool QgsPointCloudLayer::writeXml( QDomNode &layerNode, QDomDocument &doc, const QgsReadWriteContext &context ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QDomElement mapLayerNode = layerNode.toElement();
|
|
mapLayerNode.setAttribute( QStringLiteral( "type" ), QgsMapLayerFactory::typeToString( Qgis::LayerType::PointCloud ) );
|
|
|
|
if ( !subsetString().isEmpty() )
|
|
{
|
|
QDomElement subset = doc.createElement( QStringLiteral( "subset" ) );
|
|
const QDomText subsetText = doc.createTextNode( subsetString() );
|
|
subset.appendChild( subsetText );
|
|
layerNode.appendChild( subset );
|
|
}
|
|
if ( mDataProvider )
|
|
{
|
|
QDomElement provider = doc.createElement( QStringLiteral( "provider" ) );
|
|
const QDomText providerText = doc.createTextNode( providerType() );
|
|
provider.appendChild( providerText );
|
|
layerNode.appendChild( provider );
|
|
}
|
|
|
|
writeStyleManager( layerNode, doc );
|
|
|
|
QString errorMsg;
|
|
return writeSymbology( layerNode, doc, errorMsg, context );
|
|
}
|
|
|
|
bool QgsPointCloudLayer::readSymbology( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
const QDomElement elem = node.toElement();
|
|
|
|
readCommonStyle( elem, context, categories );
|
|
|
|
readStyle( node, errorMessage, context, categories );
|
|
|
|
if ( categories.testFlag( CustomProperties ) )
|
|
readCustomProperties( node, QStringLiteral( "variable" ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsPointCloudLayer::readStyle( const QDomNode &node, QString &, QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
bool result = true;
|
|
|
|
if ( categories.testFlag( Symbology3D ) )
|
|
{
|
|
bool ok;
|
|
bool sync = node.attributes().namedItem( QStringLiteral( "sync3DRendererTo2DRenderer" ) ).nodeValue().toInt( &ok );
|
|
if ( ok )
|
|
setSync3DRendererTo2DRenderer( sync );
|
|
}
|
|
|
|
if ( categories.testFlag( Symbology ) )
|
|
{
|
|
QDomElement rendererElement = node.firstChildElement( QStringLiteral( "renderer" ) );
|
|
if ( !rendererElement.isNull() )
|
|
{
|
|
std::unique_ptr< QgsPointCloudRenderer > r( QgsPointCloudRenderer::load( rendererElement, context ) );
|
|
if ( r )
|
|
{
|
|
setRenderer( r.release() );
|
|
}
|
|
else
|
|
{
|
|
result = false;
|
|
}
|
|
}
|
|
// make sure layer has a renderer - if none exists, fallback to a default renderer
|
|
if ( !mRenderer )
|
|
{
|
|
setRenderer( QgsPointCloudRendererRegistry::defaultRenderer( this ) );
|
|
}
|
|
}
|
|
|
|
if ( categories.testFlag( Symbology ) )
|
|
{
|
|
// get and set the blend mode if it exists
|
|
const QDomNode blendModeNode = node.namedItem( QStringLiteral( "blendMode" ) );
|
|
if ( !blendModeNode.isNull() )
|
|
{
|
|
const QDomElement e = blendModeNode.toElement();
|
|
setBlendMode( QgsPainting::getCompositionMode( static_cast< Qgis::BlendMode >( e.text().toInt() ) ) );
|
|
}
|
|
}
|
|
|
|
// get and set the layer transparency and scale visibility if they exists
|
|
if ( categories.testFlag( Rendering ) )
|
|
{
|
|
const QDomNode layerOpacityNode = node.namedItem( QStringLiteral( "layerOpacity" ) );
|
|
if ( !layerOpacityNode.isNull() )
|
|
{
|
|
const QDomElement e = layerOpacityNode.toElement();
|
|
setOpacity( e.text().toDouble() );
|
|
}
|
|
|
|
const bool hasScaleBasedVisibiliy { node.attributes().namedItem( QStringLiteral( "hasScaleBasedVisibilityFlag" ) ).nodeValue() == '1' };
|
|
setScaleBasedVisibility( hasScaleBasedVisibiliy );
|
|
bool ok;
|
|
const double maxScale { node.attributes().namedItem( QStringLiteral( "maxScale" ) ).nodeValue().toDouble( &ok ) };
|
|
if ( ok )
|
|
{
|
|
setMaximumScale( maxScale );
|
|
}
|
|
const double minScale { node.attributes().namedItem( QStringLiteral( "minScale" ) ).nodeValue().toDouble( &ok ) };
|
|
if ( ok )
|
|
{
|
|
setMinimumScale( minScale );
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool QgsPointCloudLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage,
|
|
const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Q_UNUSED( errorMessage )
|
|
|
|
QDomElement elem = node.toElement();
|
|
writeCommonStyle( elem, doc, context, categories );
|
|
|
|
( void )writeStyle( node, doc, errorMessage, context, categories );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsPointCloudLayer::writeStyle( QDomNode &node, QDomDocument &doc, QString &, const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QDomElement mapLayerNode = node.toElement();
|
|
|
|
if ( categories.testFlag( Symbology3D ) )
|
|
{
|
|
mapLayerNode.setAttribute( QStringLiteral( "sync3DRendererTo2DRenderer" ), mSync3DRendererTo2DRenderer ? 1 : 0 );
|
|
}
|
|
|
|
if ( categories.testFlag( Symbology ) )
|
|
{
|
|
if ( mRenderer )
|
|
{
|
|
const QDomElement rendererElement = mRenderer->save( doc, context );
|
|
node.appendChild( rendererElement );
|
|
}
|
|
}
|
|
|
|
//save customproperties
|
|
if ( categories.testFlag( CustomProperties ) )
|
|
{
|
|
writeCustomProperties( node, doc );
|
|
}
|
|
|
|
if ( categories.testFlag( Symbology ) )
|
|
{
|
|
// add the blend mode field
|
|
QDomElement blendModeElem = doc.createElement( QStringLiteral( "blendMode" ) );
|
|
const QDomText blendModeText = doc.createTextNode( QString::number( static_cast< int >( QgsPainting::getBlendModeEnum( blendMode() ) ) ) );
|
|
blendModeElem.appendChild( blendModeText );
|
|
node.appendChild( blendModeElem );
|
|
}
|
|
|
|
// add the layer opacity and scale visibility
|
|
if ( categories.testFlag( Rendering ) )
|
|
{
|
|
QDomElement layerOpacityElem = doc.createElement( QStringLiteral( "layerOpacity" ) );
|
|
const QDomText layerOpacityText = doc.createTextNode( QString::number( opacity() ) );
|
|
layerOpacityElem.appendChild( layerOpacityText );
|
|
node.appendChild( layerOpacityElem );
|
|
|
|
mapLayerNode.setAttribute( QStringLiteral( "hasScaleBasedVisibilityFlag" ), hasScaleBasedVisibility() ? 1 : 0 );
|
|
mapLayerNode.setAttribute( QStringLiteral( "maxScale" ), maximumScale() );
|
|
mapLayerNode.setAttribute( QStringLiteral( "minScale" ), minimumScale() );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void QgsPointCloudLayer::setTransformContext( const QgsCoordinateTransformContext &transformContext )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mDataProvider )
|
|
mDataProvider->setTransformContext( transformContext );
|
|
invalidateWgs84Extent();
|
|
}
|
|
|
|
void QgsPointCloudLayer::setDataSourcePrivate( const QString &dataSource, const QString &baseName, const QString &provider,
|
|
const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mDataProvider )
|
|
{
|
|
disconnect( mDataProvider.get(), &QgsPointCloudDataProvider::dataChanged, this, &QgsPointCloudLayer::dataChanged );
|
|
disconnect( mDataProvider.get(), &QgsPointCloudDataProvider::indexGenerationStateChanged, this, &QgsPointCloudLayer::onPointCloudIndexGenerationStateChanged );
|
|
}
|
|
|
|
setName( baseName );
|
|
mProviderKey = provider;
|
|
mDataSource = dataSource;
|
|
|
|
if ( mPreloadedProvider )
|
|
{
|
|
mDataProvider.reset( qobject_cast< QgsPointCloudDataProvider * >( mPreloadedProvider.release() ) );
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr< QgsScopedRuntimeProfile > profile;
|
|
if ( QgsApplication::profiler()->groupIsActive( QStringLiteral( "projectload" ) ) )
|
|
profile = std::make_unique< QgsScopedRuntimeProfile >( tr( "Create %1 provider" ).arg( provider ), QStringLiteral( "projectload" ) );
|
|
mDataProvider.reset( qobject_cast<QgsPointCloudDataProvider *>( QgsProviderRegistry::instance()->createProvider( provider, dataSource, options, flags ) ) );
|
|
}
|
|
|
|
if ( !mDataProvider )
|
|
{
|
|
QgsDebugError( QStringLiteral( "Unable to get point cloud data provider" ) );
|
|
setValid( false );
|
|
return;
|
|
}
|
|
|
|
mDataProvider->setParent( this );
|
|
QgsDebugMsgLevel( QStringLiteral( "Instantiated the point cloud data provider plugin" ), 2 );
|
|
|
|
setValid( mDataProvider->isValid() );
|
|
if ( !isValid() )
|
|
{
|
|
QgsDebugError( QStringLiteral( "Invalid point cloud provider plugin %1" ).arg( QString( mDataSource.toUtf8() ) ) );
|
|
setError( mDataProvider->error() );
|
|
return;
|
|
}
|
|
|
|
connect( mDataProvider.get(), &QgsPointCloudDataProvider::indexGenerationStateChanged, this, &QgsPointCloudLayer::onPointCloudIndexGenerationStateChanged );
|
|
connect( mDataProvider.get(), &QgsPointCloudDataProvider::dataChanged, this, &QgsPointCloudLayer::dataChanged );
|
|
|
|
// Load initial extent, crs and renderer
|
|
setCrs( mDataProvider->crs() );
|
|
if ( !( flags & Qgis::DataProviderReadFlag::SkipGetExtent ) )
|
|
{
|
|
setExtent3D( mDataProvider->extent3D() );
|
|
}
|
|
|
|
bool loadDefaultStyleFlag = false;
|
|
if ( flags & Qgis::DataProviderReadFlag::LoadDefaultStyle )
|
|
{
|
|
loadDefaultStyleFlag = true;
|
|
}
|
|
|
|
if ( !mLayerOptions.skipIndexGeneration &&
|
|
mDataProvider &&
|
|
mDataProvider->indexingState() != QgsPointCloudDataProvider::PointCloudIndexGenerationState::Indexed &&
|
|
mDataProvider->pointCount() > 0 )
|
|
{
|
|
mDataProvider->generateIndex();
|
|
}
|
|
|
|
if ( !mLayerOptions.skipStatisticsCalculation &&
|
|
mDataProvider &&
|
|
mDataProvider->indexingState() == QgsPointCloudDataProvider::PointCloudIndexGenerationState::Indexed &&
|
|
mDataProvider->pointCount() > 0 )
|
|
{
|
|
calculateStatistics();
|
|
}
|
|
|
|
if ( !mRenderer || loadDefaultStyleFlag )
|
|
{
|
|
std::unique_ptr< QgsScopedRuntimeProfile > profile;
|
|
if ( QgsApplication::profiler()->groupIsActive( QStringLiteral( "projectload" ) ) )
|
|
profile = std::make_unique< QgsScopedRuntimeProfile >( tr( "Load layer style" ), QStringLiteral( "projectload" ) );
|
|
|
|
bool defaultLoadedFlag = false;
|
|
|
|
if ( loadDefaultStyleFlag && isSpatial() && mDataProvider->capabilities() & QgsPointCloudDataProvider::CreateRenderer )
|
|
{
|
|
// first try to create a renderer directly from the data provider
|
|
std::unique_ptr< QgsPointCloudRenderer > defaultRenderer( mDataProvider->createRenderer() );
|
|
if ( defaultRenderer )
|
|
{
|
|
defaultLoadedFlag = true;
|
|
setRenderer( defaultRenderer.release() );
|
|
}
|
|
}
|
|
|
|
if ( !defaultLoadedFlag && loadDefaultStyleFlag )
|
|
{
|
|
loadDefaultStyle( defaultLoadedFlag );
|
|
}
|
|
|
|
if ( !defaultLoadedFlag )
|
|
{
|
|
// all else failed, create default renderer
|
|
setRenderer( QgsPointCloudRendererRegistry::defaultRenderer( this ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
QString QgsPointCloudLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return QgsProviderRegistry::instance()->absoluteToRelativeUri( mProviderKey, source, context );
|
|
}
|
|
|
|
QString QgsPointCloudLayer::decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return QgsProviderRegistry::instance()->relativeToAbsoluteUri( dataProvider, source, context );
|
|
}
|
|
|
|
void QgsPointCloudLayer::onPointCloudIndexGenerationStateChanged( QgsPointCloudDataProvider::PointCloudIndexGenerationState state )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
switch ( state )
|
|
{
|
|
case QgsPointCloudDataProvider::Indexed:
|
|
{
|
|
resetRenderer();
|
|
break;
|
|
}
|
|
case QgsPointCloudDataProvider::NotIndexed:
|
|
{
|
|
QgsError providerError = mDataProvider->error();
|
|
if ( !providerError.isEmpty() )
|
|
{
|
|
setError( providerError );
|
|
emit raiseError( providerError.summary() );
|
|
}
|
|
break;
|
|
}
|
|
case QgsPointCloudDataProvider::Indexing:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
QString QgsPointCloudLayer::loadDefaultStyle( bool &resultFlag )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mDataProvider->capabilities() & QgsPointCloudDataProvider::CreateRenderer )
|
|
{
|
|
// first try to create a renderer directly from the data provider
|
|
std::unique_ptr< QgsPointCloudRenderer > defaultRenderer( mDataProvider->createRenderer() );
|
|
if ( defaultRenderer )
|
|
{
|
|
resultFlag = true;
|
|
setRenderer( defaultRenderer.release() );
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
return QgsMapLayer::loadDefaultStyle( resultFlag );
|
|
}
|
|
|
|
QString QgsPointCloudLayer::htmlMetadata() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
const QgsLayerMetadataFormatter htmlFormatter( metadata() );
|
|
QString myMetadata = QStringLiteral( "<html>\n<body>\n" );
|
|
|
|
myMetadata += generalHtmlMetadata();
|
|
|
|
// Begin Provider section
|
|
myMetadata += QStringLiteral( "<h1>" ) + tr( "Information from provider" ) + QStringLiteral( "</h1>\n<hr>\n" );
|
|
myMetadata += QLatin1String( "<table class=\"list-view\">\n" );
|
|
|
|
// Extent
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Extent" ) + QStringLiteral( "</td><td>" ) + extent().toString() + QStringLiteral( "</td></tr>\n" );
|
|
|
|
// feature count
|
|
QLocale locale = QLocale();
|
|
locale.setNumberOptions( locale.numberOptions() &= ~QLocale::NumberOption::OmitGroupSeparator );
|
|
const qint64 pointCount = mDataProvider ? mDataProvider->pointCount() : -1;
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Point count" ) + QStringLiteral( "</td><td>" )
|
|
+ ( pointCount < 0 ? tr( "unknown" ) : locale.toString( static_cast<qlonglong>( pointCount ) ) )
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
|
|
if ( const QgsPointCloudDataProvider *provider = dataProvider() )
|
|
{
|
|
myMetadata += provider->htmlMetadata();
|
|
}
|
|
|
|
myMetadata += QLatin1String( "</table>\n<br><br>" );
|
|
|
|
// CRS
|
|
myMetadata += crsHtmlMetadata();
|
|
|
|
// provider metadata section
|
|
myMetadata += QStringLiteral( "<h1>" ) + tr( "Metadata" ) + QStringLiteral( "</h1>\n<hr>\n" ) + QStringLiteral( "<table class=\"list-view\">\n" );
|
|
const QVariantMap originalMetadata = mDataProvider ? mDataProvider->originalMetadata() : QVariantMap();
|
|
|
|
if ( originalMetadata.value( QStringLiteral( "creation_year" ) ).toInt() > 0 && originalMetadata.contains( QStringLiteral( "creation_doy" ) ) )
|
|
{
|
|
QDate creationDate( originalMetadata.value( QStringLiteral( "creation_year" ) ).toInt(), 1, 1 );
|
|
creationDate = creationDate.addDays( originalMetadata.value( QStringLiteral( "creation_doy" ) ).toInt() );
|
|
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Creation date" ) + QStringLiteral( "</td><td>" )
|
|
+ creationDate.toString( Qt::ISODate )
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
if ( originalMetadata.contains( QStringLiteral( "major_version" ) ) && originalMetadata.contains( QStringLiteral( "minor_version" ) ) )
|
|
{
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Version" ) + QStringLiteral( "</td><td>" )
|
|
+ QStringLiteral( "%1.%2" ).arg( originalMetadata.value( QStringLiteral( "major_version" ) ).toString(),
|
|
originalMetadata.value( QStringLiteral( "minor_version" ) ).toString() )
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
|
|
if ( !originalMetadata.value( QStringLiteral( "dataformat_id" ) ).toString().isEmpty() )
|
|
{
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Data format" ) + QStringLiteral( "</td><td>" )
|
|
+ QStringLiteral( "%1 (%2)" ).arg( QgsPointCloudDataProvider::translatedDataFormatIds().value( originalMetadata.value( QStringLiteral( "dataformat_id" ) ).toInt() ),
|
|
originalMetadata.value( QStringLiteral( "dataformat_id" ) ).toString() ).trimmed()
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Scale X" ) + QStringLiteral( "</td><td>" )
|
|
+ QString::number( originalMetadata.value( QStringLiteral( "scale_x" ) ).toDouble() )
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Scale Y" ) + QStringLiteral( "</td><td>" )
|
|
+ QString::number( originalMetadata.value( QStringLiteral( "scale_y" ) ).toDouble() )
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Scale Z" ) + QStringLiteral( "</td><td>" )
|
|
+ QString::number( originalMetadata.value( QStringLiteral( "scale_z" ) ).toDouble() )
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Offset X" ) + QStringLiteral( "</td><td>" )
|
|
+ QString::number( originalMetadata.value( QStringLiteral( "offset_x" ) ).toDouble() )
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Offset Y" ) + QStringLiteral( "</td><td>" )
|
|
+ QString::number( originalMetadata.value( QStringLiteral( "offset_y" ) ).toDouble() )
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Offset Z" ) + QStringLiteral( "</td><td>" )
|
|
+ QString::number( originalMetadata.value( QStringLiteral( "offset_z" ) ).toDouble() )
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
|
|
if ( !originalMetadata.value( QStringLiteral( "project_id" ) ).toString().isEmpty() )
|
|
{
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Project ID" ) + QStringLiteral( "</td><td>" )
|
|
+ originalMetadata.value( QStringLiteral( "project_id" ) ).toString()
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
|
|
if ( !originalMetadata.value( QStringLiteral( "system_id" ) ).toString().isEmpty() )
|
|
{
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "System ID" ) + QStringLiteral( "</td><td>" )
|
|
+ originalMetadata.value( QStringLiteral( "system_id" ) ).toString()
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
|
|
if ( !originalMetadata.value( QStringLiteral( "software_id" ) ).toString().isEmpty() )
|
|
{
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
|
|
+ tr( "Software ID" ) + QStringLiteral( "</td><td>" )
|
|
+ originalMetadata.value( QStringLiteral( "software_id" ) ).toString()
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
|
|
// End Provider section
|
|
myMetadata += QLatin1String( "</table>\n<br><br>" );
|
|
|
|
// identification section
|
|
myMetadata += QStringLiteral( "<h1>" ) + tr( "Identification" ) + QStringLiteral( "</h1>\n<hr>\n" );
|
|
myMetadata += htmlFormatter.identificationSectionHtml( );
|
|
myMetadata += QLatin1String( "<br><br>\n" );
|
|
|
|
// extent section
|
|
myMetadata += QStringLiteral( "<h1>" ) + tr( "Extent" ) + QStringLiteral( "</h1>\n<hr>\n" );
|
|
myMetadata += htmlFormatter.extentSectionHtml( isSpatial() );
|
|
myMetadata += QLatin1String( "<br><br>\n" );
|
|
|
|
// Start the Access section
|
|
myMetadata += QStringLiteral( "<h1>" ) + tr( "Access" ) + QStringLiteral( "</h1>\n<hr>\n" );
|
|
myMetadata += htmlFormatter.accessSectionHtml( );
|
|
myMetadata += QLatin1String( "<br><br>\n" );
|
|
|
|
// Attributes section
|
|
myMetadata += QStringLiteral( "<h1>" ) + tr( "Attributes" ) + QStringLiteral( "</h1>\n<hr>\n<table class=\"list-view\">\n" );
|
|
|
|
const QgsPointCloudAttributeCollection attrs = attributes();
|
|
|
|
// count attributes
|
|
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Count" ) + QStringLiteral( "</td><td>" ) + QString::number( attrs.count() ) + QStringLiteral( "</td></tr>\n" );
|
|
|
|
myMetadata += QLatin1String( "</table>\n<br><table width=\"100%\" class=\"tabular-view\">\n" );
|
|
myMetadata += QLatin1String( "<tr><th>" ) + tr( "Attribute" ) + QLatin1String( "</th><th>" ) + tr( "Type" ) + QLatin1String( "</th></tr>\n" );
|
|
|
|
for ( int i = 0; i < attrs.count(); ++i )
|
|
{
|
|
const QgsPointCloudAttribute attribute = attrs.at( i );
|
|
QString rowClass;
|
|
if ( i % 2 )
|
|
rowClass = QStringLiteral( "class=\"odd-row\"" );
|
|
myMetadata += QLatin1String( "<tr " ) + rowClass + QLatin1String( "><td>" ) + attribute.name() + QLatin1String( "</td><td>" ) + attribute.displayType() + QLatin1String( "</td></tr>\n" );
|
|
}
|
|
|
|
//close field list
|
|
myMetadata += QLatin1String( "</table>\n<br><br>" );
|
|
|
|
|
|
// Start the contacts section
|
|
myMetadata += QStringLiteral( "<h1>" ) + tr( "Contacts" ) + QStringLiteral( "</h1>\n<hr>\n" );
|
|
myMetadata += htmlFormatter.contactsSectionHtml( );
|
|
myMetadata += QLatin1String( "<br><br>\n" );
|
|
|
|
// Start the links section
|
|
myMetadata += QStringLiteral( "<h1>" ) + tr( "Links" ) + QStringLiteral( "</h1>\n<hr>\n" );
|
|
myMetadata += htmlFormatter.linksSectionHtml( );
|
|
myMetadata += QLatin1String( "<br><br>\n" );
|
|
|
|
// Start the history section
|
|
myMetadata += QStringLiteral( "<h1>" ) + tr( "History" ) + QStringLiteral( "</h1>\n<hr>\n" );
|
|
myMetadata += htmlFormatter.historySectionHtml( );
|
|
myMetadata += QLatin1String( "<br><br>\n" );
|
|
|
|
myMetadata += customPropertyHtmlMetadata();
|
|
|
|
myMetadata += QLatin1String( "\n</body>\n</html>\n" );
|
|
return myMetadata;
|
|
}
|
|
|
|
QgsMapLayerElevationProperties *QgsPointCloudLayer::elevationProperties()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mElevationProperties;
|
|
}
|
|
|
|
QgsPointCloudAttributeCollection QgsPointCloudLayer::attributes() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mDataProvider ? mDataProvider->attributes() : QgsPointCloudAttributeCollection();
|
|
}
|
|
|
|
qint64 QgsPointCloudLayer::pointCount() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mDataProvider ? mDataProvider->pointCount() : 0;
|
|
}
|
|
|
|
QgsPointCloudRenderer *QgsPointCloudLayer::renderer()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mRenderer.get();
|
|
}
|
|
|
|
const QgsPointCloudRenderer *QgsPointCloudLayer::renderer() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mRenderer.get();
|
|
}
|
|
|
|
void QgsPointCloudLayer::setRenderer( QgsPointCloudRenderer *renderer )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( renderer == mRenderer.get() )
|
|
return;
|
|
|
|
mRenderer.reset( renderer );
|
|
emit rendererChanged();
|
|
emitStyleChanged();
|
|
|
|
if ( mSync3DRendererTo2DRenderer )
|
|
convertRenderer3DFromRenderer2D();
|
|
}
|
|
|
|
bool QgsPointCloudLayer::setSubsetString( const QString &subset )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( !isValid() || !mDataProvider )
|
|
{
|
|
QgsDebugMsgLevel( QStringLiteral( "invoked with invalid layer or null mDataProvider" ), 3 );
|
|
setCustomProperty( QStringLiteral( "storedSubsetString" ), subset );
|
|
return false;
|
|
}
|
|
else if ( subset == mDataProvider->subsetString() )
|
|
return true;
|
|
|
|
bool res = mDataProvider->setSubsetString( subset );
|
|
if ( res )
|
|
{
|
|
emit subsetStringChanged();
|
|
triggerRepaint();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
QString QgsPointCloudLayer::subsetString() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( !isValid() || !mDataProvider )
|
|
{
|
|
QgsDebugMsgLevel( QStringLiteral( "invoked with invalid layer or null mDataProvider" ), 3 );
|
|
return customProperty( QStringLiteral( "storedSubsetString" ) ).toString();
|
|
}
|
|
return mDataProvider->subsetString();
|
|
}
|
|
|
|
bool QgsPointCloudLayer::convertRenderer3DFromRenderer2D()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
bool result = false;
|
|
QgsAbstractPointCloud3DRenderer *r = static_cast<QgsAbstractPointCloud3DRenderer *>( renderer3D() );
|
|
if ( r )
|
|
{
|
|
result = r->convertFrom2DRenderer( renderer() );
|
|
setRenderer3D( r );
|
|
trigger3DUpdate();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool QgsPointCloudLayer::sync3DRendererTo2DRenderer() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mSync3DRendererTo2DRenderer;
|
|
}
|
|
|
|
void QgsPointCloudLayer::setSync3DRendererTo2DRenderer( bool sync )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mSync3DRendererTo2DRenderer = sync;
|
|
if ( sync )
|
|
convertRenderer3DFromRenderer2D();
|
|
}
|
|
|
|
void QgsPointCloudLayer::calculateStatistics()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( !mDataProvider.get() || !mDataProvider->hasValidIndex() )
|
|
{
|
|
QgsMessageLog::logMessage( QObject::tr( "Failed to calculate statistics of the point cloud %1" ).arg( this->name() ) );
|
|
return;
|
|
}
|
|
if ( mStatsCalculationTask )
|
|
{
|
|
QgsMessageLog::logMessage( QObject::tr( "A statistics calculation task for the point cloud %1 is already in progress" ).arg( this->name() ) );
|
|
return;
|
|
}
|
|
|
|
QgsPointCloudStatistics indexStats = mDataProvider->metadataStatistics();
|
|
QList<QString> indexStatsAttributes = indexStats.statisticsMap().keys();
|
|
QVector<QgsPointCloudAttribute> attributes = mDataProvider->attributes().attributes();
|
|
// Do not calculate stats for attributes that the index gives us stats for
|
|
for ( int i = 0; i < attributes.size(); ++i )
|
|
{
|
|
if ( indexStatsAttributes.contains( attributes[i].name() ) )
|
|
{
|
|
attributes.remove( i );
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// 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, skip calculating anything
|
|
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]()
|
|
{
|
|
mStatistics = task->calculationResults();
|
|
|
|
// Fetch what we can directly from the index
|
|
QMap<QString, QgsPointCloudAttributeStatistics> statsMap = mStatistics.statisticsMap();
|
|
for ( const QString &attribute : indexStatsAttributes )
|
|
{
|
|
statsMap[ attribute ] = indexStats.statisticsOf( attribute );
|
|
}
|
|
mStatistics = QgsPointCloudStatistics( mStatistics.sampledPointsCount(), statsMap );
|
|
|
|
mStatisticsCalculationState = QgsPointCloudLayer::PointCloudStatisticsCalculationState::Calculated;
|
|
emit statisticsCalculationStateChanged( mStatisticsCalculationState );
|
|
resetRenderer();
|
|
mStatsCalculationTask = 0;
|
|
#ifdef HAVE_COPC
|
|
if ( mDataProvider && mDataProvider->index() && mDataProvider->index().isValid() && mDataProvider->name() == QLatin1String( "pdal" ) && mStatistics.sampledPointsCount() != 0 )
|
|
{
|
|
mDataProvider->index().writeStatistics( mStatistics );
|
|
}
|
|
#endif
|
|
} );
|
|
|
|
// In case the statistics calculation fails, QgsTask::taskTerminated will be called
|
|
connect( task, &QgsTask::taskTerminated, this, [this]()
|
|
{
|
|
if ( mStatsCalculationTask )
|
|
{
|
|
QgsMessageLog::logMessage( QObject::tr( "Failed to calculate statistics of the point cloud %1" ).arg( this->name() ) );
|
|
mStatsCalculationTask = 0;
|
|
}
|
|
} );
|
|
|
|
mStatsCalculationTask = QgsApplication::taskManager()->addTask( task );
|
|
|
|
mStatisticsCalculationState = QgsPointCloudLayer::PointCloudStatisticsCalculationState::Calculating;
|
|
emit statisticsCalculationStateChanged( mStatisticsCalculationState );
|
|
}
|
|
|
|
void QgsPointCloudLayer::resetRenderer()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mDataProvider->loadIndex();
|
|
if ( !mLayerOptions.skipStatisticsCalculation && statisticsCalculationState() == QgsPointCloudLayer::PointCloudStatisticsCalculationState::NotStarted )
|
|
{
|
|
calculateStatistics();
|
|
}
|
|
if ( !mRenderer || mRenderer->type() == QLatin1String( "extent" ) )
|
|
{
|
|
setRenderer( QgsPointCloudRendererRegistry::defaultRenderer( this ) );
|
|
}
|
|
triggerRepaint();
|
|
|
|
emit rendererChanged();
|
|
}
|
|
|
|
void QgsPointCloudLayer::loadIndexesForRenderContext( QgsRenderContext &rendererContext ) const
|
|
{
|
|
if ( mDataProvider->capabilities() & QgsPointCloudDataProvider::ContainSubIndexes )
|
|
{
|
|
QgsRectangle renderExtent;
|
|
try
|
|
{
|
|
renderExtent = rendererContext.coordinateTransform().transformBoundingBox( rendererContext.mapExtent(), Qgis::TransformDirection::Reverse );
|
|
}
|
|
catch ( QgsCsException & )
|
|
{
|
|
QgsDebugError( QStringLiteral( "Transformation of extent failed!" ) );
|
|
}
|
|
|
|
const QVector<QgsPointCloudSubIndex> subIndex = mDataProvider->subIndexes();
|
|
if ( const QgsVirtualPointCloudProvider *vpcProvider = dynamic_cast<QgsVirtualPointCloudProvider *>( mDataProvider.get() ) )
|
|
{
|
|
for ( int i = 0; i < subIndex.size(); ++i )
|
|
{
|
|
// no need to load as it's there
|
|
if ( subIndex.at( i ).index() )
|
|
continue;
|
|
|
|
if ( subIndex.at( i ).extent().intersects( renderExtent ) &&
|
|
( renderExtent.width() < vpcProvider->averageSubIndexWidth() ||
|
|
renderExtent.height() < vpcProvider->averageSubIndexHeight() ) )
|
|
{
|
|
mDataProvider->loadSubIndex( i );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool QgsPointCloudLayer::startEditing()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
if ( mEditIndex )
|
|
return false;
|
|
|
|
mEditIndex = std::make_unique<QgsPointCloudEditingIndex>( this );
|
|
|
|
if ( !mEditIndex->isValid() )
|
|
{
|
|
mEditIndex.reset();
|
|
return false;
|
|
}
|
|
|
|
emit editingStarted();
|
|
return true;
|
|
}
|
|
|
|
bool QgsPointCloudLayer::commitChanges( bool stopEditing )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
if ( !mEditIndex ||
|
|
!mEditIndex->commitChanges() )
|
|
return false;
|
|
|
|
if ( stopEditing )
|
|
{
|
|
mEditIndex.reset();
|
|
emit editingStopped();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QString QgsPointCloudLayer::commitError() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
return mCommitError;
|
|
}
|
|
|
|
bool QgsPointCloudLayer::rollBack()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
if ( !mEditIndex )
|
|
return false;
|
|
|
|
if ( isModified() )
|
|
{
|
|
emit layerModified();
|
|
triggerRepaint();
|
|
}
|
|
|
|
mEditIndex.reset();
|
|
emit editingStopped();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsPointCloudLayer::supportsEditing() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
return mDataProvider && mDataProvider->capabilities() & QgsPointCloudDataProvider::Capability::ChangeAttributeValues;
|
|
}
|
|
|
|
bool QgsPointCloudLayer::isEditable() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
if ( mEditIndex )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QgsPointCloudLayer::isModified() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
if ( !mEditIndex )
|
|
return false;
|
|
|
|
return mEditIndex->isModified();
|
|
}
|
|
|
|
bool QgsPointCloudLayer::changeAttributeValue( const QgsPointCloudNodeId &n, const QVector<int> &pts, const QgsPointCloudAttribute &attribute, double value )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
if ( !mEditIndex )
|
|
return false;
|
|
|
|
QgsPointCloudLayerEditUtils utils( this );
|
|
|
|
const bool success = utils.changeAttributeValue( n, pts, attribute, value );
|
|
if ( success )
|
|
{
|
|
emit layerModified();
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
QgsPointCloudIndex *QgsPointCloudLayer::index() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
if ( mEditIndex )
|
|
return mEditIndex.get();
|
|
|
|
if ( mDataProvider )
|
|
return mDataProvider->index();
|
|
|
|
return nullptr;
|
|
}
|