diff --git a/python/PyQt6/core/auto_generated/mesh/qgsmeshdataprovider.sip.in b/python/PyQt6/core/auto_generated/mesh/qgsmeshdataprovider.sip.in index 4a22ff18ee99..81d06988f17b 100644 --- a/python/PyQt6/core/auto_generated/mesh/qgsmeshdataprovider.sip.in +++ b/python/PyQt6/core/auto_generated/mesh/qgsmeshdataprovider.sip.in @@ -465,6 +465,17 @@ Returns the mesh driver metadata of the provider Closes the data provider and free every resources used .. versionadded:: 3.22 +%End + + virtual bool removeDatasetGroup( int index ) = 0; +%Docstring +Remove dataset group from the mesh + +emits dataChanged when successful + +:return: ``True`` on success + +.. versionadded:: 3.42 %End signals: diff --git a/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in index b6cb23c2e7f0..fe0d4d27d99a 100644 --- a/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -172,12 +172,23 @@ Returns the provider type for this layer %Docstring Adds datasets to the mesh from file with ``path``. Use the the time ``defaultReferenceTime`` as reference time is not provided in the file -:param path: the path to the atasets file +:param path: the path to the datasets file :param defaultReferenceTime: reference time used if not provided in the file :return: whether the dataset is added .. versionadded:: 3.14 +%End + + bool removeDatasets( const QString &name ); +%Docstring +Removes datasets from the mesh with given ``name``. + +:param name: name of dataset group to remove + +:return: whether the dataset is removed + +.. versionadded:: 3.42 %End bool addDatasets( QgsMeshDatasetGroup *datasetGroup /Transfer/ ); @@ -905,6 +916,17 @@ Sets labeling configuration. Takes ownership of the object. .. versionadded:: 3.36 %End + bool datasetsPathUnique( const QString &path ); +%Docstring +Checks whether that datasets path is already added to this mesh layer. Return ``True`` if the +dataset path is not already added. + +:param path: the path to the datasets file + +:return: whether the datasets path is unique + +.. versionadded:: 3.42 +%End public slots: diff --git a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in index f68ef0484ae1..eff5d28343a6 100644 --- a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in @@ -465,6 +465,17 @@ Returns the mesh driver metadata of the provider Closes the data provider and free every resources used .. versionadded:: 3.22 +%End + + virtual bool removeDatasetGroup( int index ) = 0; +%Docstring +Remove dataset group from the mesh + +emits dataChanged when successful + +:return: ``True`` on success + +.. versionadded:: 3.42 %End signals: diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index b6cb23c2e7f0..fe0d4d27d99a 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -172,12 +172,23 @@ Returns the provider type for this layer %Docstring Adds datasets to the mesh from file with ``path``. Use the the time ``defaultReferenceTime`` as reference time is not provided in the file -:param path: the path to the atasets file +:param path: the path to the datasets file :param defaultReferenceTime: reference time used if not provided in the file :return: whether the dataset is added .. versionadded:: 3.14 +%End + + bool removeDatasets( const QString &name ); +%Docstring +Removes datasets from the mesh with given ``name``. + +:param name: name of dataset group to remove + +:return: whether the dataset is removed + +.. versionadded:: 3.42 %End bool addDatasets( QgsMeshDatasetGroup *datasetGroup /Transfer/ ); @@ -905,6 +916,17 @@ Sets labeling configuration. Takes ownership of the object. .. versionadded:: 3.36 %End + bool datasetsPathUnique( const QString &path ); +%Docstring +Checks whether that datasets path is already added to this mesh layer. Return ``True`` if the +dataset path is not already added. + +:param path: the path to the datasets file + +:return: whether the datasets path is unique + +.. versionadded:: 3.42 +%End public slots: diff --git a/src/core/mesh/qgsmeshdataprovider.h b/src/core/mesh/qgsmeshdataprovider.h index 3d4bb950054a..c00c2a27bb5f 100644 --- a/src/core/mesh/qgsmeshdataprovider.h +++ b/src/core/mesh/qgsmeshdataprovider.h @@ -475,6 +475,17 @@ class CORE_EXPORT QgsMeshDataProvider: public QgsDataProvider, public QgsMeshDat */ virtual void close() = 0; + /** + * \brief Remove dataset group from the mesh + * + * emits dataChanged when successful + * + * \return TRUE on success + * + * \since QGIS 3.42 + */ + virtual bool removeDatasetGroup( int index ) = 0; + signals: //! Emitted when some new dataset groups have been added void datasetGroupsAdded( int count ); diff --git a/src/core/mesh/qgsmeshdatasetgroupstore.cpp b/src/core/mesh/qgsmeshdatasetgroupstore.cpp index e401da8f2616..f5b1f814b861 100644 --- a/src/core/mesh/qgsmeshdatasetgroupstore.cpp +++ b/src/core/mesh/qgsmeshdatasetgroupstore.cpp @@ -125,6 +125,44 @@ bool QgsMeshDatasetGroupStore::addDatasetGroup( QgsMeshDatasetGroup *group ) return true; } +void QgsMeshDatasetGroupStore::removeDatasetGroup( int index ) +{ + const QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( index ); + if ( group.first == mPersistentProvider ) + mPersistentProvider->removeDatasetGroup( group.second ); + else if ( group.first == &mExtraDatasets ) + eraseExtraDataset( group.second ); + + reindexDatasetGroups(); +} + +void QgsMeshDatasetGroupStore::reindexDatasetGroups() +{ + mRegistry.clear(); + mPersistentExtraDatasetGroupIndexes.clear(); + mGroupNameToGlobalIndex.clear(); + + int globalIndex = 0; + + for ( int i = 0; i < mPersistentProvider->datasetGroupCount(); i++ ) + { + const QString name = mPersistentProvider->datasetGroupMetadata( i ).name(); + mRegistry[globalIndex] = DatasetGroup{mPersistentProvider, i}; + mPersistentExtraDatasetGroupIndexes.append( globalIndex ); + mGroupNameToGlobalIndex.insert( name, globalIndex ); + globalIndex++; + } + + for ( int i = 0; i < mExtraDatasets.datasetGroupCount(); i++ ) + { + QgsMeshDatasetSourceInterface *source = &mExtraDatasets; + const QString name = source->datasetGroupMetadata( i ).name(); + mRegistry[globalIndex] = DatasetGroup{source, i}; + mGroupNameToGlobalIndex.insert( name, globalIndex ); + globalIndex++; + } +} + void QgsMeshDatasetGroupStore::resetDatasetGroupTreeItem() { mDatasetGroupTreeRootItem.reset( new QgsMeshDatasetGroupTreeItem ); @@ -695,10 +733,10 @@ int QgsMeshExtraDatasetStore::addDatasetGroup( QgsMeshDatasetGroup *datasetGroup return mGroups.size() - 1; } -void QgsMeshExtraDatasetStore::removeDatasetGroup( int index ) +void QgsMeshExtraDatasetStore::removeDatasetGroup( int groupIndex ) { - if ( index < datasetGroupCount() ) - mGroups.erase( mGroups.begin() + index ); + if ( groupIndex < datasetGroupCount() ) + mGroups.erase( mGroups.begin() + groupIndex ); updateTemporalCapabilities(); diff --git a/src/core/mesh/qgsmeshdatasetgroupstore.h b/src/core/mesh/qgsmeshdatasetgroupstore.h index da4bc2cbb197..2f58321cfa86 100644 --- a/src/core/mesh/qgsmeshdatasetgroupstore.h +++ b/src/core/mesh/qgsmeshdatasetgroupstore.h @@ -241,6 +241,13 @@ class QgsMeshDatasetGroupStore: public QObject */ QString groupName( int groupIndex ) const; + /** + * Removes dataset group with global index \a groupIndex + * + * \since QGIS 3.42 + */ + void removeDatasetGroup( int groupIndex ); + signals: //! Emitted after dataset groups are added void datasetGroupsAdded( QList indexes ); @@ -259,6 +266,9 @@ class QgsMeshDatasetGroupStore: public QObject void removePersistentProvider(); + //! reindex dataset group stores variables from provider and extra datasets, to keep data in sync after removal of dataset group + void reindexDatasetGroups(); + DatasetGroup datasetGroup( int index ) const; //! Returns a index that is not already used diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index 008aa52a1920..6e770e48b42c 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -253,6 +253,30 @@ QString QgsMeshLayer::loadDefaultStyle( bool &resultFlag ) return QgsMapLayer::loadDefaultStyle( resultFlag ); } +bool QgsMeshLayer::removeDatasets( const QString &name ) +{ + const int index = mDatasetGroupStore->indexFromGroupName( name ); + + if ( index == -1 ) + { + return false; + } + + const QgsMeshDatasetGroupMetadata groupMetadata = datasetGroupMetadata( index ); + + mDatasetGroupStore->removeDatasetGroup( index ); + + if ( mExtraDatasetUri.contains( groupMetadata.uri() ) ) + { + mExtraDatasetUri.removeOne( groupMetadata.uri() ); + } + + resetDatasetGroupTreeItem(); + + emit dataSourceChanged(); + return true; +} + bool QgsMeshLayer::addDatasets( const QString &path, const QDateTime &defaultReferenceTime ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -2210,3 +2234,17 @@ void QgsMeshLayer::setLabeling( QgsAbstractMeshLayerLabeling *labeling ) mLabeling = labeling; triggerRepaint(); } + +bool QgsMeshLayer::datasetsPathUnique( const QString &path ) +{ + if ( ! mDataProvider ) + { + QgsDebugMsgLevel( QStringLiteral( "Unable to get mesh data provider" ), 2 ); + return false; + } + + if ( mDataProvider->dataSourceUri().contains( path ) ) + return false; + + return !mExtraDatasetUri.contains( path ); +} diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index c3e3f6af52dd..11893cad2657 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -203,7 +203,7 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer, public QgsAbstractProfileSo /** * Adds datasets to the mesh from file with \a path. Use the the time \a defaultReferenceTime as reference time is not provided in the file * - * \param path the path to the atasets file + * \param path the path to the datasets file * \param defaultReferenceTime reference time used if not provided in the file * \return whether the dataset is added * @@ -211,6 +211,16 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer, public QgsAbstractProfileSo */ bool addDatasets( const QString &path, const QDateTime &defaultReferenceTime = QDateTime() ); + /** + * Removes datasets from the mesh with given \a name. + * + * \param name name of dataset group to remove + * \return whether the dataset is removed + * + * \since QGIS 3.42 + */ + bool removeDatasets( const QString &name ); + /** * Adds extra datasets to the mesh. Take ownership. * @@ -932,6 +942,16 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer, public QgsAbstractProfileSo */ void setLabeling( QgsAbstractMeshLayerLabeling *labeling SIP_TRANSFER ); + /** + * Checks whether that datasets path is already added to this mesh layer. Return TRUE if the + * dataset path is not already added. + * + * \param path the path to the datasets file + * \return whether the datasets path is unique + * + * \since QGIS 3.42 + */ + bool datasetsPathUnique( const QString &path ); public slots: @@ -986,7 +1006,6 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer, public QgsAbstractProfileSo * \param flags provider flags since QGIS 3.16 */ bool setDataProvider( QString const &provider, const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags = Qgis::DataProviderReadFlags() ); - #ifdef SIP_RUN QgsMeshLayer( const QgsMeshLayer &rhs ); #endif diff --git a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp index 5ff999a774a5..76b656a13d50 100644 --- a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp +++ b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp @@ -430,6 +430,26 @@ bool QgsMeshMemoryDataProvider::addDataset( const QString &uri ) return valid; } +bool QgsMeshMemoryDataProvider::removeDatasetGroup( int index ) +{ + if ( index < 0 && index > datasetGroupCount() - 1 ) + { + return false; + } + else + { + const QgsMeshDatasetGroupMetadata datasetGroupMeta = datasetGroupMetadata( index ); + + mDatasetGroups.removeAt( index ); + + if ( !mExtraDatasetUris.contains( datasetGroupMeta.uri() ) ) + mExtraDatasetUris.removeAll( datasetGroupMeta.uri() ); + + emit dataChanged(); + return true; + } +} + QStringList QgsMeshMemoryDataProvider::extraDatasets() const { return mExtraDatasetUris; diff --git a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h index 8d41130cbe75..5c28f60cf081 100644 --- a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h +++ b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h @@ -127,6 +127,9 @@ class CORE_EXPORT QgsMeshMemoryDataProvider final: public QgsMeshDataProvider * \endcode */ bool addDataset( const QString &uri ) override; + + bool removeDatasetGroup( int index ) override; + QStringList extraDatasets() const override; int datasetGroupCount() const override; int datasetCount( int groupIndex ) const override; diff --git a/src/gui/mesh/qgsmeshdatasetgrouptreewidget.cpp b/src/gui/mesh/qgsmeshdatasetgrouptreewidget.cpp index 62b62e8d3081..65d438fb45de 100644 --- a/src/gui/mesh/qgsmeshdatasetgrouptreewidget.cpp +++ b/src/gui/mesh/qgsmeshdatasetgrouptreewidget.cpp @@ -34,6 +34,7 @@ QgsMeshDatasetGroupTreeWidget::QgsMeshDatasetGroupTreeWidget( QWidget *parent ) setupUi( this ); connect( mAddDatasetButton, &QToolButton::clicked, this, &QgsMeshDatasetGroupTreeWidget::addDataset ); + connect( mRemoveDatasetButton, &QToolButton::clicked, this, &QgsMeshDatasetGroupTreeWidget::removeDataset ); connect( mCollapseButton, &QToolButton::clicked, mDatasetGroupTreeView, &QTreeView::collapseAll ); connect( mExpandButton, &QToolButton::clicked, mDatasetGroupTreeView, &QTreeView::expandAll ); connect( mCheckAllButton, &QToolButton::clicked, mDatasetGroupTreeView, &QgsMeshDatasetGroupTreeView::selectAllGroups ); @@ -42,6 +43,22 @@ QgsMeshDatasetGroupTreeWidget::QgsMeshDatasetGroupTreeWidget( QWidget *parent ) this->mDatasetGroupTreeView->resetDefault( this->mMeshLayer ); } ); + connect( mDatasetGroupTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, [this]() { + const QModelIndex index = mDatasetGroupTreeView->currentIndex(); + const QgsMeshDatasetGroupTreeItem *meshGroupItem = mDatasetGroupTreeView->datasetGroupTreeRootItem()->childFromDatasetGroupIndex( index.row() ); + if ( meshGroupItem ) + { + if ( mMeshLayer->dataProvider()->dataSourceUri().contains( meshGroupItem->description() ) ) + { + mRemoveDatasetButton->setEnabled( false ); + } + else + { + mRemoveDatasetButton->setEnabled( true ); + } + } + } ); + connect( mDatasetGroupTreeView, &QgsMeshDatasetGroupTreeView::apply, this, &QgsMeshDatasetGroupTreeWidget::apply ); } @@ -57,6 +74,24 @@ void QgsMeshDatasetGroupTreeWidget::apply() mMeshLayer->setDatasetGroupTreeRootItem( mDatasetGroupTreeView->datasetGroupTreeRootItem() ); } +void QgsMeshDatasetGroupTreeWidget::removeDataset() +{ + const QModelIndex index = mDatasetGroupTreeView->currentIndex(); + const QgsMeshDatasetGroupTreeItem *meshGroupItem = mDatasetGroupTreeView->datasetGroupTreeRootItem()->child( index.row() ); + const QString datasetGroupName = meshGroupItem->defaultName(); + if ( mMeshLayer->removeDatasets( datasetGroupName ) ) + { + QMessageBox::warning( this, tr( "Remove mesh datasets" ), tr( "Dataset Group removed from mesh." ) ); + emit datasetGroupsChanged(); + } + else + { + QMessageBox::warning( this, tr( "Remove mesh datasets" ), tr( "Could not remove mesh dataset group." ) ); + } + + mDatasetGroupTreeView->resetDefault( mMeshLayer ); +} + void QgsMeshDatasetGroupTreeWidget::addDataset() { if ( !mMeshLayer->dataProvider() ) @@ -71,6 +106,12 @@ void QgsMeshDatasetGroupTreeWidget::addDataset() return; // canceled by the user } + if ( !mMeshLayer->datasetsPathUnique( openFileString ) ) + { + QMessageBox::warning( this, tr( "Load mesh datasets" ), tr( "Could not add dataset from path that is already added to the mesh." ) ); + return; + } + const QFileInfo openFileInfo( openFileString ); settings.setValue( QStringLiteral( "lastMeshDatasetDir" ), openFileInfo.absolutePath(), QgsSettings::App ); const QFile datasetFile( openFileString ); @@ -78,7 +119,7 @@ void QgsMeshDatasetGroupTreeWidget::addDataset() if ( mMeshLayer->addDatasets( openFileString, QgsProject::instance()->timeSettings()->temporalRange().begin() ) ) { QMessageBox::information( this, tr( "Load mesh datasets" ), tr( "Datasets successfully added to the mesh layer" ) ); - emit datasetGroupAdded(); + emit datasetGroupsChanged(); } else { diff --git a/src/gui/mesh/qgsmeshdatasetgrouptreewidget.h b/src/gui/mesh/qgsmeshdatasetgrouptreewidget.h index 4ff43edd14ed..a28d730b94f4 100644 --- a/src/gui/mesh/qgsmeshdatasetgrouptreewidget.h +++ b/src/gui/mesh/qgsmeshdatasetgrouptreewidget.h @@ -44,10 +44,12 @@ class GUI_EXPORT QgsMeshDatasetGroupTreeWidget : public QWidget, private Ui::Qgs void apply(); signals: - void datasetGroupAdded(); + //! Emitted when dataset groups changed (addition or removal) + void datasetGroupsChanged(); private slots: void addDataset(); + void removeDataset(); private: QgsMeshLayer *mMeshLayer; diff --git a/src/gui/mesh/qgsmeshlayerproperties.cpp b/src/gui/mesh/qgsmeshlayerproperties.cpp index 9b0b3468f98f..c43d75f2513c 100644 --- a/src/gui/mesh/qgsmeshlayerproperties.cpp +++ b/src/gui/mesh/qgsmeshlayerproperties.cpp @@ -63,7 +63,7 @@ QgsMeshLayerProperties::QgsMeshLayerProperties( QgsMapLayer *lyr, QgsMapCanvas * mTemporalProviderTimeUnitComboBox->addItem( tr( "Days" ), static_cast( Qgis::TemporalUnit::Days ) ); connect( mCrsSelector, &QgsProjectionSelectionWidget::crsChanged, this, &QgsMeshLayerProperties::changeCrs ); - connect( mDatasetGroupTreeWidget, &QgsMeshDatasetGroupTreeWidget::datasetGroupAdded, this, &QgsMeshLayerProperties::syncToLayer ); + connect( mDatasetGroupTreeWidget, &QgsMeshDatasetGroupTreeWidget::datasetGroupsChanged, this, &QgsMeshLayerProperties::syncToLayer ); // QgsOptionsDialogBase handles saving/restoring of geometry, splitter and current tab states, // switching vertical tabs between icon/text to icon-only modes (splitter collapsed to left), diff --git a/src/providers/mdal/qgsmdalprovider.cpp b/src/providers/mdal/qgsmdalprovider.cpp index 4de0f4dcadcd..d13e476e0fcd 100644 --- a/src/providers/mdal/qgsmdalprovider.cpp +++ b/src/providers/mdal/qgsmdalprovider.cpp @@ -663,11 +663,16 @@ void QgsMdalProvider::fileMeshExtensions( QStringList &fileMeshExtensions, QStri bool QgsMdalProvider::addDataset( const QString &uri ) { + if ( mExtraDatasetUris.contains( uri ) || dataSourceUri().contains( uri ) ) + return false; + int datasetCount = datasetGroupCount(); std::string str = uri.toStdString(); MDAL_M_LoadDatasets( mMeshH, str.c_str() ); + makeLastDatasetGroupNameUnique(); + if ( datasetCount == datasetGroupCount() ) { return false; @@ -687,6 +692,62 @@ bool QgsMdalProvider::addDataset( const QString &uri ) } } +void QgsMdalProvider::makeLastDatasetGroupNameUnique() +{ + MDAL_DatasetGroupH datasetGroupH = MDAL_M_datasetGroup( mMeshH, datasetGroupCount() - 1 ); + QString lastAddedGroupName = QString( MDAL_G_name( datasetGroupH ) ); + + QSet existingNames; + + for ( int i = 0; i < datasetGroupCount() - 1; i++ ) + { + existingNames.insert( MDAL_G_name( MDAL_M_datasetGroup( mMeshH, i ) ) ); + } + + if ( existingNames.contains( lastAddedGroupName ) ) + { + const thread_local QRegularExpression reEndsNumber( "_([0-9]+)$" ); + QRegularExpressionMatch match; + + while ( existingNames.find( lastAddedGroupName ) != existingNames.end() ) + { + match = reEndsNumber.match( lastAddedGroupName ); + if ( match.hasMatch() ) + { + const int number = match.capturedTexts().constLast().toInt(); + lastAddedGroupName = lastAddedGroupName.left( lastAddedGroupName.length() - match.capturedLength() + 1 ) + QString::number( number + 1 ); + } + else + { + lastAddedGroupName = lastAddedGroupName.append( "_1" ); + } + } + MDAL_G_setName( datasetGroupH, lastAddedGroupName.toStdString().c_str() ); + } +} + +bool QgsMdalProvider::removeDatasetGroup( int index ) +{ + if ( index < 0 && index > datasetGroupCount() - 1 ) + { + return false; + } + else + { + const QgsMeshDatasetGroupMetadata datasetGroupMeta = datasetGroupMetadata( index ); + + if ( !mExtraDatasetUris.contains( datasetGroupMeta.uri() ) ) + { + return false; + } + + mExtraDatasetUris.removeOne( datasetGroupMeta.uri() ); + MDAL_M_RemoveDatasetGroup( mMeshH, index ); + emit dataChanged(); + return true; + } +} + QStringList QgsMdalProvider::extraDatasets() const { return mExtraDatasetUris; diff --git a/src/providers/mdal/qgsmdalprovider.h b/src/providers/mdal/qgsmdalprovider.h index b1156bcccb6c..ca23001b5e93 100644 --- a/src/providers/mdal/qgsmdalprovider.h +++ b/src/providers/mdal/qgsmdalprovider.h @@ -62,6 +62,9 @@ class QgsMdalProvider : public QgsMeshDataProvider void populateMesh( QgsMesh *mesh ) const override; bool addDataset( const QString &uri ) override; + + bool removeDatasetGroup( int index ) override; + QStringList extraDatasets() const override; int datasetGroupCount() const override; @@ -116,6 +119,10 @@ class QgsMdalProvider : public QgsMeshDataProvider QVector faces() const; void loadData(); void addGroupToTemporalCapabilities( int indexGroup ); + + // ensures that last added dataset group has unique name (adds suffix of underscore and number to make it unique) + void makeLastDatasetGroupNameUnique(); + MDAL_MeshH mMeshH = nullptr; QStringList mExtraDatasetUris; QgsCoordinateReferenceSystem mCrs; diff --git a/src/ui/mesh/qgsmeshdatasetgrouptreewidgetbase.ui b/src/ui/mesh/qgsmeshdatasetgrouptreewidgetbase.ui index 6035f2c27833..af99612eb5fe 100644 --- a/src/ui/mesh/qgsmeshdatasetgrouptreewidgetbase.ui +++ b/src/ui/mesh/qgsmeshdatasetgrouptreewidgetbase.ui @@ -53,7 +53,7 @@ - :/images/themes/default/mActionAdd.svg:/images/themes/default/mActionAdd.svg + :/images/themes/default/symbologyAdd.svg:/images/themes/default/symbologyAdd.svg @@ -66,6 +66,20 @@ + + + + false + + + ... + + + + :/images/themes/default/symbologyRemove.svg:/images/themes/default/symbologyRemove.svg + + + diff --git a/tests/src/core/testqgsmeshlayer.cpp b/tests/src/core/testqgsmeshlayer.cpp index 7a34e098fed6..a3291bdf1055 100644 --- a/tests/src/core/testqgsmeshlayer.cpp +++ b/tests/src/core/testqgsmeshlayer.cpp @@ -107,6 +107,8 @@ class TestQgsMeshLayer : public QgsTest void testSelectByExpression(); void testSetDataSourceRetainStyle(); + void testRemoveDatasets(); + void testDatasetsUniquePath(); void keepDatasetIndexConsistency(); void symbologyConsistencyWithName(); @@ -1229,12 +1231,8 @@ void TestQgsMeshLayer::test_reload_extra_dataset() QCOMPARE( layer.datasetGroupCount(), 2 ); QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 2 ); - // Add twice the same file - QVERIFY( layer.addDatasets( testFileDataSet.fileName() ) ); //dataset added - QCOMPARE( layer.dataProvider()->extraDatasets().count(), 1 ); //uri not dupplicated - QCOMPARE( layer.dataProvider()->datasetGroupCount(), 3 ); //dataset added - QCOMPARE( layer.datasetGroupCount(), 2 ); // meshLayer do not allow dataset group with same name - QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 2 ); + // Add twice the same file - not allow since 3.42 + QCOMPARE( layer.addDatasets( testFileDataSet.fileName() ), false ); //dataset added indexes = layer.datasetGroupsIndexes(); for ( int index : std::as_const( indexes ) ) @@ -1246,7 +1244,7 @@ void TestQgsMeshLayer::test_reload_extra_dataset() QVERIFY( layer.addDatasets( testFileDataSet_3.fileName() ) ); QCOMPARE( layer.dataProvider()->extraDatasets().count(), 2 ); - QCOMPARE( layer.dataProvider()->datasetGroupCount(), 4 ); + QCOMPARE( layer.dataProvider()->datasetGroupCount(), 3 ); QCOMPARE( layer.datasetGroupCount(), 3 ); QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 3 ); @@ -2366,5 +2364,54 @@ void TestQgsMeshLayer::testHaveSameParentQuantity() QVERIFY( !QgsMeshLayerUtils::haveSameParentQuantity( &layer2, QgsMeshDatasetIndex( 0 ), QgsMeshDatasetIndex( 1 ) ) ); } + +void TestQgsMeshLayer::testDatasetsUniquePath() +{ + QgsMeshLayer layer( + testDataPath( "mesh/quad_and_triangle.2dm" ), + QStringLiteral( "mesh" ), + QStringLiteral( "mdal" ) + ); + QVERIFY( layer.isValid() ); + + QString path = testDataPath( "/mesh/quad_and_triangle_vertex_vector.dat" ); + + // is unique + QVERIFY( layer.datasetsPathUnique( path ) ); + + // add dataset group + QVERIFY( layer.addDatasets( path ) ); + + // no longer unique - already exist + QCOMPARE( layer.datasetsPathUnique( path ), false ); +} + +void TestQgsMeshLayer::testRemoveDatasets() +{ + QgsMeshLayer layer( + testDataPath( "mesh/quad_and_triangle.2dm" ), + QStringLiteral( "mesh" ), + QStringLiteral( "mdal" ) + ); + QVERIFY( layer.isValid() ); + + // add datasets with same name + QVERIFY( layer.addDatasets( testDataPath( "/mesh/quad_and_triangle_vertex_vector.dat" ) ) ); + + const QString fileDatasetGroup = QDir::tempPath() + QStringLiteral( "/quad_and_triangle_vertex_vector.dat" ); + + QFile::remove( fileDatasetGroup ); + QVERIFY( QFile::copy( testDataPath( "/mesh/quad_and_triangle_vertex_vector.dat" ), fileDatasetGroup ) ); + + QVERIFY( layer.addDatasets( fileDatasetGroup ) ); + + // can remove but the second has added "_1" + QVERIFY( layer.removeDatasets( "VertexVectorDataset" ) ); + QVERIFY( layer.removeDatasets( "VertexVectorDataset_1" ) ); + + // cannot remove by name - does not exist + QCOMPARE( layer.removeDatasets( "Non Existing Dataset Group" ), false ); +} + QGSTEST_MAIN( TestQgsMeshLayer ) #include "testqgsmeshlayer.moc" diff --git a/tests/src/providers/testqgsmdalprovider.cpp b/tests/src/providers/testqgsmdalprovider.cpp index d6d98a95612d..ea086adb40d1 100644 --- a/tests/src/providers/testqgsmdalprovider.cpp +++ b/tests/src/providers/testqgsmdalprovider.cpp @@ -52,6 +52,8 @@ class TestQgsMdalProvider : public QgsTest void encodeDecodeUri(); void absoluteRelativeUri(); void preserveMeshMetadata(); + void uniqueDatasetNames(); + void addRemoveDatasetGroups(); private: QString mTestDataDir; @@ -207,5 +209,129 @@ void TestQgsMdalProvider::preserveMeshMetadata() delete mesh; } + +void TestQgsMdalProvider::uniqueDatasetNames() +{ + const QString file = QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/mesh/quad_and_triangle.2dm" ); + QgsDataProvider *provider = QgsProviderRegistry::instance()->createProvider( + QStringLiteral( "mdal" ), + file, + QgsDataProvider::ProviderOptions() + ); + + QgsMeshDataProvider *mp = dynamic_cast( provider ); + + QgsDataProvider *provider1 = QgsProviderRegistry::instance()->createProvider( + QStringLiteral( "mdal" ), + file, + QgsDataProvider::ProviderOptions() + ); + QgsMeshDataProvider *mp1 = dynamic_cast( provider1 ); + + // these three dataset files have the same name + const QString fileDatasetGroup1 = QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/mesh/quad_and_triangle_vertex_vector.dat" ); + const QString fileDatasetGroup2 = QDir::tempPath() + QStringLiteral( "/quad_and_triangle_vertex_vector_1.dat" ); + const QString fileDatasetGroup3 = QDir::tempPath() + QStringLiteral( "/quad_and_triangle_vertex_vector_2.dat" ); + + QFile::remove( fileDatasetGroup2 ); + QVERIFY( QFile::copy( fileDatasetGroup1, fileDatasetGroup2 ) ); + + QFile::remove( fileDatasetGroup3 ); + QVERIFY( QFile::copy( fileDatasetGroup1, fileDatasetGroup3 ) ); + + // test that if added to different provider they have same names + QVERIFY( mp->addDataset( fileDatasetGroup1 ) ); + QCOMPARE( mp->datasetGroupCount(), 2 ); + + QgsMeshDatasetGroupMetadata metadata = mp->datasetGroupMetadata( 1 ); + QCOMPARE( metadata.name(), QStringLiteral( "VertexVectorDataset" ) ); + + QVERIFY( mp1->addDataset( fileDatasetGroup2 ) ); + QCOMPARE( mp1->datasetGroupCount(), 2 ); + + metadata = mp1->datasetGroupMetadata( 1 ); + QCOMPARE( metadata.name(), QStringLiteral( "VertexVectorDataset" ) ); + + QCOMPARE( mp1->datasetGroupMetadata( 1 ).name(), mp->datasetGroupMetadata( 1 ).name() ); + + //test that if both additional files are added to the same provider, the names will be unique + QVERIFY( mp->addDataset( fileDatasetGroup2 ) ); + QCOMPARE( mp->datasetGroupCount(), 3 ); + + QVERIFY( mp->addDataset( fileDatasetGroup3 ) ); + QCOMPARE( mp->datasetGroupCount(), 4 ); + + metadata = mp->datasetGroupMetadata( 1 ); + QCOMPARE( metadata.name(), QStringLiteral( "VertexVectorDataset" ) ); + metadata = mp->datasetGroupMetadata( 2 ); + QCOMPARE( metadata.name(), QStringLiteral( "VertexVectorDataset_1" ) ); + metadata = mp->datasetGroupMetadata( 3 ); + QCOMPARE( metadata.name(), QStringLiteral( "VertexVectorDataset_2" ) ); + + delete provider; + delete provider1; +} + +void TestQgsMdalProvider::addRemoveDatasetGroups() +{ + const QString file = QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/mesh/quad_and_triangle.2dm" ); + QgsDataProvider *provider = QgsProviderRegistry::instance()->createProvider( + QStringLiteral( "mdal" ), + file, + QgsDataProvider::ProviderOptions() + ); + + QgsMeshDataProvider *mp = dynamic_cast( provider ); + QVERIFY( mp ); + QVERIFY( mp->isValid() ); + QCOMPARE( mp->datasetGroupCount(), 1 ); + + const QString fileDatasetGroup1 = QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/mesh/quad_and_triangle_vertex_vector.dat" ); + QVERIFY( mp->addDataset( fileDatasetGroup1 ) ); + QCOMPARE( mp->datasetGroupCount(), 2 ); + + // cannot add the same dataset twice + QCOMPARE( mp->addDataset( fileDatasetGroup1 ), false ); + + const QString fileDatasetGroup2 = QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/mesh/quad_and_triangle_vertex_scalar2.dat" ); + QVERIFY( mp->addDataset( fileDatasetGroup2 ) ); + QCOMPARE( mp->datasetGroupCount(), 3 ); + + QgsMeshDatasetGroupMetadata metadata = mp->datasetGroupMetadata( 0 ); + QCOMPARE( metadata.name(), QStringLiteral( "Bed Elevation" ) ); + + metadata = mp->datasetGroupMetadata( 1 ); + QCOMPARE( metadata.name(), QStringLiteral( "VertexVectorDataset" ) ); + + metadata = mp->datasetGroupMetadata( 2 ); + QCOMPARE( metadata.name(), QStringLiteral( "VertexScalarDataset2" ) ); + + // cannot remove dataset groups with index outside of range + QCOMPARE( mp->removeDatasetGroup( -1 ), false ); + QCOMPARE( mp->removeDatasetGroup( 10 ), false ); + + // cannot remove data associated with source file + QCOMPARE( mp->removeDatasetGroup( 0 ), false ); + + // can remove other datasets group + QCOMPARE( mp->removeDatasetGroup( 1 ), true ); + + // check the dataset group that are left + QCOMPARE( mp->datasetGroupCount(), 2 ); + metadata = mp->datasetGroupMetadata( 0 ); + QCOMPARE( metadata.name(), QStringLiteral( "Bed Elevation" ) ); + metadata = mp->datasetGroupMetadata( 1 ); + QCOMPARE( metadata.name(), QStringLiteral( "VertexScalarDataset2" ) ); + + // remove the second - only the original remains + QCOMPARE( mp->removeDatasetGroup( 1 ), true ); + + // check the dataset group that are left + QCOMPARE( mp->datasetGroupCount(), 1 ); + metadata = mp->datasetGroupMetadata( 0 ); + QCOMPARE( metadata.name(), QStringLiteral( "Bed Elevation" ) ); + + delete provider; +} QGSTEST_MAIN( TestQgsMdalProvider ) #include "testqgsmdalprovider.moc"