From af33a031f9eedcb16c7fe598a1c84719fb2e1977 Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 24 Jun 2020 15:55:00 -0400 Subject: [PATCH 01/10] memory mesh dataset group --- .../mesh/qgsmeshcalculator.sip.in | 39 + .../mesh/qgsmeshdataprovider.sip.in | 14 + .../auto_generated/mesh/qgsmeshdataset.sip.in | 57 ++ .../auto_generated/mesh/qgsmeshlayer.sip.in | 207 +++++- .../auto_generated/qgsprovidermetadata.sip.in | 8 +- src/3d/mesh/qgsmesh3dgeometry_p.cpp | 8 +- src/3d/mesh/qgsmesh3dmaterial_p.cpp | 4 +- src/analysis/mesh/qgsmeshcalculator.cpp | 78 +- src/analysis/mesh/qgsmeshcalculator.h | 38 + src/analysis/mesh/qgsmeshcalcutils.cpp | 77 +- src/app/mesh/qgsmeshcalculatordialog.cpp | 138 +++- src/app/mesh/qgsmeshcalculatordialog.h | 14 +- src/app/mesh/qgsmeshdatasetgrouptreeview.cpp | 225 +++++- src/app/mesh/qgsmeshdatasetgrouptreeview.h | 51 +- .../mesh/qgsmeshdatasetgrouptreewidget.cpp | 3 +- src/app/mesh/qgsmeshdatasetgrouptreewidget.h | 1 + .../qgsmeshrendereractivedatasetwidget.cpp | 2 +- .../qgsmeshrendererscalarsettingswidget.cpp | 12 +- .../qgsmeshrenderervectorsettingswidget.cpp | 8 +- src/app/mesh/qgsmeshstaticdatasetwidget.cpp | 26 +- .../mesh/qgsrenderermeshpropertieswidget.cpp | 3 +- src/app/qgisapp.cpp | 47 +- src/core/CMakeLists.txt | 4 +- src/core/mesh/qgsmeshdataprovider.cpp | 31 +- src/core/mesh/qgsmeshdataprovider.h | 18 +- ...gsmeshdataprovidertemporalcapabilities.cpp | 4 +- .../qgsmeshdataprovidertemporalcapabilities.h | 1 + src/core/mesh/qgsmeshdataset.cpp | 279 ++++++- src/core/mesh/qgsmeshdataset.h | 239 ++++++ src/core/mesh/qgsmeshdatasetgroupstore.cpp | 684 ++++++++++++++++++ src/core/mesh/qgsmeshdatasetgroupstore.h | 226 ++++++ src/core/mesh/qgsmeshlayer.cpp | 217 ++++-- src/core/mesh/qgsmeshlayer.h | 192 ++++- src/core/mesh/qgsmeshlayerrenderer.cpp | 14 +- src/core/mesh/qgsmeshlayerutils.cpp | 16 +- src/core/mesh/qgsmeshlayerutils.h | 43 -- .../meshmemory/qgsmeshmemorydataprovider.cpp | 209 ++---- .../meshmemory/qgsmeshmemorydataprovider.h | 42 +- src/core/qgsprovidermetadata.cpp | 9 +- src/core/qgsprovidermetadata.h | 11 +- src/gui/qgsmaptoolidentify.cpp | 6 +- src/providers/mdal/qgsmdalprovider.cpp | 115 ++- src/providers/mdal/qgsmdalprovider.h | 6 + src/ui/mesh/qgsmeshcalculatordialogbase.ui | 68 +- tests/src/analysis/testqgsmeshcalculator.cpp | 4 +- tests/src/core/testqgsmeshlayer.cpp | 541 +++++++++++--- 46 files changed, 3383 insertions(+), 656 deletions(-) create mode 100644 src/core/mesh/qgsmeshdatasetgroupstore.cpp create mode 100644 src/core/mesh/qgsmeshdatasetgroupstore.h diff --git a/python/analysis/auto_generated/mesh/qgsmeshcalculator.sip.in b/python/analysis/auto_generated/mesh/qgsmeshcalculator.sip.in index 57e7cb9b9065..a10d6430dac3 100644 --- a/python/analysis/auto_generated/mesh/qgsmeshcalculator.sip.in +++ b/python/analysis/auto_generated/mesh/qgsmeshcalculator.sip.in @@ -130,6 +130,45 @@ Creates calculator with geometry mask .. versionadded:: 3.12 %End + QgsMeshCalculator( const QString &formulaString, + const QString &outputGroupName, + double startTime, + double endTime, + const QgsRectangle &outputExtent, + QgsMeshLayer *layer ); +%Docstring +Creates calculator with bounding box (rectangular) mask, store the result in the memory + +:param formulaString: formula/expression to evaluate. Consists of dataset group names, operators and numbers +:param outputGroupName: output group name +:param outputExtent: spatial filter defined by rectangle +:param startTime: time filter defining the starting dataset +:param endTime: time filter defining the ending dataset +:param layer: mesh layer with dataset groups references in formulaString + +.. versionadded:: 3.16 +%End + + QgsMeshCalculator( const QString &formulaString, + const QString &outputGroupName, + double startTime, + double endTime, + const QgsGeometry &outputMask, + QgsMeshLayer *layer ); +%Docstring +Creates calculator with with geometry mask, store the result in the memory + +:param formulaString: formula/expression to evaluate. Consists of dataset group names, operators and numbers +:param outputGroupName: output group name +:param outputMask: spatial filter defined by geometry +:param startTime: time filter defining the starting dataset +:param endTime: time filter defining the ending dataset +:param layer: mesh layer with dataset groups references in formulaString + +.. versionadded:: 3.16 +%End + + Result processCalculation( QgsFeedback *feedback = 0 ); %Docstring Starts the calculation, writes new dataset group to file and adds it to the mesh layer diff --git a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in index 70acf4286b37..02f7b01f648f 100644 --- a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in @@ -166,6 +166,7 @@ Datasets are grouped in the dataset groups. A dataset group represents a measure #include "qgsmeshdataprovider.h" %End public: + QgsMeshDatasetSourceInterface(); virtual ~QgsMeshDatasetSourceInterface(); virtual bool addDataset( const QString &uri ) = 0; @@ -325,6 +326,19 @@ On success, the mesh's dataset group count is changed .. versionadded:: 3.12.3 %End + + virtual bool persistDatasetGroup( const QString &outputFilePath, + const QString &outputDriver, + QgsMeshDatasetSourceInterface *source, + int datasetGroupIndex + ) = 0; + + QgsMeshDatasetIndex datasetIndexAtTime( const QDateTime &referenceTime, + int groupIndex, + quint64 time, + QgsMeshDataProviderTemporalCapabilities::MatchingTemporalDatasetMethod method ) const; + + protected: }; diff --git a/python/core/auto_generated/mesh/qgsmeshdataset.sip.in b/python/core/auto_generated/mesh/qgsmeshdataset.sip.in index b0fb6400feff..3cba837383b5 100644 --- a/python/core/auto_generated/mesh/qgsmeshdataset.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshdataset.sip.in @@ -399,6 +399,7 @@ Constructs an empty metadata object %End QgsMeshDatasetGroupMetadata( const QString &name, + const QString uri, bool isScalar, DataType dataType, double minimum, @@ -424,6 +425,13 @@ Constructs a valid metadata object QString name() const; %Docstring Returns name of the dataset group +%End + + QString uri() const; +%Docstring +Returns the uri of the source + +.. versionadded:: 3.16 %End QMap extraOptions() const; @@ -578,12 +586,21 @@ Maximum %End public: + enum StorageType + { + None, + File, + Memory, + OnTheFly + }; + QgsMeshDatasetGroupTreeItem(); %Docstring Constructor for an empty dataset group tree item %End QgsMeshDatasetGroupTreeItem( const QString &defaultName, + const QString &providerName, bool isVector, int index ); %Docstring @@ -622,6 +639,19 @@ Appends a item child takes ownership of item %End + void removeChild( QgsMeshDatasetGroupTreeItem *item /Transfer/ ); +%Docstring +Removes a item child if exists + +:param item: the item to append + +.. note:: + + takes ownership of item + +.. versionadded:: 3.16 +%End + QgsMeshDatasetGroupTreeItem *child( int row ) const; %Docstring Returns a child @@ -684,6 +714,15 @@ The default name is still stored in the item but will not be displayed anymore except if the empty string is setted. :param name: to display +%End + + QString providerName() const; +%Docstring +Returns the name used by the provider to identify the dataset + +:return: the provider name + +.. versionadded:: 3.16 %End bool isVector() const; @@ -715,6 +754,23 @@ Sets whether the item is enabled, that is if it is displayed in view %Docstring :return: the default name +%End + + QgsMeshDatasetGroupTreeItem::StorageType storageType() const; +%Docstring + +:return: where the dataset group is stored + +.. versionadded:: 3.16 +%End + + void setStorageType( QgsMeshDatasetGroupTreeItem::StorageType storageType ); +%Docstring +Sets where the dataset is stored + +:param storeType: the type of storing + +.. versionadded:: 3.16 %End QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ); @@ -729,6 +785,7 @@ Write the item and its children in a DOM document }; + /************************************************************************ * This file has been generated automatically from * * * diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index f33f7b30f291..b17cc3ba3448 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -11,6 +11,7 @@ + class QgsMeshLayer : QgsMapLayer { %Docstring @@ -152,7 +153,7 @@ Returns the provider type for this layer bool addDatasets( const QString &path, const QDateTime &defaultReferenceTime = QDateTime() ); %Docstring -Add datasets to the mesh from file with ``path``. Use the the time ``defaultReferenceTime`` as reference time is not provided in the file +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 defaultReferenceTime: reference time used if not provided in the file @@ -163,6 +164,20 @@ Add datasets to the mesh from file with ``path``. Use the the time ``defaultRefe %End + bool saveDataset( const QString &path, int datasetGroupIndex, QString driver ); +%Docstring +Saves datasets group on file with the specified ``driver`` + +:param path: the path of the file +:param datasetGroupIndex: the index of the dataset group +:param driver: the driver to used for saving + +:return: false if succeeds + +.. versionadded:: 3.16 +%End + + void updateTriangularMesh( const QgsCoordinateTransform &transform = QgsCoordinateTransform() ); @@ -209,6 +224,165 @@ Returns (date) time in hours formatted to human readable form :return: formatted time string .. versionadded:: 3.8 +%End + + int datasetGroupCount() const; +%Docstring +Returns the dataset groups count handle by the layer + +.. versionadded:: 3.16 +%End + + int extraDatasetGroupCount() const; +%Docstring +Returns the extra dataset groups count handle by the layer + +.. versionadded:: 3.16 +%End + + QList datasetGroupsIndexes() const; +%Docstring +Returns the list of indexes of dataset groups count handled by the layer + +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes can be different from the data provider indexes. + +.. versionadded:: 3.16 +%End + + QgsMeshDatasetGroupMetadata datasetGroupMetadata( const QgsMeshDatasetIndex &index ) const; +%Docstring +Returns the dataset groups metadata + +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes can be different from the data provider indexes. + +.. versionadded:: 3.16 +%End + + int datasetCount( const QgsMeshDatasetIndex &index ) const; +%Docstring +Returns the dataset count in the dataset groups + +:param index: index of the dataset in the group + +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes can be different from the data provider indexes. + + +.. versionadded:: 3.16 +%End + + QgsMeshDatasetMetadata datasetMetadata( const QgsMeshDatasetIndex &index ) const; +%Docstring +Returns the dataset metadata + +:param index: index of the dataset + +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes can be different from the data provider indexes. + + +.. versionadded:: 3.16 +%End + + QgsMeshDatasetValue datasetValue( const QgsMeshDatasetIndex &index, int valueIndex ) const; +%Docstring +Returns vector/scalar value associated with the index from the dataset +To read multiple continuous values, use :py:func:`~QgsMeshLayer.datasetValues` + +See :py:func:`QgsMeshDatasetMetadata.isVector()` or :py:func:`QgsMeshDataBlock.type()` +to check if the returned value is vector or scalar + +Returns invalid value for DataOnVolumes + +:param index: index of the dataset +:param valueIndex: index of the value + +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes can be different from the data provider indexes. + + +.. versionadded:: 3.16 +%End + + QgsMeshDataBlock datasetValues( const QgsMeshDatasetIndex &index, int valueIndex, int count ) const; +%Docstring +Returns N vector/scalar values from the index from the dataset + +See :py:func:`QgsMeshDatasetMetadata.isVector()` or :py:func:`QgsMeshDataBlock.type()` +to check if the returned value is vector or scalar + +Returns invalid block for DataOnVolumes. Use :py:func:`QgsMeshLayerUtils.datasetValues()` if you +need block for any type of data type + +:param index: index of the dataset +:param valueIndex: index of the value +:param count: number of values to return + +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes can be different from the data provider indexes. + + +.. versionadded:: 3.16 +%End + + QgsMesh3dDataBlock dataset3dValues( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const; +%Docstring +Returns N vector/scalar values from the face index from the dataset for 3d stacked meshes + +See :py:func:`QgsMeshDatasetMetadata.isVector()` to check if the returned value is vector or scalar + +returns invalid block for DataOnFaces and DataOnVertices. + +:param index: index of the dataset +:param valueIndex: index of the value +:param count: number of values to return + +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes can be different from the data provider indexes. + + +.. versionadded:: 3.16 +%End + + bool isFaceActive( const QgsMeshDatasetIndex &index, int faceIndex ) const; +%Docstring +Returns N vector/scalar values from the face index from the dataset for 3d stacked meshes + +See :py:func:`QgsMeshDatasetMetadata.isVector()` to check if the returned value is vector or scalar + +returns invalid block for DataOnFaces and DataOnVertices. +%End + QgsMeshDataBlock areFacesActive( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const; +%Docstring +Returns whether the faces are active for particular dataset + +:param index: index of the dataset +:param valueIndex: index of the value +:param count: number of values to return + +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes are different from the data provider indexes. + + +.. versionadded:: 3.16 %End QgsMeshDatasetValue datasetValue( const QgsMeshDatasetIndex &index, const QgsPointXY &point, double searchRadius = 0 ) const; @@ -234,6 +408,12 @@ For 1D datasets, it uses :py:func:`~QgsMeshLayer.dataset1dValue` with ``searchRa previously used for rendering +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes are different from the data provider indexes. + + .. versionadded:: 3.4 %End @@ -257,6 +437,12 @@ Returns the 3d values of stacked 3d mesh defined by the given point previously used for rendering or for datasets that do not have type DataOnVolumes +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes are different from the data provider indexes. + + .. versionadded:: 3.12 %End @@ -280,6 +466,12 @@ Returns the value of 1D mesh dataset defined on edge that are in the search area outside the mesh layer and in case triangular mesh was not previously used for rendering +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes are different from the data provider indexes. + + .. versionadded:: 3.14 %End @@ -298,6 +490,12 @@ If the temporal properties is not active, returns invalid dataset index the returned dataset index depends on the matching method, see :py:func:`~QgsMeshLayer.setTemporalMatchingMethod` +.. note:: + + indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + In the layer scope, those indexes are different from the data provider indexes. + + .. versionadded:: 3.14 %End @@ -433,6 +631,13 @@ Reset the dataset group tree item to default from provider Returns the first valid time step of the dataset groups, invalid QgInterval if no time step is present .. versionadded:: 3.14 +%End + + QgsInterval datasetRelativeTime( const QgsMeshDatasetIndex &index ); +%Docstring +Returns the relative time (in milliseconds) of the dataset from the reference time of its group + +.. versionadded:: 3.16 %End public slots: diff --git a/python/core/auto_generated/qgsprovidermetadata.sip.in b/python/core/auto_generated/qgsprovidermetadata.sip.in index 34fdef5ee60f..f12442a40ee2 100644 --- a/python/core/auto_generated/qgsprovidermetadata.sip.in +++ b/python/core/auto_generated/qgsprovidermetadata.sip.in @@ -46,7 +46,8 @@ Constructs default metadata without any capabilities QgsMeshDriverMetadata( const QString &name, const QString &description, - const MeshDriverCapabilities &capabilities ); + const MeshDriverCapabilities &capabilities, + const QString &writeDatasetOnFileSuffix); %Docstring Constructs driver metadata with selected capabilities @@ -68,6 +69,11 @@ Returns the name (key) for this driver. QString description() const; %Docstring Returns the description for this driver. +%End + + QString writeDatasetOnFileSuffix() const; +%Docstring +Returns the suffix used to write datasets on file %End }; diff --git a/src/3d/mesh/qgsmesh3dgeometry_p.cpp b/src/3d/mesh/qgsmesh3dgeometry_p.cpp index 2a838b38386b..77025846e1d5 100644 --- a/src/3d/mesh/qgsmesh3dgeometry_p.cpp +++ b/src/3d/mesh/qgsmesh3dgeometry_p.cpp @@ -394,7 +394,7 @@ int QgsMeshDataset3dGeometry::extractDataset( QVector &verticalMagnitude { QgsMeshLayer *layer = meshLayer(); - if ( !layer || !layer->dataProvider() ) + if ( !layer ) return 0; QgsMeshDatasetIndex scalarDatasetIndex = layer->activeScalarDatasetAtTime( mTimeRange ); @@ -416,12 +416,12 @@ int QgsMeshDataset3dGeometry::extractDataset( QVector &verticalMagnitude { //if invalid (for example, static mode) use the scalar dataset index int vertDataSetIndex = scalarDatasetIndex.dataset(); - vertDataSetIndex = std::min( vertDataSetIndex, layer->dataProvider()->datasetCount( mVerticalGroupDatasetIndex ) - 1 ); + vertDataSetIndex = std::min( vertDataSetIndex, layer->datasetCount( mVerticalGroupDatasetIndex ) - 1 ); verticalMagDatasetIndex = QgsMeshDatasetIndex( vertDataSetIndex, mVerticalGroupDatasetIndex ); } //define the active face for vertical magnitude, the inactive faces will not be rendered // The active face flag values are defined based on the vertival magnitude dataset - activeFaceFlagValues = layer->dataProvider()->areFacesActive( verticalMagDatasetIndex, 0, nativeMesh.faces.count() ); + activeFaceFlagValues = layer->areFacesActive( verticalMagDatasetIndex, 0, nativeMesh.faces.count() ); verticalMagnitude = QgsMeshLayerUtils::calculateMagnitudeOnVertices( layer, verticalMagDatasetIndex, @@ -441,7 +441,7 @@ int QgsMeshDataset3dGeometry::extractDataset( QVector &verticalMagnitude //extract the scalar dataset used to render color shading QgsMeshDataBlock scalarActiveFaceFlagValues = - layer->dataProvider()->areFacesActive( scalarDatasetIndex, 0, nativeMesh.faces.count() ); + layer->areFacesActive( scalarDatasetIndex, 0, nativeMesh.faces.count() ); scalarMagnitude = QgsMeshLayerUtils::calculateMagnitudeOnVertices( layer, scalarDatasetIndex, diff --git a/src/3d/mesh/qgsmesh3dmaterial_p.cpp b/src/3d/mesh/qgsmesh3dmaterial_p.cpp index 5b3f3f7c7c40..87f38cd4bdb7 100644 --- a/src/3d/mesh/qgsmesh3dmaterial_p.cpp +++ b/src/3d/mesh/qgsmesh3dmaterial_p.cpp @@ -316,7 +316,7 @@ void QgsMesh3dMaterial::configure() void QgsMesh3dMaterial::configureArrows( QgsMeshLayer *layer, const QgsDateTimeRange &timeRange ) { - if ( !layer || !layer->dataProvider() ) + if ( !layer ) return; QgsMeshDatasetIndex datasetIndex = layer->activeVectorDatasetAtTime( timeRange ); @@ -325,7 +325,7 @@ void QgsMesh3dMaterial::configureArrows( QgsMeshLayer *layer, const QgsDateTimeR QColor arrowsColor = layer->rendererSettings().vectorSettings( datasetIndex.group() ).color(); mTechnique->addParameter( new Qt3DRender::QParameter( "arrowsColor", QVector4D( arrowsColor.redF(), arrowsColor.greenF(), arrowsColor.blueF(), 1.0f ) ) ) ; - QgsMeshDatasetGroupMetadata meta = layer->dataProvider()->datasetGroupMetadata( datasetIndex ); + QgsMeshDatasetGroupMetadata meta = layer->datasetGroupMetadata( datasetIndex ); QVector vectors; QSize gridSize; diff --git a/src/analysis/mesh/qgsmeshcalculator.cpp b/src/analysis/mesh/qgsmeshcalculator.cpp index 7786f0285436..b454953cc61a 100644 --- a/src/analysis/mesh/qgsmeshcalculator.cpp +++ b/src/analysis/mesh/qgsmeshcalculator.cpp @@ -104,6 +104,40 @@ QgsMeshCalculator::QgsMeshCalculator( const QString &formulaString, { } +QgsMeshCalculator::QgsMeshCalculator( const QString &formulaString, + const QString &outputGroupName, + double startTime, + double endTime, + const QgsRectangle &outputExtent, + QgsMeshLayer *layer ) + : mFormulaString( formulaString ) + , mOutputGroupName( outputGroupName ) + , mOutputExtent( outputExtent ) + , mUseMask( false ) + , mStartTime( startTime ) + , mEndTime( endTime ) + , mResultInMemory( true ) + , mMeshLayer( layer ) +{ +} + +QgsMeshCalculator::QgsMeshCalculator( const QString &formulaString, + const QString &outputGroupName, + double startTime, + double endTime, + const QgsGeometry &outputMask, + QgsMeshLayer *layer ) + : mFormulaString( formulaString ) + , mOutputGroupName( outputGroupName ) + , mOutputMask( outputMask ) + , mUseMask( true ) + , mStartTime( startTime ) + , mEndTime( endTime ) + , mResultInMemory( true ) + , mMeshLayer( layer ) +{ +} + QgsMeshCalculator::Result QgsMeshCalculator::expression_valid( const QString &formulaString, QgsMeshLayer *layer ) @@ -136,7 +170,7 @@ QgsMeshCalculator::Result QgsMeshCalculator::expressionIsValid( QgsMeshCalculator::Result QgsMeshCalculator::processCalculation( QgsFeedback *feedback ) { // check input - if ( mOutputFile.isEmpty() ) + if ( mOutputFile.isEmpty() && !mResultInMemory ) { return CreateOutputError; } @@ -164,7 +198,7 @@ QgsMeshCalculator::Result QgsMeshCalculator::processCalculation( QgsFeedback *fe } //open output dataset - std::unique_ptr outputGroup = qgis::make_unique ( mOutputFile, dsu.outputType() ); + std::unique_ptr outputGroup = qgis::make_unique ( mOutputGroupName, dsu.outputType() ); // calculate bool ok = calcNode->calculate( dsu, *outputGroup ); @@ -191,8 +225,7 @@ QgsMeshCalculator::Result QgsMeshCalculator::processCalculation( QgsFeedback *fe { dsu.filter( *outputGroup, mOutputExtent ); } - outputGroup->isScalar = true; - outputGroup->name = mOutputGroupName; + outputGroup->setIsScalar( true ); // before storing the file, find out if the process is not already canceled if ( feedback && feedback->isCanceled() ) @@ -204,22 +237,22 @@ QgsMeshCalculator::Result QgsMeshCalculator::processCalculation( QgsFeedback *fe feedback->setProgress( 80.0 ); } - // store to file + // store to file or in memory QVector datasetValues; QVector datasetActive; QVector times; - const auto datasize = outputGroup->datasets.size(); + const auto datasize = outputGroup->datasetCount(); datasetValues.reserve( datasize ); times.reserve( datasize ); for ( int i = 0; i < datasize; ++i ) { - const std::shared_ptr dataset = outputGroup->datasets.at( i ); + const std::shared_ptr dataset = outputGroup->memoryDatasets.at( i ); times.push_back( dataset->time ); datasetValues.push_back( - dataset->datasetValues( outputGroup->isScalar, + dataset->datasetValues( outputGroup->isScalar(), 0, dataset->values.size() ) ); @@ -233,15 +266,28 @@ QgsMeshCalculator::Result QgsMeshCalculator::processCalculation( QgsFeedback *fe } } + // calculate statistics + outputGroup->calculateStatistic(); + const QgsMeshDatasetGroupMetadata meta = outputGroup->groupMetadata(); - bool err = mMeshLayer->dataProvider()->persistDatasetGroup( - mOutputFile, - mOutputDriver, - meta, - datasetValues, - datasetActive, - times - ); + bool err; + + if ( mResultInMemory ) + { + err = !mMeshLayer->addDatasets( outputGroup.release() ); + } + else + { + err = mMeshLayer->dataProvider()->persistDatasetGroup( + mOutputFile, + mOutputDriver, + meta, + datasetValues, + datasetActive, + times + ); + } + if ( err ) { diff --git a/src/analysis/mesh/qgsmeshcalculator.h b/src/analysis/mesh/qgsmeshcalculator.h index 153d4a91eb45..51abf66e58c8 100644 --- a/src/analysis/mesh/qgsmeshcalculator.h +++ b/src/analysis/mesh/qgsmeshcalculator.h @@ -145,6 +145,43 @@ class ANALYSIS_EXPORT QgsMeshCalculator double endTime, QgsMeshLayer *layer ); + /** + * Creates calculator with bounding box (rectangular) mask, store the result in the memory + * \param formulaString formula/expression to evaluate. Consists of dataset group names, operators and numbers + * \param outputGroupName output group name + * \param outputExtent spatial filter defined by rectangle + * \param startTime time filter defining the starting dataset + * \param endTime time filter defining the ending dataset + * \param layer mesh layer with dataset groups references in formulaString + * + * \since QGIS 3.16 + */ + QgsMeshCalculator( const QString &formulaString, + const QString &outputGroupName, + double startTime, + double endTime, + const QgsRectangle &outputExtent, + QgsMeshLayer *layer ); + + /** + * Creates calculator with with geometry mask, store the result in the memory + * \param formulaString formula/expression to evaluate. Consists of dataset group names, operators and numbers + * \param outputGroupName output group name + * \param outputMask spatial filter defined by geometry + * \param startTime time filter defining the starting dataset + * \param endTime time filter defining the ending dataset + * \param layer mesh layer with dataset groups references in formulaString + * + * \since QGIS 3.16 + */ + QgsMeshCalculator( const QString &formulaString, + const QString &outputGroupName, + double startTime, + double endTime, + const QgsGeometry &outputMask, + QgsMeshLayer *layer ); + + /** * Starts the calculation, writes new dataset group to file and adds it to the mesh layer * \param feedback The optional feedback argument for progress reporting and cancellation support @@ -188,6 +225,7 @@ class ANALYSIS_EXPORT QgsMeshCalculator bool mUseMask = false; double mStartTime = 0.0; double mEndTime = 0.0; + bool mResultInMemory = false; QgsMeshLayer *mMeshLayer = nullptr; }; diff --git a/src/analysis/mesh/qgsmeshcalcutils.cpp b/src/analysis/mesh/qgsmeshcalcutils.cpp index 87783de4dd9c..91ee2f26d2c3 100644 --- a/src/analysis/mesh/qgsmeshcalcutils.cpp +++ b/src/analysis/mesh/qgsmeshcalcutils.cpp @@ -35,9 +35,10 @@ std::shared_ptr QgsMeshCalcUtils::create( const QStri { const auto dp = mMeshLayer->dataProvider(); std::shared_ptr grp; - for ( int groupIndex = 0; groupIndex < dp->datasetGroupCount(); ++groupIndex ) + const QList &indexes = mMeshLayer->datasetGroupsIndexes(); + for ( int groupIndex : indexes ) { - const auto meta = dp->datasetGroupMetadata( groupIndex ); + const auto meta = mMeshLayer->datasetGroupMetadata( groupIndex ); const QString name = meta.name(); if ( name == datasetGroupName ) { @@ -47,20 +48,19 @@ std::shared_ptr QgsMeshCalcUtils::create( const QStri Q_ASSERT( !( ( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ) && ( mOutputType == QgsMeshDatasetGroupMetadata::DataOnFaces ) ) ); grp = std::make_shared(); - grp->isScalar = meta.isScalar(); - grp->type = mOutputType; - grp->maximum = meta.maximum(); - grp->minimum = meta.minimum(); - grp->name = meta.name(); + grp->setIsScalar( meta.isScalar() ); + grp->setDataType( mOutputType ); + grp->setMinimumMaximum( meta.minimum(), meta.maximum() ); + grp->setName( meta.name() ); int nativeCount = ( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ) ? dp->vertexCount() : dp->faceCount(); int resultCount = ( mOutputType == QgsMeshDatasetGroupMetadata::DataOnVertices ) ? dp->vertexCount() : dp->faceCount(); - for ( int datasetIndex = 0; datasetIndex < dp->datasetCount( groupIndex ); ++datasetIndex ) + for ( int datasetIndex = 0; datasetIndex < mMeshLayer->datasetCount( groupIndex ); ++datasetIndex ) { const QgsMeshDatasetIndex index( groupIndex, datasetIndex ); - const auto dsMeta = dp->datasetMetadata( index ); - std::shared_ptr ds = create( grp->type ); + const auto dsMeta = mMeshLayer->datasetMetadata( index ); + std::shared_ptr ds = create( grp->dataType() ); ds->maximum = dsMeta.maximum(); ds->minimum = dsMeta.minimum(); ds->time = dsMeta.time(); @@ -75,8 +75,7 @@ std::shared_ptr QgsMeshCalcUtils::create( const QStri // for data on faces, there could be request to interpolate the data to vertices if ( ( meta.dataType() != QgsMeshDatasetGroupMetadata::DataOnVertices ) && ( mOutputType == QgsMeshDatasetGroupMetadata::DataOnVertices ) ) { - - if ( grp->isScalar ) + if ( grp->isScalar() ) { QVector data = QgsMeshLayerUtils::interpolateFromFacesData( @@ -133,9 +132,9 @@ std::shared_ptr QgsMeshCalcUtils::create( const QStri ds->values[value_i] = block.value( value_i ); } - if ( grp->type == QgsMeshDatasetGroupMetadata::DataOnVertices ) + if ( grp->dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ) { - const QgsMeshDataBlock active = dp->areFacesActive( index, 0, dp->faceCount() ); + const QgsMeshDataBlock active = mMeshLayer->areFacesActive( index, 0, dp->faceCount() ); Q_ASSERT( active.count() == dp->faceCount() ); for ( int value_i = 0; value_i < dp->faceCount(); ++value_i ) ds->active[value_i] = active.active( value_i ); @@ -151,7 +150,7 @@ std::shared_ptr QgsMeshCalcUtils::create( const QStri std::shared_ptr QgsMeshCalcUtils::create( const QgsMeshMemoryDatasetGroup &grp ) const { - return create( grp.type ); + return create( grp.dataType() ); } std::shared_ptr QgsMeshCalcUtils::create( const QgsMeshDatasetGroupMetadata::DataType type ) const @@ -224,7 +223,7 @@ QgsMeshCalcUtils:: QgsMeshCalcUtils( QgsMeshLayer *layer, { if ( ds->datasetCount() != mTimes.size() ) { - // different number of datasets in the groupss + // different number of datasets in the groups return; } } @@ -272,7 +271,7 @@ QgsMeshCalcUtils:: QgsMeshCalcUtils( QgsMeshLayer *layer, // check that all datasets are of the same type for ( const auto &ds : vals ) { - if ( ds->type != mOutputType ) + if ( ds->dataType() != mOutputType ) return; } @@ -407,9 +406,9 @@ void QgsMeshCalcUtils::number( QgsMeshMemoryDatasetGroup &group1, double val ) c { Q_ASSERT( isValid() ); - group1.datasets.clear(); + group1.memoryDatasets.clear(); std::shared_ptr output = number( val, mTimes[0] ); - group1.datasets.push_back( output ); + group1.memoryDatasets.push_back( output ); } @@ -479,7 +478,7 @@ void QgsMeshCalcUtils::transferDatasets( QgsMeshMemoryDatasetGroup &group1, QgsM group1.clearDatasets(); for ( int i = 0; i < group2.datasetCount(); ++i ) { - std::shared_ptr o = group2.datasets[i]; + std::shared_ptr o = group2.memoryDatasets[i]; Q_ASSERT( o ); group1.addDataset( o ); } @@ -494,7 +493,7 @@ void QgsMeshCalcUtils::expand( QgsMeshMemoryDatasetGroup &group1, const QgsMeshM { if ( group1.datasetCount() == 1 ) { - const std::shared_ptr o0 = group1.datasets[0]; + const std::shared_ptr o0 = group1.memoryDatasets[0]; Q_ASSERT( o0 ); for ( int i = 1; i < group2.datasetCount(); ++i ) { @@ -516,12 +515,12 @@ std::shared_ptr QgsMeshCalcUtils::canditateDataset( if ( group.datasetCount() > 1 ) { Q_ASSERT( group.datasetCount() > datasetIndex ); - return group.datasets[datasetIndex]; + return group.memoryDatasets[datasetIndex]; } else { Q_ASSERT( group.datasetCount() == 1 ); - return group.datasets[0]; + return group.memoryDatasets[0]; } } @@ -577,7 +576,7 @@ void QgsMeshCalcUtils::func1( QgsMeshMemoryDatasetGroup &group, output->values[n] = res_val; } - if ( group.type == QgsMeshDatasetGroupMetadata::DataOnVertices ) + if ( group.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ) activate( output ); } } @@ -588,7 +587,7 @@ void QgsMeshCalcUtils::func2( QgsMeshMemoryDatasetGroup &group1, std::function func ) const { Q_ASSERT( isValid() ); - Q_ASSERT( group1.type == group2.type ); // we do not support mixed output types + Q_ASSERT( group1.dataType() == group2.dataType() ); // we do not support mixed output types expand( group1, group2 ); @@ -607,7 +606,7 @@ void QgsMeshCalcUtils::func2( QgsMeshMemoryDatasetGroup &group1, o1->values[n] = res_val; } - if ( group1.type == QgsMeshDatasetGroupMetadata::DataOnVertices ) + if ( group1.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ) { activate( o1, o2 ); } @@ -622,7 +621,7 @@ void QgsMeshCalcUtils::funcAggr( { Q_ASSERT( isValid() ); - if ( group1.type == QgsMeshDatasetGroupMetadata::DataOnVertices ) + if ( group1.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ) { std::shared_ptr output = QgsMeshCalcUtils::create( QgsMeshDatasetGroupMetadata::DataOnVertices ); output->time = mTimes[0]; @@ -655,8 +654,8 @@ void QgsMeshCalcUtils::funcAggr( // lets do activation purely on NODATA values as we did aggregation here activate( output ); - group1.datasets.clear(); - group1.datasets.push_back( output ); + group1.memoryDatasets.clear(); + group1.memoryDatasets.push_back( output ); } else @@ -689,8 +688,8 @@ void QgsMeshCalcUtils::funcAggr( output->values[n] = res_val; } - group1.datasets.clear(); - group1.datasets.push_back( output ); + group1.memoryDatasets.clear(); + group1.memoryDatasets.push_back( output ); } } @@ -732,8 +731,8 @@ void QgsMeshCalcUtils::addIf( QgsMeshMemoryDatasetGroup &trueGroup, expand( trueGroup, condition ); expand( trueGroup, falseGroup ); - Q_ASSERT( trueGroup.type == falseGroup.type ); // we do not support mixed output types - Q_ASSERT( trueGroup.type == condition.type ); // we do not support mixed output types + Q_ASSERT( trueGroup.dataType() == falseGroup.dataType() ); // we do not support mixed output types + Q_ASSERT( trueGroup.dataType() == condition.dataType() ); // we do not support mixed output types for ( int time_index = 0; time_index < trueGroup.datasetCount(); ++time_index ) { @@ -754,7 +753,7 @@ void QgsMeshCalcUtils::addIf( QgsMeshMemoryDatasetGroup &trueGroup, true_o->values[n] = resultValue; } - if ( trueGroup.type == QgsMeshDatasetGroupMetadata::DataOnVertices ) + if ( trueGroup.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ) { // This is not ideal, as we do not check for true/false branch here in activate // problem is that activate is on elements, but condition is on nodes... @@ -773,7 +772,7 @@ void QgsMeshCalcUtils::activate( QgsMeshMemoryDatasetGroup &group ) const for ( int datasetIndex = 0; datasetIndex < group.datasetCount(); ++datasetIndex ) { std::shared_ptr o1 = canditateDataset( group, datasetIndex ); - Q_ASSERT( group.type == QgsMeshDatasetGroupMetadata::DataOnVertices ); + Q_ASSERT( group.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); activate( o1 ); } } @@ -1169,10 +1168,10 @@ void QgsMeshCalcUtils::maximum( QgsMeshMemoryDatasetGroup &group1, const QgsMesh QgsMeshDatasetGroupMetadata::DataType QgsMeshCalcUtils::determineResultDataType( QgsMeshLayer *layer, const QStringList &usedGroupNames ) { QHash names; - const QgsMeshDataProvider *dp = layer->dataProvider(); - for ( int groupId = 0; groupId < dp->datasetGroupCount(); ++groupId ) + const QList &groupIndexes = layer->datasetGroupsIndexes(); + for ( int groupId : groupIndexes ) { - const auto meta = dp->datasetGroupMetadata( groupId ); + const auto meta = layer->datasetGroupMetadata( groupId ); const QString name = meta.name(); names[ name ] = groupId; } @@ -1181,7 +1180,7 @@ QgsMeshDatasetGroupMetadata::DataType QgsMeshCalcUtils::determineResultDataType( if ( names.contains( datasetGroupName ) ) { int groupId = names.value( datasetGroupName ); - const auto meta = dp->datasetGroupMetadata( groupId ); + const auto meta = layer->datasetGroupMetadata( groupId ); if ( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ) { return QgsMeshDatasetGroupMetadata::DataOnVertices; diff --git a/src/app/mesh/qgsmeshcalculatordialog.cpp b/src/app/mesh/qgsmeshcalculatordialog.cpp index 74c82376787f..72ad803ac7bc 100644 --- a/src/app/mesh/qgsmeshcalculatordialog.cpp +++ b/src/app/mesh/qgsmeshcalculatordialog.cpp @@ -27,6 +27,7 @@ #include "qgsmaplayerproxymodel.h" #include "qgswkbtypes.h" #include "qgsfeatureiterator.h" +#include "qgsmeshdatasetgrouptreeview.h" #include "cpl_string.h" #include "gdal.h" @@ -45,14 +46,18 @@ QgsMeshCalculatorDialog::QgsMeshCalculatorDialog( QgsMeshLayer *meshLayer, QWidg QgsGui::enableAutoGeometryRestore( this ); cboLayerMask->setFilters( QgsMapLayerProxyModel::PolygonLayer ); - getDatasetGroupNames(); + QgsMeshDatasetGroupListModel *model = new QgsMeshDatasetGroupListModel( this ); + model->syncToLayer( meshLayer ); + model->setDisplayProviderName( true ); + mDatasetsListWidget->setModel( model ); getMeshDrivers(); populateDriversComboBox( ); connect( mOutputFormatComboBox, qgis::overload::of( &QComboBox::currentIndexChanged ), this, &QgsMeshCalculatorDialog::updateInfoMessage ); + connect( mOutputFormatComboBox, qgis::overload::of( &QComboBox::currentIndexChanged ), this, &QgsMeshCalculatorDialog::onOutputFormatChange ); connect( mOutputGroupNameLineEdit, &QLineEdit::textChanged, this, &QgsMeshCalculatorDialog::updateInfoMessage ); - connect( mDatasetsListWidget, &QListWidget::itemDoubleClicked, this, &QgsMeshCalculatorDialog::mDatasetsListWidget_doubleClicked ); + connect( mDatasetsListWidget, &QListView::doubleClicked, this, &QgsMeshCalculatorDialog::datasetGroupEntry ); connect( mCurrentLayerExtentButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mCurrentLayerExtentButton_clicked ); connect( mAllTimesButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mAllTimesButton_clicked ); connect( mExpressionTextEdit, &QTextEdit::textChanged, this, &QgsMeshCalculatorDialog::updateInfoMessage ); @@ -102,7 +107,11 @@ QgsMeshCalculatorDialog::QgsMeshCalculatorDialog( QgsMeshLayer *meshLayer, QWidg mOutputDatasetFileWidget->setStorageMode( QgsFileWidget::SaveFile ); mOutputDatasetFileWidget->setDialogTitle( tr( "Enter mesh dataset file" ) ); mOutputDatasetFileWidget->setDefaultRoot( settings.value( QStringLiteral( "/MeshCalculator/lastOutputDir" ), QDir::homePath() ).toString() ); + onOutputFormatChange(); connect( mOutputDatasetFileWidget, &QgsFileWidget::fileChanged, this, &QgsMeshCalculatorDialog::updateInfoMessage ); + + connect( mOutputOnFileRadioButton, &QRadioButton::toggled, this, &QgsMeshCalculatorDialog::onOutputRadioButtonChange ); + onOutputRadioButtonChange(); } QgsMeshCalculatorDialog::~QgsMeshCalculatorDialog() = default; @@ -121,7 +130,7 @@ QgsMeshLayer *QgsMeshCalculatorDialog::meshLayer() const QString QgsMeshCalculatorDialog::outputFile() const { QString ret = mOutputDatasetFileWidget->filePath(); - return addSuffix( ret ); + return controlSuffix( ret ); } QgsRectangle QgsMeshCalculatorDialog::outputExtent() const @@ -187,7 +196,7 @@ double QgsMeshCalculatorDialog::endTime() const std::unique_ptr QgsMeshCalculatorDialog::calculator() const { std::unique_ptr calc; - if ( useExtentCb->isChecked() ) + if ( useExtentCb->isChecked() && mOutputOnFileRadioButton->isChecked() ) { calc.reset( new QgsMeshCalculator( @@ -202,7 +211,7 @@ std::unique_ptr QgsMeshCalculatorDialog::calculator() const ) ); } - else + else if ( mOutputOnFileRadioButton->isChecked() ) { calc.reset( new QgsMeshCalculator( @@ -217,9 +226,41 @@ std::unique_ptr QgsMeshCalculatorDialog::calculator() const ) ); } + else if ( useExtentCb->isChecked() ) + { + calc.reset( + new QgsMeshCalculator( + formulaString(), + groupName(), + startTime(), + endTime(), + outputExtent(), + meshLayer() + ) + ); + } + else + { + calc.reset( + new QgsMeshCalculator( + formulaString(), + groupName(), + startTime(), + endTime(), + maskGeometry(), + meshLayer() + ) + ); + } return calc; } +void QgsMeshCalculatorDialog::datasetGroupEntry( const QModelIndex &index ) +{ + const QString group = quoteDatasetGroupEntry( datasetGroupName( index ) ); + mExpressionTextEdit->insertPlainText( QStringLiteral( " %1 " ).arg( group ) ); +} + void QgsMeshCalculatorDialog::toggleExtendMask( int state ) { Q_UNUSED( state ) @@ -248,6 +289,7 @@ void QgsMeshCalculatorDialog::updateInfoMessage() bool expressionValid = result == QgsMeshCalculator::Success; // selected driver is appropriate + bool useInMemory = mOutputOnMemoryRadioButton->isChecked(); bool driverValid = false; if ( expressionValid ) { @@ -276,7 +318,9 @@ void QgsMeshCalculatorDialog::updateInfoMessage() // group name bool groupNameValid = !groupName().isEmpty(); - if ( expressionValid && driverValid && filePathValid && groupNameValid ) + if ( expressionValid && + ( useInMemory || ( driverValid && filePathValid ) ) && + groupNameValid ) { mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( true ); mExpressionValidLabel->setText( tr( "Expression valid" ) ); @@ -286,22 +330,38 @@ void QgsMeshCalculatorDialog::updateInfoMessage() mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( false ); if ( !expressionValid ) mExpressionValidLabel->setText( tr( "Expression invalid" ) ); - else if ( !filePathValid ) + else if ( !filePathValid && !useInMemory ) mExpressionValidLabel->setText( tr( "Invalid file path" ) ); - else if ( !driverValid ) + else if ( !driverValid && !useInMemory ) mExpressionValidLabel->setText( tr( "Selected driver cannot store data defined on %1" ).arg( requiredCapability == QgsMeshDriverMetadata::CanWriteFaceDatasets ? tr( " faces " ) : tr( " vertices " ) ) ); else if ( !groupNameValid ) mExpressionValidLabel->setText( tr( "Invalid group name" ) ); } } -void QgsMeshCalculatorDialog::mDatasetsListWidget_doubleClicked( QListWidgetItem *item ) +void QgsMeshCalculatorDialog::onOutputRadioButtonChange() { - if ( !item ) - return; + mOutputDatasetFileWidget->setEnabled( mOutputOnFileRadioButton->isChecked() ); + mOutputFormatComboBox->setEnabled( mOutputOnFileRadioButton->isChecked() ); +} - const QString group = quoteDatasetGroupEntry( item->text() ); - mExpressionTextEdit->insertPlainText( QStringLiteral( " %1 " ).arg( group ) ); +void QgsMeshCalculatorDialog::onOutputFormatChange() +{ + QString suffix = currentOutputSuffix(); + if ( !suffix.isEmpty() ) + { + QString filter = mOutputFormatComboBox->currentText(); + filter.append( QStringLiteral( " (*.%1)" ).arg( suffix ) ); + mOutputDatasetFileWidget->setFilter( filter ); + } +} + +QString QgsMeshCalculatorDialog::datasetGroupName( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return QString(); + + return index.data( Qt::DisplayRole ).toString(); } void QgsMeshCalculatorDialog::mCurrentLayerExtentButton_clicked() @@ -446,35 +506,37 @@ QString QgsMeshCalculatorDialog::quoteDatasetGroupEntry( const QString group ) return ret; } -void QgsMeshCalculatorDialog::getDatasetGroupNames() +QString QgsMeshCalculatorDialog::controlSuffix( const QString &fileName ) const { - if ( !meshLayer() || !meshLayer()->dataProvider() ) - return; + if ( fileName.isEmpty() ) + return fileName; - const QgsMeshDataProvider *dp = meshLayer()->dataProvider(); - Q_ASSERT( dp ); + QFileInfo fileInfo( fileName ); - for ( int i = 0; i < dp->datasetGroupCount(); ++i ) + QString appropriateSuffix = currentOutputSuffix(); + + QString existingSuffix = fileInfo.suffix(); + if ( !( existingSuffix.isEmpty() && appropriateSuffix.isEmpty() ) + && existingSuffix != appropriateSuffix ) { - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( i ); - mDatasetsListWidget->addItem( meta.name() ); + int pos = fileName.lastIndexOf( '.' ); + QString ret = fileName.left( pos + 1 ); + ret.append( appropriateSuffix ); + + return ret; } + + return fileName; } -QString QgsMeshCalculatorDialog::addSuffix( const QString fileName ) const +QString QgsMeshCalculatorDialog::currentOutputSuffix() const { - if ( fileName.isEmpty() ) - return fileName; + QString currentDriver = mOutputFormatComboBox->currentData().toString(); + QString suffix; + if ( mMeshDrivers.contains( currentDriver ) ) + suffix = mMeshDrivers[currentDriver].writeDatasetOnFileSuffix(); - // TODO construct list from MDAL and drivers - // that can be used to write data - // for now, MDAL only supports dat files - const QString allowedSuffix = QStringLiteral( ".dat" ); - - if ( fileName.endsWith( allowedSuffix ) ) - return fileName; - - return fileName + allowedSuffix; + return suffix; } void QgsMeshCalculatorDialog::getMeshDrivers() @@ -527,14 +589,12 @@ void QgsMeshCalculatorDialog::useAllTimesFromLayer() QString QgsMeshCalculatorDialog::currentDatasetGroup() const { - if ( mDatasetsListWidget->count() == 0 ) + QModelIndex index = mDatasetsListWidget->currentIndex(); + + if ( !index.isValid() ) return QString(); - const QList items = mDatasetsListWidget->selectedItems(); - if ( !items.empty() ) - return items[0]->text(); - else - return mDatasetsListWidget->item( 0 )->text(); + return datasetGroupName( index ); } void QgsMeshCalculatorDialog::setTimesByDatasetGroupName( const QString group ) diff --git a/src/app/mesh/qgsmeshcalculatordialog.h b/src/app/mesh/qgsmeshcalculatordialog.h index 47cb99f41aee..c7bb6ae7d244 100644 --- a/src/app/mesh/qgsmeshcalculatordialog.h +++ b/src/app/mesh/qgsmeshcalculatordialog.h @@ -43,11 +43,13 @@ class APP_EXPORT QgsMeshCalculatorDialog: public QDialog, private Ui::QgsMeshCal std::unique_ptr calculator() const; private slots: - void mDatasetsListWidget_doubleClicked( QListWidgetItem *item ); + void datasetGroupEntry( const QModelIndex &index ); void mCurrentLayerExtentButton_clicked(); void mAllTimesButton_clicked(); void toggleExtendMask( int state ); void updateInfoMessage(); + void onOutputRadioButtonChange(); + void onOutputFormatChange(); //calculator buttons void mPlusPushButton_clicked(); @@ -89,6 +91,8 @@ class APP_EXPORT QgsMeshCalculatorDialog: public QDialog, private Ui::QgsMeshCal double startTime() const; double endTime() const; + QString datasetGroupName( const QModelIndex &index ) const; + //! Combines geometries from selected vector layer to create mask filter geometry QgsGeometry maskGeometry( QgsVectorLayer *layer ) const; @@ -110,11 +114,11 @@ class APP_EXPORT QgsMeshCalculatorDialog: public QDialog, private Ui::QgsMeshCal //! Quotes the dataset group name for calculator QString quoteDatasetGroupEntry( const QString group ); - //! Gets all datasets groups from layer and populated list - void getDatasetGroupNames(); - //! Add file suffix if not present - QString addSuffix( const QString fileName ) const; + QString controlSuffix( const QString &fileName ) const; + + //! Returns the current output suffix + QString currentOutputSuffix() const; //! Gets all mesh drivers that can persist datasets void getMeshDrivers(); diff --git a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp index 8d969619a70a..f6c21eb09fd6 100644 --- a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp +++ b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp @@ -16,11 +16,18 @@ #include "qgsmeshdatasetgrouptreeview.h" #include "qgis.h" +#include "qgsapplication.h" #include "qgsmeshlayer.h" +#include "qgsprovidermetadata.h" +#include "qgsproviderregistry.h" +#include "qgssettings.h" #include #include #include +#include +#include +#include QgsMeshDatasetGroupTreeModel::QgsMeshDatasetGroupTreeModel( QObject *parent ) @@ -76,6 +83,11 @@ QVariant QgsMeshDatasetGroupTreeModel::data( const QModelIndex &index, int role return item->datasetGroupIndex(); case Qt::CheckStateRole : return static_cast< int >( item->isEnabled() ? Qt::Checked : Qt::Unchecked ); + case IsMemory: + return item->storageType() == QgsMeshDatasetGroupTreeItem::Memory; + case Qt::DecorationRole: + if ( item->storageType() == QgsMeshDatasetGroupTreeItem::Memory ) + return QgsApplication::getThemeIcon( QStringLiteral( "mIndicatorMemory.svg" ) ); } return QVariant(); @@ -167,6 +179,14 @@ QgsMeshDatasetGroupTreeItem *QgsMeshDatasetGroupTreeModel::datasetGroupTreeRootI return mRootItem.get(); } +QgsMeshDatasetGroupTreeItem *QgsMeshDatasetGroupTreeModel::datasetGroupTreeItem( int groupIndex ) +{ + if ( mRootItem ) + return mRootItem->childFromDatasetGroupIndex( groupIndex ); + else + return nullptr; +} + bool QgsMeshDatasetGroupTreeModel::isEnabled( const QModelIndex &index ) const { if ( !index.isValid() ) @@ -206,6 +226,33 @@ void QgsMeshDatasetGroupTreeModel::setAllGroupsAsEnabled( bool isEnabled ) dataChanged( index( 0, 0 ), index( mRootItem->childCount(), 0 ) ); } +void QgsMeshDatasetGroupTreeModel::removeItem( const QModelIndex &index ) +{ + if ( !index.isValid() ) + return; + + QgsMeshDatasetGroupTreeItem *item = static_cast( index.internalPointer() ); + if ( !item || item->storageType() == QgsMeshDatasetGroupTreeItem::File ) + return; + + beginRemoveRows( index.parent(), index.row(), index.row() ); + QgsMeshDatasetGroupTreeItem *parent = item->parentItem(); + parent->removeChild( item ); + endRemoveRows(); +} + +void QgsMeshDatasetGroupTreeModel::setStorageType( const QModelIndex &index, QgsMeshDatasetGroupTreeItem::StorageType type ) +{ + if ( !index.isValid() ) + return; + + QgsMeshDatasetGroupTreeItem *item = static_cast( index.internalPointer() ); + if ( !item ) + return; + item->setStorageType( type ); + dataChanged( index, index ); +} + ///////////////////////////////////////////////////////////////////////////////////////// QgsMeshDatasetGroupProxyModel::QgsMeshDatasetGroupProxyModel( QAbstractItemModel *sourceModel ): @@ -272,6 +319,8 @@ QVariant QgsMeshDatasetGroupProxyModel::data( const QModelIndex &index, int role return item->datasetGroupIndex() == mActiveVectorGroupIndex; case Qt::CheckStateRole : return QVariant(); + case Qt::DecorationRole: + return QVariant(); } return sourceModel()->data( sourceIndex, role ); @@ -301,6 +350,7 @@ QgsMeshDatasetGroupTreeItemDelagate::QgsMeshDatasetGroupTreeItemDelagate( QObjec , mScalarDeselectedPixmap( QStringLiteral( ":/images/themes/default/propertyicons/meshcontoursoff.svg" ) ) , mVectorSelectedPixmap( QStringLiteral( ":/images/themes/default/propertyicons/meshvectors.svg" ) ) , mVectorDeselectedPixmap( QStringLiteral( ":/images/themes/default/propertyicons/meshvectorsoff.svg" ) ) + , mMemoryPixmap( QStringLiteral( ":/images/themes/default/mIndicatorMemory.svg" ) ) { } @@ -311,22 +361,31 @@ void QgsMeshDatasetGroupTreeItemDelagate::paint( QPainter *painter, const QStyle QStyledItemDelegate::paint( painter, option, index ); bool isVector = index.data( QgsMeshDatasetGroupTreeModel::IsVector ).toBool(); + bool isMemory = index.data( QgsMeshDatasetGroupTreeModel::IsMemory ).toBool(); if ( isVector ) { bool isActive = index.data( QgsMeshDatasetGroupTreeModel::IsActiveVectorDatasetGroup ).toBool(); painter->drawPixmap( iconRect( option.rect, true ), isActive ? mVectorSelectedPixmap : mVectorDeselectedPixmap ); } + + if ( isMemory ) + painter->drawPixmap( iconRect( option.rect, 3 ), mMemoryPixmap ); + bool isActive = index.data( QgsMeshDatasetGroupTreeModel::IsActiveScalarDatasetGroup ).toBool(); painter->drawPixmap( iconRect( option.rect, false ), isActive ? mScalarSelectedPixmap : mScalarDeselectedPixmap ); } -QRect QgsMeshDatasetGroupTreeItemDelagate::iconRect( const QRect rect, bool isVector ) const +QRect QgsMeshDatasetGroupTreeItemDelagate::iconRect( const QRect &rect, bool isVector ) const +{ + return iconRect( rect, isVector ? 1 : 2 ); +} + +QRect QgsMeshDatasetGroupTreeItemDelagate::iconRect( const QRect &rect, int pos ) const { int iw = mScalarSelectedPixmap.width(); int ih = mScalarSelectedPixmap.height(); int margin = ( rect.height() - ih ) / 2; - int i = isVector ? 1 : 2; - return QRect( rect.right() - i * ( iw + margin ), rect.top() + margin, iw, ih ); + return QRect( rect.right() - pos * ( iw + margin ), rect.top() + margin, iw, ih ); } QSize QgsMeshDatasetGroupTreeItemDelagate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const @@ -448,7 +507,8 @@ void QgsMeshActiveDatasetGroupTreeView::setActiveGroup() void QgsMeshDatasetGroupListModel::syncToLayer( QgsMeshLayer *layer ) { - mRootItem = layer->datasetGroupTreeRootItem(); + if ( layer ) + mRootItem = layer->datasetGroupTreeRootItem(); } int QgsMeshDatasetGroupListModel::rowCount( const QModelIndex &parent ) const @@ -472,26 +532,45 @@ QVariant QgsMeshDatasetGroupListModel::data( const QModelIndex &index, int role if ( !item ) return QVariant(); - if ( role == Qt::DisplayRole ) + switch ( role ) { - return item->name(); + case Qt::DisplayRole: + if ( mDisplayProviderName ) + return item->providerName(); + else + return item->name(); + break; + case Qt::DecorationRole: + if ( item->storageType() == QgsMeshDatasetGroupTreeItem::Memory ) + return QgsApplication::getThemeIcon( QStringLiteral( "mIndicatorMemory.svg" ) ); + break; } return QVariant(); } +void QgsMeshDatasetGroupListModel::setDisplayProviderName( bool displayProviderName ) +{ + mDisplayProviderName = displayProviderName; +} + QgsMeshDatasetGroupTreeView::QgsMeshDatasetGroupTreeView( QWidget *parent ): QTreeView( parent ) , mModel( new QgsMeshDatasetGroupTreeModel( this ) ) + , mSaveMenu( new QgsMeshDatasetGroupSaveMenu( this ) ) { setModel( mModel ); setSelectionMode( QAbstractItemView::SingleSelection ); + + connect( mSaveMenu, &QgsMeshDatasetGroupSaveMenu::datasetGroupSaved, this, &QgsMeshDatasetGroupTreeView::onDatasetGroupSaved ); } void QgsMeshDatasetGroupTreeView::syncToLayer( QgsMeshLayer *layer ) { if ( mModel ) mModel->syncToLayer( layer ); + if ( mSaveMenu ) + mSaveMenu->setMeshLayer( layer ); } void QgsMeshDatasetGroupTreeView::selectAllGroups() @@ -504,6 +583,57 @@ void QgsMeshDatasetGroupTreeView::deselectAllGroups() selectAllItem( false ); } +void QgsMeshDatasetGroupTreeView::contextMenuEvent( QContextMenuEvent *event ) +{ + QModelIndex idx = indexAt( event->pos() ); + if ( !idx.isValid() ) + setCurrentIndex( QModelIndex() ); + + std::unique_ptr menu( createContextMenu() ); + if ( menu && menu->actions().count() != 0 ) + menu->exec( mapToGlobal( event->pos() ) ); +} + +void QgsMeshDatasetGroupTreeView::removeCurrentItem() +{ + mModel->removeItem( currentIndex() ); +} + +void QgsMeshDatasetGroupTreeView::onDatasetGroupSaved() +{ + mModel->setStorageType( currentIndex(), QgsMeshDatasetGroupTreeItem::File ); + emit apply(); +} + +QMenu *QgsMeshDatasetGroupTreeView::createContextMenu() +{ + QMenu *contextMenu = new QMenu; + + const QModelIndex &index = currentIndex(); + if ( !index.isValid() ) + return nullptr; + + int groupIndex = mModel->data( index, QgsMeshDatasetGroupTreeModel::DatasetGroupIndex ).toInt(); + QgsMeshDatasetGroupTreeItem *item = mModel->datasetGroupTreeItem( groupIndex ); + + if ( !item ) + return nullptr; + + switch ( item->storageType() ) + { + case QgsMeshDatasetGroupTreeItem::None: + break; + case QgsMeshDatasetGroupTreeItem::File: + break; + case QgsMeshDatasetGroupTreeItem::Memory: + case QgsMeshDatasetGroupTreeItem::OnTheFly: + contextMenu->addAction( tr( "Remove dataset group" ), this, &QgsMeshDatasetGroupTreeView::removeCurrentItem ); + mSaveMenu->createSaveMenu( groupIndex, contextMenu ); + break; + } + return contextMenu; +} + void QgsMeshDatasetGroupTreeView::resetDefault( QgsMeshLayer *meshLayer ) { mModel->resetDefault( meshLayer ); @@ -518,3 +648,86 @@ void QgsMeshDatasetGroupTreeView::selectAllItem( bool isChecked ) { mModel->setAllGroupsAsEnabled( isChecked ); } + +QMenu *QgsMeshDatasetGroupSaveMenu::createSaveMenu( int groupIndex, QMenu *parentMenu ) +{ + if ( !mMeshLayer ) + return nullptr; + + QMenu *menu = new QMenu( parentMenu ); + menu->setTitle( QObject::tr( "Save Datasets group as..." ) ); + QgsMeshDatasetGroupMetadata groupMeta = mMeshLayer->datasetGroupMetadata( groupIndex ); + + QgsProviderMetadata *providerMetadata = QgsProviderRegistry::instance()->providerMetadata( mMeshLayer->dataProvider()->name() ); + if ( providerMetadata ) + { + const QList allDrivers = providerMetadata->meshDriversMetadata(); + for ( const QgsMeshDriverMetadata driver : allDrivers ) + { + QString driverName = driver.name(); + QString suffix = driver.writeDatasetOnFileSuffix(); + if ( ( driver.capabilities().testFlag( QgsMeshDriverMetadata::MeshDriverCapability::CanWriteFaceDatasets ) + && groupMeta.dataType() == QgsMeshDatasetGroupMetadata::DataOnFaces ) || + ( driver.capabilities().testFlag( QgsMeshDriverMetadata::MeshDriverCapability::CanWriteVertexDatasets ) + && groupMeta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ) || + ( driver.capabilities().testFlag( QgsMeshDriverMetadata::MeshDriverCapability::CanWriteEdgeDatasets ) + && groupMeta.dataType() == QgsMeshDatasetGroupMetadata::DataOnEdges ) ) + { + menu->addAction( driver.description(), [groupIndex, driverName, suffix, this] + { + this->saveDatasetGroup( groupIndex, driverName, suffix ); + } ); + } + } + } + + if ( menu->actions().isEmpty() ) + { + menu->addAction( QObject::tr( "No driver available to write this dataset group" ) ); + menu->actions().last()->setDisabled( true ); + } + + if ( parentMenu ) + parentMenu->addMenu( menu ); + + return menu; +} + +void QgsMeshDatasetGroupSaveMenu::setMeshLayer( QgsMeshLayer *meshLayer ) +{ + mMeshLayer = meshLayer; +} + +void QgsMeshDatasetGroupSaveMenu::saveDatasetGroup( int datasetGroup, const QString &driver, const QString &fileSuffix ) +{ + if ( !mMeshLayer ) + return; + + QgsSettings settings; + QString filter; + if ( !fileSuffix.isEmpty() ) + filter = QStringLiteral( "%1 (*.%2)" ).arg( driver ).arg( fileSuffix ); + QString exportFileDir = settings.value( QStringLiteral( "lastMeshDatasetDir" ), QDir::homePath(), QgsSettings::App ).toString(); + QString saveFileName = QFileDialog::getSaveFileName( nullptr, + QObject::tr( "Save mesh datasets" ), + exportFileDir, + filter ); + + if ( saveFileName.isEmpty() ) + return; + + QFileInfo openFileInfo( saveFileName ); + settings.setValue( QStringLiteral( "lastMeshDatasetDir" ), openFileInfo.absolutePath(), QgsSettings::App ); + + + if ( mMeshLayer->saveDataset( saveFileName, datasetGroup, driver ) ) + { + QMessageBox::warning( nullptr, QObject::tr( "Save mesh datasets" ), QObject::tr( "Saving datasets fails" ) ); + } + else + { + emit datasetGroupSaved(); + QMessageBox::information( nullptr, QObject::tr( "Save mesh datasets" ), QObject::tr( "Datasets successfully saved on file" ) ); + } + +} diff --git a/src/app/mesh/qgsmeshdatasetgrouptreeview.h b/src/app/mesh/qgsmeshdatasetgrouptreeview.h index 493d3b751d53..38f4c21708c7 100644 --- a/src/app/mesh/qgsmeshdatasetgrouptreeview.h +++ b/src/app/mesh/qgsmeshdatasetgrouptreeview.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,23 @@ class QgsMeshLayer; +class QgsMeshDatasetGroupSaveMenu: public QObject +{ + Q_OBJECT + public: + QgsMeshDatasetGroupSaveMenu( QObject *parent = nullptr ): QObject( parent ) {} + QMenu *createSaveMenu( int groupIndex, QMenu *parentMenu = nullptr ); + + void setMeshLayer( QgsMeshLayer *meshLayer ); + + signals: + void datasetGroupSaved(); + + private: + QgsMeshLayer *mMeshLayer; + + void saveDatasetGroup( int datasetGroup, const QString &driver, const QString &fileSuffix ); +}; /** * Item Model for QgsMeshDatasetGroupTreeItem @@ -46,7 +64,8 @@ class APP_NO_EXPORT QgsMeshDatasetGroupTreeModel : public QAbstractItemModel IsVector, IsActiveScalarDatasetGroup, IsActiveVectorDatasetGroup, - DatasetGroupIndex + DatasetGroupIndex, + IsMemory }; explicit QgsMeshDatasetGroupTreeModel( QObject *parent = nullptr ); @@ -68,6 +87,9 @@ class APP_NO_EXPORT QgsMeshDatasetGroupTreeModel : public QAbstractItemModel //! Returns the dataset group root tree item, keeps ownership QgsMeshDatasetGroupTreeItem *datasetGroupTreeRootItem(); + //! Returns the dataset group tree item with \a index, keeps ownership + QgsMeshDatasetGroupTreeItem *datasetGroupTreeItem( int groupIndex ); + //! Returns whether the dataset groups related to the QModelIndex is set as enabled bool isEnabled( const QModelIndex &index ) const; @@ -77,6 +99,12 @@ class APP_NO_EXPORT QgsMeshDatasetGroupTreeModel : public QAbstractItemModel //! Sets all groups as enabled void setAllGroupsAsEnabled( bool isEnabled ); + //! Removes an item from the tree + void removeItem( const QModelIndex &index ); + + //! Sets the storage type for specified item + void setStorageType( const QModelIndex &index, QgsMeshDatasetGroupTreeItem::StorageType type ); + private: std::unique_ptr mRootItem; }; @@ -126,7 +154,7 @@ class APP_EXPORT QgsMeshDatasetGroupTreeItemDelagate: public QStyledItemDelegate const QModelIndex &index ) const override; //! Icon rectangle for given item rectangle - QRect iconRect( const QRect rect, bool isVector ) const; + QRect iconRect( const QRect &rect, bool isVector ) const; QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const override; @@ -135,6 +163,9 @@ class APP_EXPORT QgsMeshDatasetGroupTreeItemDelagate: public QStyledItemDelegate const QPixmap mScalarDeselectedPixmap; const QPixmap mVectorSelectedPixmap; const QPixmap mVectorDeselectedPixmap; + const QPixmap mMemoryPixmap; + + QRect iconRect( const QRect &rect, int pos ) const; }; class APP_EXPORT QgsMeshDatasetGroupTreeView: public QTreeView @@ -148,13 +179,26 @@ class APP_EXPORT QgsMeshDatasetGroupTreeView: public QTreeView QgsMeshDatasetGroupTreeItem *datasetGroupTreeRootItem(); + signals: + void apply(); + public slots: void selectAllGroups(); void deselectAllGroups(); + protected: + void contextMenuEvent( QContextMenuEvent *event ) override; + + private slots: + void removeCurrentItem(); + void onDatasetGroupSaved(); + private: QgsMeshDatasetGroupTreeModel *mModel; + QgsMeshDatasetGroupSaveMenu *mSaveMenu; + void selectAllItem( bool isChecked ); + QMenu *createContextMenu(); }; /** @@ -217,8 +261,11 @@ class APP_EXPORT QgsMeshDatasetGroupListModel: public QAbstractListModel int rowCount( const QModelIndex &parent ) const override; QVariant data( const QModelIndex &index, int role ) const override; + void setDisplayProviderName( bool displayProviderName ); + private: QgsMeshDatasetGroupTreeItem *mRootItem = nullptr; + bool mDisplayProviderName = false; }; #endif // QGSMESHDATASETGROUPTREE_H diff --git a/src/app/mesh/qgsmeshdatasetgrouptreewidget.cpp b/src/app/mesh/qgsmeshdatasetgrouptreewidget.cpp index b96991eae11f..5f739d3d4778 100644 --- a/src/app/mesh/qgsmeshdatasetgrouptreewidget.cpp +++ b/src/app/mesh/qgsmeshdatasetgrouptreewidget.cpp @@ -42,6 +42,8 @@ QgsMeshDatasetGroupTreeWidget::QgsMeshDatasetGroupTreeWidget( QWidget *parent ): this->mDatasetGroupTreeView->resetDefault( this->mMeshLayer ); } ); + connect( mDatasetGroupTreeView, &QgsMeshDatasetGroupTreeView::apply, this, &QgsMeshDatasetGroupTreeWidget::apply ); + } void QgsMeshDatasetGroupTreeWidget::syncToLayer( QgsMeshLayer *meshLayer ) @@ -87,4 +89,3 @@ void QgsMeshDatasetGroupTreeWidget::addDataset() QMessageBox::warning( this, tr( "Load mesh datasets" ), tr( "Could not read mesh dataset." ) ); } } - diff --git a/src/app/mesh/qgsmeshdatasetgrouptreewidget.h b/src/app/mesh/qgsmeshdatasetgrouptreewidget.h index 17b5e1d78087..2e55b3ab09b5 100644 --- a/src/app/mesh/qgsmeshdatasetgrouptreewidget.h +++ b/src/app/mesh/qgsmeshdatasetgrouptreewidget.h @@ -33,6 +33,7 @@ class APP_EXPORT QgsMeshDatasetGroupTreeWidget: public QWidget, private Ui::QgsM //! Synchronizes widgets state with associated mesh layer void syncToLayer( QgsMeshLayer *meshLayer ); + public slots: //! Apply the dataset group tree item to the layer void apply(); diff --git a/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp b/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp index c31865e32157..b1d835214768 100644 --- a/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp +++ b/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp @@ -170,7 +170,7 @@ QString QgsMeshRendererActiveDatasetWidget::metadata( QgsMeshDatasetIndex datase .arg( tr( "Mesh type" ) ) .arg( definedOnMesh ); - const QgsMeshDatasetGroupMetadata gmeta = mMeshLayer->dataProvider()->datasetGroupMetadata( datasetIndex ); + const QgsMeshDatasetGroupMetadata gmeta = mMeshLayer->datasetGroupMetadata( datasetIndex ); QString definedOn; switch ( gmeta.dataType() ) { diff --git a/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp b/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp index c2485546c368..6b16b3610254 100644 --- a/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp +++ b/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp @@ -139,7 +139,7 @@ void QgsMeshRendererScalarSettingsWidget::syncToLayer( ) if ( !hasFaces ) mOpacityContainerWidget->setVisible( false ); - const QgsMeshDatasetGroupMetadata metadata = mMeshLayer->dataProvider()->datasetGroupMetadata( mActiveDatasetGroup ); + const QgsMeshDatasetGroupMetadata metadata = mMeshLayer->datasetGroupMetadata( mActiveDatasetGroup ); double min = metadata.minimum(); double max = metadata.maximum(); mScalarEdgeStrokeWidthVariablePushButton->setDefaultMinMaxValue( min, max ); @@ -174,7 +174,7 @@ void QgsMeshRendererScalarSettingsWidget::minMaxEdited() void QgsMeshRendererScalarSettingsWidget::recalculateMinMaxButtonClicked() { - const QgsMeshDatasetGroupMetadata metadata = mMeshLayer->dataProvider()->datasetGroupMetadata( mActiveDatasetGroup ); + const QgsMeshDatasetGroupMetadata metadata = mMeshLayer->datasetGroupMetadata( mActiveDatasetGroup ); double min = metadata.minimum(); double max = metadata.maximum(); whileBlocking( mScalarMinLineEdit )->setText( QString::number( min ) ); @@ -198,26 +198,26 @@ QgsMeshRendererScalarSettings::DataResamplingMethod QgsMeshRendererScalarSetting bool QgsMeshRendererScalarSettingsWidget::dataIsDefinedOnFaces() const { - if ( !mMeshLayer || !mMeshLayer->dataProvider() || !mMeshLayer->dataProvider()->isValid() ) + if ( !mMeshLayer ) return false; if ( mActiveDatasetGroup < 0 ) return false; - QgsMeshDatasetGroupMetadata meta = mMeshLayer->dataProvider()->datasetGroupMetadata( mActiveDatasetGroup ); + QgsMeshDatasetGroupMetadata meta = mMeshLayer->datasetGroupMetadata( mActiveDatasetGroup ); const bool onFaces = ( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnFaces ); return onFaces; } bool QgsMeshRendererScalarSettingsWidget::dataIsDefinedOnEdges() const { - if ( !mMeshLayer || !mMeshLayer->dataProvider() || !mMeshLayer->dataProvider()->isValid() ) + if ( !mMeshLayer ) return false; if ( mActiveDatasetGroup < 0 ) return false; - QgsMeshDatasetGroupMetadata meta = mMeshLayer->dataProvider()->datasetGroupMetadata( mActiveDatasetGroup ); + QgsMeshDatasetGroupMetadata meta = mMeshLayer->datasetGroupMetadata( mActiveDatasetGroup ); const bool onEdges = ( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnEdges ); return onEdges; } diff --git a/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp b/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp index db01a9739bd5..a87fc3ebdb40 100644 --- a/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp +++ b/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp @@ -298,14 +298,12 @@ void QgsMeshRendererVectorSettingsWidget::loadColorRampShader() if ( !mMeshLayer ) return; - QgsMeshDataProvider *provider = mMeshLayer->dataProvider(); int currentVectorDataSetGroupIndex = mMeshLayer->rendererSettings().activeVectorDatasetGroup(); - if ( !provider || - currentVectorDataSetGroupIndex < 0 || - !provider->datasetGroupMetadata( currentVectorDataSetGroupIndex ).isVector() ) + if ( currentVectorDataSetGroupIndex < 0 || + !mMeshLayer->datasetGroupMetadata( currentVectorDataSetGroupIndex ).isVector() ) return; - const QgsMeshDatasetGroupMetadata meta = provider->datasetGroupMetadata( currentVectorDataSetGroupIndex ); + const QgsMeshDatasetGroupMetadata meta = mMeshLayer->datasetGroupMetadata( currentVectorDataSetGroupIndex ); double min = meta.minimum(); double max = meta.maximum(); diff --git a/src/app/mesh/qgsmeshstaticdatasetwidget.cpp b/src/app/mesh/qgsmeshstaticdatasetwidget.cpp index 1a9e8574aa18..df66138ffe61 100644 --- a/src/app/mesh/qgsmeshstaticdatasetwidget.cpp +++ b/src/app/mesh/qgsmeshstaticdatasetwidget.cpp @@ -34,7 +34,7 @@ void QgsMeshStaticDatasetWidget::setLayer( QgsMeshLayer *layer ) void QgsMeshStaticDatasetWidget::syncToLayer() { - if ( !mLayer || !mLayer->dataProvider() ) + if ( !mLayer ) return; mDatasetScalarModel->setMeshLayer( mLayer ); @@ -57,9 +57,9 @@ void QgsMeshStaticDatasetWidget::setScalarDatasetGroup( int index ) mScalarDatasetGroup = index; mDatasetScalarModel->setDatasetGroup( index ); mScalarDatasetComboBox->setEnabled( mScalarDatasetGroup >= 0 ); - if ( mLayer && mLayer->dataProvider() ) + if ( mLayer ) { - mScalarName->setText( mLayer->dataProvider()->datasetGroupMetadata( index ).name() ); + mScalarName->setText( mLayer->datasetGroupMetadata( index ).name() ); setScalarDatasetIndex( mLayer->staticScalarDatasetIndex().dataset() ); } } @@ -69,16 +69,16 @@ void QgsMeshStaticDatasetWidget::setVectorDatasetGroup( int index ) mVectorDatasetGroup = index; mDatasetVectorModel->setDatasetGroup( index ); mVectorDatasetComboBox->setEnabled( mVectorDatasetGroup >= 0 ); - if ( mLayer && mLayer->dataProvider() ) + if ( mLayer ) { - mVectorName->setText( mLayer->dataProvider()->datasetGroupMetadata( index ).name() ); + mVectorName->setText( mLayer->datasetGroupMetadata( index ).name() ); setVectorDatasetIndex( mLayer->staticVectorDatasetIndex().dataset() ); } } void QgsMeshStaticDatasetWidget::setScalarDatasetIndex( int index ) { - if ( index < mLayer->dataProvider()->datasetCount( mScalarDatasetGroup ) ) + if ( index < mLayer->datasetCount( mScalarDatasetGroup ) ) mScalarDatasetComboBox->setCurrentIndex( index + 1 ); else mScalarDatasetComboBox->setCurrentIndex( 0 ); @@ -86,7 +86,7 @@ void QgsMeshStaticDatasetWidget::setScalarDatasetIndex( int index ) void QgsMeshStaticDatasetWidget::setVectorDatasetIndex( int index ) { - if ( index < mLayer->dataProvider()->datasetCount( mVectorDatasetGroup ) ) + if ( index < mLayer->datasetCount( mVectorDatasetGroup ) ) mVectorDatasetComboBox->setCurrentIndex( index + 1 ); else mVectorDatasetComboBox->setCurrentIndex( 0 ); @@ -113,8 +113,8 @@ int QgsMeshDatasetListModel::rowCount( const QModelIndex &parent ) const { Q_UNUSED( parent ) - if ( mLayer && mLayer->dataProvider() ) - return mLayer->dataProvider()->datasetCount( mDatasetGroup ) + 1; + if ( mLayer ) + return mLayer->datasetCount( mDatasetGroup ) + 1; else return 0; } @@ -126,17 +126,17 @@ QVariant QgsMeshDatasetListModel::data( const QModelIndex &index, int role ) con if ( role == Qt::DisplayRole ) { - if ( !mLayer || !mLayer->dataProvider() || mDatasetGroup < 0 || index.row() == 0 ) + if ( !mLayer || mDatasetGroup < 0 || index.row() == 0 ) return tr( "none" ); - else if ( index.row() == 1 && mLayer->dataProvider()->datasetCount( mDatasetGroup ) == 1 ) + else if ( index.row() == 1 && mLayer->datasetCount( mDatasetGroup ) == 1 ) { return tr( "Display dataset" ); } else { - qint64 time = mLayer->dataProvider()->temporalCapabilities()->datasetTime( QgsMeshDatasetIndex( mDatasetGroup, index.row() - 1 ) ); - return mLayer->formatTime( time / 3600.0 / 1000.0 ); + QgsInterval time = mLayer->datasetRelativeTime( QgsMeshDatasetIndex( mDatasetGroup, index.row() - 1 ) ); + return mLayer->formatTime( time.hours() ); } } diff --git a/src/app/mesh/qgsrenderermeshpropertieswidget.cpp b/src/app/mesh/qgsrenderermeshpropertieswidget.cpp index 99e5c0663cac..9b592d16b5f8 100644 --- a/src/app/mesh/qgsrenderermeshpropertieswidget.cpp +++ b/src/app/mesh/qgsrenderermeshpropertieswidget.cpp @@ -173,8 +173,7 @@ void QgsRendererMeshPropertiesWidget::onActiveScalarGroupChanged( int groupIndex void QgsRendererMeshPropertiesWidget::onActiveVectorGroupChanged( int groupIndex ) { - if ( !mMeshLayer->dataProvider() || - ( groupIndex >= 0 && !mMeshLayer->dataProvider()->datasetGroupMetadata( groupIndex ).isVector() ) ) + if ( groupIndex >= 0 && !mMeshLayer->datasetGroupMetadata( groupIndex ).isVector() ) groupIndex = -1; mMeshRendererVectorSettingsWidget->setActiveDatasetGroup( groupIndex ); mMeshRendererVectorSettingsWidget->syncToLayer(); diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 2d83e48bbd8c..be82ebe189ff 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -12841,6 +12841,8 @@ bool QgisApp::checkMemoryLayers() // check to see if there are any temporary layers present (with features) bool hasTemporaryLayers = false; bool hasMemoryLayers = false; + bool hasMemoryMeshLayerDatasetGroup = false; + const QMap layers = QgsProject::instance()->mapLayers(); for ( auto it = layers.begin(); it != layers.end(); ++it ) { @@ -12855,31 +12857,36 @@ bool QgisApp::checkMemoryLayers() } else if ( it.value() && it.value()->isTemporary() ) hasTemporaryLayers = true; + else if ( it.value() && it.value()->type() == QgsMapLayerType::MeshLayer ) + { + QgsMeshLayer *meshLayer = qobject_cast< QgsMeshLayer * >( it.value() ); + if ( meshLayer && meshLayer->extraDatasetGroupCount() > 0 ) + { + hasMemoryMeshLayerDatasetGroup = true; + } + } } + bool close = true; if ( hasTemporaryLayers ) - { - if ( QMessageBox::warning( this, - tr( "Close Project" ), - tr( "This project includes one or more temporary layers. These layers are not permanently saved and their contents will be lost. Are you sure you want to proceed?" ), - QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel ) == QMessageBox::Yes ) - return true; - else - return false; - } + close &= QMessageBox::warning( this, + tr( "Close Project" ), + tr( "This project includes one or more temporary layers. These layers are not permanently saved and their contents will be lost. Are you sure you want to proceed?" ), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel ) == QMessageBox::Yes ; else if ( hasMemoryLayers ) - { // use the more specific warning for memory layers - if ( QMessageBox::warning( this, - tr( "Close Project" ), - tr( "This project includes one or more temporary scratch layers. These layers are not saved to disk and their contents will be permanently lost. Are you sure you want to proceed?" ), - QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel ) == QMessageBox::Yes ) - return true; - else - return false; - } - else - return true; + close &= QMessageBox::warning( this, + tr( "Close Project" ), + tr( "This project includes one or more temporary scratch layers. These layers are not saved to disk and their contents will be permanently lost. Are you sure you want to proceed?" ), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel ) == QMessageBox::Yes; + + if ( hasMemoryMeshLayerDatasetGroup ) + close &= QMessageBox::warning( this, + tr( "Close Project" ), + tr( "This project includes one or more memory mesh layer dataset groups. These dataset groups are not saved to disk and their contents will be permanently lost. Are you sure you want to proceed?" ), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel ) == QMessageBox::Yes; + + return close; } bool QgisApp::checkTasksDependOnProject() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3a7b53ee6035..810393fe57ab 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -589,6 +589,7 @@ SET(QGIS_CORE_SRCS mesh/qgsmeshdataprovider.cpp mesh/qgsmeshdataprovidertemporalcapabilities.cpp mesh/qgsmeshdataset.cpp + mesh/qgsmeshdatasetgroupstore.cpp mesh/qgsmeshlayer.cpp mesh/qgsmeshlayerinterpolator.cpp mesh/qgsmeshlayerrenderer.cpp @@ -602,8 +603,6 @@ SET(QGIS_CORE_SRCS mesh/qgsmeshvectorrenderer.cpp mesh/qgstriangularmesh.cpp - - labeling/qgslabelfeature.cpp labeling/qgslabelingengine.cpp labeling/qgslabelingenginesettings.cpp @@ -1229,6 +1228,7 @@ SET(QGIS_CORE_HDRS mesh/qgsmeshdataprovider.h mesh/qgsmeshdataprovidertemporalcapabilities.h mesh/qgsmeshdataset.h + mesh/qgsmeshdatasetgroupstore.h mesh/qgsmeshlayer.h mesh/qgsmeshlayerinterpolator.h mesh/qgsmeshlayerrenderer.h diff --git a/src/core/mesh/qgsmeshdataprovider.cpp b/src/core/mesh/qgsmeshdataprovider.cpp index a10e7413de2d..673e2518c484 100644 --- a/src/core/mesh/qgsmeshdataprovider.cpp +++ b/src/core/mesh/qgsmeshdataprovider.cpp @@ -21,7 +21,7 @@ #include "qgsrectangle.h" QgsMeshDataProvider::QgsMeshDataProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options ) - : QgsDataProvider( uri, options ), mTemporalCapabilities( qgis::make_unique() ) + : QgsDataProvider( uri, options ) { } @@ -43,6 +43,35 @@ void QgsMeshDataProvider::setTemporalUnit( QgsUnitTypes::TemporalUnit unit ) reloadData(); } +QgsMeshDatasetIndex QgsMeshDatasetSourceInterface::datasetIndexAtTime( + const QDateTime &referenceTime, + int groupIndex, quint64 time, + QgsMeshDataProviderTemporalCapabilities::MatchingTemporalDatasetMethod method ) const +{ + QDateTime requestDateTime = referenceTime.addMSecs( time ); + quint64 providerTime; + QDateTime providerReferenceTime = mTemporalCapabilities->referenceTime(); + if ( mTemporalCapabilities->referenceTime().isValid() ) + providerTime = referenceTime.msecsTo( requestDateTime ); + else + providerTime = time; + + switch ( method ) + { + case QgsMeshDataProviderTemporalCapabilities::FindClosestDatasetBeforeStartRangeTime: + return mTemporalCapabilities->datasetIndexClosestBeforeRelativeTime( groupIndex, providerTime ); + break; + case QgsMeshDataProviderTemporalCapabilities::FindClosestDatasetFromStartRangeTime: + return mTemporalCapabilities->datasetIndexClosestFromRelativeTime( groupIndex, providerTime ); + break; + } + + return QgsMeshDatasetIndex(); +} + +QgsMeshDatasetSourceInterface::QgsMeshDatasetSourceInterface(): + mTemporalCapabilities( qgis::make_unique() ) {} + int QgsMeshDatasetSourceInterface::datasetCount( QgsMeshDatasetIndex index ) const { return datasetCount( index.group() ); diff --git a/src/core/mesh/qgsmeshdataprovider.h b/src/core/mesh/qgsmeshdataprovider.h index 734204ede07b..2ab8fb0a6c26 100644 --- a/src/core/mesh/qgsmeshdataprovider.h +++ b/src/core/mesh/qgsmeshdataprovider.h @@ -182,6 +182,7 @@ class CORE_EXPORT QgsMeshDataSourceInterface SIP_ABSTRACT class CORE_EXPORT QgsMeshDatasetSourceInterface SIP_ABSTRACT { public: + QgsMeshDatasetSourceInterface(); //! Dtor virtual ~QgsMeshDatasetSourceInterface() = default; @@ -335,6 +336,20 @@ class CORE_EXPORT QgsMeshDatasetSourceInterface SIP_ABSTRACT const QVector &datasetActive, const QVector × ) = 0; + + virtual bool persistDatasetGroup( const QString &outputFilePath, + const QString &outputDriver, + QgsMeshDatasetSourceInterface *source, + int datasetGroupIndex + ) = 0; + + QgsMeshDatasetIndex datasetIndexAtTime( const QDateTime &referenceTime, + int groupIndex, + quint64 time, + QgsMeshDataProviderTemporalCapabilities::MatchingTemporalDatasetMethod method ) const; + + protected: + std::unique_ptr mTemporalCapabilities; }; @@ -351,7 +366,6 @@ class CORE_EXPORT QgsMeshDatasetSourceInterface SIP_ABSTRACT class CORE_EXPORT QgsMeshDataProvider: public QgsDataProvider, public QgsMeshDataSourceInterface, public QgsMeshDatasetSourceInterface { Q_OBJECT - public: //! Ctor QgsMeshDataProvider( const QString &uri, const QgsDataProvider::ProviderOptions &providerOptions ); @@ -372,8 +386,6 @@ class CORE_EXPORT QgsMeshDataProvider: public QgsDataProvider, public QgsMeshDat //! Emitted when some new dataset groups have been added void datasetGroupsAdded( int count ); - private: - std::unique_ptr mTemporalCapabilities; }; #endif // QGSMESHDATAPROVIDER_H diff --git a/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp b/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp index 0e31ad2ae1fa..d52296f3cca9 100644 --- a/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp +++ b/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp @@ -159,13 +159,13 @@ QgsUnitTypes::TemporalUnit QgsMeshDataProviderTemporalCapabilities::temporalUnit qint64 QgsMeshDataProviderTemporalCapabilities::datasetTime( const QgsMeshDatasetIndex &index ) const { if ( !index.isValid() ) - return -999999; + return INVALID_MESHLAYER_TIME; const QList ×List = mDatasetTimeSinceGroupReference[index.group()]; if ( index.dataset() < timesList.count() ) return timesList.at( index.dataset() ); else - return -999999; + return INVALID_MESHLAYER_TIME; } void QgsMeshDataProviderTemporalCapabilities::clear() diff --git a/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.h b/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.h index e961e916f62b..e18735d747f0 100644 --- a/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.h +++ b/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.h @@ -22,6 +22,7 @@ #include "qgsrange.h" #include "qgsmeshdataset.h" +#define INVALID_MESHLAYER_TIME -99999 /** * \class QgsMeshDataProviderTemporalCapabilities diff --git a/src/core/mesh/qgsmeshdataset.cpp b/src/core/mesh/qgsmeshdataset.cpp index cac86542c50d..735447140ed5 100644 --- a/src/core/mesh/qgsmeshdataset.cpp +++ b/src/core/mesh/qgsmeshdataset.cpp @@ -121,6 +121,7 @@ bool QgsMeshDatasetValue::operator==( const QgsMeshDatasetValue other ) const } QgsMeshDatasetGroupMetadata::QgsMeshDatasetGroupMetadata( const QString &name, + const QString uri, bool isScalar, DataType dataType, double minimum, @@ -130,6 +131,7 @@ QgsMeshDatasetGroupMetadata::QgsMeshDatasetGroupMetadata( const QString &name, bool isTemporal, const QMap &extraOptions ) : mName( name ) + , mUri( uri ) , mIsScalar( isScalar ) , mDataType( dataType ) , mMinimumValue( minimum ) @@ -191,6 +193,11 @@ QDateTime QgsMeshDatasetGroupMetadata::referenceTime() const return mReferenceTime; } +QString QgsMeshDatasetGroupMetadata::uri() const +{ + return mUri; +} + QgsMeshDatasetMetadata::QgsMeshDatasetMetadata( double time, bool isValid, @@ -436,10 +443,11 @@ void QgsMesh3dDataBlock::setValid( bool valid ) QgsMeshDatasetGroupTreeItem::QgsMeshDatasetGroupTreeItem() = default; -QgsMeshDatasetGroupTreeItem::QgsMeshDatasetGroupTreeItem( const QString &defaultName, +QgsMeshDatasetGroupTreeItem::QgsMeshDatasetGroupTreeItem( const QString &defaultName, const QString &providerName, bool isVector, int index ) - : mProviderName( defaultName ) + : mOriginalName( defaultName ) + , mProviderName( providerName ) , mIsVector( isVector ) , mDatasetGroupIndex( index ) { @@ -451,6 +459,9 @@ QgsMeshDatasetGroupTreeItem::QgsMeshDatasetGroupTreeItem( const QDomElement &ite if ( itemElement.hasAttribute( QStringLiteral( "display-name" ) ) ) mUserName = itemElement.attribute( QStringLiteral( "display-name" ), mUserName ); + if ( itemElement.hasAttribute( QStringLiteral( "original-name" ) ) ) + mOriginalName = itemElement.attribute( QStringLiteral( "original-name" ), mOriginalName ); + if ( itemElement.hasAttribute( QStringLiteral( "provider-name" ) ) ) mProviderName = itemElement.attribute( QStringLiteral( "provider-name" ), mProviderName ); @@ -479,9 +490,10 @@ QgsMeshDatasetGroupTreeItem::~QgsMeshDatasetGroupTreeItem() QgsMeshDatasetGroupTreeItem *QgsMeshDatasetGroupTreeItem::clone() const { - QgsMeshDatasetGroupTreeItem *other = new QgsMeshDatasetGroupTreeItem( mProviderName, mIsVector, mDatasetGroupIndex ); + QgsMeshDatasetGroupTreeItem *other = new QgsMeshDatasetGroupTreeItem( mOriginalName, mProviderName, mIsVector, mDatasetGroupIndex ); other->mUserName = mUserName; other->mIsEnabled = mIsEnabled; + other->mStoreType = mStoreType; if ( !mChildren.empty() ) for ( int i = 0; i < mChildren.count(); ++i ) @@ -497,6 +509,13 @@ void QgsMeshDatasetGroupTreeItem::appendChild( QgsMeshDatasetGroupTreeItem *item mDatasetGroupIndexToChild[item->datasetGroupIndex()] = item; } +void QgsMeshDatasetGroupTreeItem::removeChild( QgsMeshDatasetGroupTreeItem *item ) +{ + mChildren.removeOne( item ); + mDatasetGroupIndexToChild.remove( item->datasetGroupIndex() ); + delete item; +} + QgsMeshDatasetGroupTreeItem *QgsMeshDatasetGroupTreeItem::child( int row ) const { if ( row < mChildren.count() ) @@ -559,7 +578,7 @@ int QgsMeshDatasetGroupTreeItem::row() const QString QgsMeshDatasetGroupTreeItem::name() const { if ( mUserName.isEmpty() ) - return mProviderName; + return mOriginalName; else return mUserName; } @@ -586,9 +605,13 @@ void QgsMeshDatasetGroupTreeItem::setIsEnabled( bool enabled ) QString QgsMeshDatasetGroupTreeItem::defaultName() const { - return mProviderName; + return mOriginalName; } +QgsMeshDatasetGroupTreeItem::StorageType QgsMeshDatasetGroupTreeItem::storageType() const {return mStoreType;} + +void QgsMeshDatasetGroupTreeItem::setStorageType( QgsMeshDatasetGroupTreeItem::StorageType storeType ) {mStoreType = storeType;} + QDomElement QgsMeshDatasetGroupTreeItem::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) { Q_UNUSED( context ); @@ -596,6 +619,7 @@ QDomElement QgsMeshDatasetGroupTreeItem::writeXml( QDomDocument &doc, const QgsR QDomElement itemElement = doc.createElement( QStringLiteral( "mesh-dataset-group-tree-item" ) ); itemElement.setAttribute( QStringLiteral( "display-name" ), mUserName ); itemElement.setAttribute( QStringLiteral( "provider-name" ), mProviderName ); + itemElement.setAttribute( QStringLiteral( "original-name" ), mOriginalName ); itemElement.setAttribute( QStringLiteral( "is-vector" ), mIsVector ? true : false ); itemElement.setAttribute( QStringLiteral( "dataset-index" ), mDatasetGroupIndex ); itemElement.setAttribute( QStringLiteral( "is-enabled" ), mIsEnabled ? true : false ); @@ -606,8 +630,253 @@ QDomElement QgsMeshDatasetGroupTreeItem::writeXml( QDomDocument &doc, const QgsR return itemElement; } +QString QgsMeshDatasetGroupTreeItem::providerName() const +{ + return mProviderName; +} + void QgsMeshDatasetGroupTreeItem::setName( const QString &name ) { mUserName = name; } + +QgsMeshDatasetValue QgsMeshMemoryDataset::datasetValue( int valueIndex ) const +{ + if ( valueIndex >= 0 && valueIndex < values.count() ) + return values[valueIndex]; + else + return QgsMeshDatasetValue(); +} + +QgsMeshDataBlock QgsMeshMemoryDataset::datasetValues( bool isScalar, int valueIndex, int count ) const +{ + QgsMeshDataBlock ret( isScalar ? QgsMeshDataBlock::ScalarDouble : QgsMeshDataBlock::Vector2DDouble, count ); + QVector buf( isScalar ? count : 2 * count ); + for ( int i = 0; i < count; ++i ) + { + int idx = valueIndex + i; + if ( ( idx < 0 ) || ( idx >= values.size() ) ) + return ret; + + QgsMeshDatasetValue val = values[ valueIndex + i ]; + if ( isScalar ) + buf[i] = val.x(); + else + { + buf[2 * i] = val.x(); + buf[2 * i + 1] = val.y(); + } + } + ret.setValues( buf ); + return ret; +} + +QgsMeshDataBlock QgsMeshMemoryDataset::areFacesActive( int faceIndex, int count ) const +{ + QgsMeshDataBlock ret( QgsMeshDataBlock::ActiveFlagInteger, count ); + if ( active.isEmpty() || + ( faceIndex < 0 ) || + ( faceIndex + count > active.size() ) + ) + ret.setValid( true ); + else + ret.setActive( active ); + return ret; +} + +QgsMeshDatasetMetadata QgsMeshMemoryDataset::metaData() const +{ + return QgsMeshDatasetMetadata( time, valid, minimum, maximum, 0 ); +} + +void QgsMeshMemoryDataset::calculateMinMax() +{ + double min = std::numeric_limits::max(); + double max = std::numeric_limits::min(); + + if ( !valid ) + return; + + + bool firstIteration = true; + for ( int i = 0; i < values.size(); ++i ) + { + double v = values[i].scalar(); + + if ( std::isnan( v ) ) + continue; + if ( firstIteration ) + { + firstIteration = false; + min = v; + max = v; + } + else + { + if ( v < min ) + min = v; + if ( v > max ) + max = v; + } + } + + minimum = min; + maximum = max; +} + +bool QgsMeshMemoryDataset::isActive( int faceIndex ) const +{ + if ( active.isEmpty() || faceIndex >= active.count() ) + return true; + else + return active.at( faceIndex ); +} + +int QgsMeshMemoryDataset::valuesCount() const +{ + return values.count(); +} + +QgsMeshMemoryDatasetGroup::QgsMeshMemoryDatasetGroup( const QString &name, QgsMeshDatasetGroupMetadata::DataType dataType ): + QgsMeshDatasetGroup( name, dataType ) +{ +} + +QgsMeshMemoryDatasetGroup::QgsMeshMemoryDatasetGroup( const QString &name ): + QgsMeshDatasetGroup( name ) +{ +} + +QgsMeshDatasetGroupMetadata QgsMeshDatasetGroup::groupMetadata() const +{ + return QgsMeshDatasetGroupMetadata( + name(), + QString(), + isScalar(), + dataType(), + minimum(), + maximum(), + 0, + QDateTime(), + datasetCount() > 1, + extraMetadata() + ); +} + +int QgsMeshMemoryDatasetGroup::datasetCount() const +{ + return memoryDatasets.size(); +} + +QgsMeshDatasetMetadata QgsMeshMemoryDatasetGroup::datasetMetadata( int datasetIndex ) +{ + if ( datasetIndex >= 0 && datasetIndex < memoryDatasets.count() ) + return memoryDatasets[datasetIndex]->metaData(); + else + return QgsMeshDatasetMetadata(); +} + +const QgsMeshDataset *QgsMeshMemoryDatasetGroup::dataset( int index ) const +{ + return memoryDatasets.at( index ).get(); +} + +void QgsMeshMemoryDatasetGroup::addDataset( std::shared_ptr dataset ) +{ + memoryDatasets.push_back( dataset ); +} + +void QgsMeshMemoryDatasetGroup::clearDatasets() +{ + memoryDatasets.clear(); +} + +std::shared_ptr QgsMeshMemoryDatasetGroup::constDataset( int index ) const +{ + return memoryDatasets[index]; +} + +void QgsMeshMemoryDatasetGroup::calculateStatistic() +{ + double min = std::numeric_limits::max(); + double max = std::numeric_limits::min(); + + int count = memoryDatasets.size(); + for ( int i = 0; i < count; ++i ) + { + memoryDatasets[i]->calculateMinMax(); + min = std::min( min, memoryDatasets[i]->minimum ); + max = std::max( max, memoryDatasets[i]->maximum ); + } + mMinimum = min; + mMaximum = max; +} + +bool QgsMeshDatasetGroup::checkValueCountPerDataset( int count ) const +{ + for ( int i = 0; i < datasetCount(); ++i ) + if ( dataset( i )->valuesCount() != count ) + return false; + return true; +} + +QgsMeshDatasetGroup::QgsMeshDatasetGroup( const QString &name, QgsMeshDatasetGroupMetadata::DataType dataType ): mName( name ), mDataType( dataType ) {} + +QgsMeshDatasetGroup::QgsMeshDatasetGroup( const QString &name ): mName( name ) {} + +double QgsMeshDatasetGroup::minimum() const +{ + return mMinimum; +} + +double QgsMeshDatasetGroup::maximum() const +{ + return mMaximum; +} + +void QgsMeshDatasetGroup::setMinimumMaximum( double min, double max ) +{ + mMinimum = min; + mMaximum = max; +} + +QString QgsMeshDatasetGroup::name() const +{ + return mName; +} + +void QgsMeshDatasetGroup::setName( const QString &name ) +{ + mName = name; +} + +QgsMeshDatasetGroupMetadata::DataType QgsMeshDatasetGroup::dataType() const +{ + return mDataType; +} + +void QgsMeshDatasetGroup::setDataType( const QgsMeshDatasetGroupMetadata::DataType &type ) +{ + mDataType = type; +} + +void QgsMeshDatasetGroup::addExtraMetadata( QString key, QString value ) +{ + mMetadata.insert( key, value ); +} + +QMap QgsMeshDatasetGroup::extraMetadata() const +{ + return mMetadata; +} + +bool QgsMeshDatasetGroup::isScalar() const +{ + return mIsScalar; +} + +void QgsMeshDatasetGroup::setIsScalar( bool isScalar ) +{ + mIsScalar = isScalar; +} diff --git a/src/core/mesh/qgsmeshdataset.h b/src/core/mesh/qgsmeshdataset.h index 3b1a967d8e6d..f1eceb93db02 100644 --- a/src/core/mesh/qgsmeshdataset.h +++ b/src/core/mesh/qgsmeshdataset.h @@ -375,6 +375,7 @@ class CORE_EXPORT QgsMeshDatasetGroupMetadata * \param extraOptions dataset's extra options stored by the provider. Usually contains the name, time value, time units, data file vendor, ... */ QgsMeshDatasetGroupMetadata( const QString &name, + const QString uri, bool isScalar, DataType dataType, double minimum, @@ -389,6 +390,13 @@ class CORE_EXPORT QgsMeshDatasetGroupMetadata */ QString name() const; + /** + * Returns the uri of the source + * + * \since QGIS 3.16 + */ + QString uri() const; + /** * Returns extra metadata options, for example description */ @@ -444,6 +452,7 @@ class CORE_EXPORT QgsMeshDatasetGroupMetadata private: QString mName; + QString mUri; bool mIsScalar = false; DataType mDataType = DataType::DataOnFaces; double mMinimumValue = std::numeric_limits::quiet_NaN(); @@ -547,6 +556,19 @@ class CORE_EXPORT QgsMeshDatasetGroupTreeItem { public: + /** + * Where the dataset group is stored + * + * \since QGIS 3.16 + */ + enum StorageType + { + None, //! Generic item that do not store dataset + File, //! Dataset group store on file + Memory, //! Temporary dataset group in memory + OnTheFly //! Dataset group which id based on expression evamluted in real-time + }; + /** * Constructor for an empty dataset group tree item */ @@ -560,6 +582,7 @@ class CORE_EXPORT QgsMeshDatasetGroupTreeItem * \param index index of the dataset group */ QgsMeshDatasetGroupTreeItem( const QString &defaultName, + const QString &providerName, bool isVector, int index ); @@ -592,6 +615,16 @@ class CORE_EXPORT QgsMeshDatasetGroupTreeItem */ void appendChild( QgsMeshDatasetGroupTreeItem *item SIP_TRANSFER ); + /** + * Removes a item child if exists + * \param item the item to append + * + * \note takes ownership of item + * + * \since QGIS 3.16 + */ + void removeChild( QgsMeshDatasetGroupTreeItem *item SIP_TRANSFER ); + /** * Returns a child * \param row the position of the child @@ -647,6 +680,15 @@ class CORE_EXPORT QgsMeshDatasetGroupTreeItem */ void setName( const QString &name ); + /** + * Returns the name used by the provider to identify the dataset + * + * \return the provider name + * + * \since QGIS 3.16 + */ + QString providerName() const; + /** * \return whether the dataset group is vector */ @@ -673,6 +715,21 @@ class CORE_EXPORT QgsMeshDatasetGroupTreeItem */ QString defaultName() const; + /** + * \return where the dataset group is stored + * + * \since QGIS 3.16 + */ + QgsMeshDatasetGroupTreeItem::StorageType storageType() const; + + /** + * Sets where the dataset is stored + * \param storeType the type of storing + * + * \since QGIS 3.16 + */ + void setStorageType( QgsMeshDatasetGroupTreeItem::StorageType storageType ); + /** * Write the item and its children in a DOM document * \param doc the DOM document @@ -688,10 +745,192 @@ class CORE_EXPORT QgsMeshDatasetGroupTreeItem // Data QString mUserName; + QString mOriginalName; QString mProviderName; + StorageType mStoreType = None; bool mIsVector = false; int mDatasetGroupIndex = -1; bool mIsEnabled = true; }; +#ifndef SIP_RUN + +/** + * \ingroup core + * + * Abstract class that represents a dataset + * + * \since QGIS 3.16 + */ +class CORE_EXPORT QgsMeshDataset +{ + public: + QgsMeshDataset() = default; + virtual ~QgsMeshDataset() = default; + + //! Returns the value with index \a valueIndex + virtual QgsMeshDatasetValue datasetValue( int valueIndex ) const = 0; + + //! Returns \a count values from \a valueIndex + virtual QgsMeshDataBlock datasetValues( bool isScalar, int valueIndex, int count ) const = 0; + + //! Returns whether faces are active + virtual QgsMeshDataBlock areFacesActive( int faceIndex, int count ) const = 0; + + //! Returns whether the face is active + virtual bool isActive( int faceIndex ) const = 0; + + //! Returns the metadata of the dataset + virtual QgsMeshDatasetMetadata metaData() const = 0; + + //! Calculates minimum and maximum of the dataset + virtual void calculateMinMax() = 0; + + //! Returns the values count + virtual int valuesCount() const = 0; +}; + +/** + * \ingroup core + * + * Class to store memory dataset + * The QgsMeshDatasetValue objects and whether the faces are active are stored in QVector containers that are exposed for efficiency + * + * \since QGIS 3.16 + */ +class CORE_EXPORT QgsMeshMemoryDataset: public QgsMeshDataset +{ + public: + QgsMeshMemoryDataset() = default; + + QgsMeshDatasetValue datasetValue( int valueIndex ) const override; + QgsMeshDataBlock datasetValues( bool isScalar, int valueIndex, int count ) const override; + QgsMeshDataBlock areFacesActive( int faceIndex, int count ) const override; + QgsMeshDatasetMetadata metaData() const override; + void calculateMinMax() override; + bool isActive( int faceIndex ) const override; + int valuesCount() const override; + + QVector values; + QVector active; + double time = -1; + bool valid = false; + double minimum = std::numeric_limits::quiet_NaN(); + double maximum = std::numeric_limits::quiet_NaN(); +}; + +/** + * \ingroup core + * + * Abstract class that represents a dataset group + * + * \since QGIS 3.16 + */ +class CORE_EXPORT QgsMeshDatasetGroup +{ + public: + //! Default constructor + QgsMeshDatasetGroup() = default; + virtual ~QgsMeshDatasetGroup() = default; + + //! Constructor with the \a name of the dataset group + QgsMeshDatasetGroup( const QString &name ); + + //! Constructor with the \a name of the dataset group and the \a dataTYpe + QgsMeshDatasetGroup( const QString &name, QgsMeshDatasetGroupMetadata::DataType dataType ); + + //! Returns the metadata of the dataset group + virtual QgsMeshDatasetGroupMetadata groupMetadata() const; + + //! Returns the metadata of the dataset with index \a datasetIndex + virtual QgsMeshDatasetMetadata datasetMetadata( int datasetIndex ) = 0; + + //! Returns the count of datasets in the group + virtual int datasetCount() const = 0; + + //! Returns the dataset with \a index + virtual const QgsMeshDataset *dataset( int index ) const = 0; + + //! Returns the minimum value of the whole dataset group + double minimum() const; + + //! Returns the maximum value of the whole dataset group + double maximum() const; + + //! Overrides the minimum and the maximum value of the whole dataset group + void setMinimumMaximum( double min, double max ); + + //! Returns the name of the dataset group + QString name() const; + + //! Sets the name of the dataset group + void setName( const QString &name ); + + //! Returns the data type of the dataset group + QgsMeshDatasetGroupMetadata::DataType dataType() const; + + //! Sets the data type of the dataset group + void setDataType( const QgsMeshDatasetGroupMetadata::DataType &dataType ); + + //! Adds extra metadata to the group + void addExtraMetadata( QString key, QString value ); + //! Returns all the extra metadata of the group + QMap extraMetadata() const; + + //! Returns whether the group contain scalar values + bool isScalar() const; + + //! Sets whether the group contain scalar values + void setIsScalar( bool isScalar ); + + //! Returns whether all the datasets contain \a count values + bool checkValueCountPerDataset( int count ) const; + + protected: + QString mName; + double mMinimum = std::numeric_limits::quiet_NaN(); + double mMaximum = std::numeric_limits::quiet_NaN(); + QgsMeshDatasetGroupMetadata::DataType mDataType = QgsMeshDatasetGroupMetadata::DataOnVertices; + QMap mMetadata; + bool mIsScalar = true; +}; + +/** + * \ingroup core + * + * Class that represents a dataset group stored in memory + * The QgsMeshMemoryDataset objects stores in a QVector container that are exposed for efficiency + * + * \since QGIS 3.16 + */ +class CORE_EXPORT QgsMeshMemoryDatasetGroup: public QgsMeshDatasetGroup +{ + public: + QgsMeshMemoryDatasetGroup() = default; + QgsMeshMemoryDatasetGroup( const QString &nm ); + QgsMeshMemoryDatasetGroup( const QString &nm, QgsMeshDatasetGroupMetadata::DataType dataType ); + + int datasetCount() const override; + QgsMeshDatasetMetadata datasetMetadata( int datasetIndex ) override; + const QgsMeshDataset *dataset( int index ) const override; + + //! Adds a memory dataset to the group + void addDataset( std::shared_ptr dataset ); + + //! Removes all the datasets from the group + void clearDatasets(); + + //! Returns the dataset with \a index + std::shared_ptr constDataset( int index ) const; + + //! Calculates the statictics (minimum and maximum) + void calculateStatistic(); + + //! Contains all the memory datasets + QVector> memoryDatasets; + +}; + +#endif //SIP_RUN + #endif // QGSMESHDATASET_H diff --git a/src/core/mesh/qgsmeshdatasetgroupstore.cpp b/src/core/mesh/qgsmeshdatasetgroupstore.cpp new file mode 100644 index 000000000000..4fe9a7f4cca1 --- /dev/null +++ b/src/core/mesh/qgsmeshdatasetgroupstore.cpp @@ -0,0 +1,684 @@ +/*************************************************************************** + qgsmeshdatasetgroupstore.cpp + --------------------- + begin : June 2020 + copyright : (C) 2020 by Vincent Cloarec + email : vcloarec 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 "qgsmeshdatasetgroupstore.h" +#include "qgsmeshlayerutils.h" + + +QList QgsMeshDatasetGroupStore::datasetGroupIndexes() const +{ + return mRegistery.keys(); +} + +int QgsMeshDatasetGroupStore::datasetGroupCount() const +{ + return mRegistery.count(); +} + +int QgsMeshDatasetGroupStore::extraDatasetGroupCount() const +{ + return mExtraDatasets->datasetGroupCount(); +} + +QgsMeshDatasetGroupStore::QgsMeshDatasetGroupStore(): + mExtraDatasets( new QgsMeshExtraDatasetStore ), + mDatasetGroupTreeRootItem( new QgsMeshDatasetGroupTreeItem ) +{} + +void QgsMeshDatasetGroupStore::setPersistentProvider( QgsMeshDataProvider *provider ) +{ + removePersistentProvider(); + mPersistentProvider = provider; + if ( !mPersistentProvider ) + return; + connect( mPersistentProvider, &QgsMeshDataProvider::datasetGroupsAdded, this, &QgsMeshDatasetGroupStore::onPersistentDatasetAdded ); + onPersistentDatasetAdded( mPersistentProvider->datasetGroupCount() ); +} + +QgsMeshDatasetGroupStore::DatasetGroup QgsMeshDatasetGroupStore::datasetGroup( int index ) const +{ + if ( mRegistery.contains( index ) ) + return mRegistery[index]; + else + return DatasetGroup{nullptr, -1}; +} + +bool QgsMeshDatasetGroupStore::addPersistentDatasets( const QString &path ) +{ + if ( !mPersistentProvider ) + return false; + return mPersistentProvider->addDataset( path ) ; +} + +bool QgsMeshDatasetGroupStore::addDatasetGroup( QgsMeshDatasetGroup *group ) +{ + if ( !mPersistentProvider && mExtraDatasets ) + return false; + + switch ( group->dataType() ) + { + case QgsMeshDatasetGroupMetadata::DataOnFaces: + if ( ! group->checkValueCountPerDataset( mPersistentProvider->faceCount() ) ) + return false; + break; + case QgsMeshDatasetGroupMetadata::DataOnVertices: + if ( ! group->checkValueCountPerDataset( mPersistentProvider->vertexCount() ) ) + return false; + break; + case QgsMeshDatasetGroupMetadata::DataOnVolumes: + return false; // volume not supported for extra dataset + break; + case QgsMeshDatasetGroupMetadata::DataOnEdges: + if ( ! group->checkValueCountPerDataset( mPersistentProvider->edgeCount() ) ) + return false; + break; + } + + mExtraDatasets->addDatasetGroup( group ); + QList groupIndexes; + groupIndexes.append( registerDatasetGroup( DatasetGroup{mExtraDatasets.get(), mExtraDatasets->datasetGroupCount() - 1} ) ); + + createDatasetGroupTreeItems( groupIndexes ); + + emit datasetGroupsAdded( groupIndexes ); + + return true; +} + +void QgsMeshDatasetGroupStore::resetDatasetGroupTreeItem() +{ + mDatasetGroupTreeRootItem.reset( new QgsMeshDatasetGroupTreeItem ); + createDatasetGroupTreeItems( datasetGroupIndexes() ); +} + +QgsMeshDatasetGroupTreeItem *QgsMeshDatasetGroupStore::datasetGroupTreeItem() const +{ + return mDatasetGroupTreeRootItem.get(); +} + +void QgsMeshDatasetGroupStore::setDatasetGroupTreeItem( QgsMeshDatasetGroupTreeItem *rootItem ) +{ + if ( rootItem ) + mDatasetGroupTreeRootItem.reset( rootItem->clone() ); + else + mDatasetGroupTreeRootItem.reset(); + + unregisterGroupNotPresentInTree(); +} + +QgsMeshDatasetGroupMetadata QgsMeshDatasetGroupStore::datasetGroupMetadata( const QgsMeshDatasetIndex &index ) const +{ + QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( index.group() ); + if ( group.first ) + return group.first->datasetGroupMetadata( group.second ); + else + return QgsMeshDatasetGroupMetadata(); +} + +int QgsMeshDatasetGroupStore::datasetCount( int groupIndex ) const +{ + QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( groupIndex ); + if ( group.first ) + return group.first->datasetCount( group.second ); + else + return 0; +} + +QgsMeshDatasetMetadata QgsMeshDatasetGroupStore::datasetMetadata( const QgsMeshDatasetIndex &index ) const +{ + QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( index.group() ); + if ( group.first ) + return group.first->datasetMetadata( QgsMeshDatasetIndex( group.second, index.dataset() ) ); + else + return QgsMeshDatasetMetadata(); +} + +QgsMeshDatasetValue QgsMeshDatasetGroupStore::datasetValue( const QgsMeshDatasetIndex &index, int valueIndex ) const +{ + QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( index.group() ); + if ( group.first ) + return group.first->datasetValue( QgsMeshDatasetIndex( group.second, index.dataset() ), valueIndex ); + else + return QgsMeshDatasetValue(); +} + +QgsMeshDataBlock QgsMeshDatasetGroupStore::datasetValues( const QgsMeshDatasetIndex &index, int valueIndex, int count ) const +{ + QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( index.group() ); + if ( group.first ) + return group.first->datasetValues( QgsMeshDatasetIndex( group.second, index.dataset() ), valueIndex, count ); + else + return QgsMeshDataBlock(); +} + +QgsMesh3dDataBlock QgsMeshDatasetGroupStore::dataset3dValues( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const +{ + QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( index.group() ); + if ( group.first ) + return group.first->dataset3dValues( QgsMeshDatasetIndex( group.second, index.dataset() ), faceIndex, count ); + else + return QgsMesh3dDataBlock(); +} + +QgsMeshDataBlock QgsMeshDatasetGroupStore::areFacesActive( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const +{ + QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( index.group() ); + if ( group.first ) + return group.first->areFacesActive( QgsMeshDatasetIndex( group.second, index.dataset() ), faceIndex, count ); + else + return QgsMeshDataBlock(); +} + +bool QgsMeshDatasetGroupStore::isFaceActive( const QgsMeshDatasetIndex &index, int faceIndex ) const +{ + QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( index.group() ); + if ( group.first ) + return group.first->isFaceActive( QgsMeshDatasetIndex( group.second, index.dataset() ), faceIndex ); + else + return false; +} + +QgsMeshDatasetIndex QgsMeshDatasetGroupStore::datasetIndexAtTime( + quint64 time, + int groupIndex, QgsMeshDataProviderTemporalCapabilities::MatchingTemporalDatasetMethod method ) const +{ + QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( groupIndex ); + if ( !group.first ) + return QgsMeshDatasetIndex(); + + const QDateTime &referenceTime = mPersistentProvider->temporalCapabilities()->referenceTime(); + + return QgsMeshDatasetIndex( groupIndex, + group.first->datasetIndexAtTime( referenceTime, group.second, time, method ).dataset() ); +} + +quint64 QgsMeshDatasetGroupStore::datasetRelativeTime( const QgsMeshDatasetIndex &index ) const +{ + QgsMeshDatasetGroupStore::DatasetGroup group = datasetGroup( index.group() ); + if ( !group.first || group.second < 0 ) + return INVALID_MESHLAYER_TIME; + + QgsMeshDatasetIndex nativeIndex( group.second, index.dataset() ); + + if ( group.first == mPersistentProvider ) + return mPersistentProvider->temporalCapabilities()->datasetTime( nativeIndex ); + else if ( group.first == mExtraDatasets.get() ) + return mExtraDatasets->datasetRelativeTime( nativeIndex ); + + return INVALID_MESHLAYER_TIME; + +} + +bool QgsMeshDatasetGroupStore::hasTemporalCapabilities() const +{ + return ( mPersistentProvider && mPersistentProvider->temporalCapabilities()->hasTemporalCapabilities() ) || + ( mExtraDatasets && mExtraDatasets->hasTemporalCapabilities() ); +} + +QDomElement QgsMeshDatasetGroupStore::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) +{ + Q_UNUSED( context ); + QDomElement storeElement = doc.createElement( QStringLiteral( "mesh-dataset-groups-store" ) ); + storeElement.appendChild( mDatasetGroupTreeRootItem->writeXml( doc, context ) ); + + QMap < int, DatasetGroup>::const_iterator it = mRegistery.begin(); + while ( it != mRegistery.end() ) + { + QDomElement elemDataset = doc.createElement( QStringLiteral( "mesh-dataset" ) ); + elemDataset.setAttribute( QStringLiteral( "global-index" ), it.key() ); + if ( it.value().first == mPersistentProvider ) + { + elemDataset.setAttribute( QStringLiteral( "source-type" ), QStringLiteral( "persitent-provider" ) ); + elemDataset.setAttribute( QStringLiteral( "source-index" ), it.value().second ); + storeElement.appendChild( elemDataset ); + } + it++; + } + + return storeElement; +} + +void QgsMeshDatasetGroupStore::readXml( const QDomElement &storeElem, const QgsReadWriteContext &context ) +{ + Q_UNUSED( context ); + mRegistery.clear(); + QDomElement datasetElem = storeElem.firstChildElement( "mesh-dataset" ); + while ( !datasetElem.isNull() ) + { + int globalIndex = datasetElem.attribute( QStringLiteral( "global-index" ) ).toInt(); + int sourceIndex = datasetElem.attribute( QStringLiteral( "source-index" ) ).toInt(); + QgsMeshDatasetSourceInterface *source = nullptr; + if ( datasetElem.attribute( QStringLiteral( "source-type" ) ) == QStringLiteral( "persitent-provider" ) ) + source = mPersistentProvider; + + if ( source ) + mRegistery[globalIndex] = DatasetGroup{source, sourceIndex}; + + datasetElem = datasetElem.nextSiblingElement( QStringLiteral( "mesh-dataset" ) ); + } + + QDomElement rootTreeItemElem = storeElem.firstChildElement( QStringLiteral( "mesh-dataset-group-tree-item" ) ); + if ( !rootTreeItemElem.isNull() ) + setDatasetGroupTreeItem( new QgsMeshDatasetGroupTreeItem( rootTreeItemElem, context ) ); + + checkDatasetConsistency( mPersistentProvider ); + removeUnregisteredItemFromTree(); +} + +bool QgsMeshDatasetGroupStore::saveDatasetGroup( QString filePath, int groupIndex, QString driver ) +{ + DatasetGroup group = datasetGroup( groupIndex ); + + bool fail = true; + if ( group.first && group.second >= 0 ) + fail = mPersistentProvider->persistDatasetGroup( filePath, driver, group.first, group.second ); + + if ( !fail ) + { + eraseDatasetGroup( group ); + group.first = mPersistentProvider; + group.second = mPersistentProvider->datasetGroupCount() - 1; + mRegistery[groupIndex] = group; + //update the item type + if ( mDatasetGroupTreeRootItem ) + { + QgsMeshDatasetGroupTreeItem *item = mDatasetGroupTreeRootItem->childFromDatasetGroupIndex( groupIndex ); + if ( item ) + item->setStorageType( QgsMeshDatasetGroupTreeItem::File ); + } + } + + return fail; +} + +void QgsMeshDatasetGroupStore::onPersistentDatasetAdded( int count ) +{ + Q_ASSERT( mPersistentProvider ); + + int providerTotalCount = mPersistentProvider->datasetGroupCount(); + int providerBeginIndex = mPersistentProvider->datasetGroupCount() - count; + QList groupIndexes; + for ( int i = providerBeginIndex; i < providerTotalCount; ++i ) + groupIndexes.append( registerDatasetGroup( DatasetGroup{mPersistentProvider, i} ) ); + + createDatasetGroupTreeItems( groupIndexes ); + emit datasetGroupsAdded( groupIndexes ); +} + +void QgsMeshDatasetGroupStore::removePersistentProvider() +{ + if ( !mPersistentProvider ) + return; + + disconnect( mPersistentProvider, &QgsMeshDataProvider::datasetGroupsAdded, this, &QgsMeshDatasetGroupStore::onPersistentDatasetAdded ); + + QMap < int, DatasetGroup>::iterator it = mRegistery.begin(); + while ( it != mRegistery.end() ) + { + if ( it.value().first == mPersistentProvider ) + it = mRegistery.erase( it ); + else + it++; + } + + mPersistentProvider = nullptr; +} + +int QgsMeshDatasetGroupStore::newIndex() +{ + int index = 0; + QMap < int, DatasetGroup>::iterator it = mRegistery.begin(); + while ( it != mRegistery.end() ) + { + if ( index <= it.key() ) + index = it.key() + 1; + it++; + } + return index; +} + +int QgsMeshDatasetGroupStore::registerDatasetGroup( const QgsMeshDatasetGroupStore::DatasetGroup &group ) +{ + int groupIndex = newIndex(); + mRegistery[newIndex()] = group; + return groupIndex; +} + +void QgsMeshDatasetGroupStore::eraseDatasetGroup( const QgsMeshDatasetGroupStore::DatasetGroup &group ) +{ + if ( group.first == mPersistentProvider ) + return; //removing persistent dataset group from the store is not allowed + else if ( group.first == mExtraDatasets.get() ) + eraseExtraDataset( group.second ); +} + +void QgsMeshDatasetGroupStore::eraseExtraDataset( int indexInExtraStore ) +{ + mExtraDatasets->removeDatasetGroup( indexInExtraStore ); + + //search dataset with index greater than indexInExtraStore and decrement it + QMap < int, DatasetGroup>::iterator it = mRegistery.begin(); + while ( it != mRegistery.end() ) + { + int localIndex = it.value().second; + if ( it.value().first == mExtraDatasets.get() && localIndex > indexInExtraStore ) + it->second = localIndex - 1; + it++; + } +} + +int QgsMeshDatasetGroupStore::nativeIndexToGroupIndex( QgsMeshDatasetSourceInterface *source, int nativeIndex ) +{ + QMap < int, DatasetGroup>::const_iterator it = mRegistery.begin(); + while ( it != mRegistery.end() ) + { + if ( it.value() == DatasetGroup{source, nativeIndex} ) + return it.key(); + it++; + } + return -1; +} + +void QgsMeshDatasetGroupStore::checkDatasetConsistency( QgsMeshDatasetSourceInterface *source ) +{ + // check if datasets of source are present, if not, add them + QList indexes; + for ( int i = 0; i < source->datasetGroupCount(); ++i ) + if ( nativeIndexToGroupIndex( source, i ) == -1 ) + indexes.append( registerDatasetGroup( DatasetGroup{source, i} ) ); + + if ( !indexes.isEmpty() ) + createDatasetGroupTreeItems( indexes ); +} + +void QgsMeshDatasetGroupStore::removeUnregisteredItemFromTree() +{ + QList itemsToCheck; + QList indexItemToRemove; + for ( int i = 0; i < mDatasetGroupTreeRootItem->childCount(); ++i ) + itemsToCheck.append( mDatasetGroupTreeRootItem->child( i ) ); + + while ( !itemsToCheck.isEmpty() ) + { + QgsMeshDatasetGroupTreeItem *item = itemsToCheck.takeFirst(); + int globalIndex = item->datasetGroupIndex(); + if ( !mRegistery.contains( globalIndex ) ) + indexItemToRemove.append( globalIndex ); + for ( int i = 0; i < item->childCount(); ++i ) + itemsToCheck.append( item->child( i ) ); + } + + for ( int i : indexItemToRemove ) + { + QgsMeshDatasetGroupTreeItem *item = mDatasetGroupTreeRootItem->childFromDatasetGroupIndex( i ); + if ( item ) + item->parentItem()->removeChild( item ); + } +} + +void QgsMeshDatasetGroupStore::unregisterGroupNotPresentInTree() +{ + if ( !mDatasetGroupTreeRootItem ) + { + mRegistery.clear(); + return; + } + + QMap < int, DatasetGroup>::iterator it = mRegistery.begin(); + while ( it != mRegistery.end() ) + { + DatasetGroup datasetGroup = it.value(); + int globalIndex = it.key(); + if ( ! mDatasetGroupTreeRootItem->childFromDatasetGroupIndex( globalIndex ) // Not in the tree item + && datasetGroup.first != mPersistentProvider ) // and not persistent + { + it = mRegistery.erase( it ); //remove from registery + eraseDatasetGroup( datasetGroup ); //remove from where the dataset group is stored + } + else + it++; + } +} + +void QgsMeshDatasetGroupStore::createDatasetGroupTreeItems( const QList &indexes ) +{ + QMap mNameToItem; + + for ( int i = 0; i < indexes.count(); ++i ) + { + int groupIndex = indexes.at( i ); + const QgsMeshDatasetGroupMetadata meta = datasetGroupMetadata( groupIndex ); + const QString name = meta.name(); + const QStringList subdatasets = name.split( '/' ); + + QString displayName = name; + QgsMeshDatasetGroupTreeItem *parent = mDatasetGroupTreeRootItem.get(); + + if ( subdatasets.size() == 2 ) + { + auto it = mNameToItem.find( subdatasets[0] ); + if ( it == mNameToItem.end() ) + QgsDebugMsg( QStringLiteral( "Unable to find parent group for %1." ).arg( name ) ); + else + { + displayName = subdatasets[1]; + parent = it.value(); + } + } + else if ( subdatasets.size() != 1 ) + QgsDebugMsg( QStringLiteral( "Ignoring too deep child group name %1." ).arg( name ) ); + + QgsMeshDatasetGroupTreeItem *item = new QgsMeshDatasetGroupTreeItem( displayName, name, meta.isVector(), groupIndex ); + parent->appendChild( item ); + if ( mNameToItem.contains( name ) ) + QgsDebugMsg( QStringLiteral( "Group %1 is not unique" ).arg( displayName ) ); + mNameToItem[name] = item; + + DatasetGroup group = datasetGroup( groupIndex ); + QgsMeshDatasetGroupTreeItem::StorageType type; + if ( group.first == mPersistentProvider ) + type = QgsMeshDatasetGroupTreeItem::File; + else if ( group.first == mExtraDatasets.get() ) + type = QgsMeshDatasetGroupTreeItem::Memory; + else + type = QgsMeshDatasetGroupTreeItem::None; + + item->setStorageType( type ); + + } +} + +void QgsMeshExtraDatasetStore::addDatasetGroup( QgsMeshDatasetGroup *datasetGroup ) +{ + int groupIndex = mGroups.size(); + mGroups.push_back( std::unique_ptr( datasetGroup ) ); + + if ( datasetGroup->datasetCount() > 1 ) + { + mTemporalCapabilities->setHasTemporalCapabilities( true ); + for ( int i = 0; i < datasetGroup->datasetCount(); ++i ) + mTemporalCapabilities->addDatasetTime( groupIndex, datasetGroup->dataset( i )->metaData().time() ); + } +} + +void QgsMeshExtraDatasetStore::removeDatasetGroup( int index ) +{ + if ( index < datasetGroupCount() ) + mGroups.erase( mGroups.begin() + index ); + + //update temporal capabilitie + mTemporalCapabilities->clear(); + bool hasTemporal = false; + for ( size_t g = 0; g < mGroups.size(); ++g ) + { + const QgsMeshDatasetGroup *group = mGroups[g].get(); + hasTemporal |= group->datasetCount() > 1; + for ( int i = 0; i < group->datasetCount(); ++i ) + mTemporalCapabilities->addDatasetTime( index, group->dataset( i )->metaData().time() ); + } + + + + mTemporalCapabilities->setHasTemporalCapabilities( hasTemporal ); +} + +bool QgsMeshExtraDatasetStore::hasTemporalCapabilities() const +{ + return mTemporalCapabilities->hasTemporalCapabilities(); +} + +quint64 QgsMeshExtraDatasetStore::datasetRelativeTime( QgsMeshDatasetIndex index ) +{ + return mTemporalCapabilities->datasetTime( index ); +} + +bool QgsMeshExtraDatasetStore::addDataset( const QString &uri ) +{ + Q_UNUSED( uri ); + return false; +} + +QStringList QgsMeshExtraDatasetStore::extraDatasets() const +{ + return QStringList(); +} + +int QgsMeshExtraDatasetStore::datasetGroupCount() const +{ + return mGroups.size(); +} + +int QgsMeshExtraDatasetStore::datasetCount( int groupIndex ) const +{ + if ( groupIndex >= 0 && groupIndex < datasetGroupCount() ) + return mGroups.at( groupIndex )->datasetCount(); + else + return 0; +} + +QgsMeshDatasetGroupMetadata QgsMeshExtraDatasetStore::datasetGroupMetadata( int groupIndex ) const +{ + if ( groupIndex >= 0 && groupIndex < datasetGroupCount() ) + return mGroups.at( groupIndex )->groupMetadata(); + else + return QgsMeshDatasetGroupMetadata(); +} + +QgsMeshDatasetMetadata QgsMeshExtraDatasetStore::datasetMetadata( QgsMeshDatasetIndex index ) const +{ + int groupIndex = index.group(); + if ( index.isValid() && groupIndex < datasetGroupCount() ) + { + int datasetIndex = index.dataset(); + const QgsMeshDatasetGroup *group = mGroups.at( groupIndex ).get(); + if ( datasetIndex < group->datasetCount() ) + return group->dataset( datasetIndex )->metaData(); + } + return QgsMeshDatasetMetadata(); +} + +QgsMeshDatasetValue QgsMeshExtraDatasetStore::datasetValue( QgsMeshDatasetIndex index, int valueIndex ) const +{ + int groupIndex = index.group(); + if ( index.isValid() && groupIndex < datasetGroupCount() ) + { + const QgsMeshDatasetGroup *group = mGroups.at( groupIndex ).get(); + int datasetIndex = index.dataset(); + if ( datasetIndex < group->datasetCount() ) + return group->dataset( datasetIndex )->datasetValue( valueIndex ); + } + + return QgsMeshDatasetValue(); +} + +QgsMeshDataBlock QgsMeshExtraDatasetStore::datasetValues( QgsMeshDatasetIndex index, int valueIndex, int count ) const +{ + int groupIndex = index.group(); + if ( index.isValid() && groupIndex < datasetGroupCount() ) + { + const QgsMeshDatasetGroup *group = mGroups.at( groupIndex ).get(); + int datasetIndex = index.dataset(); + if ( datasetIndex < group->datasetCount() ) + return group->dataset( datasetIndex )->datasetValues( group->isScalar(), valueIndex, count ); + } + + return QgsMeshDataBlock(); +} + +QgsMesh3dDataBlock QgsMeshExtraDatasetStore::dataset3dValues( QgsMeshDatasetIndex index, int faceIndex, int count ) const +{ + // Not supported for now + Q_UNUSED( index ) + Q_UNUSED( faceIndex ) + Q_UNUSED( count ) + return QgsMesh3dDataBlock(); +} + +bool QgsMeshExtraDatasetStore::isFaceActive( QgsMeshDatasetIndex index, int faceIndex ) const +{ + int groupIndex = index.group(); + if ( index.isValid() && groupIndex < datasetGroupCount() ) + { + const QgsMeshDatasetGroup *group = mGroups.at( groupIndex ).get(); + int datasetIndex = index.dataset(); + if ( datasetIndex < group->datasetCount() ) + return group->dataset( datasetIndex )->isActive( faceIndex ); + } + + return false; +} + +QgsMeshDataBlock QgsMeshExtraDatasetStore::areFacesActive( QgsMeshDatasetIndex index, int faceIndex, int count ) const +{ + int groupIndex = index.group(); + if ( index.isValid() && groupIndex < datasetGroupCount() ) + { + const QgsMeshDatasetGroup *group = mGroups.at( groupIndex ).get(); + int datasetIndex = index.dataset(); + if ( datasetIndex < group->datasetCount() ) + return group->dataset( datasetIndex )->areFacesActive( faceIndex, count ); + } + return QgsMeshDataBlock(); +} + +bool QgsMeshExtraDatasetStore::persistDatasetGroup( const QString &outputFilePath, + const QString &outputDriver, + const QgsMeshDatasetGroupMetadata &meta, + const QVector &datasetValues, + const QVector &datasetActive, + const QVector × ) +{ + Q_UNUSED( outputFilePath ) + Q_UNUSED( outputDriver ) + Q_UNUSED( meta ) + Q_UNUSED( datasetValues ) + Q_UNUSED( datasetActive ) + Q_UNUSED( times ) + return true; // not implemented/supported +} + +bool QgsMeshExtraDatasetStore::persistDatasetGroup( const QString &outputFilePath, + const QString &outputDriver, + QgsMeshDatasetSourceInterface *source, + int datasetGroupIndex ) +{ + Q_UNUSED( outputFilePath ) + Q_UNUSED( outputDriver ) + Q_UNUSED( source ) + Q_UNUSED( datasetGroupIndex ) + return true; // not implemented/supported +} diff --git a/src/core/mesh/qgsmeshdatasetgroupstore.h b/src/core/mesh/qgsmeshdatasetgroupstore.h new file mode 100644 index 000000000000..91679567f2a2 --- /dev/null +++ b/src/core/mesh/qgsmeshdatasetgroupstore.h @@ -0,0 +1,226 @@ +/*************************************************************************** + qgsmeshdatasetgroupstore.h + --------------------- + begin : June 2020 + copyright : (C) 2020 by Vincent Cloarec + email : vcloarec 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. * + * * + ***************************************************************************/ + +#ifndef QGSMESHDATASETGROUPSTORE_H +#define QGSMESHDATASETGROUPSTORE_H + +#define SIP_NO_FILE + +#include "qgsmeshdataprovider.h" +#include "qgsmeshdataset.h" + +/** + * \ingroup core + * + * Class that can be used to store and access extra dataset group, like memory dataset (temporary) + * Derived from QgsMeshDatasetSourceInterface, this class has same methods as QgsMeshDataProvider to access to the datasets. + * + * \since QGIS 3.16 + */ +class QgsMeshExtraDatasetStore: public QgsMeshDatasetSourceInterface +{ + public: + + //! Adds a dataset group + void addDatasetGroup( QgsMeshDatasetGroup *datasetGroup ); + + //! Removes the dataset group with the local \a index + void removeDatasetGroup( int index ); + + //! Returns whether if the dataset groups have temporal capabilities (a least one dataset group with more than one dataset) + bool hasTemporalCapabilities() const; + + //! Returns the relative times of the dataset index with \a index, returned value in miliseconds + quint64 datasetRelativeTime( QgsMeshDatasetIndex index ); + + int datasetGroupCount() const override; + int datasetCount( int groupIndex ) const override; + QgsMeshDatasetGroupMetadata datasetGroupMetadata( int groupIndex ) const override; + QgsMeshDatasetMetadata datasetMetadata( QgsMeshDatasetIndex index ) const override; + QgsMeshDatasetValue datasetValue( QgsMeshDatasetIndex index, int valueIndex ) const override; + QgsMeshDataBlock datasetValues( QgsMeshDatasetIndex index, int valueIndex, int count ) const override; + QgsMesh3dDataBlock dataset3dValues( QgsMeshDatasetIndex index, int faceIndex, int count ) const override; + bool isFaceActive( QgsMeshDatasetIndex index, int faceIndex ) const override; + QgsMeshDataBlock areFacesActive( QgsMeshDatasetIndex index, int faceIndex, int count ) const override; + + //! Not implemented, always returns false + bool addDataset( const QString &uri ) override; + + //! Not implemented, always returns empty list + QStringList extraDatasets() const override; + + //! Not implemented, always returns true + bool persistDatasetGroup( const QString &outputFilePath, + const QString &outputDriver, + const QgsMeshDatasetGroupMetadata &meta, + const QVector &datasetValues, + const QVector &datasetActive, + const QVector × ) override; + + //! Not implemented, always returns true + bool persistDatasetGroup( const QString &outputFilePath, + const QString &outputDriver, + QgsMeshDatasetSourceInterface *source, + int datasetGroupIndex ) override; + + + private: + std::vector> mGroups; +}; + +/** + * \ingroup core + * + * Class used to register and access all the dataset groups related to a mesh layer + * + * The registered dataset group are : + * - the ones from the data provider of the mesh layer + * - extra dataset group that can be added, for example by the mesh calculator + * + * Every dataset group has a unique global index group that can be different from the native index group of the dataset group. + * This storing class has the repsonsability to assign this unique grlobal dataset group index and to link this dataset group index with the dataset group + * + * All dataset values or information needed can be retrieved from a QgsMeshDatasetIndex with the group index corresponding to the global group index. + * The native group index is not exposed and global index can be obtained with datasetGroupIndexes() that returns the list of gloabl index available. + * The dataset index is the same than in the native source (data provider or other dataset source) + * + * This class as also the responsability to handle the dataset group tree item that contain information to display the available dataset (\see QgsMeshDatasetGroupTreeItem) + * + * \since QGIS 3.16 + */ +class QgsMeshDatasetGroupStore: public QObject +{ + Q_OBJECT + + //! Contains a pointer to the dataset source inerface and the index on this dataset groups container + typedef QPair DatasetGroup; + + public: + //! Constructor + QgsMeshDatasetGroupStore(); + + //! Sets the persistent mesh data provider + void setPersistentProvider( QgsMeshDataProvider *provider ); + + //! Adds persistent datasets from a file with \a path + bool addPersistentDatasets( const QString &path ); + + /** + * Adds a extra dataset \a group, take ownership + * + * \note as QgsMeshDatasetGroup doesn't support reference time, + * the dataset group is supposed to have the same reference time than the pesristent provider + */ + bool addDatasetGroup( QgsMeshDatasetGroup *group ); + + //! Saves on a file with \a filePath the dataset groups index with \a groupIndex with the specified \a driver + bool saveDatasetGroup( QString filePath, int groupIndex, QString driver ); + + //! Resets to default state the dataset groups tree item + void resetDatasetGroupTreeItem(); + + //! Returns a pointer to the root of the dataset groups tree item + QgsMeshDatasetGroupTreeItem *datasetGroupTreeItem() const; + + //! Sets the root of the dataset groups tree item, doesn't take onwnershib but clone the root item + void setDatasetGroupTreeItem( QgsMeshDatasetGroupTreeItem *rootItem ); + + //! Returns a list of all group indexes + QList datasetGroupIndexes() const; + + //! Returns the count of dataset groups + int datasetGroupCount() const; + + //! Returns the count of extra dataset groups + int extraDatasetGroupCount() const; + + //! Returns the total count of dataset group in the store + int datasetCount( int groupIndex ) const; + + //! Returns the metadata of the dataset group with global \a index + QgsMeshDatasetGroupMetadata datasetGroupMetadata( const QgsMeshDatasetIndex &index ) const; + + //! Returns the metadata of the dataset with global \a index + QgsMeshDatasetMetadata datasetMetadata( const QgsMeshDatasetIndex &index ) const; + + //! Returns the value of the dataset with global \a index and \a valueIndex + QgsMeshDatasetValue datasetValue( const QgsMeshDatasetIndex &index, int valueIndex ) const; + + //! Returns \a count values of the dataset with global \a index and from \a valueIndex + QgsMeshDataBlock datasetValues( const QgsMeshDatasetIndex &index, int valueIndex, int count ) const; + + //! Returns \a count 3D values of the dataset with global \a index and from \a valueIndex + QgsMesh3dDataBlock dataset3dValues( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const; + + //! Returns whether faces are active for particular dataset + QgsMeshDataBlock areFacesActive( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const; + + //! Returns whether face is active for particular dataset + bool isFaceActive( const QgsMeshDatasetIndex &index, int faceIndex ) const; + + //! Returns the global dataset index of the dataset int the dataset group with \a groupIndex, corresponding to the relative \a time and the check \a method + QgsMeshDatasetIndex datasetIndexAtTime( quint64 time, + int groupIndex, + QgsMeshDataProviderTemporalCapabilities::MatchingTemporalDatasetMethod method ) const; + + //! Returns the relative time of the dataset from the persistent provider reference time + quint64 datasetRelativeTime( const QgsMeshDatasetIndex &index ) const; + + //! Returns wether at lea&st one of stored dataset group is temporal + bool hasTemporalCapabilities() const; + + //! Writes the store's information in a DOM document + QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ); + + //! Reads the store's information from a DOM document + void readXml( const QDomElement &storeElem, const QgsReadWriteContext &context ); + + signals: + //! emited after dataset groups are added + void datasetGroupsAdded( QList indexes ); + + private slots: + void onPersistentDatasetAdded( int count ); + + private: + + QgsMeshDataProvider *mPersistentProvider = nullptr; + std::unique_ptr mExtraDatasets; + QMap < int, DatasetGroup> mRegistery; + std::unique_ptr mDatasetGroupTreeRootItem; + + void removePersistentProvider(); + + DatasetGroup datasetGroup( int index ) const; + int newIndex(); + + int registerDatasetGroup( const DatasetGroup &group ); + int nativeIndexToGroupIndex( QgsMeshDatasetSourceInterface *source, int providerIndex ); + void createDatasetGroupTreeItems( const QList &indexes ); + + //! Erases from the where this is store, not from the store (registry and tree item), for peristent dataset group, do nothing + void eraseDatasetGroup( const DatasetGroup &group ); + + //! Erases from the extra store but not from the main store (e.g. from egistry and from tree item)) + void eraseExtraDataset( int indexInExtraStore ); + + void checkDatasetConsistency( QgsMeshDatasetSourceInterface *source ); + void removeUnregisteredItemFromTree(); + void unregisterGroupNotPresentInTree(); +}; + +#endif // QGSMESHDATASETGROUPSTORE_H diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index 9ee4c8ab11c5..0798cd68711d 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -24,6 +24,7 @@ #include "qgslogger.h" #include "qgsmaplayerlegend.h" #include "qgsmeshdataprovider.h" +#include "qgsmeshdatasetgroupstore.h" #include "qgsmeshlayer.h" #include "qgsmeshlayerrenderer.h" #include "qgsmeshlayertemporalproperties.h" @@ -41,8 +42,9 @@ QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath, const QString &providerKey, const QgsMeshLayer::LayerOptions &options ) : QgsMapLayer( QgsMapLayerType::MeshLayer, baseName, meshLayerPath ), - mTemporalProperties( new QgsMeshLayerTemporalProperties( this ) ), - mDatasetGroupTreeRootItem( new QgsMeshDatasetGroupTreeItem ) + mDatasetGroupStore( new QgsMeshDatasetGroupStore ), + mTemporalProperties( new QgsMeshLayerTemporalProperties( this ) ) + { mShouldValidateCrs = !options.skipCrsValidation; @@ -58,7 +60,7 @@ QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath, if ( ok ) { setLegend( QgsMapLayerLegend::defaultMeshLegend( this ) ); - setDefaultRendererSettings(); + setDefaultRendererSettings( mDatasetGroupStore->datasetGroupIndexes() ); if ( mDataProvider ) { @@ -66,18 +68,20 @@ QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath, resetDatasetGroupTreeItem(); } } + + connect( mDatasetGroupStore.get(), &QgsMeshDatasetGroupStore::datasetGroupsAdded, this, &QgsMeshLayer::onDatasetGroupsAdded ); } -void QgsMeshLayer::setDefaultRendererSettings() +void QgsMeshLayer::setDefaultRendererSettings( const QList &groupIndexes ) { QgsMeshRendererMeshSettings meshSettings; - if ( mDataProvider && mDataProvider->datasetGroupCount() > 0 ) + if ( groupIndexes.count() > 0 ) { // Show data from the first dataset group mRendererSettings.setActiveScalarDatasetGroup( 0 ); // If the first dataset group has nan min/max, display the mesh to avoid nothing displayed - QgsMeshDatasetGroupMetadata meta = mDataProvider->datasetGroupMetadata( 0 ); + const QgsMeshDatasetGroupMetadata &meta = datasetGroupMetadata( 0 ); if ( meta.maximum() == std::numeric_limits::quiet_NaN() && meta.minimum() == std::numeric_limits::quiet_NaN() ) meshSettings.setEnabled( true ); @@ -86,15 +90,14 @@ void QgsMeshLayer::setDefaultRendererSettings() { // show at least the mesh by default meshSettings.setEnabled( true ); + return; } mRendererSettings.setNativeMeshSettings( meshSettings ); // Sets default resample method for scalar dataset - if ( !mDataProvider ) - return; - for ( int i = 0; i < mDataProvider->datasetGroupCount(); ++i ) + for ( int i : groupIndexes ) { - QgsMeshDatasetGroupMetadata meta = mDataProvider->datasetGroupMetadata( i ); + QgsMeshDatasetGroupMetadata meta = datasetGroupMetadata( i ); QgsMeshRendererScalarSettings scalarSettings = mRendererSettings.scalarSettings( i ); switch ( meta.dataType() ) { @@ -186,8 +189,7 @@ QString QgsMeshLayer::providerType() const bool QgsMeshLayer::addDatasets( const QString &path, const QDateTime &defaultReferenceTime ) { bool isTemporalBefore = dataProvider()->temporalCapabilities()->hasTemporalCapabilities(); - bool ok = dataProvider()->addDataset( path ); - if ( ok ) + if ( mDatasetGroupStore->addPersistentDatasets( path ) ) { QgsMeshLayerTemporalProperties *temporalProperties = qobject_cast< QgsMeshLayerTemporalProperties * >( mTemporalProperties ); if ( !isTemporalBefore && dataProvider()->temporalCapabilities()->hasTemporalCapabilities() ) @@ -206,9 +208,25 @@ bool QgsMeshLayer::addDatasets( const QString &path, const QDateTime &defaultRef mTemporalProperties->setIsActive( true ); } emit dataSourceChanged(); + return true; + } + + return false; +} + +bool QgsMeshLayer::addDatasets( QgsMeshDatasetGroup *datasetGroup ) +{ + if ( mDatasetGroupStore->addDatasetGroup( datasetGroup ) ) + { + emit dataChanged(); + return true; } + return false; +} - return ok; +bool QgsMeshLayer::saveDataset( const QString &path, int datasetGroupIndex, QString driver ) +{ + return mDatasetGroupStore->saveDatasetGroup( path, datasetGroupIndex, driver ); } QgsMesh *QgsMeshLayer::nativeMesh() @@ -300,6 +318,61 @@ QString QgsMeshLayer::formatTime( double hours ) return QgsMeshLayerUtils::formatTime( hours, QDateTime(), mTimeSettings ); } +int QgsMeshLayer::datasetGroupCount() const +{ + return mDatasetGroupStore->datasetGroupCount(); +} + +int QgsMeshLayer::extraDatasetGroupCount() const +{ + return mDatasetGroupStore->extraDatasetGroupCount(); +} + +QList QgsMeshLayer::datasetGroupsIndexes() const +{ + return mDatasetGroupStore->datasetGroupIndexes(); +} + +QgsMeshDatasetGroupMetadata QgsMeshLayer::datasetGroupMetadata( const QgsMeshDatasetIndex &index ) const +{ + return mDatasetGroupStore->datasetGroupMetadata( index ); +} + +int QgsMeshLayer::datasetCount( const QgsMeshDatasetIndex &index ) const +{ + return mDatasetGroupStore->datasetCount( index.group() ); +} + +QgsMeshDatasetMetadata QgsMeshLayer::datasetMetadata( const QgsMeshDatasetIndex &index ) const +{ + return mDatasetGroupStore->datasetMetadata( index ); +} + +QgsMeshDatasetValue QgsMeshLayer::datasetValue( const QgsMeshDatasetIndex &index, int valueIndex ) const +{ + return mDatasetGroupStore->datasetValue( index, valueIndex ); +} + +QgsMeshDataBlock QgsMeshLayer::datasetValues( const QgsMeshDatasetIndex &index, int valueIndex, int count ) const +{ + return mDatasetGroupStore->datasetValues( index, valueIndex, count ); +} + +QgsMesh3dDataBlock QgsMeshLayer::dataset3dValues( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const +{ + return mDatasetGroupStore->dataset3dValues( index, faceIndex, count ); +} + +QgsMeshDataBlock QgsMeshLayer::areFacesActive( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const +{ + return mDatasetGroupStore->areFacesActive( index, faceIndex, count ); +} + +bool QgsMeshLayer::isFaceActive( const QgsMeshDatasetIndex &index, int faceIndex ) const +{ + return mDatasetGroupStore->isFaceActive( index, faceIndex ); +} + QgsMeshDatasetValue QgsMeshLayer::datasetValue( const QgsMeshDatasetIndex &index, const QgsPointXY &point, double searchRadius ) const { QgsMeshDatasetValue value; @@ -316,14 +389,14 @@ QgsMeshDatasetValue QgsMeshLayer::datasetValue( const QgsMeshDatasetIndex &index if ( faceIndex >= 0 ) { int nativeFaceIndex = mesh->trianglesToNativeFaces().at( faceIndex ); - const QgsMeshDatasetGroupMetadata::DataType dataType = dataProvider()->datasetGroupMetadata( index ).dataType(); - if ( dataProvider()->isFaceActive( index, nativeFaceIndex ) ) + const QgsMeshDatasetGroupMetadata::DataType dataType = datasetGroupMetadata( index ).dataType(); + if ( isFaceActive( index, nativeFaceIndex ) ) { switch ( dataType ) { case QgsMeshDatasetGroupMetadata::DataOnFaces: { - value = dataProvider()->datasetValue( index, nativeFaceIndex ); + value = datasetValue( index, nativeFaceIndex ); } break; @@ -332,12 +405,12 @@ QgsMeshDatasetValue QgsMeshLayer::datasetValue( const QgsMeshDatasetIndex &index const QgsMeshFace &face = mesh->triangles()[faceIndex]; const int v1 = face[0], v2 = face[1], v3 = face[2]; const QgsPoint p1 = mesh->vertices()[v1], p2 = mesh->vertices()[v2], p3 = mesh->vertices()[v3]; - const QgsMeshDatasetValue val1 = dataProvider()->datasetValue( index, v1 ); - const QgsMeshDatasetValue val2 = dataProvider()->datasetValue( index, v2 ); - const QgsMeshDatasetValue val3 = dataProvider()->datasetValue( index, v3 ); + const QgsMeshDatasetValue val1 = datasetValue( index, v1 ); + const QgsMeshDatasetValue val2 = datasetValue( index, v2 ); + const QgsMeshDatasetValue val3 = datasetValue( index, v3 ); const double x = QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.x(), val2.x(), val3.x(), point ); double y = std::numeric_limits::quiet_NaN(); - bool isVector = dataProvider()->datasetGroupMetadata( index ).isVector(); + bool isVector = datasetGroupMetadata( index ).isVector(); if ( isVector ) y = QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.y(), val2.y(), val3.y(), point ); @@ -350,7 +423,7 @@ QgsMeshDatasetValue QgsMeshLayer::datasetValue( const QgsMeshDatasetIndex &index const QgsMesh3dAveragingMethod *avgMethod = mRendererSettings.averagingMethod(); if ( avgMethod ) { - const QgsMesh3dDataBlock block3d = dataProvider()->dataset3dValues( index, nativeFaceIndex, 1 ); + const QgsMesh3dDataBlock block3d = dataset3dValues( index, nativeFaceIndex, 1 ); const QgsMeshDataBlock block2d = avgMethod->calculate( block3d ); if ( block2d.isValid() ) { @@ -378,14 +451,14 @@ QgsMesh3dDataBlock QgsMeshLayer::dataset3dValue( const QgsMeshDatasetIndex &inde if ( baseTriangularMesh && dataProvider() && dataProvider()->isValid() && index.isValid() ) { - const QgsMeshDatasetGroupMetadata::DataType dataType = dataProvider()->datasetGroupMetadata( index ).dataType(); + const QgsMeshDatasetGroupMetadata::DataType dataType = datasetGroupMetadata( index ).dataType(); if ( dataType == QgsMeshDatasetGroupMetadata::DataOnVolumes ) { int faceIndex = baseTriangularMesh->faceIndexForPoint_v2( point ); if ( faceIndex >= 0 ) { int nativeFaceIndex = baseTriangularMesh->trianglesToNativeFaces().at( faceIndex ); - block3d = dataProvider()->dataset3dValues( index, nativeFaceIndex, 1 ); + block3d = dataset3dValues( index, nativeFaceIndex, 1 ); } } } @@ -400,12 +473,12 @@ QgsMeshDatasetValue QgsMeshLayer::dataset1dValue( const QgsMeshDatasetIndex &ind const QgsTriangularMesh *mesh = triangularMesh(); if ( selectedIndex >= 0 ) { - const QgsMeshDatasetGroupMetadata::DataType dataType = dataProvider()->datasetGroupMetadata( index ).dataType(); + const QgsMeshDatasetGroupMetadata::DataType dataType = datasetGroupMetadata( index ).dataType(); switch ( dataType ) { case QgsMeshDatasetGroupMetadata::DataOnEdges: { - value = dataProvider()->datasetValue( index, selectedIndex ); + value = datasetValue( index, selectedIndex ); } break; @@ -414,8 +487,8 @@ QgsMeshDatasetValue QgsMeshLayer::dataset1dValue( const QgsMeshDatasetIndex &ind const QgsMeshEdge &edge = mesh->edges()[selectedIndex]; const int v1 = edge.first, v2 = edge.second; const QgsPoint p1 = mesh->vertices()[v1], p2 = mesh->vertices()[v2]; - const QgsMeshDatasetValue val1 = dataProvider()->datasetValue( index, v1 ); - const QgsMeshDatasetValue val2 = dataProvider()->datasetValue( index, v2 ); + const QgsMeshDatasetValue val1 = datasetValue( index, v1 ); + const QgsMeshDatasetValue val2 = datasetValue( index, v2 ); double edgeLength = p1.distance( p2 ); double dist1 = p1.distance( projectedPoint.x(), projectedPoint.y() ); value = QgsMeshLayerUtils::interpolateFromVerticesData( dist1 / edgeLength, val1, val2 ); @@ -443,16 +516,7 @@ QgsMeshDatasetIndex QgsMeshLayer::datasetIndexAtTime( const QgsDateTimeRange &ti const QDateTime layerReferenceTime = mTemporalProperties->referenceTime(); qint64 startTime = layerReferenceTime.msecsTo( timeRange.begin() ); - if ( dataProvider() ) - switch ( mTemporalProperties->matchingMethod() ) - { - case QgsMeshDataProviderTemporalCapabilities::FindClosestDatasetBeforeStartRangeTime: - return dataProvider()->temporalCapabilities()->datasetIndexClosestBeforeRelativeTime( datasetGroupIndex, startTime ); - case QgsMeshDataProviderTemporalCapabilities::FindClosestDatasetFromStartRangeTime: - return dataProvider()->temporalCapabilities()->datasetIndexClosestFromRelativeTime( datasetGroupIndex, startTime ); - } - - return QgsMeshDatasetIndex(); + return mDatasetGroupStore->datasetIndexAtTime( startTime, datasetGroupIndex, mTemporalProperties->matchingMethod() ); } void QgsMeshLayer::applyClassificationOnScalarSettings( const QgsMeshDatasetGroupMetadata &meta, QgsMeshRendererScalarSettings &scalarSettings ) const @@ -571,37 +635,24 @@ void QgsMeshLayer::fillNativeMesh() dataProvider()->populateMesh( mNativeMesh.get() ); } -void QgsMeshLayer::onDatasetGroupsAdded( int count ) +void QgsMeshLayer::onDatasetGroupsAdded( const QList &datasetGroupIndexes ) { // assign default style to new dataset groups - int newDatasetGroupCount = mDataProvider->datasetGroupCount(); - for ( int i = newDatasetGroupCount - count; i < newDatasetGroupCount; ++i ) - assignDefaultStyleToDatasetGroup( i ); + for ( int i = 0; i < datasetGroupIndexes.count(); ++i ) + assignDefaultStyleToDatasetGroup( datasetGroupIndexes.at( i ) ); - if ( mDataProvider ) - { - temporalProperties()->setIsActive( mDataProvider->temporalCapabilities()->hasTemporalCapabilities() ); - - QList metadataList; - int totalCount = mDataProvider->datasetGroupCount(); - for ( int i = totalCount - count; i < totalCount; ++i ) - metadataList.append( mDataProvider->datasetGroupMetadata( i ) ); - QgsMeshLayerUtils::createDatasetGroupTreeItems( metadataList, mDatasetGroupTreeRootItem.get(), totalCount - count ); - } + temporalProperties()->setIsActive( mDatasetGroupStore->hasTemporalCapabilities() ); + emit rendererChanged(); } QgsMeshDatasetGroupTreeItem *QgsMeshLayer::datasetGroupTreeRootItem() const { - return mDatasetGroupTreeRootItem.get(); + return mDatasetGroupStore->datasetGroupTreeItem(); } void QgsMeshLayer::setDatasetGroupTreeRootItem( QgsMeshDatasetGroupTreeItem *rootItem ) { - if ( rootItem ) - mDatasetGroupTreeRootItem.reset( rootItem->clone() ); - else - mDatasetGroupTreeRootItem.reset(); - + mDatasetGroupStore->setDatasetGroupTreeItem( rootItem ); updateActiveDatasetGroups(); } @@ -739,11 +790,7 @@ QgsPointXY QgsMeshLayer::snapOnFace( const QgsPointXY &point, double searchRadiu void QgsMeshLayer::resetDatasetGroupTreeItem() { - mDatasetGroupTreeRootItem.reset( new QgsMeshDatasetGroupTreeItem ); - QList metadataList; - for ( int i = 0; i < mDataProvider->datasetGroupCount(); ++i ) - metadataList.append( mDataProvider->datasetGroupMetadata( i ) ); - QgsMeshLayerUtils::createDatasetGroupTreeItems( metadataList, mDatasetGroupTreeRootItem.get(), 0 ); + mDatasetGroupStore->resetDatasetGroupTreeItem(); updateActiveDatasetGroups(); } @@ -762,9 +809,21 @@ QgsInterval QgsMeshLayer::firstValidTimeStep() const return QgsInterval(); } +QgsInterval QgsMeshLayer::datasetRelativeTime( const QgsMeshDatasetIndex &index ) +{ + qint64 time = mDatasetGroupStore->datasetRelativeTime( index ); + + if ( time == INVALID_MESHLAYER_TIME ) + return QgsInterval(); + else + return QgsInterval( time, QgsUnitTypes::TemporalMilliseconds ); +} + void QgsMeshLayer::updateActiveDatasetGroups() { - if ( !mDatasetGroupTreeRootItem ) + QgsMeshDatasetGroupTreeItem *treeItem = mDatasetGroupStore->datasetGroupTreeItem(); + + if ( !mDatasetGroupStore->datasetGroupTreeItem() ) return; QgsMeshRendererSettings settings = rendererSettings(); @@ -772,16 +831,16 @@ void QgsMeshLayer::updateActiveDatasetGroups() int oldActiveVector = settings.activeVectorDatasetGroup(); QgsMeshDatasetGroupTreeItem *activeScalarItem = - mDatasetGroupTreeRootItem->childFromDatasetGroupIndex( oldActiveScalar ); + treeItem->childFromDatasetGroupIndex( oldActiveScalar ); - if ( !activeScalarItem && mDatasetGroupTreeRootItem->childCount() > 0 ) - activeScalarItem = mDatasetGroupTreeRootItem->child( 0 ); + if ( !activeScalarItem && treeItem->childCount() > 0 ) + activeScalarItem = treeItem->child( 0 ); if ( activeScalarItem && !activeScalarItem->isEnabled() ) { - for ( int i = 0; i < mDatasetGroupTreeRootItem->childCount(); ++i ) + for ( int i = 0; i < treeItem->childCount(); ++i ) { - activeScalarItem = mDatasetGroupTreeRootItem->child( i ); + activeScalarItem = treeItem->child( i ); if ( activeScalarItem->isEnabled() ) break; else @@ -795,7 +854,7 @@ void QgsMeshLayer::updateActiveDatasetGroups() settings.setActiveScalarDatasetGroup( -1 ); QgsMeshDatasetGroupTreeItem *activeVectorItem = - mDatasetGroupTreeRootItem->childFromDatasetGroupIndex( oldActiveVector ); + treeItem->childFromDatasetGroupIndex( oldActiveVector ); if ( !( activeVectorItem && activeVectorItem->isEnabled() ) ) settings.setActiveVectorDatasetGroup( -1 ); @@ -885,7 +944,7 @@ static QgsColorRamp *_createDefaultColorRamp() void QgsMeshLayer::assignDefaultStyleToDatasetGroup( int groupIndex ) { - const QgsMeshDatasetGroupMetadata metadata = mDataProvider->datasetGroupMetadata( groupIndex ); + const QgsMeshDatasetGroupMetadata metadata = datasetGroupMetadata( groupIndex ); double groupMin = metadata.minimum(); double groupMax = metadata.maximum(); @@ -1061,13 +1120,12 @@ bool QgsMeshLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &con mDataProvider->setTemporalUnit( static_cast( pkeyNode.toElement().attribute( QStringLiteral( "time-unit" ) ).toInt() ) ); - // read dataset group tree items - QDomElement elemDatasetGroupTree = layer_node.firstChildElement( QStringLiteral( "dataset-groups-tree" ) ); - QDomElement rootItemElement = elemDatasetGroupTree.firstChildElement( QStringLiteral( "mesh-dataset-group_tree-item" ) ); - if ( rootItemElement.isNull() ) + // read dataset group store + QDomElement elemDatasetGroupsStore = layer_node.firstChildElement( QStringLiteral( "mesh-dataset-groups-store" ) ); + if ( elemDatasetGroupsStore.isNull() ) resetDatasetGroupTreeItem(); else - mDatasetGroupTreeRootItem.reset( new QgsMeshDatasetGroupTreeItem( rootItemElement, context ) ); + mDatasetGroupStore->readXml( elemDatasetGroupsStore, context ); QString errorMsg; readSymbology( layer_node, errorMsg, context ); @@ -1128,10 +1186,8 @@ bool QgsMeshLayer::writeXml( QDomNode &layer_node, QDomDocument &document, const elemStaticDataset.setAttribute( QStringLiteral( "vector" ), mStaticVectorDatasetIndex ); layer_node.appendChild( elemStaticDataset ); - // write dataset group tree items - QDomElement elemDatasetTree = document.createElement( QStringLiteral( "dataset-groups-tree" ) ); - elemDatasetTree.appendChild( mDatasetGroupTreeRootItem->writeXml( document, context ) ); - layer_node.appendChild( elemDatasetTree ); + // write dataset group store + layer_node.appendChild( mDatasetGroupStore->writeXml( document, context ) ); // renderer specific settings QString errorMsg; @@ -1198,11 +1254,12 @@ bool QgsMeshLayer::setDataProvider( QString const &provider, const QgsDataProvid mDataSource = mDataSource + QStringLiteral( "&uid=%1" ).arg( QUuid::createUuid().toString() ); } + mDatasetGroupStore->setPersistentProvider( mDataProvider ); + for ( int i = 0; i < mDataProvider->datasetGroupCount(); ++i ) assignDefaultStyleToDatasetGroup( i ); connect( mDataProvider, &QgsMeshDataProvider::dataChanged, this, &QgsMeshLayer::dataChanged ); - connect( mDataProvider, &QgsMeshDataProvider::datasetGroupsAdded, this, &QgsMeshLayer::onDatasetGroupsAdded ); return true; } diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index fe02558dc860..0da43c05441c 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -36,6 +36,9 @@ class QgsRenderContext; struct QgsMesh; class QgsMesh3dAveragingMethod; class QgsMeshLayerTemporalProperties; +class QgsMeshDatasetGroupStore; + +struct QgsMeshMemoryDatasetGroup; /** * \ingroup core @@ -174,7 +177,7 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer QString providerType() const; /** - * Add datasets to the mesh from file with \a path. Use the the time \a defaultReferenceTime as reference time is not provided in the file + * 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 defaultReferenceTime reference time used if not provided in the file @@ -184,6 +187,28 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer */ bool addDatasets( const QString &path, const QDateTime &defaultReferenceTime = QDateTime() ); + /** + * Adds extra datasets to the mesh. Take ownership. + * + * \param datasetGroup the extra dataset group + * \return whether the dataset is effectively added + * + * \since QGIS 3.16 + */ + bool addDatasets( QgsMeshDatasetGroup *datasetGroup )SIP_SKIP; + + /** + * Saves datasets group on file with the specified \a driver + * + * \param path the path of the file + * \param datasetGroupIndex the index of the dataset group + * \param driver the driver to used for saving + * \return false if succeeds + * + * \since QGIS 3.16 + */ + bool saveDataset( const QString &path, int datasetGroupIndex, QString driver ); + /** * Returns native mesh (NULLPTR before rendering or calling to updateMesh) * @@ -269,6 +294,143 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer */ QString formatTime( double hours ); + /** + * Returns the dataset groups count handle by the layer + * + * \since QGIS 3.16 + */ + int datasetGroupCount() const; + + /** + * Returns the extra dataset groups count handle by the layer + * + * \since QGIS 3.16 + */ + int extraDatasetGroupCount() const; + + /** + * Returns the list of indexes of dataset groups count handled by the layer + * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes can be different from the data provider indexes. + * + * \since QGIS 3.16 + */ + QList datasetGroupsIndexes() const; + + /** + * Returns the dataset groups metadata + * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes can be different from the data provider indexes. + * + * \since QGIS 3.16 + */ + QgsMeshDatasetGroupMetadata datasetGroupMetadata( const QgsMeshDatasetIndex &index ) const; + + /** + * Returns the dataset count in the dataset groups + * + * \param index index of the dataset in the group + * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes can be different from the data provider indexes. + * + * \since QGIS 3.16 + */ + int datasetCount( const QgsMeshDatasetIndex &index ) const; + + /** + * Returns the dataset metadata + * + * \param index index of the dataset + * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes can be different from the data provider indexes. + * + * \since QGIS 3.16 + */ + QgsMeshDatasetMetadata datasetMetadata( const QgsMeshDatasetIndex &index ) const; + + /** + * Returns vector/scalar value associated with the index from the dataset + * To read multiple continuous values, use datasetValues() + * + * See QgsMeshDatasetMetadata::isVector() or QgsMeshDataBlock::type() + * to check if the returned value is vector or scalar + * + * Returns invalid value for DataOnVolumes + * + * \param index index of the dataset + * \param valueIndex index of the value + * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes can be different from the data provider indexes. + * + * \since QGIS 3.16 + */ + QgsMeshDatasetValue datasetValue( const QgsMeshDatasetIndex &index, int valueIndex ) const; + + /** + * Returns N vector/scalar values from the index from the dataset + * + * See QgsMeshDatasetMetadata::isVector() or QgsMeshDataBlock::type() + * to check if the returned value is vector or scalar + * + * Returns invalid block for DataOnVolumes. Use QgsMeshLayerUtils::datasetValues() if you + * need block for any type of data type + * + * \param index index of the dataset + * \param valueIndex index of the value + * \param count number of values to return + * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes can be different from the data provider indexes. + * + * \since QGIS 3.16 + */ + QgsMeshDataBlock datasetValues( const QgsMeshDatasetIndex &index, int valueIndex, int count ) const; + + /** + * Returns N vector/scalar values from the face index from the dataset for 3d stacked meshes + * + * See QgsMeshDatasetMetadata::isVector() to check if the returned value is vector or scalar + * + * returns invalid block for DataOnFaces and DataOnVertices. + * + * \param index index of the dataset + * \param valueIndex index of the value + * \param count number of values to return + * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes can be different from the data provider indexes. + * + * \since QGIS 3.16 + */ + QgsMesh3dDataBlock dataset3dValues( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const; + + /** + * Returns N vector/scalar values from the face index from the dataset for 3d stacked meshes + * + * See QgsMeshDatasetMetadata::isVector() to check if the returned value is vector or scalar + * + * returns invalid block for DataOnFaces and DataOnVertices. + */ + bool isFaceActive( const QgsMeshDatasetIndex &index, int faceIndex ) const; + /** + * Returns whether the faces are active for particular dataset + * + * \param index index of the dataset + * \param valueIndex index of the value + * \param count number of values to return + * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes are different from the data provider indexes. + * + * \since QGIS 3.16 + */ + QgsMeshDataBlock areFacesActive( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const; + /** * Interpolates the value on the given point from given dataset. * For 3D datasets, it uses dataset3dValue(), \n @@ -286,6 +448,9 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer * outside the mesh layer, nodata values and in case triangular mesh was not * previously used for rendering * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes are different from the data provider indexes. + * * \since QGIS 3.4 */ QgsMeshDatasetValue datasetValue( const QgsMeshDatasetIndex &index, const QgsPointXY &point, double searchRadius = 0 ) const; @@ -304,6 +469,9 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer * for point outside the mesh layer or in case triangular mesh was not * previously used for rendering or for datasets that do not have type DataOnVolumes * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes are different from the data provider indexes. + * * \since QGIS 3.12 */ QgsMesh3dDataBlock dataset3dValue( const QgsMeshDatasetIndex &index, const QgsPointXY &point ) const; @@ -322,6 +490,9 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer * \returns interpolated value at the projected point. Returns NaN values for values * outside the mesh layer and in case triangular mesh was not previously used for rendering * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes are different from the data provider indexes. + * * \since QGIS 3.14 */ QgsMeshDatasetValue dataset1dValue( const QgsMeshDatasetIndex &index, const QgsPointXY &point, double searchRadius ) const; @@ -336,6 +507,9 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer * * \note the returned dataset index depends on the matching method, see setTemporalMatchingMethod() * + * \note indexes are used to distinguish all the dataset groups handled by the layer (from dataprovider, extra dataset group,...) + * In the layer scope, those indexes are different from the data provider indexes. + * * \since QGIS 3.14 */ QgsMeshDatasetIndex datasetIndexAtTime( const QgsDateTimeRange &timeRange, int datasetGroupIndex ) const; @@ -477,6 +651,13 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer */ QgsInterval firstValidTimeStep() const; + /** + * Returns the relative time (in milliseconds) of the dataset from the reference time of its group + * + * \since QGIS 3.16 + */ + QgsInterval datasetRelativeTime( const QgsMeshDatasetIndex &index ); + public slots: /** @@ -527,10 +708,9 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer QgsMeshLayer( const QgsMeshLayer &rhs ); #endif - private: void fillNativeMesh(); void assignDefaultStyleToDatasetGroup( int groupIndex ); - void setDefaultRendererSettings(); + void setDefaultRendererSettings( const QList &groupIndexes ); void createSimplifiedMeshes(); int levelsOfDetailsIndex( double partOfMeshInView ) const; @@ -540,12 +720,14 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer void applyClassificationOnScalarSettings( const QgsMeshDatasetGroupMetadata &meta, QgsMeshRendererScalarSettings &scalarSettings ) const; private slots: - void onDatasetGroupsAdded( int count ); + void onDatasetGroupsAdded( const QList &datasetGroupIndexes ); private: //! Pointer to data provider derived from the abastract base class QgsMeshDataProvider QgsMeshDataProvider *mDataProvider = nullptr; + std::unique_ptr mDatasetGroupStore; + //! Pointer to native mesh structure, used as cache for rendering std::unique_ptr mNativeMesh; @@ -569,8 +751,6 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer int mStaticScalarDatasetIndex = 0; int mStaticVectorDatasetIndex = 0; - std::unique_ptr mDatasetGroupTreeRootItem; - int closestEdge( const QgsPointXY &point, double searchRadius, QgsPointXY &projectedPoint ) const; //! Returns the exact position in map coordinates of the closest vertex in the search area diff --git a/src/core/mesh/qgsmeshlayerrenderer.cpp b/src/core/mesh/qgsmeshlayerrenderer.cpp index d29a5e7ce791..a4906305e83a 100644 --- a/src/core/mesh/qgsmeshlayerrenderer.cpp +++ b/src/core/mesh/qgsmeshlayerrenderer.cpp @@ -114,7 +114,7 @@ void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer ) datasetIndex = layer->staticScalarDatasetIndex(); // Find out if we can use cache up to date. If yes, use it and return - const int datasetGroupCount = layer->dataProvider()->datasetGroupCount(); + const int datasetGroupCount = layer->datasetGroupCount(); const QgsMeshRendererScalarSettings::DataResamplingMethod method = mRendererSettings.scalarSettings( datasetIndex.group() ).dataResamplingMethod(); QgsMeshLayerRendererCache *cache = layer->rendererCache(); if ( ( cache->mDatasetGroupsCount == datasetGroupCount ) && @@ -134,7 +134,7 @@ void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer ) // Cache is not up-to-date, gather data if ( datasetIndex.isValid() ) { - const QgsMeshDatasetGroupMetadata metadata = layer->dataProvider()->datasetGroupMetadata( datasetIndex.group() ); + const QgsMeshDatasetGroupMetadata metadata = layer->datasetGroupMetadata( datasetIndex.group() ); mScalarDataType = QgsMeshLayerUtils::datasetValuesType( metadata.dataType() ); // populate scalar values @@ -156,7 +156,7 @@ void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer ) } // populate face active flag, always defined on faces - mScalarActiveFaceFlagValues = layer->dataProvider()->areFacesActive( + mScalarActiveFaceFlagValues = layer->areFacesActive( datasetIndex, 0, mNativeMesh.faces.count() ); @@ -189,7 +189,7 @@ void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer ) } - const QgsMeshDatasetMetadata datasetMetadata = layer->dataProvider()->datasetMetadata( datasetIndex ); + const QgsMeshDatasetMetadata datasetMetadata = layer->datasetMetadata( datasetIndex ); mScalarDatasetMinimum = datasetMetadata.minimum(); mScalarDatasetMaximum = datasetMetadata.maximum(); } @@ -216,7 +216,7 @@ void QgsMeshLayerRenderer::copyVectorDatasetValues( QgsMeshLayer *layer ) datasetIndex = layer->staticVectorDatasetIndex(); // Find out if we can use cache up to date. If yes, use it and return - const int datasetGroupCount = layer->dataProvider()->datasetGroupCount(); + const int datasetGroupCount = layer->datasetGroupCount(); QgsMeshLayerRendererCache *cache = layer->rendererCache(); if ( ( cache->mDatasetGroupsCount == datasetGroupCount ) && ( cache->mActiveVectorDatasetIndex == datasetIndex ) && @@ -236,7 +236,7 @@ void QgsMeshLayerRenderer::copyVectorDatasetValues( QgsMeshLayer *layer ) // Cache is not up-to-date, gather data if ( datasetIndex.isValid() ) { - const QgsMeshDatasetGroupMetadata metadata = layer->dataProvider()->datasetGroupMetadata( datasetIndex ); + const QgsMeshDatasetGroupMetadata metadata = layer->datasetGroupMetadata( datasetIndex ); bool isScalar = metadata.isScalar(); if ( isScalar ) @@ -262,7 +262,7 @@ void QgsMeshLayerRenderer::copyVectorDatasetValues( QgsMeshLayer *layer ) else mVectorDatasetValuesMag = QVector( count, std::numeric_limits::quiet_NaN() ); - const QgsMeshDatasetMetadata datasetMetadata = layer->dataProvider()->datasetMetadata( datasetIndex ); + const QgsMeshDatasetMetadata datasetMetadata = layer->datasetMetadata( datasetIndex ); mVectorDatasetMagMinimum = datasetMetadata.minimum(); mVectorDatasetMagMaximum = datasetMetadata.maximum(); } diff --git a/src/core/mesh/qgsmeshlayerutils.cpp b/src/core/mesh/qgsmeshlayerutils.cpp index 60ac82076a5e..fed9d2d8d747 100644 --- a/src/core/mesh/qgsmeshlayerutils.cpp +++ b/src/core/mesh/qgsmeshlayerutils.cpp @@ -68,17 +68,17 @@ QgsMeshDataBlock QgsMeshLayerUtils::datasetValues( if ( !meshLayer ) return block; - const QgsMeshDataProvider *provider = meshLayer->dataProvider(); - if ( !provider ) + + if ( !meshLayer->datasetCount( index ) ) return block; if ( !index.isValid() ) return block; - const QgsMeshDatasetGroupMetadata meta = meshLayer->dataProvider()->datasetGroupMetadata( index.group() ); + const QgsMeshDatasetGroupMetadata meta = meshLayer->datasetGroupMetadata( index.group() ); if ( meta.dataType() != QgsMeshDatasetGroupMetadata::DataType::DataOnVolumes ) { - block = provider->datasetValues( index, valueIndex, count ); + block = meshLayer->datasetValues( index, valueIndex, count ); if ( block.isValid() ) return block; } @@ -88,7 +88,7 @@ QgsMeshDataBlock QgsMeshLayerUtils::datasetValues( if ( !averagingMethod ) return block; - QgsMesh3dDataBlock block3d = provider->dataset3dValues( index, valueIndex, count ); + QgsMesh3dDataBlock block3d = meshLayer->dataset3dValues( index, valueIndex, count ); if ( !block3d.isValid() ) return block; @@ -115,7 +115,7 @@ QVector QgsMeshLayerUtils::griddedVectorValues( const QgsMeshLayer *m if ( !triangularMesh || !nativeMesh ) return vectors; - QgsMeshDatasetGroupMetadata meta = meshLayer->dataProvider()->datasetGroupMetadata( index ); + QgsMeshDatasetGroupMetadata meta = meshLayer->datasetGroupMetadata( index ); if ( !meta.isVector() ) return vectors; @@ -124,7 +124,7 @@ QVector QgsMeshLayerUtils::griddedVectorValues( const QgsMeshLayer *m int datacount = vectorDataOnVertices ? nativeMesh->vertices.count() : nativeMesh->faces.count(); const QgsMeshDataBlock vals = QgsMeshLayerUtils::datasetValues( meshLayer, index, 0, datacount ); - const QgsMeshDataBlock isFacesActive = meshLayer->dataProvider()->areFacesActive( index, 0, nativeMesh->faceCount() ); + const QgsMeshDataBlock isFacesActive = meshLayer->areFacesActive( index, 0, nativeMesh->faceCount() ); const QgsMeshDatasetGroupMetadata::DataType dataType = meta.dataType(); if ( dataType == QgsMeshDatasetGroupMetadata::DataOnEdges ) @@ -418,7 +418,7 @@ QVector QgsMeshLayerUtils::calculateMagnitudeOnVertices( const QgsMeshLa if ( !triangularMesh || !nativeMesh ) return ret; - const QgsMeshDatasetGroupMetadata metadata = meshLayer->dataProvider()->datasetGroupMetadata( index ); + const QgsMeshDatasetGroupMetadata metadata = meshLayer->datasetGroupMetadata( index ); bool scalarDataOnVertices = metadata.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices; // populate scalar values diff --git a/src/core/mesh/qgsmeshlayerutils.h b/src/core/mesh/qgsmeshlayerutils.h index 768df7830fc1..ffbe3e53c0c2 100644 --- a/src/core/mesh/qgsmeshlayerutils.h +++ b/src/core/mesh/qgsmeshlayerutils.h @@ -282,49 +282,6 @@ class CORE_EXPORT QgsMeshLayerUtils const QgsTriangularMesh &triangularMesh, const QVector &verticalMagnitude, bool isRelative ); - - /** - * Creates dataset group items from dataset group meta data - * \param metadataList a list of dataset group metadata - * \param rootItem the root of the items - * \param startingIndex index of the first created dataset group items - * \since QGIS 3.14 - */ - static void createDatasetGroupTreeItems( const QList &metadataList, - QgsMeshDatasetGroupTreeItem *rootItem, int startingIndex ) - { - QMap mNameToItem; - for ( int i = 0; i < metadataList.count(); ++i ) - { - int groupIndex = startingIndex + i; - const QgsMeshDatasetGroupMetadata meta = metadataList.at( i ); - const QString name = meta.name(); - const QStringList subdatasets = name.split( '/' ); - - QString displayName = name; - QgsMeshDatasetGroupTreeItem *parent = rootItem; - - if ( subdatasets.size() == 2 ) - { - auto it = mNameToItem.find( subdatasets[0] ); - if ( it == mNameToItem.end() ) - QgsDebugMsg( QStringLiteral( "Unable to find parent group for %1." ).arg( name ) ); - else - { - displayName = subdatasets[1]; - parent = it.value(); - } - } - else if ( subdatasets.size() != 1 ) - QgsDebugMsg( QStringLiteral( "Ignoring too deep child group name %1." ).arg( name ) ); - - QgsMeshDatasetGroupTreeItem *item = new QgsMeshDatasetGroupTreeItem( displayName, meta.isVector(), groupIndex ); - parent->appendChild( item ); - if ( mNameToItem.contains( name ) ) - QgsDebugMsg( QStringLiteral( "Group %1 is not unique" ).arg( displayName ) ); - mNameToItem[name] = item; - } - } }; ///@endcond diff --git a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp index c8706f4cd620..510a60b5ae16 100644 --- a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp +++ b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp @@ -20,6 +20,7 @@ #include "qgsmeshdataprovidertemporalcapabilities.h" #include "qgsmeshlayerutils.h" #include "qgstriangularmesh.h" +#include "qgsmeshmemorydataprovider.h" #include #define TEXT_PROVIDER_KEY QStringLiteral( "mesh_memory" ) @@ -186,11 +187,11 @@ bool QgsMeshMemoryDataProvider::splitDatasetSections( const QString &uri, QgsMes if ( !success ) break; std::shared_ptr dataset = std::make_shared(); - success = addDatasetValues( sections[i], dataset, datasetGroup.isScalar ); + success = addDatasetValues( sections[i], dataset, datasetGroup.isScalar() ); if ( success ) - success = checkDatasetValidity( dataset, datasetGroup.type ); + success = checkDatasetValidity( dataset, datasetGroup.dataType() ); if ( success ) - datasetGroup.datasets.push_back( dataset ); + datasetGroup.memoryDatasets.push_back( dataset ); } return success; @@ -207,15 +208,17 @@ bool QgsMeshMemoryDataProvider::setDatasetGroupType( const QString &def, QgsMesh return false; } + QgsMeshDatasetGroupMetadata::DataType type; if ( 0 == QString::compare( types[0].trimmed(), QStringLiteral( "vertex" ), Qt::CaseInsensitive ) ) - datasetGroup.type = QgsMeshDatasetGroupMetadata::DataOnVertices; + type = QgsMeshDatasetGroupMetadata::DataOnVertices; else if ( 0 == QString::compare( types[0].trimmed(), QStringLiteral( "edge" ), Qt::CaseInsensitive ) ) - datasetGroup.type = QgsMeshDatasetGroupMetadata::DataOnEdges; + type = QgsMeshDatasetGroupMetadata::DataOnEdges; else - datasetGroup.type = QgsMeshDatasetGroupMetadata::DataOnFaces; + type = QgsMeshDatasetGroupMetadata::DataOnFaces; - datasetGroup.isScalar = 0 == QString::compare( types[1].trimmed(), QStringLiteral( "scalar" ), Qt::CaseInsensitive ); - datasetGroup.name = types[2].trimmed(); + datasetGroup.setDataType( type ); + datasetGroup.setIsScalar( 0 == QString::compare( types[1].trimmed(), QStringLiteral( "scalar" ), Qt::CaseInsensitive ) ); + datasetGroup.setName( types[2].trimmed() ); return true; } @@ -233,7 +236,7 @@ bool QgsMeshMemoryDataProvider::addDatasetGroupMetadata( const QString &def, Qgs return false; } - datasetGroup.metadata.insert( keyVal.at( 0 ).trimmed(), keyVal.at( 1 ).trimmed() ); + datasetGroup.addExtraMetadata( keyVal.at( 0 ).trimmed(), keyVal.at( 1 ).trimmed() ); } return true; } @@ -352,9 +355,9 @@ void QgsMeshMemoryDataProvider::addGroupToTemporalCapabilities( int groupIndex, if ( group.datasetCount() > 1 ) //non temporal dataset groups (count=1) have no time in the capabilities { - for ( int i = 0; i < group.datasets.count(); ++i ) - if ( group.datasets.at( i ) ) - tempCap->addDatasetTime( groupIndex, group.datasets.at( i )->time ); + for ( int i = 0; i < group.memoryDatasets.count(); ++i ) + if ( group.memoryDatasets.at( i ) ) + tempCap->addDatasetTime( groupIndex, group.memoryDatasets.at( i )->time ); } } @@ -404,13 +407,14 @@ bool QgsMeshMemoryDataProvider::addDataset( const QString &uri ) QStringLiteral( "Mesh Memory Provider" ) ) ); } - calculateMinMaxForDatasetGroup( group ); + group.calculateStatistic(); mDatasetGroups.push_back( group ); addGroupToTemporalCapabilities( mDatasetGroups.count() - 1, group ); if ( valid ) { - mExtraDatasetUris << uri; + if ( !mExtraDatasetUris.contains( uri ) ) + mExtraDatasetUris << uri; temporalCapabilities()->setHasTemporalCapabilities( true ); emit datasetGroupsAdded( 1 ); emit dataChanged(); @@ -432,7 +436,7 @@ int QgsMeshMemoryDataProvider::datasetGroupCount() const int QgsMeshMemoryDataProvider::datasetCount( int groupIndex ) const { if ( ( groupIndex >= 0 ) && ( groupIndex < datasetGroupCount() ) ) - return mDatasetGroups[groupIndex].datasets.count(); + return mDatasetGroups[groupIndex].memoryDatasets.count(); else return 0; } @@ -449,40 +453,7 @@ QgsMeshDatasetGroupMetadata QgsMeshMemoryDataProvider::datasetGroupMetadata( int } } -QgsMeshDatasetGroupMetadata QgsMeshMemoryDatasetGroup::groupMetadata() const -{ - return QgsMeshDatasetGroupMetadata( - name, - isScalar, - type, - minimum, - maximum, - 0, - QDateTime(), - datasetCount() > 1, - metadata - ); -} - -int QgsMeshMemoryDatasetGroup::datasetCount() const -{ - return datasets.size(); -} - -void QgsMeshMemoryDatasetGroup::addDataset( std::shared_ptr dataset ) -{ - datasets.push_back( dataset ); -} - -void QgsMeshMemoryDatasetGroup::clearDatasets() -{ - datasets.clear(); -} -std::shared_ptr QgsMeshMemoryDatasetGroup::constDataset( int index ) const -{ - return datasets[index]; -} QgsMeshDatasetMetadata QgsMeshMemoryDataProvider::datasetMetadata( QgsMeshDatasetIndex index ) const { @@ -492,10 +463,10 @@ QgsMeshDatasetMetadata QgsMeshMemoryDataProvider::datasetMetadata( QgsMeshDatase { const QgsMeshMemoryDatasetGroup &grp = mDatasetGroups.at( index.group() ); QgsMeshDatasetMetadata metadata( - grp.datasets[index.dataset()]->time, - grp.datasets[index.dataset()]->valid, - grp.datasets[index.dataset()]->minimum, - grp.datasets[index.dataset()]->maximum, + grp.memoryDatasets[index.dataset()]->time, + grp.memoryDatasets[index.dataset()]->valid, + grp.memoryDatasets[index.dataset()]->minimum, + grp.memoryDatasets[index.dataset()]->maximum, 0 ); return metadata; @@ -510,9 +481,9 @@ QgsMeshDatasetValue QgsMeshMemoryDataProvider::datasetValue( QgsMeshDatasetIndex { if ( ( index.group() >= 0 ) && ( index.group() < datasetGroupCount() ) && ( index.dataset() >= 0 ) && ( index.dataset() < datasetCount( index.group() ) ) && - ( valueIndex >= 0 ) && ( valueIndex < mDatasetGroups[index.group()].datasets[index.dataset()]->values.count() ) ) + ( valueIndex >= 0 ) && ( valueIndex < mDatasetGroups[index.group()].memoryDatasets[index.dataset()]->values.count() ) ) { - return mDatasetGroups[index.group()].datasets[index.dataset()]->values[valueIndex]; + return mDatasetGroups[index.group()].memoryDatasets[index.dataset()]->values[valueIndex]; } else { @@ -525,10 +496,10 @@ QgsMeshDataBlock QgsMeshMemoryDataProvider::datasetValues( QgsMeshDatasetIndex i if ( ( index.group() >= 0 ) && ( index.group() < datasetGroupCount() ) ) { const QgsMeshMemoryDatasetGroup group = mDatasetGroups[index.group()]; - bool isScalar = group.isScalar; - if ( ( index.dataset() >= 0 ) && ( index.dataset() < group.datasets.size() ) ) + bool isScalar = group.isScalar(); + if ( ( index.dataset() >= 0 ) && ( index.dataset() < group.memoryDatasets.size() ) ) { - return group.datasets[index.dataset()]->datasetValues( isScalar, valueIndex, count ); + return group.memoryDatasets[index.dataset()]->datasetValues( isScalar, valueIndex, count ); } else { @@ -547,35 +518,14 @@ QgsMesh3dDataBlock QgsMeshMemoryDataProvider::dataset3dValues( QgsMeshDatasetInd return QgsMesh3dDataBlock(); } -QgsMeshDataBlock QgsMeshMemoryDataset::datasetValues( bool isScalar, int valueIndex, int count ) const -{ - QgsMeshDataBlock ret( isScalar ? QgsMeshDataBlock::ScalarDouble : QgsMeshDataBlock::Vector2DDouble, count ); - QVector buf( isScalar ? count : 2 * count ); - for ( int i = 0; i < count; ++i ) - { - int idx = valueIndex + i; - if ( ( idx < 0 ) || ( idx >= values.size() ) ) - return ret; - QgsMeshDatasetValue val = values[ valueIndex + i ]; - if ( isScalar ) - buf[i] = val.x(); - else - { - buf[2 * i] = val.x(); - buf[2 * i + 1] = val.y(); - } - } - ret.setValues( buf ); - return ret; -} bool QgsMeshMemoryDataProvider::isFaceActive( QgsMeshDatasetIndex index, int faceIndex ) const { - if ( mDatasetGroups[index.group()].datasets[index.dataset()]->active.isEmpty() ) + if ( mDatasetGroups[index.group()].memoryDatasets[index.dataset()]->active.isEmpty() ) return true; else - return mDatasetGroups[index.group()].datasets[index.dataset()]->active[faceIndex]; + return mDatasetGroups[index.group()].memoryDatasets[index.dataset()]->active[faceIndex]; } QgsMeshDataBlock QgsMeshMemoryDataProvider::areFacesActive( QgsMeshDatasetIndex index, int faceIndex, int count ) const @@ -583,9 +533,9 @@ QgsMeshDataBlock QgsMeshMemoryDataProvider::areFacesActive( QgsMeshDatasetIndex if ( ( index.group() >= 0 ) && ( index.group() < datasetGroupCount() ) ) { const QgsMeshMemoryDatasetGroup group = mDatasetGroups[index.group()]; - if ( ( index.dataset() >= 0 ) && ( index.dataset() < group.datasets.size() ) ) + if ( ( index.dataset() >= 0 ) && ( index.dataset() < group.memoryDatasets.size() ) ) { - return group.datasets[index.dataset()]->areFacesActive( faceIndex, count ); + return group.memoryDatasets[index.dataset()]->areFacesActive( faceIndex, count ); } else { @@ -598,20 +548,14 @@ QgsMeshDataBlock QgsMeshMemoryDataProvider::areFacesActive( QgsMeshDatasetIndex } } -QgsMeshDataBlock QgsMeshMemoryDataset::areFacesActive( int faceIndex, int count ) const -{ - QgsMeshDataBlock ret( QgsMeshDataBlock::ActiveFlagInteger, count ); - if ( active.isEmpty() || - ( faceIndex < 0 ) || - ( faceIndex + count > active.size() ) - ) - ret.setValid( true ); - else - ret.setActive( active ); - return ret; -} -bool QgsMeshMemoryDataProvider::persistDatasetGroup( const QString &outputFilePath, const QString &outputDriver, const QgsMeshDatasetGroupMetadata &meta, const QVector &datasetValues, const QVector &datasetActive, const QVector × ) + +bool QgsMeshMemoryDataProvider::persistDatasetGroup( const QString &outputFilePath, + const QString &outputDriver, + const QgsMeshDatasetGroupMetadata &meta, + const QVector &datasetValues, + const QVector &datasetActive, + const QVector × ) { Q_UNUSED( outputFilePath ) Q_UNUSED( outputDriver ) @@ -622,61 +566,16 @@ bool QgsMeshMemoryDataProvider::persistDatasetGroup( const QString &outputFilePa return true; // not implemented/supported } -void QgsMeshMemoryDataProvider::calculateMinMaxForDatasetGroup( QgsMeshMemoryDatasetGroup &grp ) const +bool QgsMeshMemoryDataProvider::persistDatasetGroup( const QString &outputFilePath, + const QString &outputDriver, + QgsMeshDatasetSourceInterface *source, + int datasetGroupIndex ) { - double min = std::numeric_limits::max(); - double max = std::numeric_limits::min(); - - int count = grp.datasets.size(); - for ( int i = 0; i < count; ++i ) - { - calculateMinMaxForDataset( grp.datasets[i] ); - min = std::min( min, grp.datasets[i]->minimum ); - max = std::max( max, grp.datasets[i]->maximum ); - } - - grp.minimum = min; - grp.maximum = max; -} - -void QgsMeshMemoryDataProvider::calculateMinMaxForDataset( std::shared_ptr &dataset ) const -{ - double min = std::numeric_limits::max(); - double max = std::numeric_limits::min(); - - if ( !dataset->valid ) - { - return; - } - - bool firstIteration = true; - for ( int i = 0; i < dataset->values.size(); ++i ) - { - double v = dataset->values[i].scalar(); - - if ( std::isnan( v ) ) - continue; - if ( firstIteration ) - { - firstIteration = false; - min = v; - max = v; - } - else - { - if ( v < min ) - { - min = v; - } - if ( v > max ) - { - max = v; - } - } - } - - dataset->minimum = min; - dataset->maximum = max; + Q_UNUSED( outputFilePath ) + Q_UNUSED( outputDriver ) + Q_UNUSED( source ) + Q_UNUSED( datasetGroupIndex ) + return true; // not implemented/supported } QgsRectangle QgsMeshMemoryDataProvider::calculateExtent() const @@ -693,18 +592,6 @@ QgsRectangle QgsMeshMemoryDataProvider::calculateExtent() const return rec; } -QgsMeshMemoryDatasetGroup::QgsMeshMemoryDatasetGroup( const QString &nm, QgsMeshDatasetGroupMetadata::DataType dataType ): - name( nm ), type( dataType ) -{ -} - - -QgsMeshMemoryDatasetGroup::QgsMeshMemoryDatasetGroup( const QString &nm ): - name( nm ) -{ -} -QgsMeshMemoryDatasetGroup::QgsMeshMemoryDatasetGroup() = default; -QgsMeshMemoryDataset::QgsMeshMemoryDataset() = default; ///@endcond diff --git a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h index 0207b79f3947..b32c19ea9952 100644 --- a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h +++ b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h @@ -30,40 +30,6 @@ #include "qgsmeshdataprovider.h" #include "qgsrectangle.h" -struct CORE_EXPORT QgsMeshMemoryDataset -{ - QgsMeshMemoryDataset(); - QgsMeshDataBlock datasetValues( bool isScalar, int valueIndex, int count ) const; - QgsMeshDataBlock areFacesActive( int faceIndex, int count ) const; - - QVector values; - QVector active; - double time = -1; - bool valid = false; - double minimum = std::numeric_limits::quiet_NaN(); - double maximum = std::numeric_limits::quiet_NaN(); -}; - -struct CORE_EXPORT QgsMeshMemoryDatasetGroup -{ - QgsMeshMemoryDatasetGroup( const QString &nm, QgsMeshDatasetGroupMetadata::DataType dataType ); - QgsMeshMemoryDatasetGroup( const QString &nm ); - QgsMeshMemoryDatasetGroup(); - QgsMeshDatasetGroupMetadata groupMetadata() const; - int datasetCount() const; - void addDataset( std::shared_ptr dataset ); - void clearDatasets(); - std::shared_ptr constDataset( int index ) const; - - QMap metadata; - QVector> datasets; - QString name; - bool isScalar = true; - QgsMeshDatasetGroupMetadata::DataType type = QgsMeshDatasetGroupMetadata::DataOnVertices; - double minimum = std::numeric_limits::quiet_NaN(); - double maximum = std::numeric_limits::quiet_NaN(); -}; - /** * \ingroup core * Provides data stored in-memory for QgsMeshLayer. Useful for plugins or tests. @@ -180,6 +146,12 @@ class CORE_EXPORT QgsMeshMemoryDataProvider final: public QgsMeshDataProvider const QVector × ) override; + virtual bool persistDatasetGroup( const QString &outputFilePath, + const QString &outputDriver, + QgsMeshDatasetSourceInterface *source, + int datasetGroupIndex + ) override; + //! Returns the memory provider key static QString providerKey(); //! Returns the memory provider description @@ -188,8 +160,6 @@ class CORE_EXPORT QgsMeshMemoryDataProvider final: public QgsMeshDataProvider static QgsMeshMemoryDataProvider *createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &providerOptions ); private: - void calculateMinMaxForDatasetGroup( QgsMeshMemoryDatasetGroup &grp ) const; - void calculateMinMaxForDataset( std::shared_ptr &dataset ) const; QgsRectangle calculateExtent( ) const; bool splitMeshSections( const QString &uri ); diff --git a/src/core/qgsprovidermetadata.cpp b/src/core/qgsprovidermetadata.cpp index c0dcfbcfecad..0c97b1f283c1 100644 --- a/src/core/qgsprovidermetadata.cpp +++ b/src/core/qgsprovidermetadata.cpp @@ -291,8 +291,8 @@ QMap QgsProviderMetadata::connections( bool cached ) QgsMeshDriverMetadata::QgsMeshDriverMetadata() = default; -QgsMeshDriverMetadata::QgsMeshDriverMetadata( const QString &name, const QString &description, const MeshDriverCapabilities &capabilities ) - : mName( name ), mDescription( description ), mCapabilities( capabilities ) +QgsMeshDriverMetadata::QgsMeshDriverMetadata( const QString &name, const QString &description, const MeshDriverCapabilities &capabilities, const QString &writeDatasetOnfileSuffix ) + : mName( name ), mDescription( description ), mCapabilities( capabilities ), mWriteDatasetOnFileSuffix( writeDatasetOnfileSuffix ) { } @@ -310,3 +310,8 @@ QString QgsMeshDriverMetadata::description() const { return mDescription; } + +QString QgsMeshDriverMetadata::writeDatasetOnFileSuffix() const +{ + return mWriteDatasetOnFileSuffix; +} diff --git a/src/core/qgsprovidermetadata.h b/src/core/qgsprovidermetadata.h index 34c68cba0fec..0550a18dc11c 100644 --- a/src/core/qgsprovidermetadata.h +++ b/src/core/qgsprovidermetadata.h @@ -81,7 +81,8 @@ class CORE_EXPORT QgsMeshDriverMetadata */ QgsMeshDriverMetadata( const QString &name, const QString &description, - const MeshDriverCapabilities &capabilities ); + const MeshDriverCapabilities &capabilities, + const QString &writeDatasetOnFileSuffix); /** * Returns the capabilities for this driver. @@ -98,10 +99,16 @@ class CORE_EXPORT QgsMeshDriverMetadata */ QString description() const; - private: + /** + * Returns the suffix used to write datasets on file + */ + QString writeDatasetOnFileSuffix() const; + +private: QString mName; QString mDescription; MeshDriverCapabilities mCapabilities; + QString mWriteDatasetOnFileSuffix; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QgsMeshDriverMetadata::MeshDriverCapabilities ) diff --git a/src/gui/qgsmaptoolidentify.cpp b/src/gui/qgsmaptoolidentify.cpp index a59915ce4317..e209c6efbe30 100644 --- a/src/gui/qgsmaptoolidentify.cpp +++ b/src/gui/qgsmaptoolidentify.cpp @@ -248,7 +248,7 @@ bool QgsMapToolIdentify::identifyMeshLayer( QList *results, QgsMeshLayer *layer, const QgsPointXY &point ) { QgsDebugMsgLevel( "point = " + point.toString(), 4 ); - if ( !layer || !layer->dataProvider() ) + if ( !layer ) return false; const QgsMeshRendererSettings rendererSettings = layer->rendererSettings(); @@ -264,7 +264,7 @@ bool QgsMapToolIdentify::identifyMeshLayer( QListdataProvider()->datasetGroupMetadata( scalarDatasetIndex.group() ).name(); + scalarGroup = layer->datasetGroupMetadata( scalarDatasetIndex.group() ).name(); const QgsMeshDatasetValue scalarValue = layer->datasetValue( scalarDatasetIndex, point, searchRadius ); const double scalar = scalarValue.scalar(); @@ -277,7 +277,7 @@ bool QgsMapToolIdentify::identifyMeshLayer( QListdataProvider()->datasetGroupMetadata( vectorDatasetIndex.group() ).name(); + vectorGroup = layer->datasetGroupMetadata( vectorDatasetIndex.group() ).name(); const QgsMeshDatasetValue vectorValue = layer->datasetValue( vectorDatasetIndex, point, searchRadius ); const double vectorX = vectorValue.x(); diff --git a/src/providers/mdal/qgsmdalprovider.cpp b/src/providers/mdal/qgsmdalprovider.cpp index c65d009ce324..4a9c928bc340 100644 --- a/src/providers/mdal/qgsmdalprovider.cpp +++ b/src/providers/mdal/qgsmdalprovider.cpp @@ -305,16 +305,107 @@ bool QgsMdalProvider::persistDatasetGroup( } MDAL_G_closeEditMode( g ); - if ( MDAL_LastStatus() == 0 ) { - mExtraDatasetUris << outputFilePath; - addGroupToTemporalCapabilities( datasetGroupCount() - 1 ); + QgsMeshDatasetGroupMetadata meta = datasetGroupMetadata( datasetGroupCount() - 1 ); + QString newUri = meta.uri(); + if ( !mExtraDatasetUris.contains( newUri ) ) + mExtraDatasetUris << newUri; emit datasetGroupsAdded( 1 ); emit dataChanged(); + return false; + } + else + return true; +} + +bool QgsMdalProvider::persistDatasetGroup( const QString &outputFilePath, const QString &outputDriver, QgsMeshDatasetSourceInterface *source, int datasetGroupIndex ) +{ + if ( !mMeshH ) + return true; + + QgsMeshDatasetGroupMetadata meta = source->datasetGroupMetadata( datasetGroupIndex ); + int faceValueCount = faceCount(); + int valuesCount = meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ? vertexCount() : faceValueCount; + int datasetCount = source->datasetCount( datasetGroupIndex ); + + if ( outputFilePath.isEmpty() ) + return true; + + MDAL_DriverH driver = MDAL_driverFromName( outputDriver.toStdString().c_str() ); + if ( !driver ) + return true; + + MDAL_DataLocation location = MDAL_DataLocation::DataInvalidLocation; + switch ( meta.dataType() ) + { + case QgsMeshDatasetGroupMetadata::DataOnFaces: + location = MDAL_DataLocation::DataOnFaces; + break; + case QgsMeshDatasetGroupMetadata::DataOnVertices: + location = MDAL_DataLocation::DataOnVertices; + break; + case QgsMeshDatasetGroupMetadata::DataOnEdges: + location = MDAL_DataLocation::DataOnEdges; + break; + case QgsMeshDatasetGroupMetadata::DataOnVolumes: + location = MDAL_DataLocation::DataOnVolumes; + break; } - return false; + MDAL_DatasetGroupH g = MDAL_M_addDatasetGroup( + mMeshH, + meta.name().toStdString().c_str(), + location, + meta.isScalar(), + driver, + outputFilePath.toStdString().c_str() + ); + if ( !g ) + return true; + + const auto extraOptions = meta.extraOptions(); + for ( auto it = extraOptions.cbegin(); it != extraOptions.cend(); ++it ) + { + MDAL_G_setMetadata( g, it.key().toStdString().c_str(), it.value().toStdString().c_str() ); + } + + bool fail = false; + for ( int i = 0; i < datasetCount; ++i ) + { + QgsMeshDatasetIndex index( datasetGroupIndex, i ); + QgsMeshDataBlock values = source->datasetValues( index, 0, valuesCount ); + QgsMeshDataBlock active = source->areFacesActive( index, 0, faceValueCount ); + QgsMeshDatasetMetadata dsm = source->datasetMetadata( index ); + if ( !values.isValid() || !dsm.isValid() ) + { + fail = true; + break; + } + + MDAL_G_addDataset( g, + dsm.time(), + values.values().constData(), + active.active().isEmpty() ? nullptr : active.active().constData() + ); + + } + + if ( fail ) + return true; + + MDAL_G_closeEditMode( g ); + if ( MDAL_LastStatus() == 0 ) + { + QgsMeshDatasetGroupMetadata meta = datasetGroupMetadata( datasetGroupCount() - 1 ); + QString newUri = meta.uri(); + if ( !mExtraDatasetUris.contains( newUri ) ) + mExtraDatasetUris << newUri; + addGroupToTemporalCapabilities( datasetGroupCount() - 1 ); + return false; + } + else + return true; } void QgsMdalProvider::loadData() @@ -343,7 +434,7 @@ void QgsMdalProvider::addGroupToTemporalCapabilities( int indexGroup ) QgsMeshDataProviderTemporalCapabilities *tempCap = temporalCapabilities(); QgsMeshDatasetGroupMetadata dsgMetadata = datasetGroupMetadata( indexGroup ); QDateTime refTime = dsgMetadata.referenceTime(); - refTime.setTimeSpec( Qt::UTC ); //For now provider don't support time zone and return always in local time, force UTC + refTime.setTimeSpec( Qt::UTC ); //For now provider doesn't support time zone and return always in local time, force UTC tempCap->addGroupReferenceDateTime( indexGroup, refTime ); int dsCount = datasetCount( indexGroup ); @@ -517,12 +608,15 @@ bool QgsMdalProvider::addDataset( const QString &uri ) } else { - mExtraDatasetUris << uri; + if ( !mExtraDatasetUris.contains( uri ) ) + mExtraDatasetUris << uri; int datasetCountAfterAdding = datasetGroupCount(); - emit datasetGroupsAdded( datasetCountAfterAdding - datasetCount ); - emit dataChanged(); + int datasetCountAdded = datasetCountAfterAdding - datasetCount; for ( ; datasetCount < datasetCountAfterAdding; datasetCount++ ) addGroupToTemporalCapabilities( datasetCount ); + + emit datasetGroupsAdded( datasetCountAdded ); + emit dataChanged(); return true; // Ok } } @@ -575,6 +669,7 @@ QgsMeshDatasetGroupMetadata QgsMdalProvider::datasetGroupMetadata( int groupInde } QString name = MDAL_G_name( group ); + QString uri = MDAL_G_uri( group ); double min, max; MDAL_G_minimumMaximum( group, &min, &max ); @@ -596,6 +691,7 @@ QgsMeshDatasetGroupMetadata QgsMdalProvider::datasetGroupMetadata( int groupInde QgsMeshDatasetGroupMetadata meta( name, + uri, isScalar, type, min, @@ -834,6 +930,7 @@ QList QgsMdalProviderMetadata::meshDriversMetadata() QString name = MDAL_DR_name( mdalDriver ); QString longName = MDAL_DR_longName( mdalDriver ); + QString writeDatasetSuffix = MDAL_DR_writeDatasetsSuffix( mdalDriver ); QgsMeshDriverMetadata::MeshDriverCapabilities capabilities; bool hasSaveFaceDatasetsCapability = MDAL_DR_writeDatasetsCapability( mdalDriver, MDAL_DataLocation::DataOnFaces ); @@ -845,7 +942,7 @@ QList QgsMdalProviderMetadata::meshDriversMetadata() bool hasSaveEdgeDatasetsCapability = MDAL_DR_writeDatasetsCapability( mdalDriver, MDAL_DataLocation::DataOnEdges ); if ( hasSaveEdgeDatasetsCapability ) capabilities |= QgsMeshDriverMetadata::CanWriteEdgeDatasets; - const QgsMeshDriverMetadata meta( name, longName, capabilities ); + const QgsMeshDriverMetadata meta( name, longName, capabilities, writeDatasetSuffix ); ret.push_back( meta ); } return ret; diff --git a/src/providers/mdal/qgsmdalprovider.h b/src/providers/mdal/qgsmdalprovider.h index bd7611418fce..33326edc9b4b 100644 --- a/src/providers/mdal/qgsmdalprovider.h +++ b/src/providers/mdal/qgsmdalprovider.h @@ -86,6 +86,12 @@ class QgsMdalProvider : public QgsMeshDataProvider const QVector × ) override; + bool persistDatasetGroup( const QString &outputFilePath, + const QString &outputDriver, + QgsMeshDatasetSourceInterface *source, + int datasetGroupIndex + ) override; + /** * Returns file filters for meshes and datasets to be used in Open File Dialogs * \param fileMeshFiltersString file mesh filters diff --git a/src/ui/mesh/qgsmeshcalculatordialogbase.ui b/src/ui/mesh/qgsmeshcalculatordialogbase.ui index 5b3df16fbe56..5c182fe7dd70 100644 --- a/src/ui/mesh/qgsmeshcalculatordialogbase.ui +++ b/src/ui/mesh/qgsmeshcalculatordialogbase.ui @@ -29,14 +29,7 @@ - - - QAbstractItemView::CurrentChanged|QAbstractItemView::DoubleClicked|QAbstractItemView::SelectedClicked - - - QAbstractItemView::SingleSelection - - + @@ -47,36 +40,60 @@ - - + + - Group name + Output Format - - + + - Output dataset + On Memory - - - - - + + - Output format + Group Name - + - + + + + + + + + On File + + + true + + + + + + + Output Dataset + + + + + + + Output File + + + @@ -85,7 +102,7 @@ - Current extent + Current Extent true @@ -98,7 +115,7 @@ - Mask layer + Mask Layer buttonGroup @@ -352,8 +369,6 @@ maskBox extendBox horizontalWidget - mOutputFormatLabel_2 - mOutputGroupNameLineEdit @@ -622,7 +637,6 @@ - mDatasetsListWidget mXMinSpinBox mXMaxSpinBox mYMinSpinBox diff --git a/tests/src/analysis/testqgsmeshcalculator.cpp b/tests/src/analysis/testqgsmeshcalculator.cpp index cde62992e83d..d2e4e449b797 100644 --- a/tests/src/analysis/testqgsmeshcalculator.cpp +++ b/tests/src/analysis/testqgsmeshcalculator.cpp @@ -155,7 +155,7 @@ void TestQgsMeshCalculator::dualOp() QVERIFY( node.calculate( utils, result ) ); QCOMPARE( result.datasetCount(), 1 ); - std::shared_ptr ds = result.datasets[0]; + std::shared_ptr ds = result.memoryDatasets[0]; for ( int i = 0; i < ds->values.size(); ++i ) { double val = ds->values.at( i ).scalar(); @@ -202,7 +202,7 @@ void TestQgsMeshCalculator::singleOp() QVERIFY( node.calculate( utils, result ) ); QCOMPARE( result.datasetCount(), 1 ); - std::shared_ptr ds = result.datasets[0]; + std::shared_ptr ds = result.memoryDatasets[0]; for ( int i = 0; i < ds->values.size(); ++i ) { double val = ds->values.at( i ).scalar(); diff --git a/tests/src/core/testqgsmeshlayer.cpp b/tests/src/core/testqgsmeshlayer.cpp index 2fc2dfccee3d..4ce5d9bc2a13 100644 --- a/tests/src/core/testqgsmeshlayer.cpp +++ b/tests/src/core/testqgsmeshlayer.cpp @@ -87,6 +87,9 @@ class TestQgsMeshLayer : public QObject void test_dataset_value_from_layer(); void test_dataset_group_item_tree_item(); + + void test_memory_dataset_group(); + void test_memory_dataset_group_1d(); }; QString TestQgsMeshLayer::readFile( const QString &fname ) const @@ -115,11 +118,11 @@ void TestQgsMeshLayer::initTestCase() QVERIFY( !mMemoryLayer->dataProvider()->temporalCapabilities()->hasTemporalCapabilities() ); QVERIFY( !mMemoryLayer->temporalProperties()->isActive() ); QCOMPARE( mMemoryLayer->datasetGroupTreeRootItem()->childCount(), 0 ); - mMemoryLayer->dataProvider()->addDataset( readFile( "/quad_and_triangle_bed_elevation.txt" ) ); - mMemoryLayer->dataProvider()->addDataset( readFile( "/quad_and_triangle_vertex_scalar.txt" ) ); - mMemoryLayer->dataProvider()->addDataset( readFile( "/quad_and_triangle_vertex_vector.txt" ) ); - mMemoryLayer->dataProvider()->addDataset( readFile( "/quad_and_triangle_face_scalar.txt" ) ); - mMemoryLayer->dataProvider()->addDataset( readFile( "/quad_and_triangle_face_vector.txt" ) ); + mMemoryLayer->addDatasets( readFile( "/quad_and_triangle_bed_elevation.txt" ) ); + mMemoryLayer->addDatasets( readFile( "/quad_and_triangle_vertex_scalar.txt" ) ); + mMemoryLayer->addDatasets( readFile( "/quad_and_triangle_vertex_vector.txt" ) ); + mMemoryLayer->addDatasets( readFile( "/quad_and_triangle_face_scalar.txt" ) ); + mMemoryLayer->addDatasets( readFile( "/quad_and_triangle_face_vector.txt" ) ); QCOMPARE( mMemoryLayer->dataProvider()->extraDatasets().count(), 5 ); QCOMPARE( mMemoryLayer->datasetGroupTreeRootItem()->childCount(), 5 ); QVERIFY( mMemoryLayer->dataProvider()->temporalCapabilities()->hasTemporalCapabilities() ); @@ -257,25 +260,38 @@ void TestQgsMeshLayer::test_read_1d_mesh() void TestQgsMeshLayer::test_read_1d_bed_elevation_dataset() { - QList dataProviders; - dataProviders.append( mMemory1DLayer->dataProvider() ); - dataProviders.append( mMdal1DLayer->dataProvider() ); + QList layers; + layers.append( mMemory1DLayer ); + layers.append( mMdal1DLayer ); - foreach ( auto dp, dataProviders ) + foreach ( auto ly, layers ) { + const QgsMeshDataProvider *dp = ly->dataProvider(); QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 5, dp->datasetGroupCount() ); + QCOMPARE( 5, ly->datasetGroupCount() ); + QgsMeshDatasetIndex ds( 0, 0 ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QVERIFY( meta.name().contains( "Elevation" ) ); + QVERIFY( meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); + + meta = ly->datasetGroupMetadata( ds ); QVERIFY( meta.name().contains( "Elevation" ) ); QVERIFY( meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), 0 ) ); + QVERIFY( dmeta.isValid() ); + + dmeta = ly->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), 0 ) ); QVERIFY( dmeta.isValid() ); @@ -284,32 +300,48 @@ void TestQgsMeshLayer::test_read_1d_bed_elevation_dataset() QCOMPARE( QgsMeshDatasetValue( 30 ), dp->datasetValue( ds, 1 ) ); QCOMPARE( QgsMeshDatasetValue( 40 ), dp->datasetValue( ds, 2 ) ); QCOMPARE( QgsMeshDatasetValue( 50 ), dp->datasetValue( ds, 3 ) ); + + QCOMPARE( QgsMeshDatasetValue( 20 ), ly->datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 30 ), ly->datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 40 ), ly->datasetValue( ds, 2 ) ); + QCOMPARE( QgsMeshDatasetValue( 50 ), ly->datasetValue( ds, 3 ) ); } } void TestQgsMeshLayer::test_read_1d_vertex_scalar_dataset() { - QList dataProviders; - dataProviders.append( mMemory1DLayer->dataProvider() ); - dataProviders.append( mMdal1DLayer->dataProvider() ); + QList layers; + layers.append( mMemory1DLayer ); + layers.append( mMdal1DLayer ); - foreach ( auto dp, dataProviders ) + foreach ( auto ly, layers ) { + const QgsMeshDataProvider *dp = ly->dataProvider(); QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 5, dp->datasetGroupCount() ); + QCOMPARE( 5, ly->datasetGroupCount() ); for ( int i = 0; i < 2 ; ++i ) { QgsMeshDatasetIndex ds( 1, i ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); QCOMPARE( meta.name(), QString( "VertexScalarDataset" ) ); QVERIFY( meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); + QVERIFY( dmeta.isValid() ); + + meta = ly->datasetGroupMetadata( ds ); + QCOMPARE( meta.name(), QString( "VertexScalarDataset" ) ); + QVERIFY( meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); + + dmeta = ly->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); QVERIFY( dmeta.isValid() ); @@ -318,33 +350,49 @@ void TestQgsMeshLayer::test_read_1d_vertex_scalar_dataset() QCOMPARE( QgsMeshDatasetValue( 2.0 + i ), dp->datasetValue( ds, 1 ) ); QCOMPARE( QgsMeshDatasetValue( 3.0 + i ), dp->datasetValue( ds, 2 ) ); QCOMPARE( QgsMeshDatasetValue( 4.0 + i ), dp->datasetValue( ds, 3 ) ); + + QCOMPARE( QgsMeshDatasetValue( 1.0 + i ), ly->datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 2.0 + i ), ly->datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 3.0 + i ), ly->datasetValue( ds, 2 ) ); + QCOMPARE( QgsMeshDatasetValue( 4.0 + i ), ly->datasetValue( ds, 3 ) ); } } } void TestQgsMeshLayer::test_read_1d_vertex_vector_dataset() { - QList dataProviders; - dataProviders.append( mMemory1DLayer->dataProvider() ); - dataProviders.append( mMdal1DLayer->dataProvider() ); + QList layers; + layers.append( mMemory1DLayer ); + layers.append( mMdal1DLayer ); - foreach ( auto dp, dataProviders ) + foreach ( auto ly, layers ) { + const QgsMeshDataProvider *dp = ly->dataProvider(); QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 5, dp->datasetGroupCount() ); + QCOMPARE( 5, ly->datasetGroupCount() ); for ( int i = 0; i < 2 ; ++i ) { QgsMeshDatasetIndex ds( 2, i ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QCOMPARE( meta.name(), QString( "VertexVectorDataset" ) ); + QVERIFY( !meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); + + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); + QVERIFY( dmeta.isValid() ); + + meta = ly->datasetGroupMetadata( ds ); QCOMPARE( meta.name(), QString( "VertexVectorDataset" ) ); QVERIFY( !meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + dmeta = ly->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); QVERIFY( dmeta.isValid() ); @@ -353,35 +401,53 @@ void TestQgsMeshLayer::test_read_1d_vertex_vector_dataset() QCOMPARE( QgsMeshDatasetValue( 2 + i, 1 + i ), dp->datasetValue( ds, 1 ) ); QCOMPARE( QgsMeshDatasetValue( 3 + i, 2 + i ), dp->datasetValue( ds, 2 ) ); QCOMPARE( QgsMeshDatasetValue( 2 + i, 2 + i ), dp->datasetValue( ds, 3 ) ); + + QCOMPARE( QgsMeshDatasetValue( 1 + i, 1 + i ), ly->datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 2 + i, 1 + i ), ly->datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 3 + i, 2 + i ), ly->datasetValue( ds, 2 ) ); + QCOMPARE( QgsMeshDatasetValue( 2 + i, 2 + i ), ly->datasetValue( ds, 3 ) ); } } } void TestQgsMeshLayer::test_read_1d_edge_scalar_dataset() { - QList dataProviders; - dataProviders.append( mMemory1DLayer->dataProvider() ); - dataProviders.append( mMdal1DLayer->dataProvider() ); + QList layers; + layers.append( mMemory1DLayer ); + layers.append( mMdal1DLayer ); - foreach ( auto dp, dataProviders ) + foreach ( auto ly, layers ) { + const QgsMeshDataProvider *dp = ly->dataProvider(); QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 5, dp->datasetGroupCount() ); + QCOMPARE( 5, ly->datasetGroupCount() ); for ( int i = 0; i < 2 ; ++i ) { QgsMeshDatasetIndex ds( 3, i ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); if ( dp->name() == QStringLiteral( "mesh_memory" ) ) QCOMPARE( meta.extraOptions()["description"], QString( "Edge Scalar Dataset" ) ); QCOMPARE( meta.name(), QString( "EdgeScalarDataset" ) ); QVERIFY( meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnEdges ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); + QVERIFY( dmeta.isValid() ); + + meta = ly->datasetGroupMetadata( ds ); + if ( dp->name() == QStringLiteral( "mesh_memory" ) ) + QCOMPARE( meta.extraOptions()["description"], QString( "Edge Scalar Dataset" ) ); + QCOMPARE( meta.name(), QString( "EdgeScalarDataset" ) ); + QVERIFY( meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnEdges ); + + dmeta = ly->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); QVERIFY( dmeta.isValid() ); @@ -389,6 +455,10 @@ void TestQgsMeshLayer::test_read_1d_edge_scalar_dataset() QCOMPARE( QgsMeshDatasetValue( 1 + i ), dp->datasetValue( ds, 0 ) ); QCOMPARE( QgsMeshDatasetValue( 2 + i ), dp->datasetValue( ds, 1 ) ); QCOMPARE( QgsMeshDatasetValue( 3 + i ), dp->datasetValue( ds, 2 ) ); + + QCOMPARE( QgsMeshDatasetValue( 1 + i ), ly->datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 2 + i ), ly->datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 3 + i ), ly->datasetValue( ds, 2 ) ); } } } @@ -396,29 +466,31 @@ void TestQgsMeshLayer::test_read_1d_edge_scalar_dataset() void TestQgsMeshLayer::test_read_1d_edge_vector_dataset() { - QList dataProviders; - dataProviders.append( mMemory1DLayer->dataProvider() ); - dataProviders.append( mMdal1DLayer->dataProvider() ); + QList layers; + layers.append( mMemory1DLayer ); + layers.append( mMdal1DLayer ); - foreach ( auto dp, dataProviders ) + foreach ( auto ly, layers ) { + const QgsMeshDataProvider *dp = ly->dataProvider(); QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 5, dp->datasetGroupCount() ); + QCOMPARE( 5, ly->datasetGroupCount() ); for ( int i = 0; i < 2 ; ++i ) { QgsMeshDatasetIndex ds( 4, i ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); if ( dp->name() == QStringLiteral( "mesh_memory" ) ) QCOMPARE( meta.extraOptions()["description"], QString( "Edge Vector Dataset" ) ); QCOMPARE( meta.name(), QString( "EdgeVectorDataset" ) ); QVERIFY( !meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnEdges ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); QVERIFY( dmeta.isValid() ); @@ -426,6 +498,22 @@ void TestQgsMeshLayer::test_read_1d_edge_vector_dataset() QCOMPARE( QgsMeshDatasetValue( 1 + i, 1 + i ), dp->datasetValue( ds, 0 ) ); QCOMPARE( QgsMeshDatasetValue( 2 + i, 2 + i ), dp->datasetValue( ds, 1 ) ); QCOMPARE( QgsMeshDatasetValue( 3 + i, 3 + i ), dp->datasetValue( ds, 2 ) ); + + meta = ly->datasetGroupMetadata( ds ); + if ( dp->name() == QStringLiteral( "mesh_memory" ) ) + QCOMPARE( meta.extraOptions()["description"], QString( "Edge Vector Dataset" ) ); + QCOMPARE( meta.name(), QString( "EdgeVectorDataset" ) ); + QVERIFY( !meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnEdges ); + + dmeta = ly->datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); + QVERIFY( dmeta.isValid() ); + + // We have 3 values, since dp->edgeCount() = 3 + QCOMPARE( QgsMeshDatasetValue( 1 + i, 1 + i ), ly->datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 2 + i, 2 + i ), ly->datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 3 + i, 3 + i ), ly->datasetValue( ds, 2 ) ); } } } @@ -471,25 +559,27 @@ void TestQgsMeshLayer::test_read_mesh() void TestQgsMeshLayer::test_read_bed_elevation_dataset() { - QList dataProviders; - dataProviders.append( mMemoryLayer->dataProvider() ); - dataProviders.append( mMdalLayer->dataProvider() ); + QList layers; + layers.append( mMemoryLayer ); + layers.append( mMdalLayer ); - foreach ( auto dp, dataProviders ) + foreach ( auto ly, layers ) { + const QgsMeshDataProvider *dp = ly->dataProvider(); QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 5, dp->datasetGroupCount() ); + QCOMPARE( 5, ly->datasetGroupCount() ); QgsMeshDatasetIndex ds( 0, 0 ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); QVERIFY( meta.name().contains( "Elevation" ) ); QVERIFY( meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), 0 ) ); QVERIFY( dmeta.isValid() ); @@ -501,27 +591,47 @@ void TestQgsMeshLayer::test_read_bed_elevation_dataset() QCOMPARE( QgsMeshDatasetValue( 10 ), dp->datasetValue( ds, 4 ) ); QVERIFY( dp->isFaceActive( ds, 0 ) ); + + meta = ly->datasetGroupMetadata( ds ); + QVERIFY( meta.name().contains( "Elevation" ) ); + QVERIFY( meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); + + dmeta = ly->datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), 0 ) ); + QVERIFY( dmeta.isValid() ); + + // We have 5 values, since dp->vertexCount() = 5 + QCOMPARE( QgsMeshDatasetValue( 20 ), ly->datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 30 ), ly->datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 40 ), ly->datasetValue( ds, 2 ) ); + QCOMPARE( QgsMeshDatasetValue( 50 ), ly->datasetValue( ds, 3 ) ); + QCOMPARE( QgsMeshDatasetValue( 10 ), ly->datasetValue( ds, 4 ) ); + + QVERIFY( ly->isFaceActive( ds, 0 ) ); } } void TestQgsMeshLayer::test_read_vertex_scalar_dataset() { - QList dataProviders; - dataProviders.append( mMemoryLayer->dataProvider() ); - dataProviders.append( mMdalLayer->dataProvider() ); + QList layers; + layers.append( mMemoryLayer ); + layers.append( mMdalLayer ); - foreach ( auto dp, dataProviders ) + foreach ( auto ly, layers ) { + const QgsMeshDataProvider *dp = ly->dataProvider(); QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 5, dp->datasetGroupCount() ); + QCOMPARE( 5, ly->datasetGroupCount() ); for ( int i = 0; i < 2 ; ++i ) { QgsMeshDatasetIndex ds( 1, i ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); if ( dp->name() == QStringLiteral( "mesh_memory" ) ) { QCOMPARE( meta.extraOptions()["description"], QString( "Vertex Scalar Dataset" ) ); @@ -531,7 +641,7 @@ void TestQgsMeshLayer::test_read_vertex_scalar_dataset() QVERIFY( meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); QVERIFY( dmeta.isValid() ); @@ -543,35 +653,60 @@ void TestQgsMeshLayer::test_read_vertex_scalar_dataset() QCOMPARE( QgsMeshDatasetValue( 1.0 + i ), dp->datasetValue( ds, 4 ) ); QVERIFY( dp->isFaceActive( ds, 0 ) ); + + meta = ly->datasetGroupMetadata( ds ); + if ( dp->name() == QStringLiteral( "mesh_memory" ) ) + { + QCOMPARE( meta.extraOptions()["description"], QString( "Vertex Scalar Dataset" ) ); + QCOMPARE( meta.extraOptions()["meta2"], QString( "best dataset" ) ); + } + QCOMPARE( meta.name(), QString( "VertexScalarDataset" ) ); + QVERIFY( meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); + + dmeta = ly->datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); + QVERIFY( dmeta.isValid() ); + + // We have 5 values, since dp->vertexCount() = 5 + QCOMPARE( QgsMeshDatasetValue( 1.0 + i ), ly->datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 2.0 + i ), ly->datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 3.0 + i ), ly->datasetValue( ds, 2 ) ); + QCOMPARE( QgsMeshDatasetValue( 2.0 + i ), ly->datasetValue( ds, 3 ) ); + QCOMPARE( QgsMeshDatasetValue( 1.0 + i ), ly->datasetValue( ds, 4 ) ); + + QVERIFY( ly->isFaceActive( ds, 0 ) ); } } } void TestQgsMeshLayer::test_read_vertex_vector_dataset() { - QList dataProviders; - dataProviders.append( mMemoryLayer->dataProvider() ); - dataProviders.append( mMdalLayer->dataProvider() ); + QList layers; + layers.append( mMemoryLayer ); + layers.append( mMdalLayer ); - foreach ( auto dp, dataProviders ) + foreach ( auto ly, layers ) { + const QgsMeshDataProvider *dp = ly->dataProvider(); QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 5, dp->datasetGroupCount() ); + QCOMPARE( 5, ly->datasetGroupCount() ); for ( int i = 0; i < 2 ; ++i ) { QgsMeshDatasetIndex ds( 2, i ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); if ( dp->name() == QStringLiteral( "mesh_memory" ) ) QCOMPARE( meta.extraOptions()["description"], QString( "Vertex Vector Dataset" ) ); QCOMPARE( meta.name(), QString( "VertexVectorDataset" ) ); QVERIFY( !meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); QVERIFY( dmeta.isValid() ); @@ -583,35 +718,57 @@ void TestQgsMeshLayer::test_read_vertex_vector_dataset() QCOMPARE( QgsMeshDatasetValue( 1 + i, -2 + i ), dp->datasetValue( ds, 4 ) ); QVERIFY( dp->isFaceActive( ds, 0 ) ); + + meta = ly->datasetGroupMetadata( ds ); + if ( dp->name() == QStringLiteral( "mesh_memory" ) ) + QCOMPARE( meta.extraOptions()["description"], QString( "Vertex Vector Dataset" ) ); + QCOMPARE( meta.name(), QString( "VertexVectorDataset" ) ); + QVERIFY( !meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); + + dmeta = ly->datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); + QVERIFY( dmeta.isValid() ); + + // We have 5 values, since dp->vertexCount() = 5 + QCOMPARE( QgsMeshDatasetValue( 1 + i, 1 + i ), ly->datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 2 + i, 1 + i ), ly->datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 3 + i, 2 + i ), ly->datasetValue( ds, 2 ) ); + QCOMPARE( QgsMeshDatasetValue( 2 + i, 2 + i ), ly->datasetValue( ds, 3 ) ); + QCOMPARE( QgsMeshDatasetValue( 1 + i, -2 + i ), ly->datasetValue( ds, 4 ) ); + + QVERIFY( ly->isFaceActive( ds, 0 ) ); } } } void TestQgsMeshLayer::test_read_face_scalar_dataset() { - QList dataProviders; - dataProviders.append( mMemoryLayer->dataProvider() ); - dataProviders.append( mMdalLayer->dataProvider() ); + QList layers; + layers.append( mMemoryLayer ); + layers.append( mMdalLayer ); - foreach ( auto dp, dataProviders ) + foreach ( auto ly, layers ) { + const QgsMeshDataProvider *dp = ly->dataProvider(); QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 5, dp->datasetGroupCount() ); + QCOMPARE( 5, ly->datasetGroupCount() ); for ( int i = 0; i < 2 ; ++i ) { QgsMeshDatasetIndex ds( 3, i ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); if ( dp->name() == QStringLiteral( "mesh_memory" ) ) QCOMPARE( meta.extraOptions()["description"], QString( "Face Scalar Dataset" ) ); QCOMPARE( meta.name(), QString( "FaceScalarDataset" ) ); QVERIFY( meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnFaces ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); QVERIFY( dmeta.isValid() ); @@ -620,6 +777,23 @@ void TestQgsMeshLayer::test_read_face_scalar_dataset() QCOMPARE( QgsMeshDatasetValue( 2 + i ), dp->datasetValue( ds, 1 ) ); QVERIFY( dp->isFaceActive( ds, 0 ) ); + + meta = ly->datasetGroupMetadata( ds ); + if ( dp->name() == QStringLiteral( "mesh_memory" ) ) + QCOMPARE( meta.extraOptions()["description"], QString( "Face Scalar Dataset" ) ); + QCOMPARE( meta.name(), QString( "FaceScalarDataset" ) ); + QVERIFY( meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnFaces ); + + dmeta = ly->datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); + QVERIFY( dmeta.isValid() ); + + // We have 2 values, since dp->faceCount() = 2 + QCOMPARE( QgsMeshDatasetValue( 1 + i ), ly->datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 2 + i ), ly->datasetValue( ds, 1 ) ); + + QVERIFY( ly->isFaceActive( ds, 0 ) ); } } } @@ -627,29 +801,31 @@ void TestQgsMeshLayer::test_read_face_scalar_dataset() void TestQgsMeshLayer::test_read_face_vector_dataset() { - QList dataProviders; - dataProviders.append( mMemoryLayer->dataProvider() ); - dataProviders.append( mMdalLayer->dataProvider() ); + QList layers; + layers.append( mMemoryLayer ); + layers.append( mMdalLayer ); - foreach ( auto dp, dataProviders ) + foreach ( auto ly, layers ) { + const QgsMeshDataProvider *dp = ly->dataProvider(); QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 5, dp->datasetGroupCount() ); + QCOMPARE( 5, ly->datasetGroupCount() ); for ( int i = 0; i < 2 ; ++i ) { QgsMeshDatasetIndex ds( 4, i ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); if ( dp->name() == QStringLiteral( "mesh_memory" ) ) QCOMPARE( meta.extraOptions()["description"], QString( "Face Vector Dataset" ) ); QCOMPARE( meta.name(), QString( "FaceVectorDataset" ) ); QVERIFY( !meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnFaces ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); QVERIFY( dmeta.isValid() ); @@ -658,6 +834,23 @@ void TestQgsMeshLayer::test_read_face_vector_dataset() QCOMPARE( QgsMeshDatasetValue( 2 + i, 2 + i ), dp->datasetValue( ds, 1 ) ); QVERIFY( dp->isFaceActive( ds, 0 ) ); + + meta = ly->datasetGroupMetadata( ds ); + if ( dp->name() == QStringLiteral( "mesh_memory" ) ) + QCOMPARE( meta.extraOptions()["description"], QString( "Face Vector Dataset" ) ); + QCOMPARE( meta.name(), QString( "FaceVectorDataset" ) ); + QVERIFY( !meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnFaces ); + + dmeta = ly->datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); + QVERIFY( dmeta.isValid() ); + + // We have 2 values, since dp->faceCount() = 2 + QCOMPARE( QgsMeshDatasetValue( 1 + i, 1 + i ), ly->datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 2 + i, 2 + i ), ly->datasetValue( ds, 1 ) ); + + QVERIFY( ly->isFaceActive( ds, 0 ) ); } } } @@ -671,17 +864,18 @@ void TestQgsMeshLayer::test_read_vertex_scalar_dataset_with_inactive_face() QVERIFY( dp != nullptr ); QVERIFY( dp->isValid() ); QCOMPARE( 2, dp->datasetGroupCount() ); + QCOMPARE( 2, layer.datasetGroupCount() ); for ( int i = 0; i < 2 ; ++i ) { QgsMeshDatasetIndex ds( 1, i ); - const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); + QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( ds ); QCOMPARE( meta.name(), QString( "VertexScalarDatasetWithInactiveFace1" ) ); QVERIFY( meta.isScalar() ); QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); - const QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); + QgsMeshDatasetMetadata dmeta = dp->datasetMetadata( ds ); QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); QVERIFY( dmeta.isValid() ); @@ -695,6 +889,26 @@ void TestQgsMeshLayer::test_read_vertex_scalar_dataset_with_inactive_face() // We have 2 faces QVERIFY( !dp->isFaceActive( ds, 0 ) ); QVERIFY( dp->isFaceActive( ds, 1 ) ); + + meta = layer.datasetGroupMetadata( ds ); + QCOMPARE( meta.name(), QString( "VertexScalarDatasetWithInactiveFace1" ) ); + QVERIFY( meta.isScalar() ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); + + dmeta = layer.datasetMetadata( ds ); + QVERIFY( qgsDoubleNear( dmeta.time(), i ) ); + QVERIFY( dmeta.isValid() ); + + // We have 5 values, since dp->vertexCount() = 5 + QCOMPARE( QgsMeshDatasetValue( 1.0 + i ), layer.datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 2.0 + i ), layer.datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 3.0 + i ), layer.datasetValue( ds, 2 ) ); + QCOMPARE( QgsMeshDatasetValue( 2.0 + i ), layer.datasetValue( ds, 3 ) ); + QCOMPARE( QgsMeshDatasetValue( 1.0 + i ), layer.datasetValue( ds, 4 ) ); + + // We have 2 faces + QVERIFY( !layer.isFaceActive( ds, 0 ) ); + QVERIFY( layer.isFaceActive( ds, 1 ) ); } } @@ -764,18 +978,18 @@ void TestQgsMeshLayer::test_reload() layer.updateTriangularMesh(); //to active the lazy loading of mesh data //Test if the layer matches with quad and triangle - QCOMPARE( layer.dataProvider()->datasetGroupCount(), 1 ); + QCOMPARE( layer.datasetGroupCount(), 1 ); QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 1 ); QCOMPARE( 5, layer.nativeMesh()->vertexCount() ); QCOMPARE( 2, layer.nativeMesh()->faceCount() ); //Test dataSet in quad and triangle QgsMeshDatasetIndex ds( 0, 0 ); - QCOMPARE( QgsMeshDatasetValue( 20 ), layer.dataProvider()->datasetValue( ds, 0 ) ); - QCOMPARE( QgsMeshDatasetValue( 30 ), layer.dataProvider()->datasetValue( ds, 1 ) ); - QCOMPARE( QgsMeshDatasetValue( 40 ), layer.dataProvider()->datasetValue( ds, 2 ) ); - QCOMPARE( QgsMeshDatasetValue( 50 ), layer.dataProvider()->datasetValue( ds, 3 ) ); - QCOMPARE( QgsMeshDatasetValue( 10 ), layer.dataProvider()->datasetValue( ds, 4 ) ); + QCOMPARE( QgsMeshDatasetValue( 20 ), layer.datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 30 ), layer.datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 40 ), layer.datasetValue( ds, 2 ) ); + QCOMPARE( QgsMeshDatasetValue( 50 ), layer.datasetValue( ds, 3 ) ); + QCOMPARE( QgsMeshDatasetValue( 10 ), layer.datasetValue( ds, 4 ) ); //copy the quad_flower.2dm to the temporary testFile copyToTemporaryFile( fileSource2, testFile ); @@ -842,9 +1056,10 @@ void TestQgsMeshLayer::test_reload_extra_dataset() copyToTemporaryFile( dataSetFile_1, testFileDataSet ); //add the data set from temporary file and test - QVERIFY( layer.dataProvider()->addDataset( testFileDataSet.fileName() ) ); + QVERIFY( layer.addDatasets( testFileDataSet.fileName() ) ); QCOMPARE( layer.dataProvider()->extraDatasets().count(), 1 ); QCOMPARE( layer.dataProvider()->datasetGroupCount(), 2 ); + QCOMPARE( layer.datasetGroupCount(), 2 ); QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 2 ); //copy the qad_and_triangle_vertex_scalar_incompatible_mesh.dat to the temporary testFile @@ -853,8 +1068,9 @@ void TestQgsMeshLayer::test_reload_extra_dataset() layer.reload(); //test if dataset presence - QCOMPARE( layer.dataProvider()->extraDatasets().count(), 1 ); - QCOMPARE( layer.dataProvider()->datasetGroupCount(), 1 ); + QCOMPARE( layer.dataProvider()->extraDatasets().count(), 1 ); //uri still present + QCOMPARE( layer.dataProvider()->datasetGroupCount(), 1 ); //dataset not loaded in the provider + QCOMPARE( layer.datasetGroupCount(), 2 ); //dataset group still registered in the layer QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 2 ); //dataset group tree item still have all dataset group //copy again the qad_and_triangle_vertex_scalar.dat to the temporary testFile @@ -863,10 +1079,10 @@ void TestQgsMeshLayer::test_reload_extra_dataset() layer.reload(); //add the data set from temporary tesFile and test - QVERIFY( layer.dataProvider()->addDataset( testFileDataSet.fileName() ) ); - QCOMPARE( layer.dataProvider()->extraDatasets().count(), 2 ); - QCOMPARE( layer.dataProvider()->datasetGroupCount(), 3 ); - QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 3 ); + QCOMPARE( layer.dataProvider()->extraDatasets().count(), 1 ); + QCOMPARE( layer.dataProvider()->datasetGroupCount(), 2 ); + QCOMPARE( layer.datasetGroupCount(), 2 ); + QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 2 ); //copy a invalid file to the temporary testFile QVERIFY( testFileDataSet.open() ); @@ -877,32 +1093,51 @@ void TestQgsMeshLayer::test_reload_extra_dataset() layer.reload(); //test dataset presence - QCOMPARE( layer.dataProvider()->extraDatasets().count(), 2 ); + QCOMPARE( layer.dataProvider()->extraDatasets().count(), 1 ); QCOMPARE( layer.dataProvider()->datasetGroupCount(), 1 ); - QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 3 ); + QCOMPARE( layer.datasetGroupCount(), 2 ); //dataset group still registered in the layer + QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 2 ); //dataset group tree item still have all dataset groups //copy again the qad_and_triangle_vertex_scalar.dat to the temporary testFile copyToTemporaryFile( dataSetFile_1, testFileDataSet ); layer.reload(); + QCOMPARE( layer.dataProvider()->extraDatasets().count(), 1 ); + QCOMPARE( layer.dataProvider()->datasetGroupCount(), 2 ); + 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(), 3 ); + QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 3 ); + //test dataset presence - QCOMPARE( layer.dataProvider()->extraDatasets().count(), 2 ); + QCOMPARE( layer.dataProvider()->extraDatasets().count(), 1 ); QCOMPARE( layer.dataProvider()->datasetGroupCount(), 3 ); - QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 3 ); //dataset group tree item still have all dataset groups + QCOMPARE( layer.datasetGroupCount(), 3 ); + QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 3 ); - //copy the qad_and_triangle_vertex_vector.dat to the temporary testFile - copyToTemporaryFile( dataSetFile_3, testFileDataSet ); + //add another dataset + QTemporaryFile testFileDataSet_3; + copyToTemporaryFile( dataSetFile_3, testFileDataSet_3 ); - layer.reload(); + QVERIFY( layer.addDatasets( testFileDataSet_3.fileName() ) ); + QCOMPARE( layer.dataProvider()->extraDatasets().count(), 2 ); + QCOMPARE( layer.dataProvider()->datasetGroupCount(), 4 ); + QCOMPARE( layer.datasetGroupCount(), 4 ); + QCOMPARE( layer.datasetGroupTreeRootItem()->childCount(), 4 ); //Test dataSet - QgsMeshDatasetIndex ds( 1, 0 ); - QCOMPARE( QgsMeshDatasetValue( 1, 1 ), layer.dataProvider()->datasetValue( ds, 0 ) ); - QCOMPARE( QgsMeshDatasetValue( 2, 1 ), layer.dataProvider()->datasetValue( ds, 1 ) ); - QCOMPARE( QgsMeshDatasetValue( 3, 2 ), layer.dataProvider()->datasetValue( ds, 2 ) ); - QCOMPARE( QgsMeshDatasetValue( 2, 2 ), layer.dataProvider()->datasetValue( ds, 3 ) ); - QCOMPARE( QgsMeshDatasetValue( 1, -2 ), layer.dataProvider()->datasetValue( ds, 4 ) ); + QgsMeshDatasetIndex ds( 3, 0 ); + QCOMPARE( QgsMeshDatasetValue( 1, 1 ), layer.datasetValue( ds, 0 ) ); + QCOMPARE( QgsMeshDatasetValue( 2, 1 ), layer.datasetValue( ds, 1 ) ); + QCOMPARE( QgsMeshDatasetValue( 3, 2 ), layer.datasetValue( ds, 2 ) ); + QCOMPARE( QgsMeshDatasetValue( 2, 2 ), layer.datasetValue( ds, 3 ) ); + QCOMPARE( QgsMeshDatasetValue( 1, -2 ), layer.datasetValue( ds, 4 ) ); } void TestQgsMeshLayer::test_mesh_simplification() @@ -1029,7 +1264,7 @@ void TestQgsMeshLayer::test_dataset_group_item_tree_item() QCOMPARE( rootItem->child( 0 )->child( 0 ), nullptr ); - rootItem->child( 0 )->appendChild( new QgsMeshDatasetGroupTreeItem( "added item", true, 21 ) ); + rootItem->child( 0 )->appendChild( new QgsMeshDatasetGroupTreeItem( "added item", QString(), true, 21 ) ); names << "added item"; QCOMPARE( rootItem->childFromDatasetGroupIndex( 21 ), rootItem->child( 0 )->child( 0 ) ); @@ -1119,6 +1354,124 @@ void TestQgsMeshLayer::test_dataset_group_item_tree_item() QCOMPARE( otherRoot->childFromDatasetGroupIndex( 21 ), otherRoot->child( 0 )->child( 0 ) ); } +void TestQgsMeshLayer::test_memory_dataset_group() +{ + int vertexCount = mMdalLayer->dataProvider()->vertexCount(); + int faceCount = mMdalLayer->dataProvider()->faceCount(); + + QCOMPARE( mMdalLayer->datasetGroupCount(), 5 ); + + std::unique_ptr goodDatasetGroupVertices( new QgsMeshMemoryDatasetGroup ); + std::unique_ptr badDatasetGroupVertices( new QgsMeshMemoryDatasetGroup ); + goodDatasetGroupVertices->setName( "good vertices datasets" ); + goodDatasetGroupVertices->setDataType( QgsMeshDatasetGroupMetadata::DataOnVertices ); + badDatasetGroupVertices->setDataType( QgsMeshDatasetGroupMetadata::DataOnVertices ); + for ( int i = 1; i < 10; i++ ) + { + std::shared_ptr gds = std::make_shared(); + std::shared_ptr bds = std::make_shared(); + for ( int v = 0; v < vertexCount; ++v ) + gds->values.append( QgsMeshDatasetValue( v / 2.0 ) ); + bds->values.append( QgsMeshDatasetValue( 2.0 ) ); + gds->valid = true; + bds->valid = true; + goodDatasetGroupVertices->addDataset( gds ); + badDatasetGroupVertices->addDataset( bds ); + } + + QVERIFY( mMdalLayer->addDatasets( goodDatasetGroupVertices.release() ) ); + QVERIFY( !mMdalLayer->addDatasets( badDatasetGroupVertices.release() ) ); + + QCOMPARE( mMdalLayer->datasetGroupCount(), 6 ); + QCOMPARE( mMdalLayer->extraDatasetGroupCount(), 1 ); + + std::unique_ptr goodDatasetGroupFaces( new QgsMeshMemoryDatasetGroup( "good faces datasets", QgsMeshDatasetGroupMetadata::DataOnFaces ) ); + std::unique_ptr badDatasetGroupFaces( new QgsMeshMemoryDatasetGroup ); + badDatasetGroupFaces->setDataType( QgsMeshDatasetGroupMetadata::DataOnFaces ); + for ( int i = 1; i < 10; i++ ) + { + std::shared_ptr gds = std::make_shared(); + std::shared_ptr bds = std::make_shared(); + for ( int v = 0; v < faceCount; ++v ) + gds->values.append( QgsMeshDatasetValue( v / 2.0 ) ); + bds->values.append( QgsMeshDatasetValue( 2.0 ) ); + gds->valid = true; + bds->valid = true; + goodDatasetGroupFaces->addDataset( gds ); + badDatasetGroupFaces->addDataset( bds ); + } + + QVERIFY( mMdalLayer->addDatasets( goodDatasetGroupFaces.release() ) ); + QVERIFY( !mMdalLayer->addDatasets( badDatasetGroupFaces.release() ) ); + + QCOMPARE( mMdalLayer->datasetGroupCount(), 7 ); + QCOMPARE( mMdalLayer->extraDatasetGroupCount(), 2 ); + + QCOMPARE( mMdalLayer->datasetGroupMetadata( 5 ).name(), "good vertices datasets" ); + QCOMPARE( mMdalLayer->datasetGroupMetadata( 6 ).name(), "good faces datasets" ); + + QTemporaryFile file; + QVERIFY( file.open() ); + file.close(); + + QVERIFY( !mMdalLayer->saveDataset( file.fileName(), 6, "ASCII_DAT" ) ); + QCOMPARE( mMdalLayer->datasetGroupCount(), 7 ); + QCOMPARE( mMdalLayer->extraDatasetGroupCount(), 1 ); + + QCOMPARE( mMdalLayer->datasetGroupMetadata( 5 ).name(), "good vertices datasets" ); + QCOMPARE( mMdalLayer->datasetGroupMetadata( 6 ).name(), "good faces datasets" ); + +} + +void TestQgsMeshLayer::test_memory_dataset_group_1d() +{ + int vertexCount = mMdal1DLayer->dataProvider()->vertexCount(); + int edgeCount = mMdal1DLayer->dataProvider()->edgeCount(); + + QCOMPARE( mMdal1DLayer->datasetGroupCount(), 5 ); + + std::unique_ptr goodDatasetGroupVertices( new QgsMeshMemoryDatasetGroup ); + std::unique_ptr badDatasetGroupVertices( new QgsMeshMemoryDatasetGroup ); + goodDatasetGroupVertices->setDataType( QgsMeshDatasetGroupMetadata::DataOnVertices ); + badDatasetGroupVertices->setDataType( QgsMeshDatasetGroupMetadata::DataOnVertices ); + for ( int i = 1; i < 10; i++ ) + { + std::shared_ptr gds = std::make_shared(); + std::shared_ptr bds = std::make_shared(); + for ( int v = 0; v < vertexCount; ++v ) + gds->values.append( QgsMeshDatasetValue( v / 2.0 ) ); + bds->values.append( QgsMeshDatasetValue( 2.0 ) ); + goodDatasetGroupVertices->addDataset( gds ); + badDatasetGroupVertices->addDataset( bds ); + } + + QVERIFY( mMdal1DLayer->addDatasets( goodDatasetGroupVertices.release() ) ); + QVERIFY( !mMdal1DLayer->addDatasets( badDatasetGroupVertices.release() ) ); + + QCOMPARE( mMdal1DLayer->datasetGroupCount(), 6 ); + + std::unique_ptr goodDatasetGroupEdges( new QgsMeshMemoryDatasetGroup ); + std::unique_ptr badDatasetGroupEdges( new QgsMeshMemoryDatasetGroup ); + goodDatasetGroupEdges->setDataType( QgsMeshDatasetGroupMetadata::DataOnEdges ); + badDatasetGroupEdges->setDataType( QgsMeshDatasetGroupMetadata::DataOnEdges ); + for ( int i = 1; i < 10; i++ ) + { + std::shared_ptr gds = std::make_shared(); + std::shared_ptr bds = std::make_shared(); + for ( int v = 0; v < edgeCount; ++v ) + gds->values.append( QgsMeshDatasetValue( v / 2.0 ) ); + bds->values.append( QgsMeshDatasetValue( 2.0 ) ); + goodDatasetGroupEdges->addDataset( gds ); + badDatasetGroupEdges->addDataset( bds ); + } + + QVERIFY( mMdal1DLayer->addDatasets( goodDatasetGroupEdges.release() ) ); + QVERIFY( !mMdal1DLayer->addDatasets( badDatasetGroupEdges.release() ) ); + + QCOMPARE( mMdal1DLayer->datasetGroupCount(), 7 ); + QCOMPARE( mMdal1DLayer->extraDatasetGroupCount(), 2 ); +} + void TestQgsMeshLayer::test_temporal() { qint64 relativeTime_0 = -1000; From 67f5d2b748ec3d096cfad8e1f7fa916160bae486 Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 24 Jun 2020 15:56:04 -0400 Subject: [PATCH 02/10] MDAL update --- external/mdal/api/mdal.h | 16 + external/mdal/frmts/mdal_2dm.cpp | 95 +- external/mdal/frmts/mdal_2dm.hpp | 21 +- external/mdal/frmts/mdal_3di.cpp | 2 + external/mdal/frmts/mdal_3di.hpp | 1 + external/mdal/frmts/mdal_ascii_dat.cpp | 9 +- external/mdal/frmts/mdal_ascii_dat.hpp | 2 + external/mdal/frmts/mdal_binary_dat.cpp | 5 + external/mdal/frmts/mdal_binary_dat.hpp | 2 + external/mdal/frmts/mdal_cf.cpp | 44 +- external/mdal/frmts/mdal_cf.hpp | 6 +- external/mdal/frmts/mdal_driver.cpp | 6 + external/mdal/frmts/mdal_driver.hpp | 2 + external/mdal/frmts/mdal_esri_tin.cpp | 10 +- external/mdal/frmts/mdal_flo2d.cpp | 8 +- external/mdal/frmts/mdal_gdal.cpp | 8 +- external/mdal/frmts/mdal_hec2d.cpp | 8 +- external/mdal/frmts/mdal_selafin.cpp | 1451 ++++++++++++++++------ external/mdal/frmts/mdal_selafin.hpp | 324 ++++- external/mdal/frmts/mdal_sww.cpp | 10 +- external/mdal/frmts/mdal_tuflowfv.cpp | 4 +- external/mdal/frmts/mdal_tuflowfv.hpp | 1 + external/mdal/frmts/mdal_ugrid.cpp | 39 +- external/mdal/frmts/mdal_ugrid.hpp | 4 +- external/mdal/frmts/mdal_xms_tin.cpp | 10 +- external/mdal/mdal.cpp | 27 +- external/mdal/mdal_data_model.cpp | 38 +- external/mdal/mdal_data_model.hpp | 22 +- external/mdal/mdal_datetime.cpp | 19 +- external/mdal/mdal_datetime.hpp | 5 +- external/mdal/mdal_memory_data_model.cpp | 44 +- external/mdal/mdal_memory_data_model.hpp | 32 +- external/mdal/mdal_utils.cpp | 12 + external/mdal/mdal_utils.hpp | 17 +- 34 files changed, 1683 insertions(+), 621 deletions(-) diff --git a/external/mdal/api/mdal.h b/external/mdal/api/mdal.h index 28de43d7dc18..42b711622469 100644 --- a/external/mdal/api/mdal.h +++ b/external/mdal/api/mdal.h @@ -163,6 +163,14 @@ MDAL_EXPORT bool MDAL_DR_meshLoadCapability( MDAL_DriverH driver ); */ MDAL_EXPORT bool MDAL_DR_writeDatasetsCapability( MDAL_DriverH driver, MDAL_DataLocation location ); +/** + * Returns the file suffix used to write datasets on file + * not thread-safe and valid only till next call + * + * \since MDAL 0.7.0 + */ +MDAL_EXPORT const char *MDAL_DR_writeDatasetsSuffix( MDAL_DriverH driver ); + /** * Returns whether driver has capability to save mesh */ @@ -530,6 +538,14 @@ MDAL_EXPORT const char *MDAL_G_referenceTime( MDAL_DatasetGroupH group ); */ MDAL_EXPORT bool MDAL_G_isTemporal( MDAL_DatasetGroupH group ); +/** + * Returns dataset group uri + * not thread-safe and valid only till next call + * + * \since MDAL 0.7.0 + */ +MDAL_EXPORT const char *MDAL_G_uri( MDAL_DatasetGroupH group ); + /////////////////////////////////////////////////////////////////////////////////////// /// DATASETS /////////////////////////////////////////////////////////////////////////////////////// diff --git a/external/mdal/frmts/mdal_2dm.cpp b/external/mdal/frmts/mdal_2dm.cpp index c337f01586b1..ce6a378bffd3 100644 --- a/external/mdal/frmts/mdal_2dm.cpp +++ b/external/mdal/frmts/mdal_2dm.cpp @@ -22,19 +22,11 @@ #define DRIVER_NAME "2DM" -MDAL::Mesh2dm::Mesh2dm( size_t verticesCount, - size_t edgesCount, - size_t facesCount, - size_t faceVerticesMaximumCount, - MDAL::BBox extent, +MDAL::Mesh2dm::Mesh2dm( size_t faceVerticesMaximumCount, const std::string &uri, const std::map vertexIDtoIndex ) : MemoryMesh( DRIVER_NAME, - verticesCount, - edgesCount, - facesCount, faceVerticesMaximumCount, - extent, uri ) , mVertexIDtoIndex( vertexIDtoIndex ) { @@ -125,6 +117,8 @@ std::unique_ptr MDAL::Driver2dm::load( const std::string &meshFile, size_t faceCount = 0; size_t vertexCount = 0; size_t edgesCount = 0; + size_t materialCount = 0; + bool hasMaterialsDefinitionsForElements = false; // Find out how many nodes and elements are contained in the .2dm mesh file while ( std::getline( in, line ) ) @@ -150,6 +144,12 @@ std::unique_ptr MDAL::Driver2dm::load( const std::string &meshFile, MDAL::Log::warning( MDAL_Status::Err_UnsupportedElement, name(), "found unsupported element" ); return nullptr; } + // If specified, update the number of materials of the mesh + else if ( startsWith( line, "NUM_MATERIALS_PER_ELEM" ) ) + { + hasMaterialsDefinitionsForElements = true; + materialCount = MDAL::toSizeT( split( line, ' ' )[1] ); + } } // Allocate memory @@ -157,8 +157,8 @@ std::unique_ptr MDAL::Driver2dm::load( const std::string &meshFile, Edges edges( edgesCount ); Faces faces( faceCount ); - // Basement 3.x supports definition of elevation for cell centers - std::vector elementCenteredElevation; + // .2dm mesh files may have any number of material ID columns + std::vector> faceMaterials; in.clear(); in.seekg( 0, std::ios::beg ); @@ -187,7 +187,7 @@ std::unique_ptr MDAL::Driver2dm::load( const std::string &meshFile, face.resize( faceVertexCount ); // chunks format here - // E** id vertex_id1, vertex_id2, ... material_id (elevation - optional) + // E** id vertex_id1, vertex_id2, vertex_id3, material_id [, aux_column_1, aux_column_2, ...] // vertex ids are numbered from 1 // Right now we just store node IDs here - we will convert them to node indices afterwards assert( chunks.size() > faceVertexCount + 1 ); @@ -195,18 +195,38 @@ std::unique_ptr MDAL::Driver2dm::load( const std::string &meshFile, for ( size_t i = 0; i < faceVertexCount; ++i ) face[i] = MDAL::toSizeT( chunks[i + 2] ) - 1; // 2dm is numbered from 1 - // OK, now find out if there is optional cell elevation (BASEMENT 3.x) - if ( chunks.size() == faceVertexCount + 4 ) + // NUM_MATERIALS_PER_ELEM tag provided, use new MATID parser + if ( hasMaterialsDefinitionsForElements ) { + // This assertion will fail if a mesh has fewer material ID columns than + // promised by the NUM_MATERIAL_PER_ELEM tag. + assert( chunks.size() - 5 >= materialCount ); + + if ( faceMaterials.empty() ) // Initialize dataset if still empty + { + faceMaterials = std::vector>( materialCount, std::vector( + faceCount, std::numeric_limits::quiet_NaN() ) ); + } + + // Add material ID values + for ( size_t i = 0; i < materialCount; ++i ) + { + // Offset of 2 for E** tag and element ID + faceMaterials[i][faceIndex] = MDAL::toDouble( chunks[ faceVertexCount + 2 + i] ); + } + } - // initialize dataset if it is still empty - if ( elementCenteredElevation.empty() ) + // No NUM_MATERIALS_PER_ELEM tag provided, use legacy MATID parser + else if ( chunks.size() == faceVertexCount + 4 ) + { + if ( faceMaterials.empty() ) // Initialize dataset if still empty { - elementCenteredElevation = std::vector( faceCount, std::numeric_limits::quiet_NaN() ); + // Add a single vector dataset for the "Bed Elevation (Face)" dataset + faceMaterials = std::vector>( 1, std::vector( + faceCount, std::numeric_limits::quiet_NaN() ) ); } - // add Bed Elevation (Face) value - elementCenteredElevation[faceIndex] = MDAL::toDouble( chunks[ faceVertexCount + 3 ] ); + faceMaterials[0][faceIndex] = MDAL::toDouble( chunks[ faceVertexCount + 3 ] ); } faceIndex++; @@ -276,22 +296,39 @@ std::unique_ptr MDAL::Driver2dm::load( const std::string &meshFile, std::unique_ptr< Mesh2dm > mesh( new Mesh2dm( - vertices.size(), - edges.size(), - faces.size(), MAX_VERTICES_PER_FACE_2DM, - computeExtent( vertices ), mMeshFile, vertexIDtoIndex ) ); - mesh->faces = faces; - mesh->vertices = vertices; - mesh->edges = edges; + mesh->setFaces( std::move( faces ) ); + mesh->setVertices( std::move( vertices ) ); + mesh->setEdges( std::move( edges ) ); + + // Add Bed Elevation + MDAL::addBedElevationDatasetGroup( mesh.get(), mesh->vertices() ); + + // Add material IDs + if ( hasMaterialsDefinitionsForElements ) + { + // New MATID parser: Add all MATID dataset groups + std::string dataSetName; + for ( size_t i = 0; i < materialCount; ++i ) + { + // The first two columns get special names for convenience + if ( i == 0 ) dataSetName = "Material ID"; + else if ( i == 1 ) dataSetName = "Bed Elevation (Face)"; + else dataSetName = "Auxiliary Material ID " + std::to_string( i - 1 ); - // Add Bed Elevations - MDAL::addFaceScalarDatasetGroup( mesh.get(), elementCenteredElevation, "Bed Elevation (Face)" ); - MDAL::addBedElevationDatasetGroup( mesh.get(), vertices ); + MDAL::addFaceScalarDatasetGroup( mesh.get(), faceMaterials[i], dataSetName ); + } + } + // Add "Bed Elevation (Face)" + else if ( !faceMaterials.empty() ) + { + // Legacy MATID parser: "Bed Elevation (Face)" dataset group only + MDAL::addFaceScalarDatasetGroup( mesh.get(), faceMaterials[0], "Bed Elevation (Face)" ); + } return std::unique_ptr( mesh.release() ); } diff --git a/external/mdal/frmts/mdal_2dm.hpp b/external/mdal/frmts/mdal_2dm.hpp index a1deb58402b1..7e37a8ce95a0 100644 --- a/external/mdal/frmts/mdal_2dm.hpp +++ b/external/mdal/frmts/mdal_2dm.hpp @@ -22,11 +22,7 @@ namespace MDAL class Mesh2dm: public MemoryMesh { public: - Mesh2dm( size_t verticesCount, - size_t edgesCount, - size_t facesCount, - size_t faceVerticesMaximumCount, - BBox extent, + Mesh2dm( size_t faceVerticesMaximumCount, const std::string &uri, const std::map vertexIDtoIndex ); @@ -63,12 +59,15 @@ namespace MDAL * * full specification here: https://www.xmswiki.com/wiki/SMS:2D_Mesh_Files_*.2dm * - * Exception for the official specification is for recognition of cell-centered - * elevation values supported by BASEMENT 3.x releases - * If face definition has extra column, it is parsed and recognized as - * elevation, e.g. format for triangle - * E3T id 1 2 3 mat_id elevation - * and added automatically as "Bed Elevation (Face)" + * This will process as many material IDs as promised by the NUM_MATERIALS_PER_ELEM tag and add them as face + * dataset groups. The naming for these groups is "Material ID" for the first, "Bed Elevation (Face)" for the + * second, and finally "Auxiliary Material ID " for any subsequent materials, X being a counter to ensure + * unique group names: + * E** id 1 2 3 [Material ID] [Bed Elevation (Face)] [Auxiliary Material ID 1] [Auxiliary Material ID 2] ... + * If the NUM_MATERIALS_PER_ELEM tag is not provided, a fallback mode is used that will only check for the + * second MATID column and add it under the name "Bed Elevation (Face)" if found. + * Noe that this is purely a compatibility mode for BASEMENT 3.x releases; NUM_MATERIALS_... is a required + * tag according to the 2DM specification. * * Note that some 2dm formats do have some extra columns after mat_id column with * data with unknown origin/name (e.g. tests/data/2dm/regular_grid.2dm) diff --git a/external/mdal/frmts/mdal_3di.cpp b/external/mdal/frmts/mdal_3di.cpp index b8dc67214bc8..12ef845b9c28 100644 --- a/external/mdal/frmts/mdal_3di.cpp +++ b/external/mdal/frmts/mdal_3di.cpp @@ -268,11 +268,13 @@ void MDAL::Driver3Di::parseNetCDFVariableMetadata( int varid, std::string &name, bool *is_vector, bool *isPolar, + bool *invertedDirection, bool *is_x ) { *is_vector = false; *is_x = true; *isPolar = false; + MDAL_UNUSED( invertedDirection ) std::string long_name = mNcFile->getAttrStr( "long_name", varid ); if ( long_name.empty() ) diff --git a/external/mdal/frmts/mdal_3di.hpp b/external/mdal/frmts/mdal_3di.hpp index b32a8cfb2f36..fd8b35c8395f 100644 --- a/external/mdal/frmts/mdal_3di.hpp +++ b/external/mdal/frmts/mdal_3di.hpp @@ -58,6 +58,7 @@ namespace MDAL std::string &name, bool *is_vector, bool *isPolar, + bool *invertedDirection, bool *is_x ) override; std::vector> parseClassification( int varid ) const override; diff --git a/external/mdal/frmts/mdal_ascii_dat.cpp b/external/mdal/frmts/mdal_ascii_dat.cpp index b266b864cc83..eae6448c589c 100644 --- a/external/mdal/frmts/mdal_ascii_dat.cpp +++ b/external/mdal/frmts/mdal_ascii_dat.cpp @@ -485,7 +485,9 @@ bool MDAL::DriverAsciiDat::persist( MDAL::DatasetGroup *group ) if ( !MDAL::contains( uri, "_els" ) && group->dataLocation() != MDAL_DataLocation::DataOnVertices ) { // Should contain _els in name for edges/faces dataset but it does not - uri.insert( uri.size() - 4, "_els" ); + int pos = uri.size() - 4; + uri.insert( std::max( 0, pos ), "_els" ); + group->replaceUri( uri ); } if ( ( mesh->facesCount() > 0 ) && ( mesh->edgesCount() > 0 ) ) @@ -561,3 +563,8 @@ bool MDAL::DriverAsciiDat::persist( MDAL::DatasetGroup *group ) return false; } + +std::string MDAL::DriverAsciiDat::writeDatasetOnFileSuffix() const +{ + return "dat"; +} diff --git a/external/mdal/frmts/mdal_ascii_dat.hpp b/external/mdal/frmts/mdal_ascii_dat.hpp index a90555eb7611..5756e82d3295 100644 --- a/external/mdal/frmts/mdal_ascii_dat.hpp +++ b/external/mdal/frmts/mdal_ascii_dat.hpp @@ -59,6 +59,8 @@ namespace MDAL void load( const std::string &datFile, Mesh *mesh ) override; bool persist( DatasetGroup *group ) override; + std::string writeDatasetOnFileSuffix() const override; + private: bool canReadOldFormat( const std::string &line ) const; bool canReadNewFormat( const std::string &line ) const; diff --git a/external/mdal/frmts/mdal_binary_dat.cpp b/external/mdal/frmts/mdal_binary_dat.cpp index 90110bea7105..c63d8496fbc1 100644 --- a/external/mdal/frmts/mdal_binary_dat.cpp +++ b/external/mdal/frmts/mdal_binary_dat.cpp @@ -491,3 +491,8 @@ bool MDAL::DriverBinaryDat::persist( MDAL::DatasetGroup *group ) return false; } + +std::string MDAL::DriverBinaryDat::writeDatasetOnFileSuffix() const +{ + return "dat"; +} diff --git a/external/mdal/frmts/mdal_binary_dat.hpp b/external/mdal/frmts/mdal_binary_dat.hpp index 7417adca830c..122bca106dc3 100644 --- a/external/mdal/frmts/mdal_binary_dat.hpp +++ b/external/mdal/frmts/mdal_binary_dat.hpp @@ -31,6 +31,8 @@ namespace MDAL void load( const std::string &datFile, Mesh *mesh ) override; bool persist( DatasetGroup *group ) override; + std::string writeDatasetOnFileSuffix() const override; + private: bool readVertexTimestep( const Mesh *mesh, std::shared_ptr group, diff --git a/external/mdal/frmts/mdal_cf.cpp b/external/mdal/frmts/mdal_cf.cpp index f5b6ed5404e6..f578953c048c 100644 --- a/external/mdal/frmts/mdal_cf.cpp +++ b/external/mdal/frmts/mdal_cf.cpp @@ -127,9 +127,10 @@ MDAL::cfdataset_info_map MDAL::DriverCF::parseDatasetGroupInfo() bool is_vector = true; bool is_polar = false; bool is_x = false; + bool invertedDirection = false; Classification classes = parseClassification( varid ); bool isClassified = !classes.empty(); - parseNetCDFVariableMetadata( varid, variable_name, vectorName, &is_vector, &is_polar, &is_x ); + parseNetCDFVariableMetadata( varid, variable_name, vectorName, &is_vector, &is_polar, & invertedDirection, &is_x ); Metadata meta; // check for units @@ -165,6 +166,7 @@ MDAL::cfdataset_info_map MDAL::DriverCF::parseDatasetGroupInfo() { it->second.ncid_y = varid; it->second.classification_y = classes; + it->second.isInvertedDirection = invertedDirection; } // If it is classified, we want to keep each component as scalar @@ -173,12 +175,12 @@ MDAL::cfdataset_info_map MDAL::DriverCF::parseDatasetGroupInfo() { CFDatasetGroupInfo scalarDsInfoX; scalarDsInfoX = it->second; - scalarDsInfoX.is_vector = false; - scalarDsInfoX.is_polar = false; + scalarDsInfoX.isVector = false; + scalarDsInfoX.isPolar = false; CFDatasetGroupInfo scalarDsInfoY; scalarDsInfoY = it->second; - scalarDsInfoY.is_vector = false; - scalarDsInfoX.is_polar = false; + scalarDsInfoY.isVector = false; + scalarDsInfoY.isPolar = false; scalarDsInfoX.ncid_x = it->second.ncid_x; scalarDsInfoY.ncid_x = it->second.ncid_y; @@ -222,8 +224,9 @@ MDAL::cfdataset_info_map MDAL::DriverCF::parseDatasetGroupInfo() } dsInfo.outputType = mDimensions.type( dimid ); - dsInfo.is_vector = is_vector; - dsInfo.is_polar = is_polar; + dsInfo.isVector = is_vector; + dsInfo.isPolar = is_polar; + dsInfo.isInvertedDirection = invertedDirection; dsInfo.nValues = mDimensions.size( mDimensions.type( dimid ) ); dsInfo.timeLocation = timeLocation; dsInfo.metadata = meta; @@ -241,11 +244,10 @@ MDAL::cfdataset_info_map MDAL::DriverCF::parseDatasetGroupInfo() // if ncid_y<0 set the datasetinfo to scalar for ( auto &it : dsinfo_map ) { - if ( it.second.is_vector && it.second.ncid_y < 0 ) - it.second.is_vector = false; + if ( it.second.isVector && it.second.ncid_y < 0 ) + it.second.isVector = false; } - return dsinfo_map; } @@ -319,8 +321,14 @@ void MDAL::DriverCF::addDatasetGroups( MDAL::Mesh *mesh, const std::vectorsetIsScalar( !dsi.is_vector ); - group->setIsPolar( dsi.is_polar ); + group->setIsScalar( !dsi.isVector ); + group->setIsPolar( dsi.isPolar ); + if ( dsi.isInvertedDirection ) + { + auto referenceAngle = group->referenceAngles(); + referenceAngle.second = referenceAngle.second + referenceAngle.first * 0.5; + group->setReferenceAngles( referenceAngle ); + } group->setMetadata( dsi.metadata ); if ( dsi.outputType == CFDimensions::Vertex ) @@ -343,7 +351,7 @@ void MDAL::DriverCF::addDatasetGroups( MDAL::Mesh *mesh, const std::vector::quiet_NaN(); std::vector vals_y; - if ( dsi.is_vector ) + if ( dsi.isVector ) { fill_val_y = mNcFile->getFillValue( dsi.ncid_y ); } @@ -563,17 +571,13 @@ std::unique_ptr< MDAL::Mesh > MDAL::DriverCF::load( const std::string &fileName, std::unique_ptr< MemoryMesh > mesh( new MemoryMesh( name(), - vertices.size(), - edges.size(), - faces.size(), mDimensions.size( mDimensions.MaxVerticesInFace ), - computeExtent( vertices ), mFileName ) ); - mesh->faces = faces; - mesh->edges = edges; - mesh->vertices = vertices; + mesh->setFaces( std::move( faces ) ); + mesh->setEdges( std::move( edges ) ); + mesh->setVertices( std::move( vertices ) ); addBedElevation( mesh.get() ); setProjection( mesh.get() ); diff --git a/external/mdal/frmts/mdal_cf.hpp b/external/mdal/frmts/mdal_cf.hpp index bccc8d1479b3..71f672394b80 100644 --- a/external/mdal/frmts/mdal_cf.hpp +++ b/external/mdal/frmts/mdal_cf.hpp @@ -60,8 +60,9 @@ namespace MDAL }; std::string name; //!< Dataset group name CFDimensions::Type outputType; - bool is_vector; - bool is_polar; + bool isVector; + bool isPolar; + bool isInvertedDirection; TimeLocation timeLocation; size_t nTimesteps; size_t nValues; @@ -133,6 +134,7 @@ namespace MDAL std::string &name, bool *is_vector, bool *isPolar, + bool *invertedDirection, bool *is_x ) = 0; virtual std::vector > parseClassification( int varid ) const = 0; virtual std::string getTimeVariableName() const = 0; diff --git a/external/mdal/frmts/mdal_driver.cpp b/external/mdal/frmts/mdal_driver.cpp index b497299538b4..51241a09bb92 100644 --- a/external/mdal/frmts/mdal_driver.cpp +++ b/external/mdal/frmts/mdal_driver.cpp @@ -36,6 +36,12 @@ std::string MDAL::Driver::filters() const return mFilters; } +std::string MDAL::Driver::writeDatasetOnFileSuffix() const +{ + + return std::string(); +} + bool MDAL::Driver::hasCapability( MDAL::Capability capability ) const { return capability == ( mCapabilityFlags & capability ); diff --git a/external/mdal/frmts/mdal_driver.hpp b/external/mdal/frmts/mdal_driver.hpp index 1817320056da..325ecd0707d6 100644 --- a/external/mdal/frmts/mdal_driver.hpp +++ b/external/mdal/frmts/mdal_driver.hpp @@ -42,6 +42,8 @@ namespace MDAL bool hasCapability( Capability capability ) const; bool hasWriteDatasetCapability( MDAL_DataLocation location ) const; + virtual std::string writeDatasetOnFileSuffix() const; + virtual bool canReadMesh( const std::string &uri ); virtual bool canReadDatasets( const std::string &uri ); diff --git a/external/mdal/frmts/mdal_esri_tin.cpp b/external/mdal/frmts/mdal_esri_tin.cpp index e8e50f095c0e..71b29dbc3651 100644 --- a/external/mdal/frmts/mdal_esri_tin.cpp +++ b/external/mdal/frmts/mdal_esri_tin.cpp @@ -176,21 +176,17 @@ std::unique_ptr MDAL::DriverEsriTin::load( const std::string &uri, c std::unique_ptr< MemoryMesh > mesh( new MemoryMesh( name(), - vertices.size(), - 0, - faces.size(), 3, - computeExtent( vertices ), uri ) ); //move the faces and the vertices in the mesh - mesh->faces = std::move( faces ); - mesh->vertices = std::move( vertices ); + mesh->setFaces( std::move( faces ) ); + mesh->setVertices( std::move( vertices ) ); //create the "Altitude" dataset - addBedElevationDatasetGroup( mesh.get(), mesh->vertices ); + addBedElevationDatasetGroup( mesh.get(), mesh->vertices() ); mesh->datasetGroups.back()->setName( "Altitude" ); std::string crs = getCrsWkt( uri ); diff --git a/external/mdal/frmts/mdal_flo2d.cpp b/external/mdal/frmts/mdal_flo2d.cpp index b9dc00acfcb3..7f4293c0a242 100644 --- a/external/mdal/frmts/mdal_flo2d.cpp +++ b/external/mdal/frmts/mdal_flo2d.cpp @@ -498,16 +498,12 @@ void MDAL::DriverFlo2D::createMesh( const std::vector &cells, double mMesh.reset( new MemoryMesh( name(), - vertices.size(), - 0, - faces.size(), 4, //maximum quads - computeExtent( vertices ), mDatFileName ) ); - mMesh->faces = faces; - mMesh->vertices = vertices; + mMesh->setFaces( std::move( faces ) ); + mMesh->setVertices( std::move( vertices ) ); } bool MDAL::DriverFlo2D::parseHDF5Datasets( MemoryMesh *mesh, const std::string &timedepFileName ) diff --git a/external/mdal/frmts/mdal_gdal.cpp b/external/mdal/frmts/mdal_gdal.cpp index b992ef61062a..18d0858d85ee 100644 --- a/external/mdal/frmts/mdal_gdal.cpp +++ b/external/mdal/frmts/mdal_gdal.cpp @@ -448,16 +448,12 @@ void MDAL::DriverGdal::createMesh() mMesh.reset( new MemoryMesh( name(), - vertices.size(), - 0, - faces.size(), 4, //maximum quads - computeExtent( vertices ), mFileName ) ); - mMesh->vertices = vertices; - mMesh->faces = faces; + mMesh->setVertices( std::move( vertices ) ); + mMesh->setFaces( std::move( faces ) ); bool proj_added = addSrcProj(); if ( ( !proj_added ) && is_longitude_shifted ) { diff --git a/external/mdal/frmts/mdal_hec2d.cpp b/external/mdal/frmts/mdal_hec2d.cpp index f7b2a439ad32..f078eee45518 100644 --- a/external/mdal/frmts/mdal_hec2d.cpp +++ b/external/mdal/frmts/mdal_hec2d.cpp @@ -632,16 +632,12 @@ void MDAL::DriverHec2D::parseMesh( mMesh.reset( new MemoryMesh( name(), - vertices.size(), - 0, - faces.size(), maxVerticesInFace, - computeExtent( vertices ), mFileName ) ); - mMesh->faces = faces; - mMesh->vertices = vertices; + mMesh->setFaces( std::move( faces ) ); + mMesh->setVertices( std::move( vertices ) ); } MDAL::DriverHec2D::DriverHec2D() diff --git a/external/mdal/frmts/mdal_selafin.cpp b/external/mdal/frmts/mdal_selafin.cpp index 88eb5732a5e6..d0e00dbdcee6 100644 --- a/external/mdal/frmts/mdal_selafin.cpp +++ b/external/mdal/frmts/mdal_selafin.cpp @@ -23,11 +23,18 @@ #include #include "mdal_logger.hpp" -MDAL::SerafinStreamReader::SerafinStreamReader() = default; +#define BUFFER_SIZE 2000 -void MDAL::SerafinStreamReader::initialize( const std::string &fileName ) +// ////////////////////////////// +// SelafinFile +// ////////////////////////////// + +MDAL::SelafinFile::SelafinFile( const std::string &fileName ): + mFileName( fileName ) +{} + +void MDAL::SelafinFile::initialize() { - mFileName = fileName; if ( !MDAL::fileExists( mFileName ) ) { throw MDAL::Error( MDAL_Status::Err_FileNotFound, "Did not find file " + mFileName ); @@ -42,218 +49,30 @@ void MDAL::SerafinStreamReader::initialize( const std::string &fileName ) mFileSize = mIn.tellg(); mIn.seekg( 0, mIn.beg ); - mStreamInFloatPrecision = getStreamPrecision(); mIsNativeLittleEndian = MDAL::isNativeLittleEndian(); + mParsed = false; } -bool MDAL::SerafinStreamReader::getStreamPrecision( ) -{ - ignore_array_length( ); - ignore( 72 ); - std::string varType = read_string_without_length( 8 ); - bool ret; - if ( varType == "SERAFIN" ) - { - ret = true; - } - else if ( varType == "SERAFIND" ) - { - ret = false; - } - else - { - throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Not found stream precision" ); - } - ignore_array_length( ); - return ret; -} - -std::string MDAL::SerafinStreamReader::read_string( size_t len ) -{ - size_t length = read_sizet(); - if ( length != len ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to read string" ); - std::string ret = read_string_without_length( len ); - ignore_array_length(); - return ret; -} - -std::vector MDAL::SerafinStreamReader::read_double_arr( size_t len ) -{ - size_t length = read_sizet(); - if ( mStreamInFloatPrecision ) - { - if ( length != len * 4 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading double array" ); - } - else - { - if ( length != len * 8 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading double array" ); - } - std::vector ret( len ); - for ( size_t i = 0; i < len; ++i ) - { - ret[i] = read_double(); - } - ignore_array_length(); - return ret; -} - -std::vector MDAL::SerafinStreamReader::read_int_arr( size_t len ) -{ - size_t length = read_sizet(); - if ( length != len * 4 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading int array" ); - std::vector ret( len ); - for ( size_t i = 0; i < len; ++i ) - { - ret[i] = read_int(); - } - ignore_array_length(); - return ret; -} - -std::vector MDAL::SerafinStreamReader::read_size_t_arr( size_t len ) -{ - size_t length = read_sizet(); - if ( length != len * 4 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading sizet array" ); - std::vector ret( len ); - for ( size_t i = 0; i < len; ++i ) - { - ret[i] = read_sizet(); - } - ignore_array_length(); - return ret; -} - -std::string MDAL::SerafinStreamReader::read_string_without_length( size_t len ) -{ - std::vector ptr( len ); - mIn.read( ptr.data(), static_cast( len ) ); - if ( !mIn ) - throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to open stream for reading string without length" ); - - size_t str_length = 0; - for ( size_t i = len; i > 0; --i ) - { - if ( ptr[i - 1] != 0x20 ) - { - str_length = i; - break; - } - } - std::string ret( ptr.data(), str_length ); - return ret; -} - -double MDAL::SerafinStreamReader::read_double( ) -{ - double ret; - - if ( mStreamInFloatPrecision ) - { - float ret_f; - if ( !readValue( ret_f, mIn, mIsNativeLittleEndian ) ) - throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Reading double failed" ); - ret = static_cast( ret_f ); - } - else - { - if ( !readValue( ret, mIn, mIsNativeLittleEndian ) ) - throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Reading double failed" ); - } - return ret; -} - - -int MDAL::SerafinStreamReader::read_int( ) -{ - unsigned char data[4]; - - if ( mIn.read( reinterpret_cast< char * >( &data ), 4 ) ) - if ( !mIn ) - throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to open stream for reading int" ); - if ( mIsNativeLittleEndian ) - { - std::reverse( reinterpret_cast< char * >( &data ), reinterpret_cast< char * >( &data ) + 4 ); - } - int var; - memcpy( reinterpret_cast< char * >( &var ), - reinterpret_cast< char * >( &data ), - 4 ); - return var; -} - -size_t MDAL::SerafinStreamReader::read_sizet() -{ - int var = read_int( ); - return static_cast( var ); -} - -size_t MDAL::SerafinStreamReader::remainingBytes() -{ - return static_cast( mFileSize - mIn.tellg() ); -} - -void MDAL::SerafinStreamReader::ignore( int len ) -{ - mIn.ignore( len ); - if ( !mIn ) - throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to ignore characters (invalid stream)" ); -} - -void MDAL::SerafinStreamReader::ignore_array_length( ) -{ - ignore( 4 ); -} - -// ////////////////////////////// -// DRIVER -// ////////////////////////////// -MDAL::DriverSelafin::DriverSelafin(): - Driver( "SELAFIN", - "Selafin File", - "*.slf", - Capability::ReadMesh - ) -{ -} - -MDAL::DriverSelafin *MDAL::DriverSelafin::create() -{ - return new DriverSelafin(); -} - -MDAL::DriverSelafin::~DriverSelafin() = default; - - -void MDAL::DriverSelafin::parseFile( std::vector &var_names, - double *xOrigin, - double *yOrigin, - size_t *nElem, - size_t *nPoint, - size_t *nPointsPerElem, - std::vector &ikle, - std::vector &x, - std::vector &y, - std::vector &data, - DateTime &referenceTime ) +void MDAL::SelafinFile::parseFile() { /* 1 record containing the title of the study (72 characters) and a 8 characters string indicating the type of format (SERAFIN or SERAFIND) */ - mReader.initialize( mFileName ); + readHeader(); /* 1 record containing the two integers NBV(1) and NBV(2) (number of linear and quadratic variables, NBV(2) with the value of 0 for Telemac, as quadratic values are not saved so far), numbered from 1 in docs */ - std::vector nbv = mReader.read_size_t_arr( 2 ); + std::vector nbv = readIntArr( 2 ); - /* NBV(1) records containing the names and units of each variab - le (over 32 characters) + /* NBV(1) records containing the names and units of each variable (over 32 characters) */ - for ( size_t i = 0; i < nbv[0]; ++i ) + mVariableNames.clear(); + for ( int i = 0; i < nbv[0]; ++i ) { - var_names.push_back( mReader.read_string( 32 ) ); + mVariableNames.push_back( readString( 32 ) ); } /* 1 record containing the integers table IPARAM (10 integers, of which only @@ -278,11 +97,11 @@ void MDAL::DriverSelafin::parseFile( std::vector &var_names, by the array KNOLG (total initial number of points). All the other numbers are local to the sub-domain, including IKLE */ - std::vector params = mReader.read_int_arr( 10 ); - *xOrigin = static_cast( params[2] ); - *yOrigin = static_cast( params[3] ); + mParameters = readIntArr( 10 ); + mXOrigin = static_cast( mParameters[2] ); + mYOrigin = static_cast( mParameters[3] ); - if ( params[6] != 0 ) + if ( mParameters[6] != 0 ) { // would need additional parsing throw MDAL::Error( MDAL_Status::Err_MissingDriver, "File " + mFileName + " would need additional parsing" ); @@ -292,136 +111,191 @@ void MDAL::DriverSelafin::parseFile( std::vector &var_names, if IPARAM (10) = 1: a record containing the computation starting date */ - if ( params[9] == 1 ) + if ( mParameters[9] == 1 ) { - std::vector datetime = mReader.read_int_arr( 6 ); - referenceTime = DateTime( datetime[0], datetime[1], datetime[2], datetime[3], datetime[4], double( datetime[5] ) ); + std::vector datetime = readIntArr( 6 ); + mReferenceTime = DateTime( datetime[0], datetime[1], datetime[2], datetime[3], datetime[4], double( datetime[5] ) ); } /* 1 record containing the integers NELEM,NPOIN,NDP,1 (number of elements, number of points, number of points per element and the value 1) */ - std::vector numbers = mReader.read_size_t_arr( 4 ); - *nElem = numbers[0]; - *nPoint = numbers[1]; - *nPointsPerElem = numbers[2]; + std::vector numbers = readIntArr( 4 ); + mFacesCount = numbers[0]; + mVerticesCount = numbers[1]; + mVerticesPerFace = numbers[2]; /* 1 record containing table IKLE (integer array of dimension (NDP,NELEM) which is the connectivity table. - - Attention: in TELEMAC-2D, the dimensions of this array are (NELEM,NDP)) */ - ikle = mReader.read_size_t_arr( ( *nElem ) * ( *nPointsPerElem ) ); - for ( size_t i = 0; i < ikle.size(); ++i ) - { - -- ikle[i]; //numbered from 1 - } + size_t size = mFacesCount * mVerticesPerFace; + if ( ! checkIntArraySize( size ) ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading connectivity table" ); + mConnectivityStreamPosition = passThroughIntArray( size ); /* 1 record containing table IPOBO (integer array of dimension NPOIN); the value of one element is 0 for an internal point, and gives the numbering of boundary points for the others */ - std::vector iPointBoundary = mReader.read_int_arr( *nPoint ); - MDAL_UNUSED( iPointBoundary ) + size = mVerticesCount; + if ( ! checkIntArraySize( size ) ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading IPOBO table" ); + mIPOBOStreamPosition = passThroughIntArray( size ); /* 1 record containing table X (real array of dimension NPOIN containing the - abscissae of the points) + abscisse of the points) */ - x = mReader.read_double_arr( *nPoint ); + size = mVerticesCount; + if ( ! checkDoubleArraySize( size ) ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading abscisse values" ); + mXStreamPosition = passThroughDoubleArray( size ); /* 1 record containing table Y (real array of dimension NPOIN containing the - abscissae of the points) + ordinate of the points) */ - y = mReader.read_double_arr( *nPoint ); - + size = mVerticesCount; + if ( ! checkDoubleArraySize( size ) ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading abscisse values" ); + mYStreamPosition = passThroughDoubleArray( size ); /* Next, for each time step, the following are found: - 1 record containing time T (real), - NBV(1)+NBV(2) records containing the results tables for each variable at time */ - data.resize( var_names.size() ); - size_t nTimesteps = mReader.remainingBytes() / ( 12 + ( 4 + ( *nPoint ) * 4 + 4 ) * var_names.size() ); + size_t realSize = mStreamInFloatPrecision ? 4 : 8; + size_t nTimesteps = remainingBytes() / ( 8 + realSize + ( 4 + ( mVerticesCount ) * realSize + 4 ) * mVariableNames.size() ); + mVariableStreamPosition.resize( mVariableNames.size(), std::vector( nTimesteps ) ); + mTimeSteps.resize( nTimesteps ); for ( size_t nT = 0; nT < nTimesteps; ++nT ) { - std::vector times = mReader.read_double_arr( 1 ); - double time = times[0]; - - for ( size_t i = 0; i < var_names.size(); ++i ) + std::vector times = readDoubleArr( 1 ); + mTimeSteps[nT] = RelativeTimestamp( times[0], RelativeTimestamp::seconds ); + for ( size_t i = 0; i < mVariableNames.size(); ++i ) { - timestep_map &datait = data[i]; - std::vector datavals = mReader.read_double_arr( *nPoint ); - datait[time] = datavals; + if ( ! checkDoubleArraySize( mVerticesCount ) ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading dataset values" ); + mVariableStreamPosition[i][nT] = passThroughDoubleArray( mVerticesCount ); } } + + mParsed = true; } -void MDAL::DriverSelafin::createMesh( - double xOrigin, - double yOrigin, - size_t nElems, - size_t nPoints, - size_t nPointsPerElem, - std::vector &ikle, - std::vector &x, - std::vector &y ) +std::string MDAL::SelafinFile::readHeader() { - Vertices nodes( nPoints ); - Vertex *nodesPtr = nodes.data(); - for ( size_t n = 0; n < nPoints; ++n, ++nodesPtr ) + initialize(); + std::string header = readString( 80 ); + + std::string title = header.substr( 0, 72 ); + title = trim( title ); + + std::string varType = header.substr( 72, 8 ); + varType = trim( varType ); + + if ( varType == "SERAFIN" ) { - nodesPtr->x = xOrigin + x[n]; - nodesPtr->y = yOrigin + y[n]; + mStreamInFloatPrecision = true; } - - Faces elements( nElems ); - for ( size_t e = 0; e < nElems; ++e ) + else if ( varType == "SERAFIND" ) { - if ( nPointsPerElem != 3 ) - { - throw MDAL::Error( MDAL_Status::Err_IncompatibleMesh, "Creating mesh failed, wrong number of points per element (3)" ); //we can add it, but it is uncommon for this format - } + mStreamInFloatPrecision = false; + } + else + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Not found stream precision" ); - // elemPtr->setId(e); - elements[e].resize( 3 ); - for ( size_t p = 0; p < 3; p++ ) - { - size_t val = ikle[e * 3 + p]; - if ( val > nPoints - 1 ) - { - elements[e][p] = 0; - } - else - { - elements[e][p] = val; - } - } + if ( header.size() < 80 ) // IF "SERAFIN", the readString method remove the last character that is a space + header.append( " " ); + return header; +} + +std::vector MDAL::SelafinFile::connectivityIndex( size_t offset, size_t count ) +{ + return readIntArr( mConnectivityStreamPosition, offset, count ); +} + +std::vector MDAL::SelafinFile::vertices( size_t offset, size_t count ) +{ + std::vector xValues = readDoubleArr( mXStreamPosition, offset, count ); + std::vector yValues = readDoubleArr( mYStreamPosition, offset, count ); + + if ( xValues.size() != count || yValues.size() != count ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading vertices" ); + + std::vector coordinates( count * 3 ); + for ( size_t i = 0; i < count; ++i ) + { + coordinates[i * 3] = xValues.at( i ) + mXOrigin; + coordinates[i * 3 + 1] = yValues.at( i ) + mYOrigin; + coordinates[i * 3 + 2] = 0; } + return coordinates; +} + +std::unique_ptr MDAL::SelafinFile::createMesh( const std::string &fileName ) +{ + std::shared_ptr reader = std::make_shared( fileName ); + reader->initialize(); + reader->parseFile(); + + std::unique_ptr mesh( new MeshSelafin( fileName, reader ) ); + populateDataset( mesh.get(), reader ); + + return mesh; +} + +void MDAL::SelafinFile::populateDataset( MDAL::Mesh *mesh, const std::string &fileName ) +{ + std::shared_ptr reader = std::make_shared( fileName ); + reader->initialize(); + reader->parseFile(); + + if ( mesh->verticesCount() != reader->verticesCount() || mesh->facesCount() != reader->facesCount() ) + throw MDAL::Error( MDAL_Status::Err_IncompatibleDataset, "Faces or vertices counts in the file are not the same" ); + + populateDataset( mesh, reader ); +} + +size_t MDAL::SelafinFile::facesCount() +{ + if ( !mParsed ) + parseFile(); + return mFacesCount; +} - mMesh.reset( - new MemoryMesh( - "SELAFIN", - nodes.size(), - 0, - elements.size(), - 3, //Triangles - computeExtent( nodes ), - mFileName - ) - ); - mMesh->faces = elements; - mMesh->vertices = nodes; +size_t MDAL::SelafinFile::verticesCount() +{ + if ( !mParsed ) + parseFile(); + return mVerticesCount; +} + +size_t MDAL::SelafinFile::verticesPerFace() +{ + if ( !mParsed ) + parseFile(); + return mVerticesPerFace; +} + +std::vector MDAL::SelafinFile::datasetValues( size_t timeStepIndex, size_t variableIndex, size_t offset, size_t count ) +{ + if ( !mParsed ) + parseFile(); + if ( variableIndex < mVariableStreamPosition.size() && timeStepIndex < mVariableStreamPosition[timeStepIndex].size() ) + return readDoubleArr( mVariableStreamPosition[variableIndex][timeStepIndex], offset, count ); + else + return std::vector(); } -void MDAL::DriverSelafin::addData( const std::vector &var_names, - const std::vector &data, - size_t nPoints, - const DateTime &referenceTime ) +void MDAL::SelafinFile::populateDataset( MDAL::Mesh *mesh, std::shared_ptr reader ) { - for ( size_t nName = 0; nName < var_names.size(); ++nName ) + std::map> groupsByName; + std::vector< std::shared_ptr> groupsInOrder; + + for ( size_t nName = 0; nName < reader->mVariableNames.size(); ++nName ) { - std::string var_name( var_names[nName] ); + std::string var_name( reader->mVariableNames[nName] ); var_name = MDAL::toLower( MDAL::trim( var_name ) ); var_name = MDAL::replace( var_name, "/", "" ); // slash is represented as sub-dataset group but we do not have such subdatasets groups in Selafin @@ -456,73 +330,64 @@ void MDAL::DriverSelafin::addData( const std::vector &var_names, var_name = MDAL::replace( var_name, "suivant y", "" ); } - std::shared_ptr group = mMesh->group( var_name ); - if ( !group ) + std::shared_ptr group; + auto it = groupsByName.find( var_name ); + if ( it == groupsByName.end() ) { group = std::make_shared< DatasetGroup >( - mMesh->driverName(), - mMesh.get(), - mMesh->uri(), + mesh->driverName(), + mesh, + mesh->uri(), var_name ); group->setDataLocation( MDAL_DataLocation::DataOnVertices ); group->setIsScalar( !is_vector ); - mMesh->datasetGroups.push_back( group ); + groupsInOrder.push_back( group ); + groupsByName[var_name] = group; } + else + group = it->second; - group->setReferenceTime( referenceTime ); + group->setReferenceTime( reader->mReferenceTime ); - size_t i = 0; - for ( timestep_map::const_iterator it = data[nName].begin(); it != data[nName].end(); ++it, ++i ) + for ( size_t nT = 0; nT < reader->mTimeSteps.size(); nT++ ) { - std::shared_ptr dataset; - if ( group->datasets.size() > i ) + std::shared_ptr dataset; + if ( group->datasets.size() > nT ) { - dataset = std::dynamic_pointer_cast( group->datasets[i] ); + dataset = std::dynamic_pointer_cast( group->datasets[nT] ); } else { - dataset = std::make_shared< MemoryDataset2D >( group.get(), true ); - // see https://github.com/lutraconsulting/MDAL/issues/185 - dataset->setTime( it->first, RelativeTimestamp::seconds ); + dataset = std::make_shared< DatasetSelafin >( group.get(), reader, nT ); + dataset->setTime( reader->mTimeSteps.at( nT ) ); group->datasets.push_back( dataset ); } - for ( size_t nP = 0; nP < nPoints; nP++ ) + if ( is_vector ) { - double val = it->second.at( nP ); - if ( MDAL::equals( val, 0 ) ) - { - val = std::numeric_limits::quiet_NaN(); - } - if ( is_vector ) + if ( is_x ) { - if ( is_x ) - { - dataset->setValueX( nP, val ); - } - else - { - dataset->setValueY( nP, val ); - } + dataset->setXVariableIndex( nName ); } else { - dataset->setScalarValue( nP, val ); + dataset->setYVariableIndex( nName ); } } + else + { + dataset->setXVariableIndex( nName ); + } + } } - // now activate faces and calculate statistics - for ( auto group : mMesh->datasetGroups ) + // now calculate statistics + for ( const std::shared_ptr group : groupsInOrder ) { - for ( auto dataset : group->datasets ) + for ( const std::shared_ptr dataset : group->datasets ) { - std::shared_ptr dts = std::dynamic_pointer_cast( dataset ); - if ( dts ) - dts->activateFaces( mMesh.get() ); - MDAL::Statistics stats = MDAL::calculateStatistics( dataset ); dataset->setStatistics( stats ); } @@ -530,88 +395,880 @@ void MDAL::DriverSelafin::addData( const std::vector &var_names, MDAL::Statistics stats = MDAL::calculateStatistics( group ); group->setStatistics( stats ); } + + // As everything seems to be ok (no exception thrown), push the groups in the mesh + for ( const std::shared_ptr group : groupsInOrder ) + mesh->datasetGroups.push_back( group ); } -bool MDAL::DriverSelafin::canReadMesh( const std::string &uri ) +std::string MDAL::SelafinFile::readString( size_t len ) { - if ( !MDAL::fileExists( uri ) ) return false; - - std::ifstream in( uri, std::ifstream::in | std::ifstream::binary ); - if ( !in ) return false; - - // The first four bytes of the file should contain the values (in hexadecimal): 00 00 00 50. - // This actually indicates the start of a string of length 80 in the file. - // At position 84 in the file, the eight next bytes should read (in hexadecimal): 00 00 00 50 00 00 00 04. - unsigned char data[ 92 ]; - in.read( reinterpret_cast< char * >( &data ), 92 ); - if ( !in ) - return false; - - if ( data[0] != 0 || data[1] != 0 || - data[2] != 0 || data[3] != 0x50 ) - return false; - - if ( data[84 + 0] != 0 || data[84 + 1] != 0 || - data[84 + 2] != 0 || data[84 + 3] != 0x50 || - data[84 + 4] != 0 || data[84 + 5] != 0 || - data[84 + 6] != 0 || data[84 + 7] != 8 ) - return false; - - return true; + size_t length = readSizet(); + if ( length != len ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to read string" ); + std::string ret = readStringWithoutLength( len ); + ignoreArrayLength(); + return ret; } -std::unique_ptr MDAL::DriverSelafin::load( const std::string &meshFile, const std::string & ) +std::vector MDAL::SelafinFile::readDoubleArr( size_t len ) { - MDAL::Log::resetLastStatus(); - mFileName = meshFile; - mMesh.reset(); - - std::vector var_names; - double xOrigin; - double yOrigin; - size_t nElems; - size_t nPoints; - size_t nPointsPerElem; - std::vector ikle; - std::vector x; - std::vector y; - std::vector data; - DateTime referenceTime; - - try + size_t length = readSizet(); + if ( mStreamInFloatPrecision ) { - parseFile( var_names, - &xOrigin, - &yOrigin, - &nElems, - &nPoints, - &nPointsPerElem, - ikle, - x, - y, - data, - referenceTime ); - - createMesh( xOrigin, - yOrigin, - nElems, - nPoints, - nPointsPerElem, - ikle, - x, - y ); - - addData( var_names, data, nPoints, referenceTime ); + if ( length != len * 4 ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading double array" ); } - catch ( MDAL_Status error ) + else { - MDAL::Log::error( error, name(), "Error while loading file " + meshFile ); - mMesh.reset(); + if ( length != len * 8 ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading double array" ); } - catch ( MDAL::Error err ) + std::vector ret( len ); + for ( size_t i = 0; i < len; ++i ) { - MDAL::Log::error( err, name() ); - mMesh.reset(); + ret[i] = readDouble(); + } + ignoreArrayLength(); + return ret; +} + +std::vector MDAL::SelafinFile::readDoubleArr( const std::streampos &position, size_t offset, size_t len ) +{ + std::vector ret( len ); + std::streamoff off; + if ( mStreamInFloatPrecision ) + off = offset * 4; + else + off = offset * 8; + + mIn.seekg( position + off ); + for ( size_t i = 0; i < len; ++i ) + ret[i] = readDouble(); + + return ret; +} + +std::vector MDAL::SelafinFile::readIntArr( size_t len ) +{ + size_t length = readSizet(); + if ( length != len * 4 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading int array" ); + std::vector ret( len ); + for ( size_t i = 0; i < len; ++i ) + { + ret[i] = readInt(); } - return std::unique_ptr( mMesh.release() ); + ignoreArrayLength(); + return ret; +} + +std::vector MDAL::SelafinFile::readIntArr( const std::streampos &position, size_t offset, size_t len ) +{ + std::vector ret( len ); + std::streamoff off = offset * 4; + + mIn.seekg( position + off ); + for ( size_t i = 0; i < len; ++i ) + ret[i] = readInt(); + + return ret; +} + +std::string MDAL::SelafinFile::readStringWithoutLength( size_t len ) +{ + std::vector ptr( len ); + mIn.read( ptr.data(), static_cast( len ) ); + if ( !mIn ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to open stream for reading string without length" ); + + size_t str_length = 0; + for ( size_t i = len; i > 0; --i ) + { + if ( ptr[i - 1] != 0x20 ) + { + str_length = i; + break; + } + } + std::string ret( ptr.data(), str_length ); + return ret; +} + +double MDAL::SelafinFile::readDouble( ) +{ + double ret; + + if ( mStreamInFloatPrecision ) + { + float ret_f; + if ( !readValue( ret_f, mIn, mIsNativeLittleEndian ) ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Reading double failed" ); + ret = static_cast( ret_f ); + } + else + { + if ( !readValue( ret, mIn, mIsNativeLittleEndian ) ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Reading double failed" ); + } + return ret; +} + + +int MDAL::SelafinFile::readInt( ) +{ + unsigned char data[4]; + + if ( mIn.read( reinterpret_cast< char * >( &data ), 4 ) ) + if ( !mIn ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to open stream for reading int" ); + if ( mIsNativeLittleEndian ) + { + std::reverse( reinterpret_cast< char * >( &data ), reinterpret_cast< char * >( &data ) + 4 ); + } + int var; + memcpy( reinterpret_cast< char * >( &var ), + reinterpret_cast< char * >( &data ), + 4 ); + return var; +} + +size_t MDAL::SelafinFile::readSizet() +{ + int var = readInt( ); + return static_cast( var ); +} + +bool MDAL::SelafinFile::checkIntArraySize( size_t len ) +{ + return ( len * 4 == readSizet() ); +} + +bool MDAL::SelafinFile::checkDoubleArraySize( size_t len ) +{ + if ( mStreamInFloatPrecision ) + { + return ( len * 4 ) == readSizet(); + } + else + { + return ( len * 8 ) == readSizet(); + } +} + +size_t MDAL::SelafinFile::remainingBytes() +{ + if ( mIn.eof() ) + return 0; + return static_cast( mFileSize - mIn.tellg() ); +} + +std::streampos MDAL::SelafinFile::passThroughIntArray( size_t size ) +{ + std::streampos pos = mIn.tellg(); + mIn.seekg( size * 4, std::ios_base::cur ); + ignoreArrayLength(); + return pos; +} + +std::streampos MDAL::SelafinFile::passThroughDoubleArray( size_t size ) +{ + std::streampos pos = mIn.tellg(); + if ( mStreamInFloatPrecision ) + size *= 4; + else + size *= 8; + + mIn.seekg( size, std::ios_base::cur ); + ignoreArrayLength(); + return pos; +} + +void MDAL::SelafinFile::ignore( int len ) +{ + mIn.ignore( len ); + if ( !mIn ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to ignore characters (invalid stream)" ); +} + +void MDAL::SelafinFile::ignoreArrayLength( ) +{ + ignore( 4 ); +} + +// ////////////////////////////// +// DRIVER +// ////////////////////////////// + +MDAL::DriverSelafin::DriverSelafin(): + Driver( "SELAFIN", + "Selafin File", + "*.slf", + Capability::ReadMesh | Capability::SaveMesh | Capability::WriteDatasetsOnVertices | Capability::ReadDatasets + ) +{ +} + +MDAL::DriverSelafin::~DriverSelafin() = default; + +MDAL::DriverSelafin *MDAL::DriverSelafin::create() +{ + return new DriverSelafin(); +} + +bool MDAL::DriverSelafin::canReadMesh( const std::string &uri ) +{ + if ( !MDAL::fileExists( uri ) ) return false; + + try + { + SelafinFile file( uri ); + file.readHeader(); + return true; + } + catch ( ... ) + { + return false; + } +} + +bool MDAL::DriverSelafin::canReadDatasets( const std::string &uri ) +{ + if ( !MDAL::fileExists( uri ) ) return false; + + try + { + SelafinFile file( uri ); + file.readHeader(); + return true; + } + catch ( ... ) + { + return false; + } +} + +std::unique_ptr MDAL::DriverSelafin::load( const std::string &meshFile, const std::string & ) +{ + MDAL::Log::resetLastStatus(); + std::unique_ptr mesh; + + try + { + mesh = SelafinFile::createMesh( meshFile ); + } + catch ( MDAL_Status error ) + { + MDAL::Log::error( error, name(), "Error while loading file " + meshFile ); + mesh.reset(); + } + catch ( MDAL::Error err ) + { + MDAL::Log::error( err, name() ); + mesh.reset(); + } + return mesh; +} + +void MDAL::DriverSelafin::load( const std::string &datFile, MDAL::Mesh *mesh ) +{ + MDAL::Log::resetLastStatus(); + + try + { + SelafinFile::populateDataset( mesh, datFile ); + } + catch ( MDAL_Status error ) + { + MDAL::Log::error( error, name(), "Error while loading dataset in file " + datFile ); + } + catch ( MDAL::Error err ) + { + MDAL::Log::error( err, name() ); + } +} + +bool MDAL::DriverSelafin::persist( MDAL::DatasetGroup *group ) +{ + if ( !group || ( group->dataLocation() != MDAL_DataLocation::DataOnVertices ) ) + { + MDAL::Log::error( MDAL_Status::Err_IncompatibleDataset, name(), "Selafin can store only 2D vertices datasets" ); + return true; + } + + try + { + saveDatasetGroupOnFile( group ); + return false; + } + catch ( MDAL::Error err ) + { + MDAL::Log::error( err, name() ); + return true; + } +} + +bool MDAL::DriverSelafin::saveDatasetGroupOnFile( MDAL::DatasetGroup *datasetGroup ) +{ + const std::string &fileName = datasetGroup->uri(); + + if ( ! MDAL::fileExists( fileName ) ) + { + //create a new mesh file + save( fileName, datasetGroup->mesh() ); + + if ( ! MDAL::fileExists( fileName ) ) + throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, "Unable to create new file" ); + } + + SelafinFile file( fileName ); + return file.addDatasetGroup( datasetGroup ); +} + +// ////////////////////////////// +// MeshSelafin +// ////////////////////////////// + +MDAL::MeshSelafin::MeshSelafin( const std::string &uri, + std::shared_ptr reader ): + Mesh( "SELAFIN", reader->verticesPerFace(), uri ) + , mReader( reader ) +{} + +std::unique_ptr MDAL::MeshSelafin::readVertices() +{ + return std::unique_ptr( new MeshSelafinVertexIterator( + mReader ) ); +} + +std::unique_ptr MDAL::MeshSelafin::readEdges() +{ + return std::unique_ptr(); +} + +std::unique_ptr MDAL::MeshSelafin::readFaces() +{ + return std::unique_ptr( + new MeshSelafinFaceIterator( mReader ) ); +} + +MDAL::BBox MDAL::MeshSelafin::extent() const +{ + if ( mIsExtentUpToDate ) + return mExtent; + calculateExtent(); + return mExtent; +} + +void MDAL::MeshSelafin::calculateExtent() const +{ + size_t bufferSize = 1000; + std::unique_ptr vertexIt( + new MeshSelafinVertexIterator( mReader ) ); + size_t count = 0; + BBox bbox; + std::vector vertices( mReader->verticesCount() ); + size_t index = 0; + do + { + std::vector verticesCoord( bufferSize * 3 ); + count = vertexIt->next( bufferSize, verticesCoord.data() ); + + for ( size_t i = 0; i < count; ++i ) + { + vertices[index + i].x = verticesCoord.at( i * 3 ); + vertices[index + i].y = verticesCoord.at( i * 3 + 1 ); + vertices[index + i].z = verticesCoord.at( i * 3 + 2 ); + } + index += count; + } + while ( count == 0 ); + + mExtent = MDAL::computeExtent( vertices ); + mIsExtentUpToDate = true; +} + +MDAL::MeshSelafinVertexIterator::MeshSelafinVertexIterator( std::shared_ptr reader ): + mReader( reader ) +{} + +size_t MDAL::MeshSelafinVertexIterator::next( size_t vertexCount, double *coordinates ) +{ + size_t count = std::min( vertexCount, mReader->verticesCount() - mPosition ); + + if ( count == 0 ) + return 0; + + std::vector coord = mReader->vertices( mPosition, count ); + + memcpy( coordinates, coord.data(), count * 24 ); + + mPosition += count; + + return count; +} + +MDAL::MeshSelafinFaceIterator::MeshSelafinFaceIterator( std::shared_ptr reader ): + mReader( reader ) +{} + +size_t MDAL::MeshSelafinFaceIterator::next( size_t faceOffsetsBufferLen, int *faceOffsetsBuffer, size_t vertexIndicesBufferLen, int *vertexIndicesBuffer ) +{ + assert( faceOffsetsBuffer ); + assert( vertexIndicesBuffer ); + assert( mReader->verticesPerFace() != 0 ); + + const size_t verticesPerFace = mReader->verticesPerFace(); + size_t count = std::min( faceOffsetsBufferLen, mReader->facesCount() - mPosition ); + + count = std::min( count, vertexIndicesBufferLen / verticesPerFace ); + + if ( count == 0 ) + return 0; + + std::vector indexes = mReader->connectivityIndex( mPosition * verticesPerFace, count * verticesPerFace ); + + if ( indexes.size() != count * verticesPerFace ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading faces" ); + + int vertexLocalIndex = 0; + + for ( size_t i = 0; i < count; i++ ) + { + for ( size_t j = 0; j < verticesPerFace; ++j ) + { + if ( size_t( indexes[j + i * verticesPerFace] ) > mReader->verticesCount() ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading faces" ); + vertexIndicesBuffer[vertexLocalIndex + j] = indexes[j + i * verticesPerFace] - 1; + } + vertexLocalIndex += verticesPerFace; + faceOffsetsBuffer[i] = vertexLocalIndex; + } + + mPosition += count; + + return count; + +} + +MDAL::DatasetSelafin::DatasetSelafin( MDAL::DatasetGroup *parent, + std::shared_ptr reader, size_t timeStepIndex ): + Dataset2D( parent ) + , mReader( reader ) + , mTimeStepIndex( timeStepIndex ) +{ +} + +size_t MDAL::DatasetSelafin::scalarData( size_t indexStart, size_t count, double *buffer ) +{ + count = std::min( mReader->verticesCount() - indexStart, count ); + std::vector values = mReader->datasetValues( mTimeStepIndex, mXVariableIndex, indexStart, count ); + if ( values.size() != count ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading dataset value" ); + + memcpy( buffer, values.data(), count * 8 ); + + return count; +} + +size_t MDAL::DatasetSelafin::vectorData( size_t indexStart, size_t count, double *buffer ) +{ + count = std::min( mReader->verticesCount() - indexStart, count ); + std::vector xValues = mReader->datasetValues( mTimeStepIndex, mXVariableIndex, indexStart, count ); + std::vector yValues = mReader->datasetValues( mTimeStepIndex, mYVariableIndex, indexStart, count ); + + if ( xValues.size() != count || yValues.size() != count ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format problem while reading dataset value" ); + + for ( size_t i = 0; i < count; ++i ) + { + buffer[2 * i] = xValues[i]; + buffer[2 * i + 1] = yValues[i]; + } + + return count; +} + +void MDAL::DatasetSelafin::setXVariableIndex( size_t index ) +{ + mXVariableIndex = index; +} + +void MDAL::DatasetSelafin::setYVariableIndex( size_t index ) +{ + mYVariableIndex = index; +} + +template +static void writeValue( std::ofstream &file, T value ) +{ + bool isNativeLittleEndian = MDAL::isNativeLittleEndian(); + MDAL::writeValue( value, file, isNativeLittleEndian ); +} + +static void writeInt( std::ofstream &file, int i ) +{ + bool isNativeLittleEndian = MDAL::isNativeLittleEndian(); + MDAL::writeValue( i, file, isNativeLittleEndian ); +} + +static void writeStringRecord( std::ofstream &file, const std::string &str ) +{ + writeInt( file, str.size() ); + file.write( str.data(), str.size() ); + writeInt( file, str.size() ); +} + +template +static void writeValueArrayRecord( std::ofstream &file, const std::vector &array ) +{ + writeValue( file, int( array.size()*sizeof( T ) ) ); + for ( const T value : array ) + writeValue( file, value ); + writeValue( file, int( array.size()*sizeof( T ) ) ); +} + +template +static void writeValueArray( std::ofstream &file, const std::vector &array ) +{ + for ( const T value : array ) + writeValue( file, value ); +} + +template +static void writeVertices( std::ofstream &file, MDAL::Mesh *mesh ) +{ + std::unique_ptr vertexIter = mesh->readVertices(); + size_t verticesCount = mesh->verticesCount(); + std::vector xValues( verticesCount ); + std::vector yValues( verticesCount ); + size_t count = 0; + size_t vertexIndex = 0; + size_t bufferSize = BUFFER_SIZE; + do + { + std::vector coordinates( bufferSize * 3 ); + count = vertexIter->next( bufferSize, coordinates.data() ); + for ( size_t i = 0; i < count; ++i ) + { + xValues[vertexIndex + i] = coordinates[i * 3]; + yValues[vertexIndex + i] = coordinates[i * 3 + 1]; + } + vertexIndex += count; + } + while ( count != 0 ); + writeValueArrayRecord( file, xValues ); + writeValueArrayRecord( file, yValues ); +} + +void MDAL::DriverSelafin::save( const std::string &uri, MDAL::Mesh *mesh ) +{ + std::ofstream file( uri.c_str(), std::ofstream::binary ); + + std::string header( "Selafin file created by MDAL library" ); + std::string remainingStr( " ", 72 - header.size() ); + header.append( remainingStr ); + header.append( "SERAFIND" ); + assert( header.size() == 80 ); + writeStringRecord( file, header ); + +// NBV(1) NBV(2) size + std::vector nbvSize( 2 ); + nbvSize[0] = 0; + nbvSize[1] = 0; + writeValueArrayRecord( file, nbvSize ); + + //don't write variable name + + //parameter table, all values are 0 + std::vector param( 10, 0 ); + writeValueArrayRecord( file, param ); + + //NELEM,NPOIN,NDP,1 + size_t verticesPerFace = mesh->faceVerticesMaximumCount(); + size_t verticesCount = mesh->verticesCount(); + size_t facesCount = mesh->facesCount(); + std::vector elem( 4 ); + elem[0] = facesCount; + elem[1] = verticesCount; + elem[2] = verticesPerFace; + elem[3] = 1; + writeValueArrayRecord( file, elem ); + + //connectivity table + int bufferSize = BUFFER_SIZE; + std::vector faceOffsetBuffer( bufferSize ); + std::unique_ptr faceIter = mesh->readFaces(); + size_t count = 0; + writeInt( file, facesCount * verticesPerFace * 4 ); + do + { + std::vector inkle( bufferSize * verticesPerFace ); + count = faceIter->next( bufferSize, faceOffsetBuffer.data(), bufferSize * verticesPerFace, inkle.data() ); + inkle.resize( count * verticesPerFace ); + for ( size_t i = 0; i < inkle.size(); ++i ) + inkle[i]++; + + writeValueArray( file, inkle ); + } + while ( count != 0 ); + writeInt( file, facesCount * verticesPerFace * 4 ); + + // IPOBO filled with 0 + writeValueArrayRecord( file, std::vector( verticesCount, 0 ) ); + + //Vertices + writeVertices( file, mesh ); + + file.close(); +} + +std::string MDAL::DriverSelafin::writeDatasetOnFileSuffix() const +{ + return "slf"; +} + +// return false if fails +static void streamToStream( std::ostream &destination, + std::ifstream &source, + std::ios_base::streampos sourceStartPosition, + std::ios_base::streamoff len, + std::ios_base::streamoff maxBufferSize ) +{ + assert( maxBufferSize != 0 ); + std::ios_base::streampos position = sourceStartPosition; + source.seekg( 0, source.end ); + std::streampos end = std::min( source.tellg(), sourceStartPosition + len ); + source.seekg( sourceStartPosition ); + + while ( position < end ) + { + size_t bufferSize = std::min( maxBufferSize, end - position ); + std::vector buffer( bufferSize ); + source.read( &buffer[0], bufferSize ); + destination.write( &buffer[0], bufferSize ); + position += bufferSize; + } +} + +static void writeScalarDataset( std::ofstream &file, MDAL::Dataset *dataset, bool isFloat ) +{ + size_t valuesCount = dataset->valuesCount(); + size_t bufferSize = BUFFER_SIZE; + int count = 0; + int indexStart = 0; + writeInt( file, valuesCount * ( isFloat ? 4 : 8 ) ); + do + { + std::vector values( bufferSize ); + count = dataset->scalarData( indexStart, bufferSize, values.data() ); + values.resize( count ); + if ( isFloat ) + { + std::vector floatValues( count ); + for ( int i = 0; i < count; ++i ) + floatValues[i] = values[i]; + writeValueArray( file, floatValues ); + } + else + writeValueArray( file, values ); + + indexStart += count; + } + while ( count != 0 ); + writeInt( file, valuesCount * ( isFloat ? 4 : 8 ) ); +} + +template +static void writeVectorDataset( std::ofstream &file, MDAL::Dataset *dataset ) +{ + size_t valuesCount = dataset->valuesCount(); + + std::vector valuesX( valuesCount ); + std::vector valuesY( valuesCount ); + size_t bufferSize = BUFFER_SIZE; + int count = 0; + int indexStart = 0; + do + { + std::vector values( bufferSize * 2 ); + count = dataset->vectorData( indexStart, bufferSize, values.data() ); + values.resize( count * 2 ); + for ( int i = 0; i < count; ++i ) + { + valuesX[indexStart + i] = values[i * 2]; + valuesY[indexStart + i] = values[i * 2 + 1]; + } + indexStart += count; + } + while ( count != 0 ); + writeValueArrayRecord( file, valuesX ); + writeValueArrayRecord( file, valuesY ); +} + +bool MDAL::SelafinFile::addDatasetGroup( MDAL::DatasetGroup *datasetGroup ) +{ + // Create a new file with same data but with another datasetGroup + initialize(); + + if ( !mIn.is_open() ) + return false; + + parseFile(); + + size_t realSize; + if ( mStreamInFloatPrecision ) + realSize = 4; + else + realSize = 8; + + std::string tempFileName = mFileName; + tempFileName.append( ".tmp" ); + + mIn.seekg( 0, mIn.beg ); + std::ofstream out( tempFileName, std::ios_base::binary ); + if ( ! out.is_open() ) + throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, "Unable to add dataset in file" ); + + //write the same header + writeStringRecord( out, readHeader() ); + + //Read the NBV1//NBV2 size, and add 1 to NBV1 + std::vector nbv = readIntArr( 2 ); + + int addedVariable = 1; + if ( !datasetGroup->isScalar() ) + addedVariable = 2; + + nbv[0] = nbv[0] + addedVariable; + writeValueArrayRecord( out, nbv ); + + // write pre-existing dataset name + for ( size_t i = 0; i < mVariableNames.size(); ++i ) + { + std::string variableName = mVariableNames.at( i ); + variableName.resize( 32, ' ' ); + writeStringRecord( out, variableName ); + } + + // write new(s) variable name + std::string datasetGroupName = datasetGroup->name(); + if ( datasetGroup->isScalar() ) + { + datasetGroupName.resize( 32, ' ' ); + writeStringRecord( out, datasetGroupName ); + } + else + { + if ( datasetGroupName.size() > 27 ) + datasetGroupName.resize( 27 ); + std::string xName = datasetGroupName + " along x"; + std::string yName = datasetGroupName + " along y"; + xName.resize( 32 ); + yName.resize( 32 ); + writeStringRecord( out, xName ); + writeStringRecord( out, yName ); + } + + //check if valid reference time + if ( !mReferenceTime.isValid() && datasetGroup->referenceTime().isValid() ) + mReferenceTime = datasetGroup->referenceTime(); + + //handlle time step + if ( mVariableNames.empty() ) //if no variable name, no valid time step before + { + mTimeSteps = std::vector( datasetGroup->datasets.size() ); + for ( size_t i = 0; i < datasetGroup->datasets.size(); ++i ) + { + mTimeSteps[i] = RelativeTimestamp( datasetGroup->datasets.at( i )->timestamp() ); + } + } + else + { + if ( datasetGroup->datasets.size() != mTimeSteps.size() ) + throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Incomaptible dataset group : time steps count are not the same" ); + } + + //parameters table + mParameters[9] = mReferenceTime.isValid() ? 1 : 0; + writeValueArrayRecord( out, mParameters ); + + if ( mReferenceTime.isValid() ) + { + writeValueArrayRecord( out, mReferenceTime.expandToCalendarArray() ); + } + + //elems count + writeValueArrayRecord( out, std::vector {int( mFacesCount ), int( mVerticesCount ), int( mVerticesPerFace ), 1} ); + + //IKLE + writeInt( out, mFacesCount * mVerticesPerFace * 4 ); + streamToStream( out, mIn, mConnectivityStreamPosition, mFacesCount * mVerticesPerFace * 4, BUFFER_SIZE ); + writeInt( out, mFacesCount * mVerticesPerFace * 4 ); + //vertices + + //IPOBO + writeInt( out, mVerticesCount * 4 ); + streamToStream( out, mIn, mIPOBOStreamPosition, mVerticesCount * 4, BUFFER_SIZE ); + writeInt( out, mVerticesCount * 4 ); + + //X Vertices + writeInt( out, mVerticesCount * realSize ); + streamToStream( out, mIn, mXStreamPosition, mVerticesCount * realSize, BUFFER_SIZE ); + writeInt( out, mVerticesCount * realSize ); + //Y Vertices + writeInt( out, mVerticesCount * realSize ); + streamToStream( out, mIn, mYStreamPosition, mVerticesCount * realSize, BUFFER_SIZE ); + writeInt( out, mVerticesCount * realSize ); + + // Write datasets + for ( size_t nT = 0; nT < mTimeSteps.size(); nT++ ) + { + // Time step + if ( mStreamInFloatPrecision ) + { + std::vector time( 1, mTimeSteps.at( nT ).value( RelativeTimestamp::seconds ) ); + writeValueArrayRecord( out, time ); + } + else + { + std::vector time( 1, mTimeSteps.at( nT ).value( RelativeTimestamp::seconds ) ); + writeValueArrayRecord( out, time ); + } + + // First, prexisting datasets from the original file + for ( int i = 0; i < nbv[0] - addedVariable; ++i ) + { + writeInt( out, mVerticesCount * realSize ); + streamToStream( out, mIn, mVariableStreamPosition[i][nT], realSize * mVerticesCount, BUFFER_SIZE ); + writeInt( out, mVerticesCount * realSize ); + } + + // Then, new datasets from the new dataset group + Dataset *dataset = datasetGroup->datasets[nT].get(); + if ( datasetGroup->isScalar() ) + { + writeScalarDataset( out, dataset, mStreamInFloatPrecision ); + } + else + { + if ( mStreamInFloatPrecision ) + writeVectorDataset( out, dataset ); + else + writeVectorDataset( out, dataset ); + } + } + + out.close(); + mIn.close(); + + if ( std::remove( mFileName.c_str() ) != 0 ) + { + std::remove( tempFileName.c_str() ); + throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, "Unable to write dataset in file" ); + } + + std::rename( tempFileName.c_str(), mFileName.c_str() ); + + parseFile(); + + return true; } diff --git a/external/mdal/frmts/mdal_selafin.hpp b/external/mdal/frmts/mdal_selafin.hpp index 08a1e7ad932c..1aec0768db3c 100644 --- a/external/mdal/frmts/mdal_selafin.hpp +++ b/external/mdal/frmts/mdal_selafin.hpp @@ -19,41 +19,293 @@ namespace MDAL { - class SerafinStreamReader + /** + * This class is used to read the selafin file format. + * The file is opened with initialize() and stay opened until this object is destroyed + * + * \note SelafinFile object is shared between different datasets, with the mesh and its iterators. + * As SelafinFile is not thread safe, it has to be shared in the same thread. + * + * This class can be used to create a mesh with all the dataset contained in a file with the static method createMessh() + * It is also pôssible to add all the dataset of a file in a separate existing mesh with the static method populateDataset() + * + * All the method to access with lazy loading to the mesh data or datasets are encapsulted and only accessible by the friend classes : + * MeshSelafin + * MeshSelafinVertexIterator + * MeshSelafinFaceIterator + * DatasetSelafin + * + * This ecapsulation protects these lazy loading access methods because they can't be used before the instance of SelafinFile has been initialized and parsed. + * + */ + class SelafinFile { public: - SerafinStreamReader(); - void initialize( const std::string &fileName ); + //! Constructor + SelafinFile( const std::string &fileName ); - std::string read_string( size_t len ); - std::vector read_double_arr( size_t len ); - std::vector read_int_arr( size_t len ); - std::vector read_size_t_arr( size_t len ); + //! Returns a mesh created with the file + static std::unique_ptr createMesh( const std::string &fileName ); + //! Populates the mesh with dataset from the file + static void populateDataset( Mesh *mesh, const std::string &fileName ); - double read_double( ); - int read_int( ); - size_t read_sizet( ); + //! Read the header of the file and return the project name + std::string readHeader(); + + //! Add the dataset group to the file (persist), replace dataset in the new group by Selafindataset with lazy loading + bool addDatasetGroup( DatasetGroup *datasetGroup ); - size_t remainingBytes(); private: - void ignore_array_length( ); - std::string read_string_without_length( size_t len ); + + //! Initializes and open the file file with the \a fileName + void initialize(); + + //! Extracts data from files + void parseFile(); + + //! Returns the vertices count in the mesh stored in the file + size_t verticesCount(); + //! Returns the faces count in the mesh stored in the file + size_t facesCount(); + //! Returns the vertices count per face for the mesh stored in the file + size_t verticesPerFace(); + + //! Returns \a count values at \a timeStepIndex and \a variableIndex, and an \a offset from the start + std::vector datasetValues( size_t timeStepIndex, size_t variableIndex, size_t offset, size_t count ); + //! Returns \a count vertex indexex in face with an \a offset from the start + std::vector connectivityIndex( size_t offset, size_t count ); + //! Returns \a count vertices with an \a offset from the start + std::vector vertices( size_t offset, size_t count ); + + //! Reads a string record with a size \a len from current position in the stream, throws an exception if the size in not compaitble + std::string readString( size_t len ); + + /** + * Reads a double array record with a size \a len from current position in the stream, + * throws an exception if the size in not compatible + */ + std::vector readDoubleArr( size_t len ); + + /** + * Reads a int array record with a size \a len from current position in the stream, + * throws an exception if the size in not compatible + */ + std::vector readIntArr( size_t len ); + + /** + * Reads some values in a double array record. The values count is \a len, + * the reading begin at the stream \a position with the \a offset + */ + std::vector readDoubleArr( const std::streampos &position, size_t offset, size_t len ); + + /** + * Reads some values in a int array record. The values count is \a len, + * the reading begin at the stream \a position with the \a offset + */ + std::vector readIntArr( const std::streampos &position, size_t offset, size_t len ); + + //! Returns whether there is a int array with size \a len at the current position in the stream + bool checkIntArraySize( size_t len ); + + //! Returns whether there is a double array with size \a len at the current position in the stream + bool checkDoubleArraySize( size_t len ); + + //! Returns the remaining bytes in the stream from current position until the end + size_t remainingBytes(); + + /** + * Set the position in the stream just after the int array with \a size, returns position of the beginning of the array + * The presence of int array can be check with checkIntArraySize() + */ + std::streampos passThroughIntArray( size_t size ); + + /** + * Set the position in the stream just after the double array with \a size, returns position of the beginning of the array + * The presence of double array can be check with checkDoubleArraySize() + */ + std::streampos passThroughDoubleArray( size_t size ); + + double readDouble( ); + int readInt( ); + size_t readSizet( ); + + void ignoreArrayLength( ); + std::string readStringWithoutLength( size_t len ); void ignore( int len ); - bool getStreamPrecision(); + + static void populateDataset( Mesh *mesh, std::shared_ptr reader ); + + // /////// + // attribute updated by parseFile() method + // ////// + std::vector mParameters; + // Dataset + DateTime mReferenceTime; + std::vector> mVariableStreamPosition; //! [variableIndex][timeStep] + std::vector mTimeSteps; + std::vector mVariableNames; + // Mesh + size_t mVerticesCount; + size_t mFacesCount; + size_t mVerticesPerFace; + std::streampos mXStreamPosition; + std::streampos mYStreamPosition; + std::streampos mConnectivityStreamPosition; + std::streampos mIPOBOStreamPosition; + double mXOrigin; + double mYOrigin; std::string mFileName; bool mStreamInFloatPrecision = true; bool mIsNativeLittleEndian = true; long long mFileSize = -1; + std::ifstream mIn; + bool mParsed = false; + + + friend class MeshSelafin; + friend class MeshSelafinVertexIterator; + friend class MeshSelafinFaceIterator; + friend class DatasetSelafin; + }; + + class DatasetSelafin : public Dataset2D + { + public: + /** + * Contructs a dataset with a SelafinFile object and the index of the time step + * + * \note SelafinFile object is shared between different dataset, with the mesh and its iterators. + * As SerafinStreamReader is not thread safe, it has to be shared in the same thread. + * + * Position of array(s) in the stream has to be set after construction (default = begin of the stream), + * see setXStreamPosition() and setYStreamPosition() (X for scalar dataset, X and Y for vector dataset) + */ + DatasetSelafin( DatasetGroup *parent, + std::shared_ptr reader, + size_t timeStepIndex ); + + size_t scalarData( size_t indexStart, size_t count, double *buffer ) override; + size_t vectorData( size_t indexStart, size_t count, double *buffer ) override; + + //! Sets the position of the X array in the stream + void setXVariableIndex( size_t index ); + //! Sets the position of the Y array in the stream + void setYVariableIndex( size_t index ); + + private: + std::shared_ptr mReader; + + size_t mXVariableIndex = 0; + size_t mYVariableIndex = 0; + size_t mTimeStepIndex = 0; + }; + + class MeshSelafinVertexIterator: public MeshVertexIterator + { + public: + /** + * Contructs a vertex iterator with a SerafinFile instance + * + * \note SerafinFile instance is shared between different dataset, with the mesh and its iterators. + * As SerafinStreamReader is not thread safe, it has to be shared in the same thread. + */ + MeshSelafinVertexIterator( std::shared_ptr reader ); + + size_t next( size_t vertexCount, double *coordinates ) override; + + private: + std::shared_ptr mReader; + size_t mPosition = 0; + }; + + class MeshSelafinFaceIterator: public MeshFaceIterator + { + public: + /** + * Contructs a face iterator with a SerafinFile instance + * + * \note SerafinFile instance is shared between different dataset, with the mesh and its iterators. + * As SerafinFile is not thread safe, it has to be shared in the same thread. + */ + MeshSelafinFaceIterator( std::shared_ptr reader ); + + size_t next( size_t faceOffsetsBufferLen, int *faceOffsetsBuffer, size_t vertexIndicesBufferLen, int *vertexIndicesBuffer ) override; + + private: + std::shared_ptr mReader; + size_t mPosition = 0; + }; + + class MeshSelafin: public Mesh + { + public: + /** + * Contructs a dataset with a SerafinFile instance \a reader + * + * \note SerafinFile instance is shared between different dataset, with the mesh and its iterators. + * As SerafinFile is not thread safe, it has to be shared in the same thread. + */ + MeshSelafin( const std::string &uri, + std::shared_ptr reader ); + + std::unique_ptr readVertices() override; + + //! Selafin format doesn't support edges in MDAL, returns a void unique_ptr + std::unique_ptr readEdges() override; + + std::unique_ptr readFaces() override; + + size_t verticesCount() const override {return mReader->verticesCount();} + size_t edgesCount() const override {return 0;} + size_t facesCount() const override {return mReader->facesCount();} + BBox extent() const override; + + private: + mutable bool mIsExtentUpToDate = false; + mutable BBox mExtent; + + std::shared_ptr mReader; + + void calculateExtent() const; }; /** * Serafin format (also called Selafin) * * Binary format for triangular mesh with datasets defined on vertices - * http://www.opentelemac.org/downloads/Archive/v6p0/telemac2d_user_manual_v6p0.pdf Appendix 3 + * Source of this doc come from : + * http://www.opentelemac.org/downloads/MANUALS/TELEMAC-2D/telemac-2d_user_manual_en_v7p0.pdf Appendix 3 * https://www.gdal.org/drv_selafin.html + * + * The Selafin file records are listed below: + * - 1 record containing the title of the study (72 characters) and a 8 characters string indicating the type + * of format (SERAFIN or SERAFIND) + * - record containing the two integers NBV(1)and NBV(2)(number of linear and quadratic variables, NBV(2)with the value of 0 for Telemac, + * cas quadratic values are not saved so far), + * - NBV(1)records containing the names and units of each variable (over 32 characters), + * - 1 record containing the integers table IPARAM(10 integers, of which only the 6are currently being used), + * - if IPARAM (3)!=0: the value corresponds to the x-coordinate of the origin of the mesh, + * - if IPARAM (4)!=0: the value corresponds to the y-coordinate of the origin of the mesh, + * - if IPARAM (7): the value corresponds to the number of planes on the vertical (3D computation), + * - if IPARAM (8)!=0: the value corresponds to the number of boundary points (in parallel), + * - if IPARAM (9)!=0: the value corresponds to the number of interface points (in parallel), + * - if IPARAM(8 ) or IPARAM(9) !=0: the array IPOBO below is replaced by the array KNOLG(total initial number of points). + * All the other numbers are local to the sub-domain, including IKLE. + * + * - if IPARAM(10)= 1: a record containing the computation starting date, + * - 1 record containing the integers NELEM,NPOIN,NDP,1(number of elements, number of points, number of points per element and the value 1), + * - 1 record containing table IKLE(integer array of dimension (NDP,NELEM) which is the connectivity table. + * N.B.: in TELEMAC-2D, the dimensions of this array are (NELEM,NDP)), + * - 1 record containing table IPOBO(integer array of dimension NPOIN); + * the value of one element is 0 for an internal point, and gives the numbering of boundary points for the others, + * - 1 record containing table X(real array of dimension NPOINcontaining the abscissae of the points), + * - 1 record containing table Y(real array of dimension NPOINcontaining the ordinates of the points), + * + * Next, for each time step, the following are found: + * - 1 record containing time T(real), + * - NBV(1)+NBV(2)records containing the results tables for each variable at time T. */ class DriverSelafin: public Driver { @@ -63,40 +315,20 @@ namespace MDAL DriverSelafin *create() override; bool canReadMesh( const std::string &uri ) override; + bool canReadDatasets( const std::string &uri ) override; + std::unique_ptr< Mesh > load( const std::string &meshFile, const std::string &meshName = "" ) override; + void load( const std::string &datFile, Mesh *mesh ) override; + + bool persist( DatasetGroup *group ) override; + + int faceVerticesMaximumCount() const override {return 3;} + void save( const std::string &uri, Mesh *mesh ) override; + + std::string writeDatasetOnFileSuffix() const override; private: - typedef std::map > timestep_map; //TIME (sorted), nodeVal - - void createMesh( double xOrigin, - double yOrigin, - size_t nElems, - size_t nPoints, - size_t nPointsPerElem, - std::vector &ikle, - std::vector &x, - std::vector &y ); - void addData( const std::vector &var_names, - const std::vector &data, - size_t nPoints, - const DateTime &referenceTime ); - void parseFile( std::vector &var_names, - double *xOrigin, - double *yOrigin, - size_t *nElem, - size_t *nPoint, - size_t *nPointsPerElem, - std::vector &ikle, - std::vector &x, - std::vector &y, - std::vector &data, - DateTime &referenceTime ); - - bool getStreamPrecision( std::ifstream &in ); - - std::unique_ptr< MDAL::MemoryMesh > mMesh; - std::string mFileName; - SerafinStreamReader mReader; + bool saveDatasetGroupOnFile( DatasetGroup *datasetGroup ); }; } // namespace MDAL diff --git a/external/mdal/frmts/mdal_sww.cpp b/external/mdal/frmts/mdal_sww.cpp index c9cbc241e87d..1136da8609fa 100644 --- a/external/mdal/frmts/mdal_sww.cpp +++ b/external/mdal/frmts/mdal_sww.cpp @@ -113,7 +113,7 @@ void MDAL::DriverSWW::addBedElevation( const NetCDFFile &ncFile, } else { - MDAL::addBedElevationDatasetGroup( mesh, mesh->vertices ); + MDAL::addBedElevationDatasetGroup( mesh, mesh->vertices() ); } } @@ -447,16 +447,12 @@ std::unique_ptr MDAL::DriverSWW::load( std::unique_ptr< MDAL::MemoryMesh > mesh( new MemoryMesh( name(), - vertices.size(), - 0, - faces.size(), 3, // triangles - computeExtent( vertices ), mFileName ) ); - mesh->faces = faces; - mesh->vertices = vertices; + mesh->setFaces( std::move( faces ) ); + mesh->setVertices( std::move( vertices ) ); // Read times std::vector times = readTimes( ncFile ); diff --git a/external/mdal/frmts/mdal_tuflowfv.cpp b/external/mdal/frmts/mdal_tuflowfv.cpp index 68cabb104528..ae2e05c39e56 100644 --- a/external/mdal/frmts/mdal_tuflowfv.cpp +++ b/external/mdal/frmts/mdal_tuflowfv.cpp @@ -429,7 +429,7 @@ void MDAL::DriverTuflowFV::calculateMaximumLevelCount() void MDAL::DriverTuflowFV::addBedElevation( MDAL::MemoryMesh *mesh ) { - MDAL::addBedElevationDatasetGroup( mesh, mesh->vertices ); + MDAL::addBedElevationDatasetGroup( mesh, mesh->vertices() ); } std::string MDAL::DriverTuflowFV::getCoordinateSystemVariableName() @@ -466,8 +466,10 @@ void MDAL::DriverTuflowFV::parseNetCDFVariableMetadata( int varid, std::string &name, bool *is_vector, bool *isPolar, + bool *invertedDirection, bool *is_x ) { + MDAL_UNUSED( invertedDirection ); *is_vector = false; *is_x = true; *isPolar = false; diff --git a/external/mdal/frmts/mdal_tuflowfv.hpp b/external/mdal/frmts/mdal_tuflowfv.hpp index 4e24773257b5..22ce490b3a91 100644 --- a/external/mdal/frmts/mdal_tuflowfv.hpp +++ b/external/mdal/frmts/mdal_tuflowfv.hpp @@ -129,6 +129,7 @@ namespace MDAL std::string &name, bool *is_vector, bool *isPolar, + bool *invertedDirection, bool *is_x ) override; std::vector> parseClassification( int varid ) const override; std::string getTimeVariableName() const override; diff --git a/external/mdal/frmts/mdal_ugrid.cpp b/external/mdal/frmts/mdal_ugrid.cpp index 69e30523e16c..8a152ad23960 100644 --- a/external/mdal/frmts/mdal_ugrid.cpp +++ b/external/mdal/frmts/mdal_ugrid.cpp @@ -365,7 +365,7 @@ void MDAL::DriverUgrid::populateFaces( MDAL::Faces &faces ) void MDAL::DriverUgrid::addBedElevation( MDAL::MemoryMesh *mesh ) { - if ( mNcFile->hasArr( nodeZVariableName() ) ) MDAL::addBedElevationDatasetGroup( mesh, mesh->vertices ); + if ( mNcFile->hasArr( nodeZVariableName() ) ) MDAL::addBedElevationDatasetGroup( mesh, mesh->vertices() ); } std::string MDAL::DriverUgrid::getCoordinateSystemVariableName() @@ -493,12 +493,13 @@ void MDAL::DriverUgrid::parseNetCDFVariableMetadata( int varid, std::string &name, bool *isVector, bool *isPolar, + bool *invertedDirection, bool *isX ) { - *isVector = false; *isX = true; *isPolar = false; + *invertedDirection = false; std::string longName = mNcFile->getAttrStr( "long_name", varid ); if ( longName.empty() ) @@ -522,6 +523,23 @@ void MDAL::DriverUgrid::parseNetCDFVariableMetadata( int varid, *isX = false; name = MDAL::replace( standardName, "_y_", "" ); } + else if ( MDAL::contains( standardName, "_from_direction" ) ) + { + *isVector = true; + *isPolar = true; + *isX = false; + *invertedDirection = true; + name = MDAL::replace( standardName, "_speed", "_velocity" ); + name = MDAL::replace( name, "_from_direction", "" ); + } + else if ( MDAL::contains( standardName, "_to_direction" ) ) + { + *isVector = true; + *isPolar = true; + *isX = false; + name = MDAL::replace( standardName, "_speed", "_velocity" ); + name = MDAL::replace( name, "_to_direction", "" ); + } else { name = standardName; @@ -544,19 +562,28 @@ void MDAL::DriverUgrid::parseNetCDFVariableMetadata( int varid, name = MDAL::replace( longName, ", y-component", "" ); name = MDAL::replace( name, "v component of ", "" ); } - else if ( MDAL::contains( longName, "velocity magnitude" ) ) + else if ( MDAL::contains( longName, " magnitude" ) ) { *isVector = true; *isPolar = true; *isX = true; - name = MDAL::replace( longName, " magnitude", "" ); + name = MDAL::replace( longName, "speed", "velocity" ); + name = MDAL::removeFrom( name, " magnitude" ); } - else if ( MDAL::contains( longName, "velocity direction" ) ) + else if ( MDAL::contains( longName, "direction" ) ) { *isVector = true; *isPolar = true; *isX = false; - name = MDAL::replace( longName, " direction", "" ); + + // check from_/to_direction in standard_name + std::string standardName = mNcFile->getAttrStr( "standard_name", varid ); + *invertedDirection = MDAL::contains( longName, "from direction" ); + + name = MDAL::replace( longName, "speed", "velocity" ); + name = MDAL::removeFrom( name, " from direction" ); + name = MDAL::removeFrom( name, " to direction" ); + name = MDAL::removeFrom( name, " direction" ); } else { diff --git a/external/mdal/frmts/mdal_ugrid.hpp b/external/mdal/frmts/mdal_ugrid.hpp index c3eed4d6bee1..eed3494465b0 100644 --- a/external/mdal/frmts/mdal_ugrid.hpp +++ b/external/mdal/frmts/mdal_ugrid.hpp @@ -41,7 +41,9 @@ namespace MDAL void parseNetCDFVariableMetadata( int varid, std::string &variableName, std::string &name, - bool *is_vector, bool *isPolar, + bool *is_vector, + bool *isPolar, + bool *invertedDirection, bool *is_x ) override; std::vector> parseClassification( int varid ) const override; std::string getTimeVariableName() const override; diff --git a/external/mdal/frmts/mdal_xms_tin.cpp b/external/mdal/frmts/mdal_xms_tin.cpp index ce9bd41fab4f..01f4ca7691d1 100644 --- a/external/mdal/frmts/mdal_xms_tin.cpp +++ b/external/mdal/frmts/mdal_xms_tin.cpp @@ -148,19 +148,15 @@ std::unique_ptr MDAL::DriverXmsTin::load( const std::string &meshFil std::unique_ptr< MemoryMesh > mesh( new MemoryMesh( DRIVER_NAME, - vertices.size(), - 0, - faces.size(), MAX_VERTICES_PER_FACE_TIN, - computeExtent( vertices ), meshFile ) ); - mesh->faces = faces; - mesh->vertices = vertices; + mesh->setFaces( std::move( faces ) ); + mesh->setVertices( std::move( vertices ) ); // Add Bed Elevation - MDAL::addBedElevationDatasetGroup( mesh.get(), vertices ); + MDAL::addBedElevationDatasetGroup( mesh.get(), mesh->vertices() ); return std::unique_ptr( mesh.release() ); } diff --git a/external/mdal/mdal.cpp b/external/mdal/mdal.cpp index a21fafeff5f7..060bef650c55 100644 --- a/external/mdal/mdal.cpp +++ b/external/mdal/mdal.cpp @@ -21,7 +21,7 @@ static const char *EMPTY_STR = ""; const char *MDAL_Version() { - return "0.6.1"; + return "0.6.90"; } MDAL_Status MDAL_LastStatus() @@ -826,6 +826,7 @@ bool MDAL_G_isInEditMode( MDAL_DatasetGroupH group ) void MDAL_G_closeEditMode( MDAL_DatasetGroupH group ) { + MDAL::Log::resetLastStatus(); if ( !group ) { MDAL::Log::error( MDAL_Status::Err_IncompatibleDataset, "Dataset Group is not valid (null)" ); @@ -870,7 +871,7 @@ const char *MDAL_G_referenceTime( MDAL_DatasetGroupH group ) return EMPTY_STR; } MDAL::DatasetGroup *g = static_cast< MDAL::DatasetGroup * >( group ); - return _return_str( g->referenceTime().toStandartCalendarISO8601() ); + return _return_str( g->referenceTime().toStandardCalendarISO8601() ); } void MDAL_G_setMetadata( MDAL_DatasetGroupH group, const char *key, const char *val ) @@ -1179,3 +1180,25 @@ bool MDAL_G_isTemporal( MDAL_DatasetGroupH group ) MDAL::DatasetGroup *g = static_cast< MDAL::DatasetGroup * >( group ); return g->datasets.size() > 1; } + +const char *MDAL_G_uri( MDAL_DatasetGroupH group ) +{ + if ( !group ) + { + MDAL::Log::error( MDAL_Status::Err_IncompatibleDataset, "Dataset Group is not valid (null)" ); + return EMPTY_STR; + } + MDAL::DatasetGroup *g = static_cast< MDAL::DatasetGroup * >( group ); + return _return_str( g->uri() ); +} +const char *MDAL_DR_writeDatasetsSuffix( MDAL_DriverH driver ) +{ + if ( !driver ) + { + MDAL::Log::error( MDAL_Status::Err_MissingDriver, "Driver is not valid (null)" ); + return EMPTY_STR; + } + + MDAL::Driver *d = static_cast< MDAL::Driver * >( driver ); + return _return_str( d->writeDatasetOnFileSuffix() ); +} diff --git a/external/mdal/mdal_data_model.cpp b/external/mdal/mdal_data_model.cpp index 91319a6d309a..31fea0bb9d28 100644 --- a/external/mdal/mdal_data_model.cpp +++ b/external/mdal/mdal_data_model.cpp @@ -62,6 +62,11 @@ double MDAL::Dataset::time( RelativeTimestamp::Unit unit ) const return mTime.value( unit ); } +MDAL::RelativeTimestamp MDAL::Dataset::timestamp() const +{ + return mTime; +} + void MDAL::Dataset::setTime( double time, RelativeTimestamp::Unit unit ) { mTime = RelativeTimestamp( time, unit ); @@ -208,6 +213,11 @@ std::string MDAL::DatasetGroup::uri() const return mUri; } +void MDAL::DatasetGroup::replaceUri( std::string uri ) +{ + mUri = uri; +} + MDAL::Statistics MDAL::DatasetGroup::statistics() const { return mStatistics; @@ -307,18 +317,10 @@ void MDAL::DatasetGroup::setIsScalar( bool isScalar ) } MDAL::Mesh::Mesh( const std::string &driverName, - size_t verticesCount, - size_t edgesCount, - size_t facesCount, size_t faceVerticesMaximumCount, - MDAL::BBox extent, const std::string &uri ) : mDriverName( driverName ) - , mVerticesCount( verticesCount ) - , mEdgesCount( edgesCount ) - , mFacesCount( facesCount ) , mFaceVerticesMaximumCount( faceVerticesMaximumCount ) - , mExtent( extent ) , mUri( uri ) { } @@ -361,31 +363,11 @@ void MDAL::Mesh::setSourceCrsFromPrjFile( const std::string &filename ) setSourceCrs( proj ); } -size_t MDAL::Mesh::verticesCount() const -{ - return mVerticesCount; -} - -size_t MDAL::Mesh::edgesCount() const -{ - return mEdgesCount; -} - -size_t MDAL::Mesh::facesCount() const -{ - return mFacesCount; -} - std::string MDAL::Mesh::uri() const { return mUri; } -MDAL::BBox MDAL::Mesh::extent() const -{ - return mExtent; -} - std::string MDAL::Mesh::crs() const { return mCrs; diff --git a/external/mdal/mdal_data_model.hpp b/external/mdal/mdal_data_model.hpp index 74968216750a..f04203589338 100644 --- a/external/mdal/mdal_data_model.hpp +++ b/external/mdal/mdal_data_model.hpp @@ -78,6 +78,7 @@ namespace MDAL Mesh *mesh() const; double time( RelativeTimestamp::Unit unit ) const; + RelativeTimestamp timestamp() const; void setTime( double time, RelativeTimestamp::Unit unit = RelativeTimestamp::hours ); void setTime( const RelativeTimestamp &time ); @@ -166,6 +167,7 @@ namespace MDAL void setDataLocation( MDAL_DataLocation dataLocation ); std::string uri() const; + void replaceUri( std::string uri ); Statistics statistics() const; void setStatistics( const Statistics &statistics ); @@ -193,8 +195,8 @@ namespace MDAL const std::string mDriverName; Mesh *mParent = nullptr; bool mIsScalar = true; - bool mIsPolar = true; - std::pair mReferenceAngles = {360, 0}; + bool mIsPolar = false; + std::pair mReferenceAngles = {-360, 0}; //default full rotation is negative to be consistent with usual geographical clockwise MDAL_DataLocation mDataLocation = MDAL_DataLocation::DataOnVertices; std::string mUri; // file/uri from where it came Statistics mStatistics; @@ -236,11 +238,7 @@ namespace MDAL { public: Mesh( const std::string &driverName, - size_t verticesCount, - size_t edgesCount, - size_t facesCount, size_t faceVerticesMaximumCount, - BBox extent, const std::string &uri ); virtual ~Mesh(); @@ -261,21 +259,17 @@ namespace MDAL //! Find a dataset group by name std::shared_ptr group( const std::string &name ); - size_t verticesCount() const; - size_t edgesCount() const; - size_t facesCount() const; + virtual size_t verticesCount() const = 0; + virtual size_t edgesCount() const = 0; + virtual size_t facesCount() const = 0; + virtual BBox extent() const = 0; std::string uri() const; - BBox extent() const; std::string crs() const; size_t faceVerticesMaximumCount() const; private: const std::string mDriverName; - size_t mVerticesCount = 0; // non-zero for any mesh types - size_t mEdgesCount = 0; // usually 0 for 2D meshes/3D meshes - size_t mFacesCount = 0; // usually 0 for 1D meshes size_t mFaceVerticesMaximumCount = 0; //typically 3 or 4, sometimes up to 9 - BBox mExtent; const std::string mUri; // file/uri from where it came std::string mCrs; }; diff --git a/external/mdal/mdal_datetime.cpp b/external/mdal/mdal_datetime.cpp index f62841bf31cf..c5f44ebee495 100644 --- a/external/mdal/mdal_datetime.cpp +++ b/external/mdal/mdal_datetime.cpp @@ -49,7 +49,7 @@ MDAL::DateTime::DateTime( double value, Epoch epoch ): mValid( true ) } } -std::string MDAL::DateTime::toStandartCalendarISO8601() const +std::string MDAL::DateTime::toStandardCalendarISO8601() const { if ( mValid ) { @@ -71,6 +71,23 @@ std::string MDAL::DateTime::toJulianDayString() const return std::to_string( toJulianDay() ); } +std::vector MDAL::DateTime::expandToCalendarArray() const +{ + std::vector dateTimeArray( 6, 0 ); + if ( mValid ) + { + DateTimeValues value = dateTimeGregorianProleptic(); + dateTimeArray[0] = value.year; + dateTimeArray[1] = value.month; + dateTimeArray[2] = value.day; + dateTimeArray[3] = value.hours; + dateTimeArray[4] = value.minutes; + dateTimeArray[5] = int( value.seconds + 0.5 ); + } + + return dateTimeArray; +} + MDAL::DateTime MDAL::DateTime::operator+( const MDAL::RelativeTimestamp &duration ) const { diff --git a/external/mdal/mdal_datetime.hpp b/external/mdal/mdal_datetime.hpp index dc8a92d8aec7..a22bc671b558 100644 --- a/external/mdal/mdal_datetime.hpp +++ b/external/mdal/mdal_datetime.hpp @@ -69,7 +69,7 @@ namespace MDAL //! Returns a string with the date/time expressed in Greogrian proleptic calendar with ISO8601 format (local time zone) //! Do not support negative year - std::string toStandartCalendarISO8601() const; + std::string toStandardCalendarISO8601() const; //! Returns the Julian day value double toJulianDay() const; @@ -77,6 +77,9 @@ namespace MDAL //! Returns the Julain day value expressed with a string std::string toJulianDayString() const; + //! Returns a array of int with {year,month,day,hours,minutes,seconds} with standard calendar format + std::vector expandToCalendarArray() const; + //! operators RelativeTimestamp operator-( const DateTime &other ) const; DateTime operator+( const RelativeTimestamp &duration ) const; diff --git a/external/mdal/mdal_memory_data_model.cpp b/external/mdal/mdal_memory_data_model.cpp index 6ebbc3ca8e94..2e9a1ebae001 100644 --- a/external/mdal/mdal_memory_data_model.cpp +++ b/external/mdal/mdal_memory_data_model.cpp @@ -50,9 +50,10 @@ void MDAL::MemoryDataset2D::activateFaces( MDAL::MemoryMesh *mesh ) // Activate only Faces that do all Vertex's outputs with some data const size_t nFaces = mesh->facesCount(); + const Faces &faces = mesh->faces(); for ( unsigned int idx = 0; idx < nFaces; ++idx ) { - const Face &elem = mesh->faces.at( idx ); + const Face &elem = faces.at( idx ); const std::size_t elemSize = elem.size(); for ( size_t i = 0; i < elemSize; ++i ) { @@ -115,21 +116,12 @@ size_t MDAL::MemoryDataset2D::vectorData( size_t indexStart, size_t count, doubl } MDAL::MemoryMesh::MemoryMesh( const std::string &driverName, - size_t verticesCount, - size_t edgesCount, - size_t facesCount, size_t faceVerticesMaximumCount, - MDAL::BBox extent, const std::string &uri ) : MDAL::Mesh( driverName, - verticesCount, - edgesCount, - facesCount, faceVerticesMaximumCount, - extent, uri ) -{ -} +{} std::unique_ptr MDAL::MemoryMesh::readVertices() { @@ -149,6 +141,27 @@ std::unique_ptr MDAL::MemoryMesh::readFaces() return it; } +void MDAL::MemoryMesh::setVertices( Vertices vertices ) +{ + mExtent = MDAL::computeExtent( vertices ); + mVertices = std::move( vertices ); +} + +void MDAL::MemoryMesh::setFaces( MDAL::Faces faces ) +{ + mFaces = std::move( faces ); +} + +void MDAL::MemoryMesh::setEdges( MDAL::Edges edges ) +{ + mEdges = std::move( edges ); +} + +MDAL::BBox MDAL::MemoryMesh::extent() const +{ + return mExtent; +} + MDAL::MemoryMesh::~MemoryMesh() = default; MDAL::MemoryMeshVertexIterator::MemoryMeshVertexIterator( const MDAL::MemoryMesh *mesh ) @@ -172,6 +185,7 @@ size_t MDAL::MemoryMeshVertexIterator::next( size_t vertexCount, double *coordin return 0; size_t i = 0; + const Vertices &vertices = mMemoryMesh->vertices(); while ( true ) { @@ -181,7 +195,7 @@ size_t MDAL::MemoryMeshVertexIterator::next( size_t vertexCount, double *coordin if ( i >= vertexCount ) break; - const Vertex v = mMemoryMesh->vertices[mLastVertexIndex + i]; + const Vertex &v = vertices[mLastVertexIndex + i]; coordinates[3 * i] = v.x; coordinates[3 * i + 1] = v.y; coordinates[3 * i + 2] = v.z; @@ -209,6 +223,7 @@ size_t MDAL::MemoryMeshEdgeIterator::next( size_t edgeCount, assert( endVertexIndices ); size_t maxEdges = mMemoryMesh->edgesCount(); + const Edges &edges = mMemoryMesh->edges(); if ( edgeCount > maxEdges ) edgeCount = maxEdges; @@ -226,7 +241,7 @@ size_t MDAL::MemoryMeshEdgeIterator::next( size_t edgeCount, if ( i >= edgeCount ) break; - const Edge e = mMemoryMesh->edges[mLastEdgeIndex + i]; + const Edge &e = edges[mLastEdgeIndex + i]; startVertexIndices[i] = e.startVertex; endVertexIndices[i] = e.endVertex; @@ -256,6 +271,7 @@ size_t MDAL::MemoryMeshFaceIterator::next( size_t faceVerticesMaximumCount = mMemoryMesh->faceVerticesMaximumCount(); size_t vertexIndex = 0; size_t faceIndex = 0; + const Faces &faces = mMemoryMesh->faces(); while ( true ) { @@ -268,7 +284,7 @@ size_t MDAL::MemoryMeshFaceIterator::next( if ( mLastFaceIndex + faceIndex >= maxFaces ) break; - const Face &f = mMemoryMesh->faces[mLastFaceIndex + faceIndex]; + const Face &f = faces[mLastFaceIndex + faceIndex]; const std::size_t faceSize = f.size(); for ( size_t faceVertexIndex = 0; faceVertexIndex < faceSize; ++faceVertexIndex ) { diff --git a/external/mdal/mdal_memory_data_model.hpp b/external/mdal/mdal_memory_data_model.hpp index 66d53a37ca40..f88a28db4c44 100644 --- a/external/mdal/mdal_memory_data_model.hpp +++ b/external/mdal/mdal_memory_data_model.hpp @@ -171,22 +171,40 @@ namespace MDAL class MemoryMesh: public Mesh { public: + //! Constructs an empty mesh MemoryMesh( const std::string &driverName, - size_t verticesCount, - size_t edgesCount, - size_t facesCount, size_t faceVerticesMaximumCount, - BBox extent, const std::string &uri ); + ~MemoryMesh() override; std::unique_ptr readVertices() override; std::unique_ptr readEdges() override; std::unique_ptr readFaces() override; - Vertices vertices; - Faces faces; - Edges edges; + const Vertices &vertices() const {return mVertices;} + const Faces &faces() const {return mFaces;} + const Edges &edges() const {return mEdges;} + + //! Sets all vertices using std::move if possible + void setVertices( Vertices vertices ); + + //! Sets all faces using std::move if possible + void setFaces( Faces faces ); + + //! Sets all edges using std::move if possible + void setEdges( Edges edges ); + + size_t verticesCount() const override {return mVertices.size();} + size_t edgesCount() const override {return mEdges.size();} + size_t facesCount() const override {return mFaces.size();} + BBox extent() const override; + + private: + BBox mExtent; + Vertices mVertices; + Faces mFaces; + Edges mEdges; }; class MemoryMeshVertexIterator: public MeshVertexIterator diff --git a/external/mdal/mdal_utils.cpp b/external/mdal/mdal_utils.cpp index 9ca60d64c7e4..e9f4f19eb336 100644 --- a/external/mdal/mdal_utils.cpp +++ b/external/mdal/mdal_utils.cpp @@ -289,6 +289,18 @@ std::string MDAL::replace( const std::string &str, const std::string &substr, co return res; } +std::string MDAL::removeFrom( const std::string &str, const std::string &substr ) +{ + std::string res( str ); + size_t pos = res.rfind( substr ); + if ( pos != std::string::npos ) + { + res = res.substr( 0, pos ); + } + return res; + +} + // http://www.cplusplus.com/faq/sequences/strings/trim/ std::string MDAL::trim( const std::string &s, const std::string &delimiters ) { diff --git a/external/mdal/mdal_utils.hpp b/external/mdal/mdal_utils.hpp index b9d1bb86821d..ac6a4060459c 100644 --- a/external/mdal/mdal_utils.hpp +++ b/external/mdal/mdal_utils.hpp @@ -62,6 +62,8 @@ namespace MDAL bool contains( const std::string &str, const std::string &substr, ContainsBehaviour behaviour = CaseSensitive ); bool contains( const std::vector &list, const std::string &str ); std::string replace( const std::string &str, const std::string &substr, const std::string &replacestr, ContainsBehaviour behaviour = CaseSensitive ); + std::string removeFrom( const std::string &str, const std::string &substr ); + //! left justify and truncate, resulting string will always have width chars std::string leftJustified( const std::string &str, size_t width, char fill = ' ' ); @@ -137,7 +139,7 @@ namespace MDAL //! Adds altitude dataset group to mesh void addFaceScalarDatasetGroup( MDAL::Mesh *mesh, const std::vector &values, const std::string &name ); - //! function used to read all of type of value. Option to change the endianness is provided + //! Reads all of type of value. Option to change the endianness is provided template bool readValue( T &value, std::ifstream &in, bool changeEndianness = false ) { @@ -152,6 +154,19 @@ namespace MDAL return true; } + //! Writes all of type of value. Option to change the endianness is provided + template + void writeValue( T &value, std::ofstream &out, bool changeEndianness = false ) + { + T v = value; + char *const p = reinterpret_cast( &v ); + + if ( changeEndianness ) + std::reverse( p, p + sizeof( T ) ); + + out.write( p, sizeof( T ) ); + } + //! Prepend 0 to string to have n char std::string prependZero( const std::string &str, size_t length ); From de59c54651dc94deebc6c18e76644218f020fccb Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 24 Jun 2020 17:05:57 -0400 Subject: [PATCH 03/10] several minor fixes --- .../mesh/qgsmeshdataprovider.sip.in | 26 +++++++++++++++++++ .../auto_generated/mesh/qgsmeshlayer.sip.in | 2 +- .../auto_generated/qgsprovidermetadata.sip.in | 2 +- src/app/mesh/qgsmeshdatasetgrouptreeview.h | 2 +- src/core/mesh/qgsmeshdataprovider.h | 25 ++++++++++++++++++ src/core/mesh/qgsmeshdataset.h | 11 ++++++-- src/core/mesh/qgsmeshdatasetgroupstore.h | 1 + src/core/mesh/qgsmeshlayer.h | 3 +-- .../meshmemory/qgsmeshmemorydataprovider.cpp | 1 - src/core/qgsprovidermetadata.h | 4 +-- 10 files changed, 67 insertions(+), 10 deletions(-) diff --git a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in index 02f7b01f648f..b23497cd3b2d 100644 --- a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in @@ -327,16 +327,42 @@ On success, the mesh's dataset group count is changed .. versionadded:: 3.12.3 %End + virtual bool persistDatasetGroup( const QString &outputFilePath, const QString &outputDriver, QgsMeshDatasetSourceInterface *source, int datasetGroupIndex ) = 0; +%Docstring +Saves a an existing dataset group provided by ``source`` to a file with a specified driver + +On success, the mesh's dataset group count is changed + +:param outputFilePath: destination path of the stored file +:param outputDriver: output driver name +:param source: source of the dataset group +:param datasetGroupIndex: index of the dataset group in the ``source`` + +:return: ``True`` on failure, ``False`` on success + + +.. versionadded:: 3.16 +%End QgsMeshDatasetIndex datasetIndexAtTime( const QDateTime &referenceTime, int groupIndex, quint64 time, QgsMeshDataProviderTemporalCapabilities::MatchingTemporalDatasetMethod method ) const; +%Docstring +Returns the dataset index of the dataset in a specific dataet group at ``time`` from the ``reference`` time + +:param referenceTime: the reference time from where to find the dataset +:param groupIndex: the index of the dataset group +:param time: the relative time from reference time +:param method: the method used to check the time + +:return: the dataset index +%End protected: }; diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index b17cc3ba3448..00c58ef7fa69 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -11,7 +11,6 @@ - class QgsMeshLayer : QgsMapLayer { %Docstring @@ -368,6 +367,7 @@ See :py:func:`QgsMeshDatasetMetadata.isVector()` to check if the returned value returns invalid block for DataOnFaces and DataOnVertices. %End + QgsMeshDataBlock areFacesActive( const QgsMeshDatasetIndex &index, int faceIndex, int count ) const; %Docstring Returns whether the faces are active for particular dataset diff --git a/python/core/auto_generated/qgsprovidermetadata.sip.in b/python/core/auto_generated/qgsprovidermetadata.sip.in index f12442a40ee2..cb166b080e3a 100644 --- a/python/core/auto_generated/qgsprovidermetadata.sip.in +++ b/python/core/auto_generated/qgsprovidermetadata.sip.in @@ -47,7 +47,7 @@ Constructs default metadata without any capabilities QgsMeshDriverMetadata( const QString &name, const QString &description, const MeshDriverCapabilities &capabilities, - const QString &writeDatasetOnFileSuffix); + const QString &writeDatasetOnFileSuffix ); %Docstring Constructs driver metadata with selected capabilities diff --git a/src/app/mesh/qgsmeshdatasetgrouptreeview.h b/src/app/mesh/qgsmeshdatasetgrouptreeview.h index 38f4c21708c7..c4176517241a 100644 --- a/src/app/mesh/qgsmeshdatasetgrouptreeview.h +++ b/src/app/mesh/qgsmeshdatasetgrouptreeview.h @@ -46,7 +46,7 @@ class QgsMeshDatasetGroupSaveMenu: public QObject void datasetGroupSaved(); private: - QgsMeshLayer *mMeshLayer; + QgsMeshLayer *mMeshLayer = nullptr; void saveDatasetGroup( int datasetGroup, const QString &driver, const QString &fileSuffix ); }; diff --git a/src/core/mesh/qgsmeshdataprovider.h b/src/core/mesh/qgsmeshdataprovider.h index 2ab8fb0a6c26..7264bb3f6ddc 100644 --- a/src/core/mesh/qgsmeshdataprovider.h +++ b/src/core/mesh/qgsmeshdataprovider.h @@ -337,12 +337,37 @@ class CORE_EXPORT QgsMeshDatasetSourceInterface SIP_ABSTRACT const QVector × ) = 0; + + /** + * Saves a an existing dataset group provided by \a source to a file with a specified driver + * + * On success, the mesh's dataset group count is changed + * + * \param outputFilePath destination path of the stored file + * \param outputDriver output driver name + * \param source source of the dataset group + * \param datasetGroupIndex index of the dataset group in the \a source + * + * \returns TRUE on failure, FALSE on success + * + * \since QGIS 3.16 + */ virtual bool persistDatasetGroup( const QString &outputFilePath, const QString &outputDriver, QgsMeshDatasetSourceInterface *source, int datasetGroupIndex ) = 0; + /** + * Returns the dataset index of the dataset in a specific dataet group at \a time from the \a reference time + * + * \param referenceTime the reference time from where to find the dataset + * \param groupIndex the index of the dataset group + * \param time the relative time from reference time + * \param method the method used to check the time + * + * \return the dataset index + */ QgsMeshDatasetIndex datasetIndexAtTime( const QDateTime &referenceTime, int groupIndex, quint64 time, diff --git a/src/core/mesh/qgsmeshdataset.h b/src/core/mesh/qgsmeshdataset.h index f1eceb93db02..85ee582d9b9b 100644 --- a/src/core/mesh/qgsmeshdataset.h +++ b/src/core/mesh/qgsmeshdataset.h @@ -765,7 +765,10 @@ class CORE_EXPORT QgsMeshDatasetGroupTreeItem class CORE_EXPORT QgsMeshDataset { public: + //! Constructor QgsMeshDataset() = default; + + //! Destructor virtual ~QgsMeshDataset() = default; //! Returns the value with index \a valueIndex @@ -801,6 +804,7 @@ class CORE_EXPORT QgsMeshDataset class CORE_EXPORT QgsMeshMemoryDataset: public QgsMeshDataset { public: + //! Constructor QgsMeshMemoryDataset() = default; QgsMeshDatasetValue datasetValue( int valueIndex ) const override; @@ -906,9 +910,12 @@ class CORE_EXPORT QgsMeshDatasetGroup class CORE_EXPORT QgsMeshMemoryDatasetGroup: public QgsMeshDatasetGroup { public: + //! Constructor QgsMeshMemoryDatasetGroup() = default; - QgsMeshMemoryDatasetGroup( const QString &nm ); - QgsMeshMemoryDatasetGroup( const QString &nm, QgsMeshDatasetGroupMetadata::DataType dataType ); + //! Constructor with the \a name of the group + QgsMeshMemoryDatasetGroup( const QString &name ); + //! Constructor with the \a name of the group and the type of data \a dataType + QgsMeshMemoryDatasetGroup( const QString &name, QgsMeshDatasetGroupMetadata::DataType dataType ); int datasetCount() const override; QgsMeshDatasetMetadata datasetMetadata( int datasetIndex ) override; diff --git a/src/core/mesh/qgsmeshdatasetgroupstore.h b/src/core/mesh/qgsmeshdatasetgroupstore.h index 91679567f2a2..35cce3478196 100644 --- a/src/core/mesh/qgsmeshdatasetgroupstore.h +++ b/src/core/mesh/qgsmeshdatasetgroupstore.h @@ -88,6 +88,7 @@ class QgsMeshExtraDatasetStore: public QgsMeshDatasetSourceInterface * Class used to register and access all the dataset groups related to a mesh layer * * The registered dataset group are : + * * - the ones from the data provider of the mesh layer * - extra dataset group that can be added, for example by the mesh calculator * diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index 0da43c05441c..a85d607daef9 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -38,8 +38,6 @@ class QgsMesh3dAveragingMethod; class QgsMeshLayerTemporalProperties; class QgsMeshDatasetGroupStore; -struct QgsMeshMemoryDatasetGroup; - /** * \ingroup core * @@ -417,6 +415,7 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer * returns invalid block for DataOnFaces and DataOnVertices. */ bool isFaceActive( const QgsMeshDatasetIndex &index, int faceIndex ) const; + /** * Returns whether the faces are active for particular dataset * diff --git a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp index 510a60b5ae16..7eb99d8c55de 100644 --- a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp +++ b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp @@ -20,7 +20,6 @@ #include "qgsmeshdataprovidertemporalcapabilities.h" #include "qgsmeshlayerutils.h" #include "qgstriangularmesh.h" -#include "qgsmeshmemorydataprovider.h" #include #define TEXT_PROVIDER_KEY QStringLiteral( "mesh_memory" ) diff --git a/src/core/qgsprovidermetadata.h b/src/core/qgsprovidermetadata.h index 0550a18dc11c..611239f7b6d2 100644 --- a/src/core/qgsprovidermetadata.h +++ b/src/core/qgsprovidermetadata.h @@ -82,7 +82,7 @@ class CORE_EXPORT QgsMeshDriverMetadata QgsMeshDriverMetadata( const QString &name, const QString &description, const MeshDriverCapabilities &capabilities, - const QString &writeDatasetOnFileSuffix); + const QString &writeDatasetOnFileSuffix ); /** * Returns the capabilities for this driver. @@ -104,7 +104,7 @@ class CORE_EXPORT QgsMeshDriverMetadata */ QString writeDatasetOnFileSuffix() const; -private: + private: QString mName; QString mDescription; MeshDriverCapabilities mCapabilities; From f35334719382dbd5c0af9ad2f0a2de400d42f226 Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 24 Jun 2020 17:32:01 -0400 Subject: [PATCH 04/10] other fixes --- external/mdal/api/mdal.h | 2 ++ src/analysis/mesh/qgsmeshcalcutils.h | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/external/mdal/api/mdal.h b/external/mdal/api/mdal.h index 42b711622469..269a15eec778 100644 --- a/external/mdal/api/mdal.h +++ b/external/mdal/api/mdal.h @@ -216,8 +216,10 @@ MDAL_EXPORT MDAL_MeshH MDAL_LoadMesh( const char *uri ); * not thread-safe and valid only till next call * * Parameter uri can be in format: + * * - :"meshfile" - function then returns uris with provided driver and meshfile * - "meshfile" or meshfile - function then finds proper driver and returns uris with it + * * The uris can be used directly in MDAL_LoadMesh to load particular meshes * * \since MDAL 0.6.0 diff --git a/src/analysis/mesh/qgsmeshcalcutils.h b/src/analysis/mesh/qgsmeshcalcutils.h index 0aad8a6d9f66..2bc9dd48e1e3 100644 --- a/src/analysis/mesh/qgsmeshcalcutils.h +++ b/src/analysis/mesh/qgsmeshcalcutils.h @@ -36,9 +36,6 @@ #include "qgsmeshdataprovider.h" #include "qgis_analysis.h" -struct QgsMeshMemoryDatasetGroup; -struct QgsMeshMemoryDataset; - /** * \ingroup analysis * \class QgsMeshCalcUtils From b9ae988a267f0d0127154a2e244aa08d86b7d70a Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 24 Jun 2020 19:32:22 -0400 Subject: [PATCH 05/10] other fixes and override QgsMapLayer::IsTemporary() --- .../auto_generated/mesh/qgsmeshcalculator.sip.in | 1 - python/core/auto_generated/mesh/qgsmeshlayer.sip.in | 1 + python/core/auto_generated/qgsmaplayer.sip.in | 5 +++-- src/analysis/mesh/qgsmeshcalcnode.h | 2 -- src/analysis/mesh/qgsmeshcalculator.h | 3 --- src/app/mesh/qgsmeshdatasetgrouptreeview.cpp | 12 ++++++------ src/app/qgisapp.cpp | 9 +++------ src/core/mesh/qgsmeshlayer.cpp | 8 ++++++++ src/core/mesh/qgsmeshlayer.h | 1 + src/core/qgsmaplayer.h | 5 +++-- 10 files changed, 25 insertions(+), 22 deletions(-) diff --git a/python/analysis/auto_generated/mesh/qgsmeshcalculator.sip.in b/python/analysis/auto_generated/mesh/qgsmeshcalculator.sip.in index a10d6430dac3..833832337dbe 100644 --- a/python/analysis/auto_generated/mesh/qgsmeshcalculator.sip.in +++ b/python/analysis/auto_generated/mesh/qgsmeshcalculator.sip.in @@ -10,7 +10,6 @@ - class QgsMeshCalculator { %Docstring diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index 00c58ef7fa69..605181c4489d 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -144,6 +144,7 @@ QgsMeshLayer cannot be copied. virtual QStringList subLayers() const; + bool isTemporary() const; QString providerType() const; %Docstring diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in index cc8abf60221c..837bee8cb7de 100644 --- a/python/core/auto_generated/qgsmaplayer.sip.in +++ b/python/core/auto_generated/qgsmaplayer.sip.in @@ -523,8 +523,9 @@ Returns ``True`` if the layer is considered a spatial layer, ie it has some form %Docstring Returns ``True`` if the layer is considered a temporary layer. -These include memory-only layers such as those created by the "memory" data provider, or layers -stored inside a local temporary folder (such as the "/tmp" folder on Linux). +These include memory-only layers such as those created by the "memory" data provider, layers +stored inside a local temporary folder (such as the "/tmp" folder on Linux) +or layer with temporary data (as temporary mesh layer dataset) .. versionadded:: 3.10.1 %End diff --git a/src/analysis/mesh/qgsmeshcalcnode.h b/src/analysis/mesh/qgsmeshcalcnode.h index 0ae5f1c1b61e..418d81151112 100644 --- a/src/analysis/mesh/qgsmeshcalcnode.h +++ b/src/analysis/mesh/qgsmeshcalcnode.h @@ -32,8 +32,6 @@ #include "qgsmeshcalcutils.h" -struct QgsMeshMemoryDatasetGroup; - /** * \ingroup analysis * \class QgsMeshCalcNode diff --git a/src/analysis/mesh/qgsmeshcalculator.h b/src/analysis/mesh/qgsmeshcalculator.h index 51abf66e58c8..54955721c3d4 100644 --- a/src/analysis/mesh/qgsmeshcalculator.h +++ b/src/analysis/mesh/qgsmeshcalculator.h @@ -30,9 +30,6 @@ #include "qgsprovidermetadata.h" #include "qgsfeedback.h" -struct QgsMeshMemoryDatasetGroup; -struct QgsMeshMemoryDataset; - /** * \ingroup analysis * \class QgsMeshCalculator diff --git a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp index f6c21eb09fd6..ddbce200932d 100644 --- a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp +++ b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp @@ -627,7 +627,7 @@ QMenu *QgsMeshDatasetGroupTreeView::createContextMenu() break; case QgsMeshDatasetGroupTreeItem::Memory: case QgsMeshDatasetGroupTreeItem::OnTheFly: - contextMenu->addAction( tr( "Remove dataset group" ), this, &QgsMeshDatasetGroupTreeView::removeCurrentItem ); + contextMenu->addAction( tr( "Remove Dataset Group" ), this, &QgsMeshDatasetGroupTreeView::removeCurrentItem ); mSaveMenu->createSaveMenu( groupIndex, contextMenu ); break; } @@ -655,7 +655,7 @@ QMenu *QgsMeshDatasetGroupSaveMenu::createSaveMenu( int groupIndex, QMenu *paren return nullptr; QMenu *menu = new QMenu( parentMenu ); - menu->setTitle( QObject::tr( "Save Datasets group as..." ) ); + menu->setTitle( QObject::tr( "Save Datasets Group as..." ) ); QgsMeshDatasetGroupMetadata groupMeta = mMeshLayer->datasetGroupMetadata( groupIndex ); QgsProviderMetadata *providerMetadata = QgsProviderRegistry::instance()->providerMetadata( mMeshLayer->dataProvider()->name() ); @@ -683,7 +683,7 @@ QMenu *QgsMeshDatasetGroupSaveMenu::createSaveMenu( int groupIndex, QMenu *paren if ( menu->actions().isEmpty() ) { - menu->addAction( QObject::tr( "No driver available to write this dataset group" ) ); + menu->addAction( QObject::tr( "No Driver Available to Write this Dataset Group" ) ); menu->actions().last()->setDisabled( true ); } @@ -709,7 +709,7 @@ void QgsMeshDatasetGroupSaveMenu::saveDatasetGroup( int datasetGroup, const QStr filter = QStringLiteral( "%1 (*.%2)" ).arg( driver ).arg( fileSuffix ); QString exportFileDir = settings.value( QStringLiteral( "lastMeshDatasetDir" ), QDir::homePath(), QgsSettings::App ).toString(); QString saveFileName = QFileDialog::getSaveFileName( nullptr, - QObject::tr( "Save mesh datasets" ), + QObject::tr( "Save Mesh Datasets" ), exportFileDir, filter ); @@ -722,12 +722,12 @@ void QgsMeshDatasetGroupSaveMenu::saveDatasetGroup( int datasetGroup, const QStr if ( mMeshLayer->saveDataset( saveFileName, datasetGroup, driver ) ) { - QMessageBox::warning( nullptr, QObject::tr( "Save mesh datasets" ), QObject::tr( "Saving datasets fails" ) ); + QMessageBox::warning( nullptr, QObject::tr( "Save Mesh Datasets" ), QObject::tr( "Saving datasets fails" ) ); } else { emit datasetGroupSaved(); - QMessageBox::information( nullptr, QObject::tr( "Save mesh datasets" ), QObject::tr( "Datasets successfully saved on file" ) ); + QMessageBox::information( nullptr, QObject::tr( "Save Mesh Datasets" ), QObject::tr( "Datasets successfully saved on file" ) ); } } diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index be82ebe189ff..6e305cff6994 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -12856,14 +12856,11 @@ bool QgisApp::checkMemoryLayers() } } else if ( it.value() && it.value()->isTemporary() ) - hasTemporaryLayers = true; - else if ( it.value() && it.value()->type() == QgsMapLayerType::MeshLayer ) { - QgsMeshLayer *meshLayer = qobject_cast< QgsMeshLayer * >( it.value() ); - if ( meshLayer && meshLayer->extraDatasetGroupCount() > 0 ) - { + if ( it.value()->type() == QgsMapLayerType::MeshLayer ) hasMemoryMeshLayerDatasetGroup = true; - } + else + hasTemporaryLayers = true; } } diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index 0798cd68711d..f48a234142aa 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -1222,6 +1222,14 @@ QStringList QgsMeshLayer::subLayers() const return QStringList(); } +bool QgsMeshLayer::isTemporary() const +{ + if ( mDatasetGroupStore && mDatasetGroupStore->extraDatasetGroupCount() > 0 ) + return true; + + return false; +} + bool QgsMeshLayer::setDataProvider( QString const &provider, const QgsDataProvider::ProviderOptions &options ) { delete mDataProvider; diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index a85d607daef9..ceeabb4b7015 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -170,6 +170,7 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer QgsMapLayerTemporalProperties *temporalProperties() override; void reload() override; QStringList subLayers() const override; + bool isTemporary() const; //! Returns the provider type for this layer QString providerType() const; diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index 94ec7b7e76c6..7087550b14bc 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -532,8 +532,9 @@ class CORE_EXPORT QgsMapLayer : public QObject /** * Returns TRUE if the layer is considered a temporary layer. * - * These include memory-only layers such as those created by the "memory" data provider, or layers - * stored inside a local temporary folder (such as the "/tmp" folder on Linux). + * These include memory-only layers such as those created by the "memory" data provider, layers + * stored inside a local temporary folder (such as the "/tmp" folder on Linux) + * or layer with temporary data (as temporary mesh layer dataset) * * \since QGIS 3.10.1 */ From 5cb76e6adccdd4e8b6030612cc5ef3b8f1cf8a6f Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 24 Jun 2020 19:55:35 -0400 Subject: [PATCH 06/10] add a message box before removing dataset group and minor fixes --- external/mdal/api/mdal.h | 4 ++-- src/app/mesh/qgsmeshdatasetgrouptreeview.cpp | 3 ++- src/core/mesh/qgsmeshlayer.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/external/mdal/api/mdal.h b/external/mdal/api/mdal.h index 269a15eec778..993ca86dea1d 100644 --- a/external/mdal/api/mdal.h +++ b/external/mdal/api/mdal.h @@ -217,8 +217,8 @@ MDAL_EXPORT MDAL_MeshH MDAL_LoadMesh( const char *uri ); * * Parameter uri can be in format: * - * - :"meshfile" - function then returns uris with provided driver and meshfile - * - "meshfile" or meshfile - function then finds proper driver and returns uris with it + * - :"meshfile" - function then returns uris with provided driver and meshfile + * - "meshfile" or meshfile - function then finds proper driver and returns uris with it * * The uris can be used directly in MDAL_LoadMesh to load particular meshes * diff --git a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp index ddbce200932d..1c8a75c939be 100644 --- a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp +++ b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp @@ -596,7 +596,8 @@ void QgsMeshDatasetGroupTreeView::contextMenuEvent( QContextMenuEvent *event ) void QgsMeshDatasetGroupTreeView::removeCurrentItem() { - mModel->removeItem( currentIndex() ); + if ( QMessageBox::question( this, tr( "Remove Dataset Group" ), tr( "Remove dataset group?" ) ) == QMessageBox::Ok ) + mModel->removeItem( currentIndex() ); } void QgsMeshDatasetGroupTreeView::onDatasetGroupSaved() diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index ceeabb4b7015..8df9bd3114b3 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -170,7 +170,7 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer QgsMapLayerTemporalProperties *temporalProperties() override; void reload() override; QStringList subLayers() const override; - bool isTemporary() const; + bool isTemporary() const override; //! Returns the provider type for this layer QString providerType() const; From aa5dafee3f93d4e9ce0d5fcdecc73fa6be3e15a4 Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 24 Jun 2020 20:14:37 -0400 Subject: [PATCH 07/10] fix sip --- python/core/auto_generated/mesh/qgsmeshlayer.sip.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index 605181c4489d..a15ec95464ed 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -144,7 +144,8 @@ QgsMeshLayer cannot be copied. virtual QStringList subLayers() const; - bool isTemporary() const; + virtual bool isTemporary() const; + QString providerType() const; %Docstring From e37187a20f5e6aa2999ee7f56062f465f3e738bc Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 24 Jun 2020 21:19:50 -0400 Subject: [PATCH 08/10] fix compare string in test --- tests/src/core/testqgsmeshlayer.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/src/core/testqgsmeshlayer.cpp b/tests/src/core/testqgsmeshlayer.cpp index 4ce5d9bc2a13..a10dcf113087 100644 --- a/tests/src/core/testqgsmeshlayer.cpp +++ b/tests/src/core/testqgsmeshlayer.cpp @@ -1363,7 +1363,7 @@ void TestQgsMeshLayer::test_memory_dataset_group() std::unique_ptr goodDatasetGroupVertices( new QgsMeshMemoryDatasetGroup ); std::unique_ptr badDatasetGroupVertices( new QgsMeshMemoryDatasetGroup ); - goodDatasetGroupVertices->setName( "good vertices datasets" ); + goodDatasetGroupVertices->setName( QStringLiteral( "good vertices datasets" ) ); goodDatasetGroupVertices->setDataType( QgsMeshDatasetGroupMetadata::DataOnVertices ); badDatasetGroupVertices->setDataType( QgsMeshDatasetGroupMetadata::DataOnVertices ); for ( int i = 1; i < 10; i++ ) @@ -1385,7 +1385,8 @@ void TestQgsMeshLayer::test_memory_dataset_group() QCOMPARE( mMdalLayer->datasetGroupCount(), 6 ); QCOMPARE( mMdalLayer->extraDatasetGroupCount(), 1 ); - std::unique_ptr goodDatasetGroupFaces( new QgsMeshMemoryDatasetGroup( "good faces datasets", QgsMeshDatasetGroupMetadata::DataOnFaces ) ); + std::unique_ptr goodDatasetGroupFaces( + new QgsMeshMemoryDatasetGroup( QStringLiteral( "good faces datasets" ), QgsMeshDatasetGroupMetadata::DataOnFaces ) ); std::unique_ptr badDatasetGroupFaces( new QgsMeshMemoryDatasetGroup ); badDatasetGroupFaces->setDataType( QgsMeshDatasetGroupMetadata::DataOnFaces ); for ( int i = 1; i < 10; i++ ) @@ -1407,8 +1408,8 @@ void TestQgsMeshLayer::test_memory_dataset_group() QCOMPARE( mMdalLayer->datasetGroupCount(), 7 ); QCOMPARE( mMdalLayer->extraDatasetGroupCount(), 2 ); - QCOMPARE( mMdalLayer->datasetGroupMetadata( 5 ).name(), "good vertices datasets" ); - QCOMPARE( mMdalLayer->datasetGroupMetadata( 6 ).name(), "good faces datasets" ); + QCOMPARE( mMdalLayer->datasetGroupMetadata( 5 ).name(), QStringLiteral( "good vertices datasets" ) ); + QCOMPARE( mMdalLayer->datasetGroupMetadata( 6 ).name(), QStringLiteral( "good faces datasets" ) ); QTemporaryFile file; QVERIFY( file.open() ); @@ -1418,8 +1419,8 @@ void TestQgsMeshLayer::test_memory_dataset_group() QCOMPARE( mMdalLayer->datasetGroupCount(), 7 ); QCOMPARE( mMdalLayer->extraDatasetGroupCount(), 1 ); - QCOMPARE( mMdalLayer->datasetGroupMetadata( 5 ).name(), "good vertices datasets" ); - QCOMPARE( mMdalLayer->datasetGroupMetadata( 6 ).name(), "good faces datasets" ); + QCOMPARE( mMdalLayer->datasetGroupMetadata( 5 ).name(), QStringLiteral( "good vertices datasets" ) ); + QCOMPARE( mMdalLayer->datasetGroupMetadata( 6 ).name(), QStringLiteral( "good faces datasets" ) ); } From b5a16fae63e10793a46a15e393a656805a3b77be Mon Sep 17 00:00:00 2001 From: vcloarec Date: Mon, 29 Jun 2020 20:23:36 -0400 Subject: [PATCH 09/10] post fix --> pre fix operator --- src/core/mesh/qgsmeshdatasetgroupstore.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/mesh/qgsmeshdatasetgroupstore.cpp b/src/core/mesh/qgsmeshdatasetgroupstore.cpp index 4fe9a7f4cca1..be1359629289 100644 --- a/src/core/mesh/qgsmeshdatasetgroupstore.cpp +++ b/src/core/mesh/qgsmeshdatasetgroupstore.cpp @@ -246,7 +246,7 @@ QDomElement QgsMeshDatasetGroupStore::writeXml( QDomDocument &doc, const QgsRead elemDataset.setAttribute( QStringLiteral( "source-index" ), it.value().second ); storeElement.appendChild( elemDataset ); } - it++; + ++it; } return storeElement; @@ -332,7 +332,7 @@ void QgsMeshDatasetGroupStore::removePersistentProvider() if ( it.value().first == mPersistentProvider ) it = mRegistery.erase( it ); else - it++; + ++it; } mPersistentProvider = nullptr; @@ -346,7 +346,7 @@ int QgsMeshDatasetGroupStore::newIndex() { if ( index <= it.key() ) index = it.key() + 1; - it++; + ++it; } return index; } @@ -377,7 +377,7 @@ void QgsMeshDatasetGroupStore::eraseExtraDataset( int indexInExtraStore ) int localIndex = it.value().second; if ( it.value().first == mExtraDatasets.get() && localIndex > indexInExtraStore ) it->second = localIndex - 1; - it++; + ++it; } } @@ -388,7 +388,7 @@ int QgsMeshDatasetGroupStore::nativeIndexToGroupIndex( QgsMeshDatasetSourceInter { if ( it.value() == DatasetGroup{source, nativeIndex} ) return it.key(); - it++; + ++it; } return -1; } @@ -450,7 +450,7 @@ void QgsMeshDatasetGroupStore::unregisterGroupNotPresentInTree() eraseDatasetGroup( datasetGroup ); //remove from where the dataset group is stored } else - it++; + ++it; } } From 404091c3c7fdee20aeaece6bef81eee5f557312b Mon Sep 17 00:00:00 2001 From: vcloarec Date: Tue, 30 Jun 2020 10:42:15 -0400 Subject: [PATCH 10/10] change memory icon policy --- src/app/mesh/qgsmeshdatasetgrouptreeview.cpp | 21 +++++++++++--------- src/app/mesh/qgsmeshdatasetgrouptreeview.h | 10 +++++++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp index 1c8a75c939be..975f08a5486c 100644 --- a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp +++ b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp @@ -350,7 +350,6 @@ QgsMeshDatasetGroupTreeItemDelagate::QgsMeshDatasetGroupTreeItemDelagate( QObjec , mScalarDeselectedPixmap( QStringLiteral( ":/images/themes/default/propertyicons/meshcontoursoff.svg" ) ) , mVectorSelectedPixmap( QStringLiteral( ":/images/themes/default/propertyicons/meshvectors.svg" ) ) , mVectorDeselectedPixmap( QStringLiteral( ":/images/themes/default/propertyicons/meshvectorsoff.svg" ) ) - , mMemoryPixmap( QStringLiteral( ":/images/themes/default/mIndicatorMemory.svg" ) ) { } @@ -361,16 +360,12 @@ void QgsMeshDatasetGroupTreeItemDelagate::paint( QPainter *painter, const QStyle QStyledItemDelegate::paint( painter, option, index ); bool isVector = index.data( QgsMeshDatasetGroupTreeModel::IsVector ).toBool(); - bool isMemory = index.data( QgsMeshDatasetGroupTreeModel::IsMemory ).toBool(); if ( isVector ) { bool isActive = index.data( QgsMeshDatasetGroupTreeModel::IsActiveVectorDatasetGroup ).toBool(); painter->drawPixmap( iconRect( option.rect, true ), isActive ? mVectorSelectedPixmap : mVectorDeselectedPixmap ); } - if ( isMemory ) - painter->drawPixmap( iconRect( option.rect, 3 ), mMemoryPixmap ); - bool isActive = index.data( QgsMeshDatasetGroupTreeModel::IsActiveScalarDatasetGroup ).toBool(); painter->drawPixmap( iconRect( option.rect, false ), isActive ? mScalarSelectedPixmap : mScalarDeselectedPixmap ); } @@ -540,10 +535,6 @@ QVariant QgsMeshDatasetGroupListModel::data( const QModelIndex &index, int role else return item->name(); break; - case Qt::DecorationRole: - if ( item->storageType() == QgsMeshDatasetGroupTreeItem::Memory ) - return QgsApplication::getThemeIcon( QStringLiteral( "mIndicatorMemory.svg" ) ); - break; } return QVariant(); @@ -559,6 +550,7 @@ QgsMeshDatasetGroupTreeView::QgsMeshDatasetGroupTreeView( QWidget *parent ): , mModel( new QgsMeshDatasetGroupTreeModel( this ) ) , mSaveMenu( new QgsMeshDatasetGroupSaveMenu( this ) ) { + setItemDelegate( &mDelegate ); setModel( mModel ); setSelectionMode( QAbstractItemView::SingleSelection ); @@ -732,3 +724,14 @@ void QgsMeshDatasetGroupSaveMenu::saveDatasetGroup( int datasetGroup, const QStr } } + +QgsMeshDatasetGroupTreeView::Delegate::Delegate( QObject *parent ): QStyledItemDelegate( parent ) +{ +} + +void QgsMeshDatasetGroupTreeView::Delegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const +{ + QStyleOptionViewItem opt = option; + opt.decorationPosition = QStyleOptionViewItem::Right; + QStyledItemDelegate::paint( painter, opt, index ); +} diff --git a/src/app/mesh/qgsmeshdatasetgrouptreeview.h b/src/app/mesh/qgsmeshdatasetgrouptreeview.h index c4176517241a..46ac6b7fe4fd 100644 --- a/src/app/mesh/qgsmeshdatasetgrouptreeview.h +++ b/src/app/mesh/qgsmeshdatasetgrouptreeview.h @@ -163,7 +163,6 @@ class APP_EXPORT QgsMeshDatasetGroupTreeItemDelagate: public QStyledItemDelegate const QPixmap mScalarDeselectedPixmap; const QPixmap mVectorSelectedPixmap; const QPixmap mVectorDeselectedPixmap; - const QPixmap mMemoryPixmap; QRect iconRect( const QRect &rect, int pos ) const; }; @@ -199,6 +198,15 @@ class APP_EXPORT QgsMeshDatasetGroupTreeView: public QTreeView void selectAllItem( bool isChecked ); QMenu *createContextMenu(); + + class Delegate: public QStyledItemDelegate + { + public: + Delegate( QObject *parent = nullptr ); + void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const; + }; + + Delegate mDelegate; }; /**