diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index d130920ec8b..0f77a2f5142 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -257,7 +257,7 @@ Returns the extra dataset groups count handle by the layer QList datasetGroupsIndexes() const; %Docstring -Returns the list of indexes of dataset groups count handled by the layer +Returns the list of indexes of dataset groups handled by the layer .. note:: diff --git a/python/core/auto_generated/processing/qgsprocessingcontext.sip.in b/python/core/auto_generated/processing/qgsprocessingcontext.sip.in index d24332c8038..4a3c8870be1 100644 --- a/python/core/auto_generated/processing/qgsprocessingcontext.sip.in +++ b/python/core/auto_generated/processing/qgsprocessingcontext.sip.in @@ -173,6 +173,24 @@ If not explicitly set, the unit will default to the :py:func:`~QgsProcessingCont .. seealso:: :py:func:`setDistanceUnit` .. versionadded:: 3.16 +%End + + QgsDateTimeRange currentTimeRange() const; +%Docstring +Returns the current time range to use for temporal operations. + +.. seealso:: :py:func:`setCurrentTimeRange` + +.. versionadded:: 3.18 +%End + + void setCurrentTimeRange( const QgsDateTimeRange ¤tTimeRange ); +%Docstring +Sets the ``current`` time range to use for temporal operations. + +.. seealso:: :py:func:`currentTimeRange` + +.. versionadded:: 3.18 %End QgsMapLayerStore *temporaryLayerStore(); diff --git a/python/core/auto_generated/processing/qgsprocessingparametermeshdataset.sip.in b/python/core/auto_generated/processing/qgsprocessingparametermeshdataset.sip.in new file mode 100644 index 00000000000..874bcecc86f --- /dev/null +++ b/python/core/auto_generated/processing/qgsprocessingparametermeshdataset.sip.in @@ -0,0 +1,180 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/processing/qgsprocessingparametermeshdataset.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + +class QgsProcessingParameterMeshDatasetGroups : QgsProcessingParameterDefinition +{ +%Docstring +A parameter for processing algorithms that need a list of mesh dataset groups +A valid value for this parameter is a list (QVariantList) of dataset groups index in the mesh layer scope +Dataset group index can be evaluated with the method :py:func:`~valueAsDatasetGroup` + +.. note:: + + This parameter is dependent on a mesh layer parameter (see QgsProcessingParameterMeshLayer) + +.. versionadded:: 3.18 +%End + +%TypeHeaderCode +#include "qgsprocessingparametermeshdataset.h" +%End + public: + + QgsProcessingParameterMeshDatasetGroups( const QString &name, + const QString &description = QString(), + const QString &meshLayerParameterName = QString(), + QgsMeshDatasetGroupMetadata::DataType dataType = QgsMeshDatasetGroupMetadata::DataOnVertices, + bool optional = false ); +%Docstring +Constructor + +:param name: name of the parameter +:param description: description of the parameter +:param meshLayerParameterName: name of the associated mesh layer parameter +:param dataType: data type supported by the parameter +:param optional: whether the parameter is optional +%End + + virtual QgsProcessingParameterDefinition *clone() const /Factory/; + + virtual QString type() const; + + virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const; + + virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const; + + virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const; + + virtual QStringList dependsOnOtherParameters() const; + + + static QString typeName(); +%Docstring +Returns the type name for the parameter class. +%End + + QString meshLayerParameterName() const; +%Docstring +Returns the name of the mesh layer parameter +%End + + QgsMeshDatasetGroupMetadata::DataType dataType() const; +%Docstring +Returns the data type supported by the parameter +%End + + static QList valueAsDatasetGroup( const QVariant &value ); +%Docstring +Returns the ``value`` as a list if dataset group indexes +%End + +}; + + +class QgsProcessingParameterMeshDatasetTime : QgsProcessingParameterDefinition +{ +%Docstring +A parameter for processing algorithms that need a list of mesh dataset index from time parameter +A valid value for this parameter is a map (QVariantMap) with in this form: + +- "type" : the type of time settings "current-context-time", "defined-date-time", "dataset-time-step" or "none" if all the dataset groups are static +- "value" : nothing if type is "static" or "current-context-time", QDateTime if "defined-date-time" or, for "dataset_time_step", list of two int representing the dataset index that is the reference for the time step + +.. note:: + + This parameter is dependent on a mesh layer parameter (:py:class:`QgsProcessingParameterMeshLayer`) + and on mesh datast group parameter (:py:class:`QgsProcessingParameterMeshDatasetGroups`) + +.. versionadded:: 3.18 +%End + +%TypeHeaderCode +#include "qgsprocessingparametermeshdataset.h" +%End + public: + + QgsProcessingParameterMeshDatasetTime( + const QString &name, + const QString &description = QString(), + const QString &meshLayerParameterName = QString(), + const QString &datasetGroupParameterName = QString() ); +%Docstring +Constructor + +:param name: name of the parameter +:param description: description of the parameter +:param meshLayerParameterName: name of the associated mesh layer parameter (:py:class:`QgsProcessingParameterMeshLayer`) +:param datasetGroupParameterName: name of the associated dataset group parameter (:py:class:`QgsProcessingParameterMeshDatasetGroups`) +%End + + virtual QgsProcessingParameterDefinition *clone() const /Factory/; + + virtual QString type() const; + + virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const; + + virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const; + + virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const; + + virtual QStringList dependsOnOtherParameters() const; + + + static QString typeName(); +%Docstring +Returns the type name for the parameter class. +%End + + QString meshLayerParameterName() const; +%Docstring +Returns the name of the mesh layer parameter +%End + + QString datasetGroupParameterName() const; +%Docstring +Returns the name of the dataset groups parameter +%End + + static QString valueAsTimeType( const QVariant &value ); +%Docstring +Returns the ``dataset`` value time type as a string : +current-context-time : the time is store in the processing context (e.g. current canvas time), in this case the value does not contain any time value +defined-date-time : absolute time of type QDateTime +dataset-time-step : a time step of existing dataset, in this case the time takes the form of a QMeshDatasetIndex with value to the corresponding dataset index +static : dataset groups are all static, in this case the value does not contain any time value +%End + + static QgsMeshDatasetIndex timeValueAsDatasetIndex( const QVariant &value ); +%Docstring +Returns the ``value`` as a QgsMeshDatasetIndex if the value has "dataset-time-step" type. +If the value has the wrong type return an invalid dataset index + +.. seealso:: :py:func:`valueAsTimeType` +%End + + static QDateTime timeValueAsDefinedDateTime( const QVariant &value ); +%Docstring +Returns the ``value`` as a QDateTime if the value has "defined-date-time" type. +If the value has the wrong type return an invalid QDatetime + +.. seealso:: :py:func:`valueAsTimeType` +%End + +}; + + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/processing/qgsprocessingparametermeshdataset.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in index 8863e00073f..a10dcf6d0f5 100644 --- a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in +++ b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in @@ -230,6 +230,7 @@ their acceptable ranges, defaults, etc. #include "qgsprocessingparameterfieldmap.h" #include "qgsprocessingparametertininputlayers.h" #include "qgsprocessingparametervectortilewriterlayers.h" +#include "qgsprocessingparametermeshdataset.h" %End %ConvertToSubClassCode if ( sipCpp->type() == QgsProcessingParameterBoolean::typeName() ) @@ -314,6 +315,10 @@ their acceptable ranges, defaults, etc. sipType = sipType_QgsProcessingParameterVectorTileWriterLayers; else if ( sipCpp->type() == QgsProcessingParameterDxfLayers::typeName() ) sipType = sipType_QgsProcessingParameterDxfLayers; + else if ( sipCpp->type() == QgsProcessingParameterMeshDatasetGroups::typeName() ) + sipType = sipType_QgsProcessingParameterMeshDatasetGroups; + else if ( sipCpp->type() == QgsProcessingParameterMeshDatasetTime::typeName() ) + sipType = sipType_QgsProcessingParameterMeshDatasetTime; else sipType = nullptr; %End diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 0d310c46abb..f801be76e0f 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -477,6 +477,7 @@ %Include auto_generated/processing/qgsprocessingparameteraggregate.sip %Include auto_generated/processing/qgsprocessingparameterdxflayers.sip %Include auto_generated/processing/qgsprocessingparameterfieldmap.sip +%Include auto_generated/processing/qgsprocessingparametermeshdataset.sip %Include auto_generated/processing/qgsprocessingparameters.sip %Include auto_generated/processing/qgsprocessingparametertininputlayers.sip %Include auto_generated/processing/qgsprocessingparametertype.sip diff --git a/python/plugins/processing/tools/dataobjects.py b/python/plugins/processing/tools/dataobjects.py index b79e97891c4..96421578ffd 100644 --- a/python/plugins/processing/tools/dataobjects.py +++ b/python/plugins/processing/tools/dataobjects.py @@ -76,6 +76,9 @@ def createContext(feedback=None): context.setExpressionContext(createExpressionContext()) + if iface and iface.mapCanvas() and iface.mapCanvas().mapSettings().isTemporal(): + context.setCurrentTimeRange(iface.mapCanvas().mapSettings().temporalRange()) + return context diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index ff152b83a74..5b5c059ad2c 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -61,6 +61,7 @@ SET(QGIS_ANALYSIS_SRCS processing/qgsalgorithmexecutespatialitequeryregistered.cpp processing/qgsalgorithmexplode.cpp processing/qgsalgorithmexplodehstore.cpp + processing/qgsalgorithmexportmeshvertices.cpp processing/qgsalgorithmextendlines.cpp processing/qgsalgorithmextentfromlayer.cpp processing/qgsalgorithmextenttolayer.cpp diff --git a/src/analysis/processing/qgsalgorithmexportmeshvertices.cpp b/src/analysis/processing/qgsalgorithmexportmeshvertices.cpp new file mode 100644 index 00000000000..23d37d681d2 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmexportmeshvertices.cpp @@ -0,0 +1,291 @@ +/*************************************************************************** + qgsalgorithmtinmeshcreation.h + --------------------------- + begin : October 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 "qgsalgorithmexportmeshvertices.h" +#include "qgsprocessingparametermeshdataset.h" +#include "qgsmeshdataset.h" +#include "qgsmeshlayer.h" +#include "qgsmeshlayerutils.h" +#include "qgsmeshlayertemporalproperties.h" + +///@cond PRIVATE + +QString QgsExportMeshVerticesAlgorithm::group() const +{ + return QObject::tr( "Mesh" ); +} + +QString QgsExportMeshVerticesAlgorithm::groupId() const +{ + return QStringLiteral( "mesh" ); +} + +QString QgsExportMeshVerticesAlgorithm::shortHelpString() const +{ + return QObject::tr( "Exports a mesh layer's vertices to a point vector layer, with the dataset values on vertices as attribute values" ); +} + +QString QgsExportMeshVerticesAlgorithm::name() const +{ + return QStringLiteral( "exportmeshvertices" ); +} + +QString QgsExportMeshVerticesAlgorithm::displayName() const +{ + return QObject::tr( "Export mesh vertices" ); +} + +QgsProcessingAlgorithm *QgsExportMeshVerticesAlgorithm::createInstance() const +{ + return new QgsExportMeshVerticesAlgorithm(); +} + +void QgsExportMeshVerticesAlgorithm::initAlgorithm( const QVariantMap &configuration ) +{ + Q_UNUSED( configuration ); + + addParameter( new QgsProcessingParameterMeshLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input Mesh Layer" ) ) ); + + + addParameter( new QgsProcessingParameterMeshDatasetGroups( + QStringLiteral( "DATASET_GROUPS" ), + QObject::tr( "Dataset groups" ), + QStringLiteral( "INPUT" ), + QgsMeshDatasetGroupMetadata::DataOnVertices ) ); + + addParameter( new QgsProcessingParameterMeshDatasetTime( + QStringLiteral( "DATASET_TIME" ), + QObject::tr( "Dataset time" ), + QStringLiteral( "INPUT" ), + QStringLiteral( "DATASET_GROUPS" ) ) ); + + addParameter( new QgsProcessingParameterCrs( QStringLiteral( "CRS_OUTPUT" ), QObject::tr( "Output coordinate system" ), QVariant(), true ) ); + + QStringList exportVectorOptions; + exportVectorOptions << QObject::tr( "Cartesian (x,y)" ) + << QObject::tr( "Polar (magnitude,degree)" ) + << QObject::tr( "Cartesian and Polar" ); + addParameter( new QgsProcessingParameterEnum( QStringLiteral( "VECTOR_OPTION" ), QObject::tr( "Export vector option" ), exportVectorOptions, false, 0 ) ); + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Output vector layer" ), QgsProcessing::TypeVectorPoint ) ); +} + +static QVector vectorValue( const QgsMeshDatasetValue &value, int exportOption ) +{ + QVector ret( exportOption == 2 ? 4 : 2 ); + + if ( exportOption == 0 || exportOption == 2 ) + { + ret[0] = value.x(); + ret[1] = value.y(); + } + if ( exportOption == 1 || exportOption == 2 ) + { + double x = value.x(); + double y = value.y(); + double magnitude = sqrt( x * x + y * y ); + double direction = ( asin( x / magnitude ) ) / M_PI * 180; + if ( y < 0 ) + direction = 180 - direction; + + if ( exportOption == 1 ) + { + ret[0] = magnitude; + ret[1] = direction; + } + if ( exportOption == 2 ) + { + ret[2] = magnitude; + ret[3] = direction; + } + } + return ret; +} + +bool QgsExportMeshVerticesAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + QgsMeshLayer *meshLayer = parameterAsMeshLayer( parameters, QStringLiteral( "INPUT" ), context ); + + if ( !meshLayer || !meshLayer->isValid() ) + return false; + + QgsCoordinateReferenceSystem outputCrs = parameterAsCrs( parameters, QStringLiteral( "CRS_OUTPUT" ), context ); + mTransform = QgsCoordinateTransform( meshLayer->crs(), outputCrs, context.transformContext() ); + if ( !meshLayer->nativeMesh() ) + meshLayer->updateTriangularMesh( mTransform ); //necessary to load the native mesh + + mNativeMesh = *meshLayer->nativeMesh(); + + QList datasetGroups = + QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( parameters.value( QStringLiteral( "DATASET_GROUPS" ) ) ); + + if ( feedback ) + { + feedback->setProgressText( QObject::tr( "Preparing data" ) ); + } + + QDateTime layerReferenceTime = static_cast( meshLayer->temporalProperties() )->referenceTime(); + + //! Extract the date time used to export dataset values under a relative time + QgsInterval relativeTime( 0 ); + QVariant parameterTimeVariant = parameters.value( QStringLiteral( "DATASET_TIME" ) ); + + QString timeType = QgsProcessingParameterMeshDatasetTime::valueAsTimeType( parameterTimeVariant ); + + if ( timeType == QStringLiteral( "dataset-time-step" ) ) + { + QgsMeshDatasetIndex datasetIndex = QgsProcessingParameterMeshDatasetTime::timeValueAsDatasetIndex( parameterTimeVariant ); + relativeTime = meshLayer->datasetRelativeTime( datasetIndex ); + } + else if ( timeType == QStringLiteral( "defined-date-time" ) ) + { + QDateTime dateTime = QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( parameterTimeVariant ); + if ( dateTime.isValid() ) + relativeTime = QgsInterval( layerReferenceTime.secsTo( dateTime ) ); + } + else if ( timeType == QStringLiteral( "current-context-time" ) ) + { + QDateTime dateTime = context.currentTimeRange().begin(); + if ( dateTime.isValid() ) + relativeTime = QgsInterval( layerReferenceTime.secsTo( dateTime ) ); + } + + for ( int i = 0; i < datasetGroups.count(); ++i ) + { + int groupIndex = datasetGroups.at( i ); + QgsMeshDatasetIndex datasetIndex = meshLayer->datasetIndexAtRelativeTime( relativeTime, groupIndex ); + + DataGroup dataGroup; + dataGroup.metadata = meshLayer->datasetGroupMetadata( datasetIndex ); + int dataCount = dataGroup.metadata.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ? mNativeMesh.vertexCount() : mNativeMesh.faceCount(); + dataGroup.datasetValues = meshLayer->datasetValues( datasetIndex, 0, dataCount ); + dataGroup.activeFaceFlagValues = meshLayer->areFacesActive( datasetIndex, 0, mNativeMesh.faceCount() ); + + mDataPerGroup.append( dataGroup ); + if ( feedback ) + feedback->setProgress( 100 * i / datasetGroups.count() ); + } + + mExportVectorOption = parameterAsInt( parameters, QStringLiteral( "VECTOR_OPTION" ), context ); + + return true; +} + +QVariantMap QgsExportMeshVerticesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + feedback->setProgress( 0 ); + feedback->setProgressText( QObject::tr( "Creating output vector layer" ) ); + } + + QgsFields fields; + for ( int i = 0; i < mDataPerGroup.count(); ++i ) + { + const DataGroup &dataGroup = mDataPerGroup.at( i ); + if ( dataGroup.metadata.isVector() ) + { + if ( mExportVectorOption == 0 or mExportVectorOption == 2 ) + { + fields.append( QStringLiteral( "%1_x" ).arg( dataGroup.metadata.name() ) ); + fields.append( QStringLiteral( "%1_y" ).arg( dataGroup.metadata.name() ) ); + } + + if ( mExportVectorOption == 1 or mExportVectorOption == 2 ) + { + fields.append( QStringLiteral( "%1_mag" ).arg( dataGroup.metadata.name() ) ); + fields.append( QStringLiteral( "%1_dir" ).arg( dataGroup.metadata.name() ) ); + } + } + else + fields.append( dataGroup.metadata.name() ); + } + + QgsCoordinateReferenceSystem outputCrs = parameterAsCrs( parameters, QStringLiteral( "CRS_OUTPUT" ), context ); + QString identifier; + QgsFeatureSink *sink = parameterAsSink( parameters, + QStringLiteral( "OUTPUT" ), + context, + identifier, + fields, + QgsWkbTypes::PointZ, + outputCrs ); + if ( !sink ) + return QVariantMap(); + + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + feedback->setProgress( 0 ); + feedback->setProgressText( QObject::tr( "Creating points for each vertices" ) ); + } + + int verticesCount = mNativeMesh.vertexCount(); + for ( int i = 0; i < verticesCount; ++i ) + { + QgsAttributes attributes; + for ( const DataGroup &dataGroup : mDataPerGroup ) + { + const QgsMeshDatasetGroupMetadata &meta = dataGroup.metadata; + const QgsMeshDatasetValue &value = dataGroup.datasetValues.value( i ); + if ( meta.isVector() ) + { + QVector vector = vectorValue( dataGroup.datasetValues.value( i ), mExportVectorOption ); + for ( double value : vector ) + { + attributes.append( value ); + } + } + else + attributes.append( value.scalar() ); + } + + QgsFeature feat; + QgsGeometry geom( new QgsPoint( mNativeMesh.vertex( i ) ) ); + try + { + geom.transform( mTransform ); + } + catch ( QgsCsException &e ) + { + geom = QgsGeometry( new QgsPoint( mNativeMesh.vertex( i ) ) ); + feedback->reportError( QObject::tr( "Could not transform point to destination CRS" ) ); + } + feat.setGeometry( geom ); + feat.setAttributes( attributes ); + + sink->addFeature( feat ); + + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + feedback->setProgress( 100 * i / verticesCount ); + } + } + + const QString fileName = parameterAsFile( parameters, QStringLiteral( "OUTPUT" ), context ); + + QVariantMap ret; + ret[QStringLiteral( "OUTPUT" )] = identifier; + + return ret; +} + +///@endcond PRIVATE diff --git a/src/analysis/processing/qgsalgorithmexportmeshvertices.h b/src/analysis/processing/qgsalgorithmexportmeshvertices.h new file mode 100644 index 00000000000..d38dac2d490 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmexportmeshvertices.h @@ -0,0 +1,62 @@ +/*************************************************************************** + qgsalgorithmtinmeshcreation.h + --------------------------- + begin : October 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 QGSALGORITHMEXPORTMESHVERTICES_H +#define QGSALGORITHMEXPORTMESHVERTICES_H + +#define SIP_NO_FILE + +#include "qgsprocessingalgorithm.h" +#include "qgsmeshdataset.h" +#include "qgsmeshdataprovider.h" + +///@cond PRIVATE + +class QgsExportMeshVerticesAlgorithm : public QgsProcessingAlgorithm +{ + public: + QString group() const override; + QString groupId() const override; + QString shortHelpString() const override; + QString name() const override; + QString displayName() const override; + + protected: + QgsProcessingAlgorithm *createInstance() const override; + + void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + QgsMesh mNativeMesh; + + struct DataGroup + { + QgsMeshDatasetGroupMetadata metadata; + QgsMeshDataBlock datasetValues; + QgsMeshDataBlock activeFaceFlagValues; + }; + + QList mDataPerGroup; + QgsCoordinateTransform mTransform; + int mExportVectorOption = 2; +}; + +///@endcond PRIVATE + +#endif // QGSALGORITHMEXPORTMESHVERTICES_H diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index 7849a7e90b5..639943b74b7 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -55,6 +55,7 @@ #include "qgsalgorithmexecutepostgisquery.h" #include "qgsalgorithmexecutespatialitequery.h" #include "qgsalgorithmexecutespatialitequeryregistered.h" +#include "qgsalgorithmexportmeshvertices.h" #include "qgsalgorithmexplode.h" #include "qgsalgorithmexplodehstore.h" #include "qgsalgorithmextendlines.h" @@ -283,6 +284,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsExecuteSpatialiteQueryAlgorithm() ); addAlgorithm( new QgsExplodeAlgorithm() ); addAlgorithm( new QgsExplodeHstoreAlgorithm() ); + addAlgorithm( new QgsExportMeshVerticesAlgorithm ); addAlgorithm( new QgsExtendLinesAlgorithm() ); addAlgorithm( new QgsExtentFromLayerAlgorithm() ); addAlgorithm( new QgsExtentToLayerAlgorithm() ); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 782d90ddd3b..c9c875e851f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -164,6 +164,7 @@ SET(QGIS_CORE_SRCS processing/qgsprocessingparameteraggregate.cpp processing/qgsprocessingparameterdxflayers.cpp processing/qgsprocessingparameterfieldmap.cpp + processing/qgsprocessingparametermeshdataset.cpp processing/qgsprocessingparameters.cpp processing/qgsprocessingparametertininputlayers.cpp processing/qgsprocessingparametertype.cpp @@ -1358,6 +1359,7 @@ SET(QGIS_CORE_HDRS processing/qgsprocessingparameteraggregate.h processing/qgsprocessingparameterdxflayers.h processing/qgsprocessingparameterfieldmap.h + processing/qgsprocessingparametermeshdataset.h processing/qgsprocessingparameters.h processing/qgsprocessingparametertininputlayers.h processing/qgsprocessingparametertype.h diff --git a/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp b/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp index d52296f3cca..4c90b631315 100644 --- a/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp +++ b/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp @@ -123,7 +123,6 @@ QgsDateTimeRange QgsMeshDataProviderTemporalCapabilities::timeExtent( const QDat if ( !groupReference.isValid() ) //the dataset group has not a valid reference time -->take global reference groupReference = mGlobalReferenceDateTime; - if ( !groupReference.isValid() ) groupReference = reference; diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index f3191b284fd..31c21c2484f 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -327,7 +327,7 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer int extraDatasetGroupCount() const; /** - * Returns the list of indexes of dataset groups count handled by the layer + * Returns the list of indexes of dataset groups 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. diff --git a/src/core/mesh/qgsmeshlayerutils.cpp b/src/core/mesh/qgsmeshlayerutils.cpp index 46134c94be8..477cd172878 100644 --- a/src/core/mesh/qgsmeshlayerutils.cpp +++ b/src/core/mesh/qgsmeshlayerutils.cpp @@ -345,17 +345,27 @@ QVector QgsMeshLayerUtils::interpolateFromFacesData( { assert( nativeMesh ); Q_UNUSED( method ); - assert( method == QgsMeshRendererScalarSettings::NeighbourAverage ); + // assuming that native vertex count = triangular vertex count assert( nativeMesh->vertices.size() == triangularMesh->vertices().size() ); - int vertexCount = triangularMesh->vertices().size(); + + return interpolateFromFacesData( valuesOnFaces, *nativeMesh, active, method ); +} + +QVector QgsMeshLayerUtils::interpolateFromFacesData( const QVector &valuesOnFaces, + const QgsMesh &nativeMesh, + QgsMeshDataBlock *active, + QgsMeshRendererScalarSettings::DataResamplingMethod method ) +{ + int vertexCount = nativeMesh.vertexCount(); + assert( method == QgsMeshRendererScalarSettings::NeighbourAverage ); QVector res( vertexCount, 0.0 ); // for face datasets do simple average of the valid values of all faces that contains this vertex QVector count( vertexCount, 0 ); - for ( int i = 0; i < nativeMesh->faces.size(); ++i ) + for ( int i = 0; i < nativeMesh.faceCount(); ++i ) { if ( !active || active->active( i ) ) { @@ -363,7 +373,7 @@ QVector QgsMeshLayerUtils::interpolateFromFacesData( if ( !std::isnan( val ) ) { // assign for all vertices - const QgsMeshFace &face = nativeMesh->faces.at( i ); + const QgsMeshFace &face = nativeMesh.faces.at( i ); for ( int j = 0; j < face.size(); ++j ) { int vertexIndex = face[j]; @@ -449,9 +459,21 @@ QVector QgsMeshLayerUtils::calculateMagnitudeOnVertices( const QgsMeshLa 0, datacount ); - if ( vals.isValid() ) + return calculateMagnitudeOnVertices( *nativeMesh, metadata, vals, *activeFaceFlagValues, method ); +} + +QVector QgsMeshLayerUtils::calculateMagnitudeOnVertices( const QgsMesh &nativeMesh, + const QgsMeshDatasetGroupMetadata &groupMetadata, + const QgsMeshDataBlock &datasetValues, + QgsMeshDataBlock &activeFaceFlagValues, + const QgsMeshRendererScalarSettings::DataResamplingMethod method ) +{ + QVector ret; + bool scalarDataOnVertices = groupMetadata.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices; + + if ( datasetValues.isValid() ) { - ret = QgsMeshLayerUtils::calculateMagnitudes( vals ); + ret = QgsMeshLayerUtils::calculateMagnitudes( datasetValues ); if ( !scalarDataOnVertices ) { @@ -459,8 +481,7 @@ QVector QgsMeshLayerUtils::calculateMagnitudeOnVertices( const QgsMeshLa ret = QgsMeshLayerUtils::interpolateFromFacesData( ret, nativeMesh, - triangularMesh, - activeFaceFlagValues, + &activeFaceFlagValues, method ); } } diff --git a/src/core/mesh/qgsmeshlayerutils.h b/src/core/mesh/qgsmeshlayerutils.h index d02e25c9222..68143196374 100644 --- a/src/core/mesh/qgsmeshlayerutils.h +++ b/src/core/mesh/qgsmeshlayerutils.h @@ -235,6 +235,18 @@ class CORE_EXPORT QgsMeshLayerUtils QgsMeshRendererScalarSettings::DataResamplingMethod method ); + /** + * Interpolate values on vertices from values on faces + * + * \since QGIS 3.18 + */ + static QVector interpolateFromFacesData( + const QVector &valuesOnFaces, + const QgsMesh &nativeMesh, + QgsMeshDataBlock *active, + QgsMeshRendererScalarSettings::DataResamplingMethod method + ); + /** * Resamples values on vertices to values on faces * @@ -250,11 +262,11 @@ class CORE_EXPORT QgsMeshLayerUtils /** * Calculates magnitude values ont vertices from the given QgsMeshDataBlock. - * If the values are defined on faces, + * If the values are defined on faces, the values are interpolated with the given method * \param meshLayer the mesh layer * \param index the dataset index that contains the data * \param activeFaceFlagValues pointer to the QVector containing active face flag values - * \param method used to inteprolate the values on vertices if needed + * \param method used to interpolate the values on vertices if needed * \returns magnitude values of the dataset on all the vertices * \since QGIS 3.14 */ @@ -264,6 +276,25 @@ class CORE_EXPORT QgsMeshLayerUtils QgsMeshDataBlock *activeFaceFlagValues, const QgsMeshRendererScalarSettings::DataResamplingMethod method = QgsMeshRendererScalarSettings::NeighbourAverage ); + /** + * Calculates magnitude values on vertices from the given QgsMeshDataBlock. + * If the values are defined on faces, the values are interpolated with the given method + * This method is thread safe. + * \param nativeMesh the native mesh + * \param groupMetadata the metadata of the group where come from the dataset values + * \param datasetValues block containing the dataset values + * \param activeFaceFlagValues block containing active face flag values + * \param method used to interpolate the values on vertices if needed + * \returns magnitude values of the dataset on all the vertices + * \since QGIS 3.18 + */ + static QVector calculateMagnitudeOnVertices( + const QgsMesh &nativeMesh, + const QgsMeshDatasetGroupMetadata &groupMetadata, + const QgsMeshDataBlock &datasetValues, + QgsMeshDataBlock &activeFaceFlagValues, + const QgsMeshRendererScalarSettings::DataResamplingMethod method = QgsMeshRendererScalarSettings::NeighbourAverage ); + /** * Calculates the bounding box of the triangle * \param p1 first vertex of the triangle diff --git a/src/core/mesh/qgsmeshtimesettings.h b/src/core/mesh/qgsmeshtimesettings.h index b74bcfe2fc0..fbefc5fc7bb 100644 --- a/src/core/mesh/qgsmeshtimesettings.h +++ b/src/core/mesh/qgsmeshtimesettings.h @@ -72,7 +72,7 @@ class CORE_EXPORT QgsMeshTimeSettings private: QString mRelativeTimeFormat = QStringLiteral( "d hh:mm:ss" ); - QString mAbsoluteTimeFormat = QStringLiteral( "dd.MM.yyyy hh:mm:ss" ); + QString mAbsoluteTimeFormat = QStringLiteral( "yyyy-MM-dd HH:mm:ss" ); }; Q_DECLARE_METATYPE( QgsMeshTimeSettings ); diff --git a/src/core/processing/qgsprocessingcontext.cpp b/src/core/processing/qgsprocessingcontext.cpp index 38074bdc5ae..eb323827f2a 100644 --- a/src/core/processing/qgsprocessingcontext.cpp +++ b/src/core/processing/qgsprocessingcontext.cpp @@ -128,6 +128,16 @@ QgsMapLayer *QgsProcessingContext::takeResultLayer( const QString &id ) return tempLayerStore.takeMapLayer( tempLayerStore.mapLayer( id ) ); } +QgsDateTimeRange QgsProcessingContext::currentTimeRange() const +{ + return mCurrentTimeRange; +} + +void QgsProcessingContext::setCurrentTimeRange( const QgsDateTimeRange ¤tTimeRange ) +{ + mCurrentTimeRange = currentTimeRange; +} + QString QgsProcessingContext::ellipsoid() const { return mEllipsoid; diff --git a/src/core/processing/qgsprocessingcontext.h b/src/core/processing/qgsprocessingcontext.h index ba2b97f2578..a947f817f93 100644 --- a/src/core/processing/qgsprocessingcontext.h +++ b/src/core/processing/qgsprocessingcontext.h @@ -216,6 +216,22 @@ class CORE_EXPORT QgsProcessingContext */ void setAreaUnit( QgsUnitTypes::AreaUnit areaUnit ); + /** + * Returns the current time range to use for temporal operations. + * + * \see setCurrentTimeRange() + * \since QGIS 3.18 + */ + QgsDateTimeRange currentTimeRange() const; + + /** + * Sets the \a current time range to use for temporal operations. + * + * \see currentTimeRange() + * \since QGIS 3.18 + */ + void setCurrentTimeRange( const QgsDateTimeRange ¤tTimeRange ); + /** * Returns a reference to the layer store used for storing temporary layers during * algorithm execution. @@ -619,6 +635,8 @@ class CORE_EXPORT QgsProcessingContext QgsUnitTypes::DistanceUnit mDistanceUnit = QgsUnitTypes::DistanceUnknownUnit; QgsUnitTypes::AreaUnit mAreaUnit = QgsUnitTypes::AreaUnknownUnit; + QgsDateTimeRange mCurrentTimeRange; + //! Temporary project owned by the context, used for storing temporarily loaded map layers QgsMapLayerStore tempLayerStore; QgsExpressionContext mExpressionContext; diff --git a/src/core/processing/qgsprocessingparametermeshdataset.cpp b/src/core/processing/qgsprocessingparametermeshdataset.cpp new file mode 100644 index 00000000000..37bf0e5b894 --- /dev/null +++ b/src/core/processing/qgsprocessingparametermeshdataset.cpp @@ -0,0 +1,293 @@ +/*************************************************************************** + qgsprocessingparametermeshdataset.cpp + --------------------- + Date : October 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 "qgsprocessingparametermeshdataset.h" + +/// @cond PRIVATE +/// +QgsProcessingParameterMeshDatasetGroups::QgsProcessingParameterMeshDatasetGroups( const QString &name, + const QString &description, + const QString &meshLayerParameterName, + const QgsMeshDatasetGroupMetadata::DataType dataType, + bool optional ): + QgsProcessingParameterDefinition( name, description, QVariant(), optional, QString() ), + mMeshLayerParameterName( meshLayerParameterName ), + mDataType( dataType ) +{ +} + +QgsProcessingParameterDefinition *QgsProcessingParameterMeshDatasetGroups::clone() const +{ + return new QgsProcessingParameterMeshDatasetGroups( name(), description(), mMeshLayerParameterName, mDataType ); +} + +QString QgsProcessingParameterMeshDatasetGroups::type() const +{ + return typeName(); +} + +bool QgsProcessingParameterMeshDatasetGroups::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context ) const +{ + Q_UNUSED( context ); + return valueIsAcceptable( input, mFlags & FlagOptional ); +} + +QString QgsProcessingParameterMeshDatasetGroups::valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const +{ + Q_UNUSED( context ); + QStringList parts; + const QVariantList variantDatasetGroupIndexes = value.toList(); + for ( const QVariant &variantIndex : variantDatasetGroupIndexes ) + parts.append( QString::number( variantIndex.toInt() ) ); + + return parts.join( ',' ).prepend( '[' ).append( ']' ); +} + +QString QgsProcessingParameterMeshDatasetGroups::asPythonString( QgsProcessing::PythonOutputType outputType ) const +{ + switch ( outputType ) + { + case QgsProcessing::PythonQgsProcessingAlgorithmSubclass: + { + QString code = QStringLiteral( "QgsProcessingParameterMeshDatasetGroups('%1', '%2'" ) + .arg( name(), description() ); + if ( !mMeshLayerParameterName.isEmpty() ) + code += QStringLiteral( ", meshLayerParameterName=%1" ).arg( QgsProcessingUtils::stringToPythonLiteral( mMeshLayerParameterName ) ); + + switch ( mDataType ) + { + case QgsMeshDatasetGroupMetadata::DataOnFaces: + code += QStringLiteral( ", dataType=QgsMeshDatasetGroupMetadata.DataOnFaces" ); + break; + case QgsMeshDatasetGroupMetadata::DataOnVertices: + code += QStringLiteral( ", dataType=QgsMeshDatasetGroupMetadata.DataOnVertices" ); + break; + case QgsMeshDatasetGroupMetadata::DataOnVolumes: + code += QStringLiteral( ", dataType=QgsMeshDatasetGroupMetadata.DataOnVolumes" ); + break; + case QgsMeshDatasetGroupMetadata::DataOnEdges: + code += QStringLiteral( ", dataType=QgsMeshDatasetGroupMetadata.DataOnEdges" ); + break; + } + + if ( mFlags & FlagOptional ) + code += QStringLiteral( ", optional=True" ); + code += ')'; + return code; + } + } + return QString(); +} + +QStringList QgsProcessingParameterMeshDatasetGroups::dependsOnOtherParameters() const +{ + if ( mMeshLayerParameterName.isEmpty() ) + return QStringList(); + else + return QStringList() << mMeshLayerParameterName; +} + +QString QgsProcessingParameterMeshDatasetGroups::meshLayerParameterName() const +{ + return mMeshLayerParameterName; +} + +QList QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( const QVariant &value ) +{ + if ( !valueIsAcceptable( value, true ) ) + return QList(); + QVariantList list = value.toList(); + QList ret; + for ( const QVariant &v : list ) + ret.append( v.toInt() ); + + return ret; +} + +bool QgsProcessingParameterMeshDatasetGroups::valueIsAcceptable( const QVariant &input, bool allowEmpty ) +{ + if ( !input.isValid() ) + return allowEmpty; + + if ( input.type() != QVariant::List ) + return false; + const QVariantList list = input.toList(); + + if ( !allowEmpty && list.isEmpty() ) + return false; + + for ( const QVariant &var : list ) + if ( var.type() != QVariant::Int ) + return false; + + return true; +} + +QgsProcessingParameterMeshDatasetTime::QgsProcessingParameterMeshDatasetTime( const QString &name, + const QString &description, + const QString &meshLayerParameterName, + const QString &datasetGroupParameterName ) + : QgsProcessingParameterDefinition( name, description, QVariant() ) + , mMeshLayerParameterName( meshLayerParameterName ) + , mDatasetGroupParameterName( datasetGroupParameterName ) +{ + +} + +QgsProcessingParameterDefinition *QgsProcessingParameterMeshDatasetTime::clone() const +{ + return new QgsProcessingParameterMeshDatasetTime( name(), description(), mMeshLayerParameterName, mDatasetGroupParameterName ); +} + +QString QgsProcessingParameterMeshDatasetTime::type() const +{ + return typeName(); +} + +bool QgsProcessingParameterMeshDatasetTime::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context ) const +{ + Q_UNUSED( context ); + return valueIsAcceptable( input, mFlags & FlagOptional ); +} + +QString QgsProcessingParameterMeshDatasetTime::valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const +{ + Q_UNUSED( context ); + QStringList parts; + const QVariantMap variantTimeDataset = value.toMap(); + parts << QStringLiteral( "'type': " ) + QgsProcessingUtils::variantToPythonLiteral( variantTimeDataset.value( QStringLiteral( "type" ) ).toString() ); + + if ( variantTimeDataset.value( QStringLiteral( "type" ) ) == QStringLiteral( "dataset-time-step" ) ) + { + QVariantList datasetIndex = variantTimeDataset.value( QStringLiteral( "value" ) ).toList(); + parts << QStringLiteral( "'value': " ) + QString( "QgsMeshDatasetIndex(%1,%2)" ).arg( datasetIndex.at( 0 ).toString() ).arg( datasetIndex.at( 1 ).toString() ); + } + else if ( variantTimeDataset.value( QStringLiteral( "type" ) ) == QStringLiteral( "defined-date-time" ) ) + { + parts << QStringLiteral( "'value': " ) + QgsProcessingUtils::variantToPythonLiteral( variantTimeDataset.value( QStringLiteral( "value" ) ) ); + } + + return parts.join( ',' ).prepend( '{' ).append( '}' ); +} + +QString QgsProcessingParameterMeshDatasetTime::asPythonString( QgsProcessing::PythonOutputType outputType ) const +{ + switch ( outputType ) + { + case QgsProcessing::PythonQgsProcessingAlgorithmSubclass: + { + QString code = QStringLiteral( "QgsProcessingParameterMeshDatasetTime('%1', '%2'" ) + .arg( name(), description() ); + if ( !mMeshLayerParameterName.isEmpty() ) + code += QStringLiteral( ", meshLayerParameterName=%1" ).arg( QgsProcessingUtils::stringToPythonLiteral( mMeshLayerParameterName ) ); + + if ( !mDatasetGroupParameterName.isEmpty() ) + code += QStringLiteral( ", datasetGroupParameterName=%1" ).arg( QgsProcessingUtils::stringToPythonLiteral( mDatasetGroupParameterName ) ); + + if ( mFlags & FlagOptional ) + code += QStringLiteral( ", optional=True" ); + code += ')'; + return code; + } + } + return QString(); +} + +QStringList QgsProcessingParameterMeshDatasetTime::dependsOnOtherParameters() const +{ + QStringList otherParameters; + if ( !mMeshLayerParameterName.isEmpty() ) + otherParameters << mMeshLayerParameterName; + + if ( !mDatasetGroupParameterName.isEmpty() ) + otherParameters << mMeshLayerParameterName << mDatasetGroupParameterName; + + return otherParameters; +} + +QString QgsProcessingParameterMeshDatasetTime::meshLayerParameterName() const +{ + return mMeshLayerParameterName; +} + +QString QgsProcessingParameterMeshDatasetTime::datasetGroupParameterName() const +{ + return mDatasetGroupParameterName; +} + +QString QgsProcessingParameterMeshDatasetTime::valueAsTimeType( const QVariant &value ) +{ + if ( !valueIsAcceptable( value, false ) ) + return QString(); + + return value.toMap().value( QStringLiteral( "type" ) ).toString(); +} + +QgsMeshDatasetIndex QgsProcessingParameterMeshDatasetTime::timeValueAsDatasetIndex( const QVariant &value ) +{ + if ( !valueIsAcceptable( value, false ) || valueAsTimeType( value ) != QStringLiteral( "dataset-time-step" ) ) + return QgsMeshDatasetIndex( -1, -1 ); + + QVariantList list = value.toMap().value( QStringLiteral( "value" ) ).toList(); + return QgsMeshDatasetIndex( list.at( 0 ).toInt(), list.at( 1 ).toInt() ); +} + +QDateTime QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( const QVariant &value ) +{ + if ( !valueIsAcceptable( value, false ) && valueAsTimeType( value ) != QStringLiteral( "defined-date-time" ) ) + return QDateTime(); + + return value.toMap().value( QStringLiteral( "value" ) ).toDateTime(); +} + +bool QgsProcessingParameterMeshDatasetTime::valueIsAcceptable( const QVariant &input, bool allowEmpty ) +{ + if ( !input.isValid() ) + return allowEmpty; + + if ( input.type() != QVariant::Map ) + return false; + const QVariantMap map = input.toMap(); + if ( ! map.contains( QStringLiteral( "type" ) ) ) + return false; + + QString type = map.value( QStringLiteral( "type" ) ).toString(); + QVariant value = map.value( QStringLiteral( "value" ) ); + + if ( type == QStringLiteral( "static" ) || type == QStringLiteral( "current-context-time" ) ) + return true; + + if ( type == QStringLiteral( "dataset-time-step" ) ) + { + if ( value.type() != QVariant::List ) + return false; + QVariantList list = value.toList(); + if ( value.toList().count() != 2 ) + return false; + if ( list.at( 0 ).type() != QVariant::Int || list.at( 1 ).type() != QVariant::Int ) + return false; + } + else if ( type == QStringLiteral( "defined-date-time" ) ) + { + if ( value.type() != QVariant::DateTime ) + return false; + } + else + return false; + + return true; +} + +/// @endcond PRIVATE diff --git a/src/core/processing/qgsprocessingparametermeshdataset.h b/src/core/processing/qgsprocessingparametermeshdataset.h new file mode 100644 index 00000000000..2045954c3f4 --- /dev/null +++ b/src/core/processing/qgsprocessingparametermeshdataset.h @@ -0,0 +1,258 @@ +/*************************************************************************** + qgsprocessingparametermeshdataset.h + --------------------- + Date : October 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 QGSPROCESSINGPARAMETERMESHDATASET_H +#define QGSPROCESSINGPARAMETERMESHDATASET_H + +#include "qgsprocessingparameters.h" +#include "qgsprocessingparametertype.h" +#include "qgsmeshdataset.h" + +/** + * A parameter for processing algorithms that need a list of mesh dataset groups + * A valid value for this parameter is a list (QVariantList) of dataset groups index in the mesh layer scope + * Dataset group index can be evaluated with the method valueAsDatasetGroup() + * + * \note This parameter is dependent on a mesh layer parameter (see QgsProcessingParameterMeshLayer) + * + * \ingroup core + * \since QGIS 3.18 + */ +class CORE_EXPORT QgsProcessingParameterMeshDatasetGroups : public QgsProcessingParameterDefinition +{ + public: + + /** + * Constructor + * \param name name of the parameter + * \param description description of the parameter + * \param meshLayerParameterName name of the associated mesh layer parameter + * \param dataType data type supported by the parameter + * \param optional whether the parameter is optional + */ + QgsProcessingParameterMeshDatasetGroups( const QString &name, + const QString &description = QString(), + const QString &meshLayerParameterName = QString(), + QgsMeshDatasetGroupMetadata::DataType dataType = QgsMeshDatasetGroupMetadata::DataOnVertices, + bool optional = false ); + + QgsProcessingParameterDefinition *clone() const override SIP_FACTORY; + QString type() const override; + bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override; + QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const override; + QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const override; + QStringList dependsOnOtherParameters() const override; + + //! Returns the type name for the parameter class. + static QString typeName() { return QStringLiteral( "meshdatasetgroups" ); } + + //! Returns the name of the mesh layer parameter + QString meshLayerParameterName() const; + + //! Returns the data type supported by the parameter + QgsMeshDatasetGroupMetadata::DataType dataType() const {return mDataType;} + + //! Returns the \a value as a list if dataset group indexes + static QList valueAsDatasetGroup( const QVariant &value ); + + private: + QString mMeshLayerParameterName; + QgsMeshDatasetGroupMetadata::DataType mDataType = QgsMeshDatasetGroupMetadata::DataOnVertices; + + static bool valueIsAcceptable( const QVariant &input, bool allowEmpty ); +}; + +#ifndef SIP_RUN +///@cond PRIVATE + +/** + * Parameter type definition for QgsProcessingParameterMeshDatasetGroups. + * + * \ingroup core + * \since QGIS 3.18 + */ +class CORE_EXPORT QgsProcessingParameterTypeMeshDatasetGroups : public QgsProcessingParameterType +{ + public: + QgsProcessingParameterDefinition *create( const QString &name ) const override SIP_FACTORY + { + return new QgsProcessingParameterMeshDatasetGroups( name ); + } + + QString description() const override + { + return QCoreApplication::translate( "Processing", "An input allowing selection dataset groups from a mesh layer" ); + } + + QString name() const override + { + return QCoreApplication::translate( "Processing", "Mesh dataset groups" ); + } + + QString id() const override + { + return QgsProcessingParameterMeshDatasetGroups::typeName(); + } + + QString pythonImportString() const override + { + return QStringLiteral( "from qgis.core import QgsProcessingParameterMeshDatasetGroups" ); + } + + QString className() const override + { + return QStringLiteral( "QgsProcessingParameterMeshDatasetGroups" ); + } + + QStringList acceptedPythonTypes() const override + { + return QStringList() << QObject::tr( "list[int]: list of dataset group indexes, see QgsProcessingParameterMeshDatasetGroups docs" ); + } +}; + +///@endcond +#endif //SIP_RUN + +/** + * A parameter for processing algorithms that need a list of mesh dataset index from time parameter + * A valid value for this parameter is a map (QVariantMap) with in this form: + * + * - "type" : the type of time settings "current-context-time", "defined-date-time", "dataset-time-step" or "none" if all the dataset groups are static + * - "value" : nothing if type is "static" or "current-context-time", QDateTime if "defined-date-time" or, for "dataset_time_step", list of two int representing the dataset index that is the reference for the time step + * + * \note This parameter is dependent on a mesh layer parameter (\see QgsProcessingParameterMeshLayer) + * and on mesh datast group parameter (\see QgsProcessingParameterMeshDatasetGroups) + * + * \ingroup core + * \since QGIS 3.18 + */ +class CORE_EXPORT QgsProcessingParameterMeshDatasetTime : public QgsProcessingParameterDefinition +{ + public: + + /** + * Constructor + * \param name name of the parameter + * \param description description of the parameter + * \param meshLayerParameterName name of the associated mesh layer parameter (\see QgsProcessingParameterMeshLayer) + * \param datasetGroupParameterName name of the associated dataset group parameter (\see QgsProcessingParameterMeshDatasetGroups) + */ + QgsProcessingParameterMeshDatasetTime( + const QString &name, + const QString &description = QString(), + const QString &meshLayerParameterName = QString(), + const QString &datasetGroupParameterName = QString() ); + + QgsProcessingParameterDefinition *clone() const override SIP_FACTORY; + QString type() const override; + bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override; + QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const override; + QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const override; + QStringList dependsOnOtherParameters() const override; + + //! Returns the type name for the parameter class. + static QString typeName() { return QStringLiteral( "meshdatasettime" ); } + + //! Returns the name of the mesh layer parameter + QString meshLayerParameterName() const; + + //! Returns the name of the dataset groups parameter + QString datasetGroupParameterName() const; + + /** + * Returns the \a dataset value time type as a string : + * current-context-time : the time is store in the processing context (e.g. current canvas time), in this case the value does not contain any time value + * defined-date-time : absolute time of type QDateTime + * dataset-time-step : a time step of existing dataset, in this case the time takes the form of a QMeshDatasetIndex with value to the corresponding dataset index + * static : dataset groups are all static, in this case the value does not contain any time value + */ + static QString valueAsTimeType( const QVariant &value ); + + /** + * Returns the \a value as a QgsMeshDatasetIndex if the value has "dataset-time-step" type. + * If the value has the wrong type return an invalid dataset index + * + * \see valueAsTimeType() + */ + static QgsMeshDatasetIndex timeValueAsDatasetIndex( const QVariant &value ); + + /** + * Returns the \a value as a QDateTime if the value has "defined-date-time" type. + * If the value has the wrong type return an invalid QDatetime + * + * \see valueAsTimeType() + */ + static QDateTime timeValueAsDefinedDateTime( const QVariant &value ); + + private: + QString mMeshLayerParameterName; + QString mDatasetGroupParameterName; + + static bool valueIsAcceptable( const QVariant &input, bool allowEmpty ); +}; + +#ifndef SIP_RUN +///@cond PRIVATE + +/** + * Parameter type definition for QgsProcessingParameterMeshDatasetTime. + * + * \ingroup core + * \since QGIS 3.18 + */ +class CORE_EXPORT QgsProcessingParameterTypeMeshDatasetTime: public QgsProcessingParameterType +{ + public: + QgsProcessingParameterDefinition *create( const QString &name ) const override SIP_FACTORY + { + return new QgsProcessingParameterMeshDatasetTime( name ); + } + + QString description() const override + { + return QCoreApplication::translate( "Processing", "An input allowing selection of dataset index from a mesh layer by time setting" ); + } + + QString name() const override + { + return QCoreApplication::translate( "Processing", "Mesh dataset time" ); + } + + QString id() const override + { + return QgsProcessingParameterMeshDatasetTime::typeName(); + } + + QString pythonImportString() const override + { + return QStringLiteral( "from qgis.core import QgsProcessingParameterMeshDatasetTime" ); + } + + QString className() const override + { + return QStringLiteral( "QgsProcessingParameterMeshDatasetTime" ); + } + + QStringList acceptedPythonTypes() const override + { + return QStringList() << QObject::tr( "dict{}: dictionary, see QgsProcessingParameterMeshDatasetTime docs" ); + } +}; + +///@endcond +#endif //SIP_RUN + + +#endif // QGSPROCESSINGPARAMETERMESHDATASET_H diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index 54ea3e86268..1d7f7f090f9 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -337,6 +337,7 @@ class CORE_EXPORT QgsProcessingParameterDefinition #include "qgsprocessingparameterfieldmap.h" #include "qgsprocessingparametertininputlayers.h" #include "qgsprocessingparametervectortilewriterlayers.h" +#include "qgsprocessingparametermeshdataset.h" % End SIP_CONVERT_TO_SUBCLASS_CODE if ( sipCpp->type() == QgsProcessingParameterBoolean::typeName() ) @@ -421,6 +422,10 @@ class CORE_EXPORT QgsProcessingParameterDefinition sipType = sipType_QgsProcessingParameterVectorTileWriterLayers; else if ( sipCpp->type() == QgsProcessingParameterDxfLayers::typeName() ) sipType = sipType_QgsProcessingParameterDxfLayers; + else if ( sipCpp->type() == QgsProcessingParameterMeshDatasetGroups::typeName() ) + sipType = sipType_QgsProcessingParameterMeshDatasetGroups; + else if ( sipCpp->type() == QgsProcessingParameterMeshDatasetTime::typeName() ) + sipType = sipType_QgsProcessingParameterMeshDatasetTime; else sipType = nullptr; SIP_END diff --git a/src/core/processing/qgsprocessingregistry.cpp b/src/core/processing/qgsprocessingregistry.cpp index e4f0db9dd8f..eb897ba215f 100644 --- a/src/core/processing/qgsprocessingregistry.cpp +++ b/src/core/processing/qgsprocessingregistry.cpp @@ -18,6 +18,7 @@ #include "qgsprocessingregistry.h" #include "qgsvectorfilewriter.h" #include "qgsprocessingparametertypeimpl.h" +#include "qgsprocessingparametermeshdataset.h" #include "qgsprocessingparametervectortilewriterlayers.h" #include "qgsprocessingparametertininputlayers.h" #include "qgsprocessingparameterfieldmap.h" @@ -71,6 +72,8 @@ QgsProcessingRegistry::QgsProcessingRegistry( QObject *parent SIP_TRANSFERTHIS ) addParameterType( new QgsProcessingParameterTypeAggregate() ); addParameterType( new QgsProcessingParameterTypeTinInputLayers() ); addParameterType( new QgsProcessingParameterTypeDxfLayers() ); + addParameterType( new QgsProcessingParameterTypeMeshDatasetGroups() ); + addParameterType( new QgsProcessingParameterTypeMeshDatasetTime() ); } QgsProcessingRegistry::~QgsProcessingRegistry() diff --git a/src/core/processing/qgsprocessingutils.cpp b/src/core/processing/qgsprocessingutils.cpp index b12196b40b7..d090e88ec84 100644 --- a/src/core/processing/qgsprocessingutils.cpp +++ b/src/core/processing/qgsprocessingutils.cpp @@ -603,6 +603,18 @@ QString QgsProcessingUtils::variantToPythonLiteral( const QVariant &value ) return parts.join( ',' ).prepend( '{' ).append( '}' ); } + case QVariant::DateTime: + { + const QDateTime dateTime = value.toDateTime(); + return QStringLiteral( "QDateTime(QDate(%1, %2, %3), QTime(%4, %5, %6))" ) + .arg( dateTime.date().year() ) + .arg( dateTime.date().month() ) + .arg( dateTime.date().day() ) + .arg( dateTime.time().hour() ) + .arg( dateTime.time().minute() ) + .arg( dateTime.time().second() ); + } + default: break; } diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 373c81bf380..bea930a4410 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -300,6 +300,7 @@ SET(QGIS_GUI_SRCS processing/qgsprocessingmaplayercombobox.cpp processing/qgsprocessingmatrixmodelerwidget.cpp processing/qgsprocessingmatrixparameterdialog.cpp + processing/qgsprocessingmeshdatasetwidget.cpp processing/qgsprocessingmodelerparameterwidget.cpp processing/qgsprocessingmultipleselectiondialog.cpp processing/qgsprocessingoutputdestinationwidget.cpp @@ -1061,6 +1062,7 @@ SET(QGIS_GUI_HDRS processing/qgsprocessingmaplayercombobox.h processing/qgsprocessingmatrixmodelerwidget.h processing/qgsprocessingmatrixparameterdialog.h + processing/qgsprocessingmeshdatasetwidget.h processing/qgsprocessingmodelerparameterwidget.h processing/qgsprocessingmultipleselectiondialog.h processing/qgsprocessingoutputdestinationwidget.h diff --git a/src/gui/processing/qgsprocessingguiregistry.cpp b/src/gui/processing/qgsprocessingguiregistry.cpp index 53f99492281..a68616e4024 100644 --- a/src/gui/processing/qgsprocessingguiregistry.cpp +++ b/src/gui/processing/qgsprocessingguiregistry.cpp @@ -24,6 +24,7 @@ #include "qgsprocessingdxflayerswidgetwrapper.h" #include "qgsprocessingwidgetwrapperimpl.h" #include "qgsprocessingtininputlayerswidget.h" +#include "qgsprocessingmeshdatasetwidget.h" #include "qgsprocessingparameters.h" #include "qgis.h" #include "qgslogger.h" @@ -75,6 +76,8 @@ QgsProcessingGuiRegistry::QgsProcessingGuiRegistry() addParameterWidgetFactory( new QgsProcessingAggregateWidgetWrapper() ); addParameterWidgetFactory( new QgsProcessingTinInputLayersWidgetWrapper() ); addParameterWidgetFactory( new QgsProcessingDxfLayersWidgetWrapper() ); + addParameterWidgetFactory( new QgsProcessingMeshDatasetGroupsWidgetWrapper() ); + addParameterWidgetFactory( new QgsProcessingMeshDatasetTimeWidgetWrapper() ); } QgsProcessingGuiRegistry::~QgsProcessingGuiRegistry() diff --git a/src/gui/processing/qgsprocessingmeshdatasetwidget.cpp b/src/gui/processing/qgsprocessingmeshdatasetwidget.cpp new file mode 100644 index 00000000000..0471f3da365 --- /dev/null +++ b/src/gui/processing/qgsprocessingmeshdatasetwidget.cpp @@ -0,0 +1,686 @@ +/*************************************************************************** + qgsprocessingmeshdatasetgroupswidget.h + --------------------- + Date : October 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 "qgsprocessingmeshdatasetwidget.h" +#include "qgsdatetimeedit.h" +#include "qgsprocessingmultipleselectiondialog.h" +#include "qgsmeshlayer.h" +#include "qgsmeshlayerutils.h" +#include "qgsmeshlayertemporalproperties.h" +#include "qgspanelwidget.h" +#include "qgsmapcanvas.h" + +#include +#include +#include +#include +#include + +/// @cond PRIVATE + +QgsProcessingMeshDatasetGroupsWidget::QgsProcessingMeshDatasetGroupsWidget( QWidget *parent, const QgsProcessingParameterMeshDatasetGroups *param ) + : QWidget( parent ), + mParam( param ) +{ + QHBoxLayout *hl = new QHBoxLayout(); + hl->setContentsMargins( 0, 0, 0, 0 ); + + mLineEdit = new QLineEdit(); + mLineEdit->setEnabled( false ); + hl->addWidget( mLineEdit, 1 ); + + mToolButton = new QToolButton(); + mToolButton->setText( QString( QChar( 0x2026 ) ) ); + hl->addWidget( mToolButton ); + + setLayout( hl ); + + mLineEdit->setText( tr( "%1 dataset groups selected" ).arg( 0 ) ); + + mToolButton->setPopupMode( QToolButton::InstantPopup ); + QMenu *toolButtonMenu = new QMenu( this ); + mActionCurrentActiveDatasetGroups = toolButtonMenu->addAction( tr( "Current Active Dataset Group" ) ); + connect( mActionCurrentActiveDatasetGroups, + &QAction::triggered, this, &QgsProcessingMeshDatasetGroupsWidget::selectCurrentActiveDatasetGroup ); + + mActionAvailableDatasetGroups = toolButtonMenu->addAction( tr( "Select in Available Dataset Groups" ) ); + connect( mActionAvailableDatasetGroups, &QAction::triggered, this, &QgsProcessingMeshDatasetGroupsWidget::showDialog ); + + mToolButton->setMenu( toolButtonMenu ); +} + +void QgsProcessingMeshDatasetGroupsWidget::setMeshLayer( QgsMeshLayer *layer, bool layerFromProject ) +{ + mActionCurrentActiveDatasetGroups->setEnabled( layer && layerFromProject ); + mActionAvailableDatasetGroups->setEnabled( layer ); + + if ( mMeshLayer == layer ) + return; + + mDatasetGroupsNames.clear(); + + if ( layerFromProject ) + mMeshLayer = layer; + else + { + mMeshLayer = nullptr; + if ( layer ) + { + QList datasetGroupsIndexes = layer->datasetGroupsIndexes(); + for ( int i : datasetGroupsIndexes ) + { + QgsMeshDatasetGroupMetadata meta = layer->datasetGroupMetadata( i ); + if ( meta.dataType() == mParam->dataType() ) + { + mDatasetGroupsNames[i] = meta.name(); + } + } + } + } + mValue.clear(); + updateSummaryText(); + emit changed(); +} + +void QgsProcessingMeshDatasetGroupsWidget::setValue( const QVariant &value ) +{ + if ( value.isValid() ) + mValue = value.type() == QVariant::List ? value.toList() : QVariantList() << value; + else + mValue.clear(); + + updateSummaryText(); + emit changed(); +} + +QVariant QgsProcessingMeshDatasetGroupsWidget::value() const +{ + return mValue; +} + +void QgsProcessingMeshDatasetGroupsWidget::showDialog() +{ + QList datasetGroupsIndexes; + QStringList options; + QVariantList availableOptions; + if ( mMeshLayer ) + { + datasetGroupsIndexes = mMeshLayer->datasetGroupsIndexes(); + for ( int i : datasetGroupsIndexes ) + { + QgsMeshDatasetGroupMetadata meta = mMeshLayer->datasetGroupMetadata( i ); + if ( meta.dataType() == mParam->dataType() ) + { + availableOptions.append( i ); + options.append( meta.name() ); + } + + } + } + else + { + for ( int i : mDatasetGroupsNames.keys() ) + { + availableOptions.append( i ); + options.append( mDatasetGroupsNames.value( i ) ); + } + } + + QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); + if ( panel && panel->dockMode() ) + { + QgsProcessingMultipleSelectionPanelWidget *widget = new QgsProcessingMultipleSelectionPanelWidget( availableOptions, mValue ); + widget->setPanelTitle( tr( "Dataset Groups Available" ) ); + + widget->setValueFormatter( [availableOptions, options]( const QVariant & v ) -> QString + { + const int index = v.toInt(); + const int pos = availableOptions.indexOf( index ); + return ( pos >= 0 && pos < options.size() ) ? options.at( pos ) : QString(); + } ); + + connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged, this, [ = ]() + { + setValue( widget->selectedOptions() ); + } ); + connect( widget, &QgsProcessingMultipleSelectionPanelWidget::acceptClicked, widget, &QgsPanelWidget::acceptPanel ); + panel->openPanel( widget ); + } + else + { + QgsProcessingMultipleSelectionDialog dlg( availableOptions, mValue, this, Qt::WindowFlags() ); + + dlg.setValueFormatter( [datasetGroupsIndexes, options]( const QVariant & v ) -> QString + { + const int index = v.toInt(); + const int pos = datasetGroupsIndexes.indexOf( index ); + return ( pos >= 0 && pos < options.size() ) ? options.at( pos ) : QString(); + } ); + if ( dlg.exec() ) + { + setValue( dlg.selectedOptions() ); + } + } +} + +void QgsProcessingMeshDatasetGroupsWidget::selectCurrentActiveDatasetGroup() +{ + QVariantList options; + if ( mMeshLayer && mParam ) + { + int scalarDatasetGroup = mMeshLayer->rendererSettings().activeScalarDatasetGroup(); + int vectorDatasetGroup = mMeshLayer->rendererSettings().activeVectorDatasetGroup(); + + if ( scalarDatasetGroup >= 0 && mMeshLayer->datasetGroupMetadata( scalarDatasetGroup ).dataType() == mParam->dataType() ) + options.append( scalarDatasetGroup ); + + if ( vectorDatasetGroup >= 0 + && mMeshLayer->datasetGroupMetadata( vectorDatasetGroup ).dataType() == mParam->dataType() + && vectorDatasetGroup != scalarDatasetGroup ) + options.append( vectorDatasetGroup ); + } + + setValue( options ); +} + +QgsProcessingMeshDatasetGroupsWidgetWrapper::QgsProcessingMeshDatasetGroupsWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent ): + QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent ) +{} + +QString QgsProcessingMeshDatasetGroupsWidgetWrapper::parameterType() const +{ + return QgsProcessingParameterMeshDatasetGroups::typeName(); +} + +QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingMeshDatasetGroupsWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) +{ + return new QgsProcessingMeshDatasetGroupsWidgetWrapper( parameter, type ); +} + +void QgsProcessingMeshDatasetGroupsWidgetWrapper::postInitialize( const QList &wrappers ) +{ + QgsAbstractProcessingParameterWidgetWrapper::postInitialize( wrappers ); //necessary here? + switch ( type() ) + { + case QgsProcessingGui::Standard: + case QgsProcessingGui::Batch: + { + for ( const QgsAbstractProcessingParameterWidgetWrapper *wrapper : wrappers ) + { + if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterMeshDatasetGroups * >( parameterDefinition() )->meshLayerParameterName() ) + { + setMeshLayerWrapperValue( wrapper ); + connect( wrapper, &QgsAbstractProcessingParameterWidgetWrapper::widgetValueHasChanged, this, [ = ] + { + setMeshLayerWrapperValue( wrapper ); + } ); + break; + } + } + break; + } + + case QgsProcessingGui::Modeler: + break; + } +} + +void QgsProcessingMeshDatasetGroupsWidgetWrapper::setMeshLayerWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *wrapper ) +{ + if ( ! mWidget ) + return; + + // evaluate value to layer + QgsProcessingContext *context = nullptr; + if ( mProcessingContextGenerator ) + context = mProcessingContextGenerator->processingContext(); + + bool layerFromProject; + QgsMeshLayer *meshLayer; + if ( !context ) + { + QgsProcessingContext dummyContext; + meshLayer = QgsProcessingParameters::parameterAsMeshLayer( wrapper->parameterDefinition(), wrapper->parameterValue(), dummyContext ); + layerFromProject = false; + } + else + { + meshLayer = QgsProcessingParameters::parameterAsMeshLayer( wrapper->parameterDefinition(), wrapper->parameterValue(), *context ); + layerFromProject = context->project() && context->project()->layerStore()->layers().contains( meshLayer ); + } + + if ( mWidget ) + mWidget->setMeshLayer( meshLayer, layerFromProject ); +} + +QWidget *QgsProcessingMeshDatasetGroupsWidgetWrapper::createWidget() SIP_FACTORY +{ + mWidget = new QgsProcessingMeshDatasetGroupsWidget( nullptr, static_cast( parameterDefinition() ) ); + connect( mWidget, &QgsProcessingMeshDatasetGroupsWidget::changed, this, [ = ] + { + emit widgetValueHasChanged( this ); + } ); + + return mWidget; +} + +void QgsProcessingMeshDatasetGroupsWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context ) +{ + Q_UNUSED( context ); + if ( mWidget ) + mWidget->setValue( value ); +} + +QVariant QgsProcessingMeshDatasetGroupsWidgetWrapper::widgetValue() const +{ + if ( mWidget ) + return mWidget->value(); + return QVariant(); +} + + +void QgsProcessingMeshDatasetGroupsWidget::updateSummaryText() +{ + mLineEdit->setText( tr( "%1 options selected" ).arg( mValue.count() ) ); +} + +QgsProcessingMeshDatasetTimeWidgetWrapper::QgsProcessingMeshDatasetTimeWidgetWrapper( const QgsProcessingParameterDefinition *parameter, + QgsProcessingGui::WidgetType type, + QWidget *parent ) + : QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent ) +{ + +} + +QString QgsProcessingMeshDatasetTimeWidgetWrapper::parameterType() const +{ + return QgsProcessingParameterMeshDatasetTime::typeName(); +} + +QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingMeshDatasetTimeWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) +{ + return new QgsProcessingMeshDatasetTimeWidgetWrapper( parameter, type ); +} + +void QgsProcessingMeshDatasetTimeWidgetWrapper::postInitialize( const QList &wrappers ) +{ + QgsAbstractProcessingParameterWidgetWrapper::postInitialize( wrappers ); //necessary here? + switch ( type() ) + { + case QgsProcessingGui::Standard: + case QgsProcessingGui::Batch: + { + const QgsAbstractProcessingParameterWidgetWrapper *layerParameterWrapper = nullptr; + const QgsAbstractProcessingParameterWidgetWrapper *datasetGroupsParameterWrapper = nullptr; + for ( const QgsAbstractProcessingParameterWidgetWrapper *wrapper : wrappers ) + { + if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterMeshDatasetTime * >( parameterDefinition() )->meshLayerParameterName() ) + layerParameterWrapper = wrapper; + + if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterMeshDatasetTime * >( parameterDefinition() )->datasetGroupParameterName() ) + datasetGroupsParameterWrapper = wrapper; + } + setMeshLayerWrapperValue( layerParameterWrapper ); + setDatasetGroupIndexesWrapperValue( datasetGroupsParameterWrapper ); + connect( datasetGroupsParameterWrapper, &QgsAbstractProcessingParameterWidgetWrapper::widgetValueHasChanged, this, [ = ] + { + setMeshLayerWrapperValue( layerParameterWrapper ); + setDatasetGroupIndexesWrapperValue( datasetGroupsParameterWrapper ); + } ); + + break; + } + + case QgsProcessingGui::Modeler: + break; + } +} + +void QgsProcessingMeshDatasetTimeWidgetWrapper::setMeshLayerWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *wrapper ) +{ + if ( !mWidget ) + return; + + // evaluate value to layer + QgsProcessingContext *context = nullptr; + if ( mProcessingContextGenerator ) + context = mProcessingContextGenerator->processingContext(); + + bool layerFromProject; + QgsMeshLayer *meshLayer; + if ( !context ) + { + QgsProcessingContext dummyContext; + meshLayer = QgsProcessingParameters::parameterAsMeshLayer( wrapper->parameterDefinition(), wrapper->parameterValue(), dummyContext ); + layerFromProject = false; + } + else + { + meshLayer = QgsProcessingParameters::parameterAsMeshLayer( wrapper->parameterDefinition(), wrapper->parameterValue(), *context ); + layerFromProject = context->project() && context->project()->layerStore()->layers().contains( meshLayer ); + } + + mWidget->setMeshLayer( meshLayer, layerFromProject ); +} + +void QgsProcessingMeshDatasetTimeWidgetWrapper::setDatasetGroupIndexesWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *wrapper ) +{ + if ( !mWidget ) + return; + + QVariant datasetGroupsVariant = wrapper->parameterValue(); + + if ( !datasetGroupsVariant.isValid() || datasetGroupsVariant.type() != QVariant::List ) + mWidget->setDatasetGroupIndexes( QList() ); + + QVariantList datasetGroupsListVariant = datasetGroupsVariant.toList(); + + QList datasetGroupsIndexes; + for ( const QVariant &variantIndex : datasetGroupsListVariant ) + datasetGroupsIndexes << variantIndex.toInt(); + + mWidget->setDatasetGroupIndexes( datasetGroupsIndexes ); + +} + +QWidget *QgsProcessingMeshDatasetTimeWidgetWrapper::createWidget() +{ + mWidget = new QgsProcessingMeshDatasetTimeWidget( nullptr, static_cast( parameterDefinition() ), widgetContext() ); + + QgsMapCanvas *canvas = widgetContext().mapCanvas(); + if ( canvas ) + { + connect( canvas, &QgsMapCanvas::temporalRangeChanged, mWidget, &QgsProcessingMeshDatasetTimeWidget::updateValue ); + } + connect( mWidget, &QgsProcessingMeshDatasetTimeWidget::changed, this, [ = ] + { + emit widgetValueHasChanged( this ); + } ); + + return mWidget; +} + +void QgsProcessingMeshDatasetTimeWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context ) +{ + Q_UNUSED( context ); + if ( mWidget ) + mWidget->setValue( value ); +} + +QVariant QgsProcessingMeshDatasetTimeWidgetWrapper::widgetValue() const +{ + if ( mWidget ) + return mWidget->value(); + return QVariant(); +} + +QgsProcessingMeshDatasetTimeWidget::QgsProcessingMeshDatasetTimeWidget( QWidget *parent, + const QgsProcessingParameterMeshDatasetTime *param, + const QgsProcessingParameterWidgetContext &context ): + QWidget( parent ), + mParam( param ) +{ + setupUi( this ); + + dateTimeEdit->setDisplayFormat( "yyyy-MM-dd HH:mm:ss" ); + + mCanvas = context.mapCanvas(); + + connect( radioButtonCurrentCanvasTime, &QRadioButton::toggled, [this]( bool isChecked ) {if ( isChecked ) this->updateValue();} ); + connect( radioButtonDefinedDateTime, &QRadioButton::toggled, [this]( bool isChecked ) {if ( isChecked ) this->updateValue();} ); + connect( radioButtonDatasetGroupTimeStep, &QRadioButton::toggled, [this]( bool isChecked ) {if ( isChecked ) this->updateValue();} ); + connect( dateTimeEdit, &QgsDateTimeEdit::dateTimeChanged, this, &QgsProcessingMeshDatasetTimeWidget::updateValue ); + connect( comboBoxDatasetTimeStep, qgis::overload::of( &QComboBox::currentIndexChanged ), + this, &QgsProcessingMeshDatasetTimeWidget::updateValue ); + + updateWidget(); +} + +void QgsProcessingMeshDatasetTimeWidget::setMeshLayer( QgsMeshLayer *layer, bool layerFromProject ) +{ + if ( mMeshLayer == layer ) + return; + + if ( layerFromProject ) + { + mMeshLayer = layer; + mReferenceTime = static_cast( layer->temporalProperties() )->referenceTime(); + } + else + { + mMeshLayer = nullptr; + mReferenceTime = layer->dataProvider()->temporalCapabilities()->referenceTime(); + storeTimeStepsFromLayer( layer ); + } + + if ( mReferenceTime.isValid() ) + whileBlocking( dateTimeEdit )->setDateTime( mReferenceTime ); + + updateValue(); +} + +void QgsProcessingMeshDatasetTimeWidget::setDatasetGroupIndexes( const QList datasetGroupIndexes ) +{ + if ( datasetGroupIndexes == mDatasetGroupIndexes ) + return; + mDatasetGroupIndexes = datasetGroupIndexes; + populateTimeSteps(); + updateValue(); +} + +void QgsProcessingMeshDatasetTimeWidget::setValue( const QVariant &value ) +{ + if ( !value.isValid() || value.type() != QVariant::Map ) + return; + + mValue = value.toMap(); + + if ( !mValue.contains( QStringLiteral( "type" ) ) || !mValue.contains( QStringLiteral( "value" ) ) ) + return; + + QString type = mValue.value( QStringLiteral( "type" ) ).toString(); + + setEnabled( true ); + if ( type == QStringLiteral( "static" ) ) + { + setEnabled( false ); + } + else if ( type == QStringLiteral( "dataset-time-step" ) ) + { + whileBlocking( radioButtonDatasetGroupTimeStep )->setChecked( true ); + whileBlocking( comboBoxDatasetTimeStep )->setCurrentIndex( comboBoxDatasetTimeStep->findData( mValue.value( QStringLiteral( "value" ) ) ) ); + } + else if ( type == QStringLiteral( "dataset-time-step" ) ) + { + radioButtonDefinedDateTime->setChecked( true ); + whileBlocking( dateTimeEdit )->setDate( mValue.value( QStringLiteral( "value" ) ).toDate() ); + } + else if ( type == QStringLiteral( "current-context-time" ) ) + { + whileBlocking( radioButtonCurrentCanvasTime )->setChecked( true ); + } + + emit changed(); + updateWidget(); +} + +QVariant QgsProcessingMeshDatasetTimeWidget::value() const +{ + return mValue; +} + +void QgsProcessingMeshDatasetTimeWidget::updateWidget() +{ + bool isStatic = !hasTemporalDataset(); + setEnabled( !isStatic ); + + if ( mCanvas != nullptr && mCanvas->mapSettings().isTemporal() ) + { + whileBlocking( radioButtonCurrentCanvasTime )->setEnabled( true && mReferenceTime.isValid() ); + labelCurrentCanvasTime->setText( mCanvas->mapSettings().temporalRange().begin().toString( "yyyy-MM-dd HH:mm:ss" ) ); + } + else + { + whileBlocking( radioButtonCurrentCanvasTime )->setEnabled( false ); + if ( radioButtonCurrentCanvasTime->isChecked() ) + whileBlocking( radioButtonDefinedDateTime )->setChecked( true ); + } + + if ( ! mReferenceTime.isValid() ) + whileBlocking( radioButtonDatasetGroupTimeStep )->setChecked( true ); + + whileBlocking( radioButtonDefinedDateTime )->setEnabled( mReferenceTime.isValid() ); + + dateTimeEdit->setVisible( radioButtonDefinedDateTime->isChecked() && !isStatic ); + labelCurrentCanvasTime->setVisible( radioButtonCurrentCanvasTime->isChecked() && !isStatic ); + comboBoxDatasetTimeStep->setVisible( radioButtonDatasetGroupTimeStep->isChecked() && !isStatic ); +} + +bool QgsProcessingMeshDatasetTimeWidget::hasTemporalDataset() const +{ + for ( int index : mDatasetGroupIndexes ) + { + if ( mMeshLayer && mMeshLayer->datasetGroupMetadata( index ).isTemporal() ) + return true; + else if ( mDatasetTimeSteps.contains( index ) ) + return true; + } + + return false; +} + + +void QgsProcessingMeshDatasetTimeWidget::populateTimeSteps() +{ + if ( mMeshLayer ) + { + populateTimeStepsFromLayer(); + return; + } + + QMap timeStep; + for ( int groupIndex : mDatasetGroupIndexes ) + { + if ( !mDatasetTimeSteps.contains( groupIndex ) ) + continue; + const QList relativeTimeSteps = mDatasetTimeSteps.value( groupIndex ); + for ( int index = 0; index < relativeTimeSteps.count(); ++index ) + { + QgsMeshDatasetIndex datasetIndex( groupIndex, index ); + if ( timeStep.contains( relativeTimeSteps.at( index ) ) ) + continue; + timeStep[relativeTimeSteps.at( index )] = datasetIndex; + } + } + + for ( qint64 key : timeStep.keys() ) + { + QString stringTime = QgsMeshLayerUtils::formatTime( key / 1000 / 3600, mReferenceTime, QgsMeshTimeSettings() ); + QVariantList data; + const QgsMeshDatasetIndex &index = timeStep.value( key ); + data << index.group() << index.dataset(); + whileBlocking( comboBoxDatasetTimeStep )->addItem( stringTime, data ); + } + +} + +void QgsProcessingMeshDatasetTimeWidget::populateTimeStepsFromLayer() +{ + whileBlocking( comboBoxDatasetTimeStep )->clear(); + + if ( !mMeshLayer ) + return; + + QMap timeStep; + for ( int groupIndex : mDatasetGroupIndexes ) + { + QgsMeshDatasetGroupMetadata meta = mMeshLayer->datasetGroupMetadata( groupIndex ); + if ( !meta.isTemporal() ) + continue; + int datasetCount = mMeshLayer->datasetCount( groupIndex ); + + for ( int index = 0; index < datasetCount; ++index ) + { + QgsMeshDatasetIndex datasetIndex( groupIndex, index ); + qint64 relativeTime = mMeshLayer->datasetRelativeTimeInMilliseconds( datasetIndex ); + if ( timeStep.contains( relativeTime ) ) + continue; + timeStep[relativeTime] = datasetIndex; + } + } + + for ( qint64 key : timeStep.keys() ) + { + QString stringTime = mMeshLayer->formatTime( key / 1000 / 3600 ); + QVariantList data; + const QgsMeshDatasetIndex &index = timeStep.value( key ); + data << index.group() << index.dataset(); + whileBlocking( comboBoxDatasetTimeStep )->addItem( stringTime, data ); + } +} + +void QgsProcessingMeshDatasetTimeWidget::storeTimeStepsFromLayer( QgsMeshLayer *layer ) +{ + mDatasetTimeSteps.clear(); + if ( !layer ) + return; + QList datasetGroupsList = layer->datasetGroupsIndexes(); + for ( int groupIndex : datasetGroupsList ) + { + QgsMeshDatasetGroupMetadata meta = layer->datasetGroupMetadata( groupIndex ); + if ( !meta.isTemporal() ) + continue; + int datasetCount = layer->datasetCount( groupIndex ); + QList relativeTimeSteps; + relativeTimeSteps.reserve( datasetCount ); + for ( int index = 0; index < datasetCount; ++index ) + relativeTimeSteps.append( layer->datasetRelativeTimeInMilliseconds( QgsMeshDatasetIndex( groupIndex, index ) ) ); + mDatasetTimeSteps[groupIndex] = relativeTimeSteps; + } +} + +void QgsProcessingMeshDatasetTimeWidget::buildValue() +{ + mValue.clear(); + + if ( !isEnabled() ) + { + mValue[QStringLiteral( "type" )] = QStringLiteral( "static" ); + } + else if ( radioButtonDatasetGroupTimeStep->isChecked() ) + { + mValue[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" ); + mValue[QStringLiteral( "value" )] = comboBoxDatasetTimeStep->currentData(); + } + else if ( radioButtonDefinedDateTime->isChecked() ) + { + mValue[QStringLiteral( "type" )] = QStringLiteral( "defined-date-time" ); + mValue[QStringLiteral( "value" )] = dateTimeEdit->dateTime(); + } + else if ( radioButtonCurrentCanvasTime->isChecked() && mCanvas ) + { + mValue[QStringLiteral( "type" )] = QStringLiteral( "current-context-time" ); + } + + emit changed(); +} + +void QgsProcessingMeshDatasetTimeWidget::updateValue() +{ + updateWidget(); + buildValue(); +} + +///@endcond diff --git a/src/gui/processing/qgsprocessingmeshdatasetwidget.h b/src/gui/processing/qgsprocessingmeshdatasetwidget.h new file mode 100644 index 00000000000..621a0962539 --- /dev/null +++ b/src/gui/processing/qgsprocessingmeshdatasetwidget.h @@ -0,0 +1,183 @@ +/*************************************************************************** + qgsprocessingmeshdatasetwidget.h + --------------------- + Date : October 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 QGSPROCESSINGMESHDATASETWIDGET_H +#define QGSPROCESSINGMESHDATASETWIDGET_H + +#include + +#include "qgsprocessingwidgetwrapperimpl.h" +#include "qgsprocessingparametermeshdataset.h" + +#include "ui_qgsprocessingmeshdatasettimewidget.h" + +#define SIP_NO_FILE + +/// @cond PRIVATE + +class GUI_EXPORT QgsProcessingMeshDatasetGroupsWidget : public QWidget +{ + Q_OBJECT + public: + QgsProcessingMeshDatasetGroupsWidget( QWidget *parent = nullptr, const QgsProcessingParameterMeshDatasetGroups *param = nullptr ); + + /** + * Set the mesh layer for populate the dataset group names, + * if \a layerFromProject is false, the layer will not stay referenced in the instance of this object but + * all that is needed is stored in the instance + */ + void setMeshLayer( QgsMeshLayer *layer, bool layerFromProject = false ); + void setValue( const QVariant &value ); + QVariant value() const; + + signals: + void changed(); + + private slots: + void showDialog(); + void selectCurrentActiveDatasetGroup(); + + private: + const QgsProcessingParameterMeshDatasetGroups *mParam = nullptr; + QVariantList mValue; + + QPointer mLineEdit = nullptr; + QPointer mToolButton = nullptr; + QPointer mActionCurrentActiveDatasetGroups = nullptr; + QPointer mActionAvailableDatasetGroups = nullptr; + QgsMeshLayer *mMeshLayer = nullptr; + QMap mDatasetGroupsNames; //used to store the dataet groups name if layer is not referenced + + QStringList datasetGroupsNames(); + void updateSummaryText(); + + friend class TestProcessingGui; +}; + + +class GUI_EXPORT QgsProcessingMeshDatasetGroupsWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper, public QgsProcessingParameterWidgetFactoryInterface +{ + Q_OBJECT + + public: + QgsProcessingMeshDatasetGroupsWidgetWrapper( const QgsProcessingParameterDefinition *parameter = nullptr, + QgsProcessingGui::WidgetType type = QgsProcessingGui::Standard, QWidget *parent = nullptr ); + + QString parameterType() const override; + QgsAbstractProcessingParameterWidgetWrapper *createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) override; + void postInitialize( const QList &wrappers ) override; + + //! Sets the layer parameter widget wrapper + void setMeshLayerWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *wrapper ); + + protected: + QStringList compatibleParameterTypes() const override {return QStringList();} + QStringList compatibleOutputTypes() const override {return QStringList();} + QWidget *createWidget() override; + void setWidgetValue( const QVariant &value, QgsProcessingContext &context ) override; + QVariant widgetValue() const override; + + private: + QgsProcessingMeshDatasetGroupsWidget *mWidget = nullptr; + std::unique_ptr mTemporarytMeshLayer; + + friend class TestProcessingGui; +}; + + +class GUI_EXPORT QgsProcessingMeshDatasetTimeWidget : public QWidget, private Ui::QgsProcessingMeshDatasetTimeWidgetBase +{ + Q_OBJECT + public: + QgsProcessingMeshDatasetTimeWidget( QWidget *parent = nullptr, + const QgsProcessingParameterMeshDatasetTime *param = nullptr, + const QgsProcessingParameterWidgetContext &context = QgsProcessingParameterWidgetContext() ); + + /** + * Set the mesh layer for populate the time steps, + * if \a layerFromProject is false, the layer will not stay referenced in the instance of this object but + * all that is needed is stored in the instance + */ + void setMeshLayer( QgsMeshLayer *layer, bool layerFromProject = false ); + void setDatasetGroupIndexes( const QList datasetGroupIndexes ); + + void setValue( const QVariant &value ); + QVariant value() const; + + public slots: + void updateValue(); + + signals: + void changed(); + + private: + const QgsProcessingParameterMeshDatasetTime *mParam = nullptr; + QVariantMap mValue; + QgsMapCanvas *mCanvas; + + QLineEdit *mLineEdit = nullptr; + QToolButton *mToolButton = nullptr; + QgsMeshLayer *mMeshLayer = nullptr; + QList mDatasetGroupIndexes; + QDateTime mReferenceTime; + + void populateTimeSteps(); + bool hasTemporalDataset() const; + //! Populates diretly the time steps combo box with the referenced layer, used if layer comes from project + void populateTimeStepsFromLayer(); + //! Stores the dataset time steps to use them later depending of chosen dataset groups (setDatasetGroupIndexes()), used if layer does not come from project + void storeTimeStepsFromLayer( QgsMeshLayer *layer ); + QMap> mDatasetTimeSteps; //used if layer does not come from project + + void updateWidget(); + void buildValue(); + + friend class TestProcessingGui; +}; + +class GUI_EXPORT QgsProcessingMeshDatasetTimeWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper, public QgsProcessingParameterWidgetFactoryInterface +{ + Q_OBJECT + + public: + QgsProcessingMeshDatasetTimeWidgetWrapper( const QgsProcessingParameterDefinition *parameter = nullptr, + QgsProcessingGui::WidgetType type = QgsProcessingGui::Standard, QWidget *parent = nullptr ); + + QString parameterType() const override; + QgsAbstractProcessingParameterWidgetWrapper *createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) override; + void postInitialize( const QList &wrappers ) override; + + //! Sets the layer parameter widget wrapper + void setMeshLayerWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *wrapper ); + + //! Sets the dataset group indexes widget wrapper + void setDatasetGroupIndexesWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *wrapper ); + + protected: + QStringList compatibleParameterTypes() const override {return QStringList();} + QStringList compatibleOutputTypes() const override {return QStringList();} + QWidget *createWidget() override; + void setWidgetValue( const QVariant &value, QgsProcessingContext &context ) override; + QVariant widgetValue() const override; + + private: + QgsProcessingMeshDatasetTimeWidget *mWidget = nullptr; + std::unique_ptr mTemporarytMeshLayer; + friend class TestProcessingGui; +}; + +///@endcond + +#endif // QGSPROCESSINGMESHDATASETWIDGET_H diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h index 6283fb17343..4746170e9fb 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h @@ -1465,7 +1465,6 @@ class GUI_EXPORT QgsProcessingDateTimeWidgetWrapper : public QgsAbstractProcessi QVariant widgetValue() const override; QStringList compatibleParameterTypes() const override; - QStringList compatibleOutputTypes() const override; QString modelerExpressionFormatString() const override; diff --git a/src/ui/processing/qgsprocessingmeshdatasettimewidget.ui b/src/ui/processing/qgsprocessingmeshdatasettimewidget.ui new file mode 100644 index 00000000000..58af9e2bc7c --- /dev/null +++ b/src/ui/processing/qgsprocessingmeshdatasettimewidget.ui @@ -0,0 +1,72 @@ + + + QgsProcessingMeshDatasetTimeWidgetBase + + + + 0 + 0 + 465 + 122 + + + + Form + + + + + + + + Current canvas time + + + true + + + + + + + Defined date/time + + + false + + + + + + + Dataset group time step + + + + + + + + + + + + true + + + Qt::UTC + + + + + + + Current canvas time + + + + + + + + diff --git a/src/ui/qgstemporalcontrollerwidgetbase.ui b/src/ui/qgstemporalcontrollerwidgetbase.ui index 5f2fba1c180..e92f0093651 100644 --- a/src/ui/qgstemporalcontrollerwidgetbase.ui +++ b/src/ui/qgstemporalcontrollerwidgetbase.ui @@ -129,7 +129,7 @@ - 0 + 1 @@ -556,8 +556,6 @@ - - diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index 1fd34f7816b..ca8e288b9f4 100644 --- a/tests/src/analysis/testqgsprocessing.cpp +++ b/tests/src/analysis/testqgsprocessing.cpp @@ -52,6 +52,7 @@ #include "qgsprocessingparameteraggregate.h" #include "qgsprocessingparametertininputlayers.h" #include "qgsprocessingparameterdxflayers.h" +#include "qgsprocessingparametermeshdataset.h" #include "qgsdxfexport.h" class DummyAlgorithm : public QgsProcessingAlgorithm @@ -605,6 +606,8 @@ class TestQgsProcessing: public QObject void parameterFieldMapping(); void parameterAggregate(); void parameterTinInputLayers(); + void parameterMeshDatasetGroups(); + void parameterMeshDatasetTime(); void parameterDxfLayers(); void checkParamValues(); void combineLayerExtent(); @@ -8014,6 +8017,140 @@ void TestQgsProcessing::parameterTinInputLayers() QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterTinInputLayers('tin input layer', '')" ) ); } +void TestQgsProcessing::parameterMeshDatasetGroups() +{ + QgsProcessingContext context; + QgsProject project; + context.setProject( &project ); + + std::unique_ptr< QgsProcessingParameterMeshDatasetGroups> def( new QgsProcessingParameterMeshDatasetGroups( QStringLiteral( "dataset groups" ), QStringLiteral( "groups" ) ) ); + + QVERIFY( def->type() == QStringLiteral( "meshdatasetgroups" ) ); + QVERIFY( def->dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); + QVERIFY( !def->checkValueIsAcceptable( 1 ) ); + QVERIFY( !def->checkValueIsAcceptable( 1.0 ) ); + QVERIFY( !def->checkValueIsAcceptable( "test" ) ); + QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); + QVariantList groupsList; + QVERIFY( !def->checkValueIsAcceptable( groupsList ) ); + groupsList.append( 0 ); + QVERIFY( def->checkValueIsAcceptable( groupsList ) ); + groupsList.append( 5 ); + QVERIFY( def->checkValueIsAcceptable( groupsList ) ); + + QVERIFY( def->dependsOnOtherParameters().isEmpty() ); + + QString valueAsPythonString = def->valueAsPythonString( groupsList, context ); + QCOMPARE( valueAsPythonString, QStringLiteral( "[0,5]" ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsList ), QList() << 0 << 5 ); + + QString pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMeshDatasetGroups('dataset groups', 'groups', dataType=QgsMeshDatasetGroupMetadata.DataOnVertices)" ) ); + + // optional, layer parameter and data on faces + def.reset( new QgsProcessingParameterMeshDatasetGroups( + QStringLiteral( "dataset groups" ), + QStringLiteral( "groups" ), + QStringLiteral( "layer parameter" ), + QgsMeshDatasetGroupMetadata::DataOnFaces, true ) ); + QVERIFY( def->dataType() == QgsMeshDatasetGroupMetadata::DataOnFaces ); + QVERIFY( !def->checkValueIsAcceptable( 1 ) ); + QVERIFY( !def->checkValueIsAcceptable( 1.0 ) ); + QVERIFY( !def->checkValueIsAcceptable( "test" ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); + QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); + QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); + groupsList = QVariantList(); + QVERIFY( def->checkValueIsAcceptable( groupsList ) ); + groupsList.append( 2 ); + QVERIFY( def->checkValueIsAcceptable( groupsList ) ); + groupsList.append( 6 ); + QVERIFY( def->checkValueIsAcceptable( groupsList ) ); + + valueAsPythonString = def->valueAsPythonString( groupsList, context ); + QCOMPARE( valueAsPythonString, QStringLiteral( "[2,6]" ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsList ), QList() << 2 << 6 ); + + QVERIFY( !def->dependsOnOtherParameters().isEmpty() ); + QCOMPARE( def->meshLayerParameterName(), QStringLiteral( "layer parameter" ) ); + + pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMeshDatasetGroups('dataset groups', 'groups', meshLayerParameterName='layer parameter', dataType=QgsMeshDatasetGroupMetadata.DataOnFaces, optional=True)" ) ); +} + +void TestQgsProcessing::parameterMeshDatasetTime() +{ + QgsProcessingContext context; + QgsProject project; + context.setProject( &project ); + + std::unique_ptr< QgsProcessingParameterMeshDatasetTime> def( new QgsProcessingParameterMeshDatasetTime( QStringLiteral( "dataset groups" ), QStringLiteral( "groups" ) ) ); + QVERIFY( def->type() == QStringLiteral( "meshdatasettime" ) ); + QVERIFY( !def->checkValueIsAcceptable( 1 ) ); + QVERIFY( !def->checkValueIsAcceptable( 1.0 ) ); + QVERIFY( !def->checkValueIsAcceptable( "test" ) ); + QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); + + QVariantMap value; + value[QStringLiteral( "test" )] = QStringLiteral( "test" ); + QVERIFY( !def->checkValueIsAcceptable( value ) ); + + value.clear(); + value[QStringLiteral( "type" )] = QStringLiteral( "test" ); + QVERIFY( !def->checkValueIsAcceptable( value ) ); + + value[QStringLiteral( "type" )] = QStringLiteral( "static" ); + QVERIFY( def->checkValueIsAcceptable( value ) ); + QCOMPARE( def->valueAsPythonString( value, context ), QStringLiteral( "{'type': 'static'}" ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( value ), QStringLiteral( "static" ) ); + + + value[QStringLiteral( "type" )] = QStringLiteral( "current-context-time" ); + QVERIFY( def->checkValueIsAcceptable( value ) ); + QCOMPARE( def->valueAsPythonString( value, context ), QStringLiteral( "{'type': 'current-context-time'}" ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( value ), QStringLiteral( "current-context-time" ) ); + + value[QStringLiteral( "type" )] = QStringLiteral( "defined-date-time" ); + QVERIFY( !def->checkValueIsAcceptable( value ) ); + value[QStringLiteral( "value" )] = QDateTime( QDate( 2123, 1, 2 ), QTime( 1, 2, 3 ) ); + QVERIFY( def->checkValueIsAcceptable( value ) ); + QCOMPARE( def->valueAsPythonString( value, context ), QStringLiteral( "{'type': 'defined-date-time','value': QDateTime(QDate(2123, 1, 2), QTime(1, 2, 3))}" ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( value ), QStringLiteral( "defined-date-time" ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( value ), QDateTime( QDate( 2123, 1, 2 ), QTime( 1, 2, 3 ) ) ); + QVERIFY( !QgsProcessingParameterMeshDatasetTime::timeValueAsDatasetIndex( value ).isValid() ); + + value.clear(); + value[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" ); + QVERIFY( !def->checkValueIsAcceptable( value ) ); + value[QStringLiteral( "value" )] = QVariantList() << 1 << 5; + QVERIFY( def->checkValueIsAcceptable( value ) ); + QCOMPARE( def->valueAsPythonString( value, context ), QStringLiteral( "{'type': 'dataset-time-step','value': QgsMeshDatasetIndex(1,5)}" ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( value ), QStringLiteral( "dataset-time-step" ) ); + QVERIFY( !QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( value ).isValid() ); + QVERIFY( QgsProcessingParameterMeshDatasetTime::timeValueAsDatasetIndex( value ) == QgsMeshDatasetIndex( 1, 5 ) ); + + QString pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMeshDatasetTime('dataset groups', 'groups')" ) ); + + QVERIFY( def->dependsOnOtherParameters().isEmpty() ); + + def.reset( new QgsProcessingParameterMeshDatasetTime( QStringLiteral( "dataset groups" ), QStringLiteral( "groups" ), QStringLiteral( "layer parameter" ), QStringLiteral( "dataset group parameter" ) ) ); + pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMeshDatasetTime('dataset groups', 'groups', meshLayerParameterName='layer parameter', datasetGroupParameterName='dataset group parameter')" ) ); + + QVERIFY( !def->dependsOnOtherParameters().isEmpty() ); + QCOMPARE( def->meshLayerParameterName(), QStringLiteral( "layer parameter" ) ); + QCOMPARE( def->datasetGroupParameterName(), QStringLiteral( "dataset group parameter" ) ); +} + void TestQgsProcessing::parameterDateTime() { QgsProcessingContext context; @@ -11160,6 +11297,7 @@ void TestQgsProcessing::variantToPythonLiteral() QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QStringLiteral( "a 'string'" ) ), QStringLiteral( "'a \\'string\\''" ) ); QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QStringLiteral( "a \"string\"" ) ), QStringLiteral( "'a \\\"string\\\"'" ) ); QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QStringLiteral( "a \n str\tin\\g" ) ), QStringLiteral( "'a \\n str\\tin\\\\g'" ) ); + QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QDateTime( QDate( 2345, 1, 2 ), QTime( 6, 57, 58 ) ) ), QStringLiteral( "QDateTime(QDate(2345, 1, 2), QTime(6, 57, 58))" ) ); QVariantMap map; map.insert( QStringLiteral( "list" ), QVariantList() << 1 << 2 << "a" ); map.insert( QStringLiteral( "another" ), 4 ); @@ -11176,6 +11314,8 @@ void TestQgsProcessing::stringToPythonLiteral() QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a \n str\tin\\g" ) ), QStringLiteral( "'a \\n str\\tin\\\\g'" ) ); } + + void TestQgsProcessing::defaultExtensionsForProvider() { DummyProvider3 provider; diff --git a/tests/src/analysis/testqgsprocessingalgs.cpp b/tests/src/analysis/testqgsprocessingalgs.cpp index dff5ccd4482..63c26d825c0 100644 --- a/tests/src/analysis/testqgsprocessingalgs.cpp +++ b/tests/src/analysis/testqgsprocessingalgs.cpp @@ -149,6 +149,7 @@ class TestQgsProcessingAlgs: public QObject void exportAtlasLayoutPng(); void tinMeshCreation(); + void exportMeshVertices(); private: @@ -227,6 +228,19 @@ void TestQgsProcessingAlgs::initTestCase() QgsProject::instance()->addMapLayers( QList() << mPolygonLayer ); QVERIFY( mPolygonLayer->isValid() ); + + //add a mesh layer + QString uri( dataDir + "/mesh/quad_and_triangle.2dm" ); + QString meshLayerName = QStringLiteral( "mesh layer" ); + QgsMeshLayer *meshLayer = new QgsMeshLayer( uri, meshLayerName, QStringLiteral( "mdal" ) ); + // Register the layer with the registry + QgsProject::instance()->addMapLayer( meshLayer ); + QVERIFY( meshLayer->isValid() ); + meshLayer->addDatasets( dataDir + "/mesh/quad_and_triangle_vertex_scalar.dat" ); + meshLayer->addDatasets( dataDir + "/mesh/quad_and_triangle_vertex_vector.dat" ); + meshLayer->addDatasets( dataDir + "/mesh/quad_and_triangle_els_face_scalar.dat" ); + meshLayer->addDatasets( dataDir + "/mesh/quad_and_triangle_els_face_vector.dat" ); + QCOMPARE( meshLayer->datasetGroupCount(), 5 ); } void TestQgsProcessingAlgs::cleanupTestCase() @@ -4797,6 +4811,89 @@ void TestQgsProcessingAlgs::tinMeshCreation() QVERIFY( qgsDoubleNear( meshLayer.datasetValue( QgsMeshDatasetIndex( 0, 0 ), QgsPointXY( -86.0, 35.0 ) ).scalar(), 1.855, 0.001 ) ) ; } +void TestQgsProcessingAlgs::exportMeshVertices() +{ + std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:exportmeshvertices" ) ) ); + QVERIFY( alg != nullptr ); + + QVariantMap parameters; + parameters.insert( QStringLiteral( "INPUT" ), "mesh layer" ); + + QVariantList datasetGroup; + datasetGroup << 1 << 2; + parameters.insert( QStringLiteral( "DATASET_GROUPS" ), datasetGroup ); + + QVariantMap datasetTime; + datasetTime[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" ); + QVariantList datasetIndex; + datasetIndex << 1 << 1; + datasetTime[QStringLiteral( "value" )] = datasetIndex; + parameters.insert( QStringLiteral( "DATASET_TIME" ), datasetTime ); + + parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT ); + parameters.insert( QStringLiteral( "VECTOR_OPTION" ), 2 ); + + std::unique_ptr< QgsProcessingContext > context = qgis::make_unique< QgsProcessingContext >(); + context->setProject( QgsProject::instance() ); + QgsProcessingFeedback feedback; + QVariantMap results; + bool ok = false; + results = alg->run( parameters, *context, &feedback, &ok ); + QVERIFY( ok ); + + QgsVectorLayer *resultLayer = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) ); + QVERIFY( resultLayer ); + QVERIFY( resultLayer->isValid() ); + QVERIFY( resultLayer->geometryType() == QgsWkbTypes::PointGeometry ); + QCOMPARE( resultLayer->featureCount(), 5l ); + QgsAttributeList attributeList = resultLayer->attributeList(); + QCOMPARE( resultLayer->fields().count(), 5 ); + QCOMPARE( resultLayer->fields().at( 0 ).name(), QStringLiteral( "VertexScalarDataset" ) ); + QCOMPARE( resultLayer->fields().at( 1 ).name(), QStringLiteral( "VertexVectorDataset_x" ) ); + QCOMPARE( resultLayer->fields().at( 2 ).name(), QStringLiteral( "VertexVectorDataset_y" ) ); + QCOMPARE( resultLayer->fields().at( 3 ).name(), QStringLiteral( "VertexVectorDataset_mag" ) ); + QCOMPARE( resultLayer->fields().at( 4 ).name(), QStringLiteral( "VertexVectorDataset_dir" ) ); + + QgsFeatureIterator featIt = resultLayer->getFeatures(); + QgsFeature feat; + featIt.nextFeature( feat ); + QCOMPARE( QStringLiteral( "PointZ (1000 2000 20)" ), feat.geometry().asWkt() ); + QCOMPARE( feat.attributes().at( 0 ).toDouble(), 2.0 ); + QCOMPARE( feat.attributes().at( 1 ).toDouble(), 2.0 ); + QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.0 ); + QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 2.828, 2 ) ); + QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 45.0, 2 ) ); + + featIt.nextFeature( feat ); + QCOMPARE( QStringLiteral( "PointZ (2000 2000 30)" ), feat.geometry().asWkt() ); + QCOMPARE( feat.attributes().at( 0 ).toDouble(), 3.0 ); + QCOMPARE( feat.attributes().at( 1 ).toDouble(), 3.0 ); + QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.0 ); + QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 3.605, 2 ) ); + QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 56.3099, 2 ) ); + featIt.nextFeature( feat ); + QCOMPARE( QStringLiteral( "PointZ (3000 2000 40)" ), feat.geometry().asWkt() ); + QCOMPARE( feat.attributes().at( 0 ).toDouble(), 4.0 ); + QCOMPARE( feat.attributes().at( 1 ).toDouble(), 4.0 ); + QCOMPARE( feat.attributes().at( 2 ).toDouble(), 3.0 ); + QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 5.0, 2 ) ); + QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 53.130, 2 ) ); + featIt.nextFeature( feat ); + QCOMPARE( QStringLiteral( "PointZ (2000 3000 50)" ), feat.geometry().asWkt() ); + QCOMPARE( feat.attributes().at( 0 ).toDouble(), 3.0 ); + QCOMPARE( feat.attributes().at( 1 ).toDouble(), 3.0 ); + QCOMPARE( feat.attributes().at( 2 ).toDouble(), 3.0 ); + QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 4.242, 2 ) ); + QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 45, 2 ) ); + featIt.nextFeature( feat ); + QCOMPARE( QStringLiteral( "PointZ (1000 3000 10)" ), feat.geometry().asWkt() ); + QCOMPARE( feat.attributes().at( 0 ).toDouble(), 2.0 ); + QCOMPARE( feat.attributes().at( 1 ).toDouble(), 2.0 ); + QCOMPARE( feat.attributes().at( 2 ).toDouble(), -1.0 ); + QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 2.236, 2 ) ); + QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 116.565, 2 ) ); +} + bool TestQgsProcessingAlgs::imageCheck( const QString &testName, const QString &renderedImage ) { QgsRenderChecker checker; diff --git a/tests/src/app/testqgsmaptoolidentifyaction.cpp b/tests/src/app/testqgsmaptoolidentifyaction.cpp index 6682ae72811..7b1ccdce779 100644 --- a/tests/src/app/testqgsmaptoolidentifyaction.cpp +++ b/tests/src/app/testqgsmaptoolidentifyaction.cpp @@ -668,7 +668,7 @@ void TestQgsMapToolIdentifyAction::identifyMesh() QCOMPARE( results[0].mAttributes[ QStringLiteral( "Scalar Value" )], QStringLiteral( "42" ) ); QCOMPARE( results[0].mDerivedAttributes[QStringLiteral( "Source" )], mesh ); - QCOMPARE( results[1].mDerivedAttributes[ QStringLiteral( "Time Step" )], QStringLiteral( "01.01.1950 00:00:00" ) ); + QCOMPARE( results[1].mDerivedAttributes[ QStringLiteral( "Time Step" )], QStringLiteral( "1950-01-01 00:00:00" ) ); QCOMPARE( results[1].mLabel, QStringLiteral( "VertexVectorDataset" ) ); QCOMPARE( results[1].mDerivedAttributes[QStringLiteral( "Source" )], vectorDs ); diff --git a/tests/src/gui/testprocessinggui.cpp b/tests/src/gui/testprocessinggui.cpp index 5f2a0f942c9..3242676b91a 100644 --- a/tests/src/gui/testprocessinggui.cpp +++ b/tests/src/gui/testprocessinggui.cpp @@ -80,6 +80,7 @@ #include "qgsprocessingfeaturesourceoptionswidget.h" #include "qgsextentwidget.h" #include "qgsrasterbandcombobox.h" +#include "qgsmeshlayertemporalproperties.h" #include "qgsmodelgraphicsscene.h" #include "qgsmodelgraphicsview.h" #include "qgsmodelcomponentgraphicitem.h" @@ -91,6 +92,7 @@ #include "qgsprocessingtininputlayerswidget.h" #include "qgsprocessingparameterdxflayers.h" #include "qgsprocessingdxflayerswidgetwrapper.h" +#include "qgsprocessingmeshdatasetwidget.h" class TestParamType : public QgsProcessingParameterDefinition @@ -254,6 +256,8 @@ class TestProcessingGui : public QObject void testFolderOutWrapper(); void testTinInputLayerWrapper(); void testDxfLayersWrapper(); + void testMeshDatasetWrapperLayerInProject(); + void testMeshDatasetWrapperLayerOutsideProject(); void testModelGraphicsView(); private: @@ -8907,6 +8911,311 @@ void TestProcessingGui::testDxfLayersWrapper() QCOMPARE( valueAsPythonString, QStringLiteral( "[{'layer': '%1','attributeIndex': -1}]" ).arg( vectorLayer->source() ) ); } +void TestProcessingGui::testMeshDatasetWrapperLayerInProject() +{ + QgsProcessingParameterMeshLayer layerDefinition( QStringLiteral( "layer" ), QStringLiteral( "layer" ) ); + QgsProcessingMeshLayerWidgetWrapper layerWrapper( &layerDefinition ); + + QgsProcessingParameterMeshDatasetGroups groupsDefinition( QStringLiteral( "groups" ), + QStringLiteral( "groups" ), + QStringLiteral( "layer" ), + QgsMeshDatasetGroupMetadata::DataOnVertices ); + QgsProcessingMeshDatasetGroupsWidgetWrapper groupsWrapper( &groupsDefinition ); + + QgsProcessingParameterMeshDatasetTime timeDefinition( QStringLiteral( "time" ), QStringLiteral( "time" ), QStringLiteral( "layer" ), QStringLiteral( "groups" ) ); + QgsProcessingMeshDatasetTimeWidgetWrapper timeWrapper( &timeDefinition ); + + QList wrappers; + wrappers << &layerWrapper << &groupsWrapper << &timeWrapper; + + QgsProject project; + QgsProcessingContext context; + context.setProject( &project ); + QgsProcessingParameterWidgetContext widgetContext; + std::unique_ptr mapCanvas = qgis::make_unique(); + widgetContext.setMapCanvas( mapCanvas.get() ); + + widgetContext.setProject( &project ); + layerWrapper.setWidgetContext( widgetContext ); + groupsWrapper.setWidgetContext( widgetContext ); + timeWrapper.setWidgetContext( widgetContext ); + + TestProcessingContextGenerator generator( context ); + layerWrapper.registerProcessingContextGenerator( &generator ); + groupsWrapper.registerProcessingContextGenerator( &generator ); + timeWrapper.registerProcessingContextGenerator( &generator ); + + + QSignalSpy layerSpy( &layerWrapper, &QgsProcessingMeshLayerWidgetWrapper::widgetValueHasChanged ); + QSignalSpy groupsSpy( &groupsWrapper, &QgsProcessingMeshDatasetGroupsWidgetWrapper::widgetValueHasChanged ); + QSignalSpy timeSpy( &timeWrapper, &QgsProcessingMeshDatasetTimeWidgetWrapper::widgetValueHasChanged ); + + std::unique_ptr layerWidget( layerWrapper.createWrappedWidget( context ) ); + std::unique_ptr groupWidget( groupsWrapper.createWrappedWidget( context ) ); + std::unique_ptr timeWidget( timeWrapper.createWrappedWidget( context ) ); + QgsProcessingMeshDatasetGroupsWidget *datasetGroupWidget = qobject_cast( groupWidget.get() ); + QgsProcessingMeshDatasetTimeWidget *datasetTimeWidget = qobject_cast( timeWidget.get() ); + + QVERIFY( layerWidget ); + QVERIFY( groupWidget ); + QVERIFY( datasetGroupWidget ); + QVERIFY( timeWidget ); + + groupsWrapper.postInitialize( wrappers ); + timeWrapper.postInitialize( wrappers ); + + QString dataDir = QString( TEST_DATA_DIR ); //defined in CmakeLists.txt + dataDir += "/mesh"; + QString uri( dataDir + "/quad_and_triangle.2dm" ); + QString meshLayerName = QStringLiteral( "mesh layer" ); + QgsMeshLayer *layer = new QgsMeshLayer( uri, meshLayerName, QStringLiteral( "mdal" ) ); + QVERIFY( layer->isValid() ); + layer->addDatasets( dataDir + "/quad_and_triangle_vertex_scalar.dat" ); + layer->addDatasets( dataDir + "/quad_and_triangle_vertex_vector.dat" ); + layer->addDatasets( dataDir + "/quad_and_triangle_els_face_scalar.dat" ); + layer->addDatasets( dataDir + "/quad_and_triangle_els_face_vector.dat" ); + QgsMeshRendererSettings settings = layer->rendererSettings(); + // 1 dataset on vertices and 1 dataset on faces + settings.setActiveScalarDatasetGroup( 1 ); + settings.setActiveVectorDatasetGroup( 4 ); + layer->setRendererSettings( settings ); + QCOMPARE( layer->datasetGroupCount(), 5 ); + + layerSpy.clear(); + groupsSpy.clear(); + timeSpy.clear(); + + // without layer in the project + QString meshOutOfProject( dataDir + "/trap_steady_05_3D.nc" ); + layerWrapper.setWidgetValue( meshOutOfProject, context ); + + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 1 ); + QCOMPARE( timeSpy.count(), 1 ); + + QVERIFY( datasetTimeWidget->radioButtonDatasetGroupTimeStep->isChecked() ); + + QVariantList groups; + groups << 0; + groupsWrapper.setWidgetValue( groups, context ); + QVERIFY( groupsDefinition.checkValueIsAcceptable( groupsWrapper.widgetValue() ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsWrapper.widgetValue() ), QList() << 0 ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "static" ) ); + + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 2 ); + QCOMPARE( timeSpy.count(), 3 ); + + // with layer in the project + layerSpy.clear(); + groupsSpy.clear(); + timeSpy.clear(); + + project.addMapLayer( layer ); + static_cast( layer->temporalProperties() )->setReferenceTime( + QDateTime( QDate( 2020, 01, 01 ), QTime( 0, 0, 0 ), Qt::UTC ), layer->dataProvider()->temporalCapabilities() ); + layerWrapper.setWidgetValue( meshLayerName, context ); + + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 1 ); + QCOMPARE( timeSpy.count(), 2 ); + + datasetGroupWidget->selectCurrentActiveDatasetGroup(); + + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 2 ); + QCOMPARE( timeSpy.count(), 3 ); + + QVariant groupsValue = groupsWrapper.widgetValue(); + QVERIFY( groupsValue.type() == QVariant::List ); + QVariantList groupsList = groupsValue.toList(); + QCOMPARE( groupsList.count(), 1 ); + QCOMPARE( groupsList.at( 0 ).toInt(), 1 ); + QString pythonString = groupsDefinition.valueAsPythonString( groupsValue, context ); + QCOMPARE( pythonString, QStringLiteral( "[1]" ) ); + QVERIFY( groupsDefinition.checkValueIsAcceptable( groupsValue ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsValue ), QList( {1} ) ); + + // 2 datasets on vertices + settings = layer->rendererSettings(); + settings.setActiveVectorDatasetGroup( 2 ); + layer->setRendererSettings( settings ); + datasetGroupWidget->selectCurrentActiveDatasetGroup(); + + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 3 ); + QCOMPARE( timeSpy.count(), 4 ); + + pythonString = groupsDefinition.valueAsPythonString( groupsWrapper.widgetValue(), context ); + QCOMPARE( pythonString, QStringLiteral( "[1,2]" ) ); + QVERIFY( groupsDefinition.checkValueIsAcceptable( groupsWrapper.widgetValue() ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsWrapper.widgetValue() ), QList() << 1 << 2 ); + + datasetTimeWidget->radioButtonDatasetGroupTimeStep->setChecked( true ); + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 3 ); + QCOMPARE( timeSpy.count(), 4 ); //radioButtonDatasetGroupTimeStep already checked + + QVariant timeValue = timeWrapper.widgetValue(); + QVERIFY( timeValue.type() == QVariant::Map ); + QVariantMap timeValueMap = timeValue.toMap(); + QCOMPARE( timeValueMap[QStringLiteral( "type" )].toString(), QStringLiteral( "dataset-time-step" ) ); + pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context ); + QCOMPARE( pythonString, QStringLiteral( "{'type': 'dataset-time-step','value': QgsMeshDatasetIndex(1,0)}" ) ); + QVERIFY( timeDefinition.checkValueIsAcceptable( timeValue ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeValue ), QStringLiteral( "dataset-time-step" ) ); + QVERIFY( QgsProcessingParameterMeshDatasetTime::timeValueAsDatasetIndex( timeValue ) == QgsMeshDatasetIndex( 1, 0 ) ); + + datasetTimeWidget->radioButtonDefinedDateTime->setChecked( true ); + QDateTime dateTime = QDateTime( QDate( 2020, 1, 1 ), QTime( 0, 1, 0 ), Qt::UTC ); + datasetTimeWidget->dateTimeEdit->setDateTime( dateTime ); + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 3 ); + QCOMPARE( timeSpy.count(), 6 ); + pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context ); + QCOMPARE( pythonString, QStringLiteral( "{'type': 'defined-date-time','value': QDateTime(QDate(2020, 1, 1), QTime(0, 1, 0))}" ) ); + QVERIFY( timeDefinition.checkValueIsAcceptable( timeWrapper.widgetValue() ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "defined-date-time" ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( timeWrapper.widgetValue() ), dateTime ); + + QVERIFY( !datasetTimeWidget->radioButtonCurrentCanvasTime->isEnabled() ); + mapCanvas->setTemporalRange( QgsDateTimeRange( QDateTime( QDate( 2021, 1, 1 ), QTime( 0, 3, 0 ), Qt::UTC ), QDateTime( QDate( 2020, 1, 1 ), QTime( 0, 5, 0 ), Qt::UTC ) ) ); + QVERIFY( datasetTimeWidget->radioButtonCurrentCanvasTime->isEnabled() ); + + datasetTimeWidget->radioButtonCurrentCanvasTime->setChecked( true ); + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 3 ); + QCOMPARE( timeSpy.count(), 8 ); + pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context ); + QCOMPARE( pythonString, QStringLiteral( "{'type': 'current-context-time'}" ) ); + QVERIFY( timeDefinition.checkValueIsAcceptable( timeWrapper.widgetValue() ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "current-context-time" ) ); + + // 0 dataset on vertices + settings = layer->rendererSettings(); + settings.setActiveScalarDatasetGroup( -1 ); + settings.setActiveVectorDatasetGroup( -1 ); + layer->setRendererSettings( settings ); + datasetGroupWidget->selectCurrentActiveDatasetGroup(); + QVERIFY( !datasetTimeWidget->isEnabled() ); + pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context ); + QCOMPARE( pythonString, QStringLiteral( "{'type': 'static'}" ) ); + QVERIFY( timeDefinition.checkValueIsAcceptable( timeWrapper.widgetValue() ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "static" ) ); + + // 1 static dataset on vertices + settings = layer->rendererSettings(); + settings.setActiveScalarDatasetGroup( 0 ); + settings.setActiveVectorDatasetGroup( -1 ); + layer->setRendererSettings( settings ); + datasetGroupWidget->selectCurrentActiveDatasetGroup(); + QVERIFY( !datasetTimeWidget->isEnabled() ); + pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context ); + QCOMPARE( pythonString, QStringLiteral( "{'type': 'static'}" ) ); + QVERIFY( timeDefinition.checkValueIsAcceptable( timeWrapper.widgetValue() ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "static" ) ); +} + +void TestProcessingGui::testMeshDatasetWrapperLayerOutsideProject() +{ + QgsProcessingParameterMeshLayer layerDefinition( QStringLiteral( "layer" ), QStringLiteral( "layer" ) ); + QgsProcessingMeshLayerWidgetWrapper layerWrapper( &layerDefinition ); + + QgsProcessingParameterMeshDatasetGroups groupsDefinition( QStringLiteral( "groups" ), + QStringLiteral( "groups" ), + QStringLiteral( "layer" ), + QgsMeshDatasetGroupMetadata::DataOnFaces ); + QgsProcessingMeshDatasetGroupsWidgetWrapper groupsWrapper( &groupsDefinition ); + + QgsProcessingParameterMeshDatasetTime timeDefinition( QStringLiteral( "time" ), QStringLiteral( "time" ), QStringLiteral( "layer" ), QStringLiteral( "groups" ) ); + QgsProcessingMeshDatasetTimeWidgetWrapper timeWrapper( &timeDefinition ); + + QList wrappers; + wrappers << &layerWrapper << &groupsWrapper << &timeWrapper; + + QgsProject project; + QgsProcessingContext context; + context.setProject( &project ); + QgsProcessingParameterWidgetContext widgetContext; + std::unique_ptr mapCanvas = qgis::make_unique(); + widgetContext.setMapCanvas( mapCanvas.get() ); + + widgetContext.setProject( &project ); + layerWrapper.setWidgetContext( widgetContext ); + groupsWrapper.setWidgetContext( widgetContext ); + timeWrapper.setWidgetContext( widgetContext ); + + TestProcessingContextGenerator generator( context ); + layerWrapper.registerProcessingContextGenerator( &generator ); + groupsWrapper.registerProcessingContextGenerator( &generator ); + timeWrapper.registerProcessingContextGenerator( &generator ); + + QSignalSpy layerSpy( &layerWrapper, &QgsProcessingMeshLayerWidgetWrapper::widgetValueHasChanged ); + QSignalSpy groupsSpy( &groupsWrapper, &QgsProcessingMeshDatasetGroupsWidgetWrapper::widgetValueHasChanged ); + QSignalSpy timeSpy( &timeWrapper, &QgsProcessingMeshDatasetTimeWidgetWrapper::widgetValueHasChanged ); + + std::unique_ptr layerWidget( layerWrapper.createWrappedWidget( context ) ); + std::unique_ptr groupWidget( groupsWrapper.createWrappedWidget( context ) ); + std::unique_ptr timeWidget( timeWrapper.createWrappedWidget( context ) ); + QgsProcessingMeshDatasetGroupsWidget *datasetGroupWidget = qobject_cast( groupWidget.get() ); + QgsProcessingMeshDatasetTimeWidget *datasetTimeWidget = qobject_cast( timeWidget.get() ); + + QVERIFY( layerWidget ); + QVERIFY( groupWidget ); + QVERIFY( datasetGroupWidget ); + QVERIFY( timeWidget ); + + groupsWrapper.postInitialize( wrappers ); + timeWrapper.postInitialize( wrappers ); + + layerSpy.clear(); + groupsSpy.clear(); + timeSpy.clear(); + + QString dataDir = QString( TEST_DATA_DIR ); //defined in CmakeLists.txt + QString meshOutOfProject( dataDir + "/mesh/trap_steady_05_3D.nc" ); + layerWrapper.setWidgetValue( meshOutOfProject, context ); + + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 1 ); + QCOMPARE( timeSpy.count(), 1 ); + + QVariantList groups; + groups << 0; + groupsWrapper.setWidgetValue( groups, context ); + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 2 ); + QCOMPARE( timeSpy.count(), 3 ); + QVERIFY( groupsDefinition.checkValueIsAcceptable( groupsWrapper.widgetValue() ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsWrapper.widgetValue() ), QList() << 0 ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "static" ) ); + QVERIFY( !datasetTimeWidget->isEnabled() ); + + groups << 11; + groupsWrapper.setWidgetValue( groups, context ); + QCOMPARE( layerSpy.count(), 1 ); + QCOMPARE( groupsSpy.count(), 3 ); + QCOMPARE( timeSpy.count(), 5 ); + QVERIFY( datasetTimeWidget->isEnabled() ); + QVERIFY( groupsDefinition.checkValueIsAcceptable( groupsWrapper.widgetValue() ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsWrapper.widgetValue() ), QList() << 0 << 11 ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "dataset-time-step" ) ); + QVERIFY( QgsProcessingParameterMeshDatasetTime::timeValueAsDatasetIndex( timeWrapper.widgetValue() ) == QgsMeshDatasetIndex( 11, 0 ) ); + + QVERIFY( datasetTimeWidget->radioButtonDefinedDateTime->isEnabled() ); + QVERIFY( !datasetTimeWidget->radioButtonCurrentCanvasTime->isEnabled() ); + + datasetTimeWidget->radioButtonDefinedDateTime->setChecked( true ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "defined-date-time" ) ); + QCOMPARE( QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( timeWrapper.widgetValue() ), + QDateTime( QDate( 1990, 1, 1 ), QTime( 0, 0, 0 ), Qt::UTC ) ); + + + mapCanvas->setTemporalRange( QgsDateTimeRange( QDateTime( QDate( 2021, 1, 1 ), QTime( 0, 3, 0 ), Qt::UTC ), QDateTime( QDate( 2020, 1, 1 ), QTime( 0, 5, 0 ), Qt::UTC ) ) ); + QVERIFY( datasetTimeWidget->radioButtonCurrentCanvasTime->isEnabled() ); + +} + void TestProcessingGui::testModelGraphicsView() { // test model