diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index 7c5c22663f2b..5d10aa2e64fb 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -3278,7 +3278,8 @@ # monkey patching scoped based enum Qgis.MeshElevationMode.FixedElevationRange.__doc__ = "Layer has a fixed elevation range" Qgis.MeshElevationMode.FromVertices.__doc__ = "Elevation should be taken from mesh vertices" -Qgis.MeshElevationMode.__doc__ = "Mesh layer elevation modes.\n\n.. versionadded:: 3.38\n\n" + '* ``FixedElevationRange``: ' + Qgis.MeshElevationMode.FixedElevationRange.__doc__ + '\n' + '* ``FromVertices``: ' + Qgis.MeshElevationMode.FromVertices.__doc__ +Qgis.MeshElevationMode.FixedRangePerGroup.__doc__ = "Layer has a fixed (manually specified) elevation range per group" +Qgis.MeshElevationMode.__doc__ = "Mesh layer elevation modes.\n\n.. versionadded:: 3.38\n\n" + '* ``FixedElevationRange``: ' + Qgis.MeshElevationMode.FixedElevationRange.__doc__ + '\n' + '* ``FromVertices``: ' + Qgis.MeshElevationMode.FromVertices.__doc__ + '\n' + '* ``FixedRangePerGroup``: ' + Qgis.MeshElevationMode.FixedRangePerGroup.__doc__ # -- Qgis.MeshElevationMode.baseClass = Qgis # monkey patching scoped based enum diff --git a/python/PyQt6/core/auto_generated/mesh/qgsmeshdataset.sip.in b/python/PyQt6/core/auto_generated/mesh/qgsmeshdataset.sip.in index 5df433842ae5..a0725f4ddc7d 100644 --- a/python/PyQt6/core/auto_generated/mesh/qgsmeshdataset.sip.in +++ b/python/PyQt6/core/auto_generated/mesh/qgsmeshdataset.sip.in @@ -12,6 +12,7 @@ + class QgsMeshDatasetIndex { %Docstring(signature="appended") @@ -431,6 +432,16 @@ Constructs a valid metadata object QString name() const; %Docstring Returns name of the dataset group +%End + + QString parentQuantityName() const; +%Docstring +Returns the name of the dataset's parent quantity, if available. + +The quantity can be used to collect dataset groups which represent a single quantity +but at different values (e.g. groups which represent different elevations). + +.. versionadded:: 3.38 %End QString uri() const; diff --git a/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in index ad3f120c534f..89931877d2c7 100644 --- a/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -592,52 +592,54 @@ Dataset index is valid even the temporal properties is inactive. This method is .. versionadded:: 3.22 %End - QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) const; %Docstring Returns dataset index from active scalar group depending on the time range. If the temporal properties is not active, return the static dataset -:param timeRange: the time range - -:return: dataset index +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. note:: the returned dataset index depends on the matching method, see :py:func:`~QgsMeshLayer.setTemporalMatchingMethod` - .. versionadded:: 3.14 %End - QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) const; %Docstring Returns dataset index from active vector group depending on the time range If the temporal properties is not active, return the static dataset -:param timeRange: the time range - -:return: dataset index +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. note:: the returned dataset index depends on the matching method, see :py:func:`~QgsMeshLayer.setTemporalMatchingMethod` - .. versionadded:: 3.14 %End - QgsMeshDatasetIndex staticScalarDatasetIndex() const; + QgsMeshDatasetIndex staticScalarDatasetIndex( int group = -1 ) const; %Docstring -Returns the static scalar dataset index that is rendered if the temporal properties is not active +Returns the static scalar dataset index that is rendered if the temporal properties is not active. + +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. versionadded:: 3.14 %End - QgsMeshDatasetIndex staticVectorDatasetIndex() const; + QgsMeshDatasetIndex staticVectorDatasetIndex( int group = -1 ) const; %Docstring -Returns the static vector dataset index that is rendered if the temporal properties is not active +Returns the static vector dataset index that is rendered if the temporal properties is not active. + +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. versionadded:: 3.14 %End diff --git a/python/PyQt6/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in b/python/PyQt6/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in index f15a152d4cf7..28266b6c237c 100644 --- a/python/PyQt6/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in +++ b/python/PyQt6/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in @@ -100,6 +100,42 @@ Sets the fixed elevation ``range`` for the mesh. .. seealso:: :py:func:`fixedRange` +.. versionadded:: 3.38 +%End + + QMap fixedRangePerGroup() const; +%Docstring +Returns the fixed elevation range for each group. + +.. note:: + + This is only considered when :py:func:`~QgsMeshLayerElevationProperties.mode` is :py:class:`Qgis`.MeshElevationMode.FixedRangePerGroup. + +.. note:: + + When a fixed range is set any :py:func:`~QgsMeshLayerElevationProperties.zOffset` and :py:func:`~QgsMeshLayerElevationProperties.zScale` is ignored. + + +.. seealso:: :py:func:`setFixedRangePerGroup` + +.. versionadded:: 3.38 +%End + + void setFixedRangePerGroup( const QMap &ranges ); +%Docstring +Sets the fixed elevation range for each group. + +.. note:: + + This is only considered when :py:func:`~QgsMeshLayerElevationProperties.mode` is :py:class:`Qgis`.MeshElevationMode.FixedRangePerGroup. + +.. note:: + + When a fixed range is set any :py:func:`~QgsMeshLayerElevationProperties.zOffset` and :py:func:`~QgsMeshLayerElevationProperties.zScale` is ignored. + + +.. seealso:: :py:func:`fixedRangePerGroup` + .. versionadded:: 3.38 %End diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index 2c84aca227df..879b61c981c4 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -1888,7 +1888,8 @@ The development version enum class MeshElevationMode /BaseType=IntEnum/ { FixedElevationRange, - FromVertices + FromVertices, + FixedRangePerGroup, }; enum class BetweenLineConstraint /BaseType=IntEnum/ diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index d04309e17602..e47789aef6aa 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -3223,7 +3223,8 @@ # monkey patching scoped based enum Qgis.MeshElevationMode.FixedElevationRange.__doc__ = "Layer has a fixed elevation range" Qgis.MeshElevationMode.FromVertices.__doc__ = "Elevation should be taken from mesh vertices" -Qgis.MeshElevationMode.__doc__ = "Mesh layer elevation modes.\n\n.. versionadded:: 3.38\n\n" + '* ``FixedElevationRange``: ' + Qgis.MeshElevationMode.FixedElevationRange.__doc__ + '\n' + '* ``FromVertices``: ' + Qgis.MeshElevationMode.FromVertices.__doc__ +Qgis.MeshElevationMode.FixedRangePerGroup.__doc__ = "Layer has a fixed (manually specified) elevation range per group" +Qgis.MeshElevationMode.__doc__ = "Mesh layer elevation modes.\n\n.. versionadded:: 3.38\n\n" + '* ``FixedElevationRange``: ' + Qgis.MeshElevationMode.FixedElevationRange.__doc__ + '\n' + '* ``FromVertices``: ' + Qgis.MeshElevationMode.FromVertices.__doc__ + '\n' + '* ``FixedRangePerGroup``: ' + Qgis.MeshElevationMode.FixedRangePerGroup.__doc__ # -- Qgis.MeshElevationMode.baseClass = Qgis # monkey patching scoped based enum diff --git a/python/core/auto_generated/mesh/qgsmeshdataset.sip.in b/python/core/auto_generated/mesh/qgsmeshdataset.sip.in index 16f891990df0..fae0e459b1c2 100644 --- a/python/core/auto_generated/mesh/qgsmeshdataset.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshdataset.sip.in @@ -12,6 +12,7 @@ + class QgsMeshDatasetIndex { %Docstring(signature="appended") @@ -431,6 +432,16 @@ Constructs a valid metadata object QString name() const; %Docstring Returns name of the dataset group +%End + + QString parentQuantityName() const; +%Docstring +Returns the name of the dataset's parent quantity, if available. + +The quantity can be used to collect dataset groups which represent a single quantity +but at different values (e.g. groups which represent different elevations). + +.. versionadded:: 3.38 %End QString uri() const; diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index ad3f120c534f..89931877d2c7 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -592,52 +592,54 @@ Dataset index is valid even the temporal properties is inactive. This method is .. versionadded:: 3.22 %End - QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) const; %Docstring Returns dataset index from active scalar group depending on the time range. If the temporal properties is not active, return the static dataset -:param timeRange: the time range - -:return: dataset index +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. note:: the returned dataset index depends on the matching method, see :py:func:`~QgsMeshLayer.setTemporalMatchingMethod` - .. versionadded:: 3.14 %End - QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) const; %Docstring Returns dataset index from active vector group depending on the time range If the temporal properties is not active, return the static dataset -:param timeRange: the time range - -:return: dataset index +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. note:: the returned dataset index depends on the matching method, see :py:func:`~QgsMeshLayer.setTemporalMatchingMethod` - .. versionadded:: 3.14 %End - QgsMeshDatasetIndex staticScalarDatasetIndex() const; + QgsMeshDatasetIndex staticScalarDatasetIndex( int group = -1 ) const; %Docstring -Returns the static scalar dataset index that is rendered if the temporal properties is not active +Returns the static scalar dataset index that is rendered if the temporal properties is not active. + +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. versionadded:: 3.14 %End - QgsMeshDatasetIndex staticVectorDatasetIndex() const; + QgsMeshDatasetIndex staticVectorDatasetIndex( int group = -1 ) const; %Docstring -Returns the static vector dataset index that is rendered if the temporal properties is not active +Returns the static vector dataset index that is rendered if the temporal properties is not active. + +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. versionadded:: 3.14 %End diff --git a/python/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in b/python/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in index f15a152d4cf7..28266b6c237c 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in @@ -100,6 +100,42 @@ Sets the fixed elevation ``range`` for the mesh. .. seealso:: :py:func:`fixedRange` +.. versionadded:: 3.38 +%End + + QMap fixedRangePerGroup() const; +%Docstring +Returns the fixed elevation range for each group. + +.. note:: + + This is only considered when :py:func:`~QgsMeshLayerElevationProperties.mode` is :py:class:`Qgis`.MeshElevationMode.FixedRangePerGroup. + +.. note:: + + When a fixed range is set any :py:func:`~QgsMeshLayerElevationProperties.zOffset` and :py:func:`~QgsMeshLayerElevationProperties.zScale` is ignored. + + +.. seealso:: :py:func:`setFixedRangePerGroup` + +.. versionadded:: 3.38 +%End + + void setFixedRangePerGroup( const QMap &ranges ); +%Docstring +Sets the fixed elevation range for each group. + +.. note:: + + This is only considered when :py:func:`~QgsMeshLayerElevationProperties.mode` is :py:class:`Qgis`.MeshElevationMode.FixedRangePerGroup. + +.. note:: + + When a fixed range is set any :py:func:`~QgsMeshLayerElevationProperties.zOffset` and :py:func:`~QgsMeshLayerElevationProperties.zScale` is ignored. + + +.. seealso:: :py:func:`fixedRangePerGroup` + .. versionadded:: 3.38 %End diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index 93dec02db9a6..7eb6600c38a1 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -1888,7 +1888,8 @@ The development version enum class MeshElevationMode { FixedElevationRange, - FromVertices + FromVertices, + FixedRangePerGroup, }; enum class BetweenLineConstraint diff --git a/python/testing/__init__.py b/python/testing/__init__.py index 800a7fda454a..f7608a27518f 100644 --- a/python/testing/__init__.py +++ b/python/testing/__init__.py @@ -305,6 +305,19 @@ def render_layout_check( return result + @staticmethod + def get_test_data_path(file_path: str) -> Path: + """ + Returns the full path to a file contained within the test data + directory. + """ + from utilities import unitTestDataPath + + return ( + Path(unitTestDataPath()) / + (file_path[1:] if file_path.startswith('/') else file_path) + ) + def assertLayersEqual(self, layer_expected, layer_result, **kwargs): """ :param layer_expected: The first layer to compare diff --git a/src/app/mesh/qgsmeshelevationpropertieswidget.cpp b/src/app/mesh/qgsmeshelevationpropertieswidget.cpp index cd5ede8081b1..25e3150317ea 100644 --- a/src/app/mesh/qgsmeshelevationpropertieswidget.cpp +++ b/src/app/mesh/qgsmeshelevationpropertieswidget.cpp @@ -20,6 +20,10 @@ #include "qgsmeshlayerelevationproperties.h" #include "qgslinesymbol.h" #include "qgsfillsymbol.h" +#include "qgsexpressionbuilderdialog.h" +#include "qgsexpressioncontextutils.h" +#include +#include QgsMeshElevationPropertiesWidget::QgsMeshElevationPropertiesWidget( QgsMeshLayer *layer, QgsMapCanvas *canvas, QWidget *parent ) : QgsMapLayerConfigWidget( layer, canvas, parent ) @@ -29,6 +33,7 @@ QgsMeshElevationPropertiesWidget::QgsMeshElevationPropertiesWidget( QgsMeshLayer mModeComboBox->addItem( tr( "From Vertices" ), QVariant::fromValue( Qgis::MeshElevationMode::FromVertices ) ); mModeComboBox->addItem( tr( "Fixed Elevation Range" ), QVariant::fromValue( Qgis::MeshElevationMode::FixedElevationRange ) ); + mModeComboBox->addItem( tr( "Fixed Elevation Range Per Group" ), QVariant::fromValue( Qgis::MeshElevationMode::FixedRangePerGroup ) ); mLimitsComboBox->addItem( tr( "Include Lower and Upper" ), QVariant::fromValue( Qgis::RangeLimits::IncludeBoth ) ); mLimitsComboBox->addItem( tr( "Include Lower, Exclude Upper" ), QVariant::fromValue( Qgis::RangeLimits::IncludeLowerExcludeUpper ) ); @@ -52,6 +57,29 @@ QgsMeshElevationPropertiesWidget::QgsMeshElevationPropertiesWidget( QgsMeshLayer mFixedLowerSpinBox->clear(); mFixedUpperSpinBox->clear(); + mFixedRangePerGroupModel = new QgsMeshGroupFixedElevationRangeModel( this ); + mGroupElevationTable->verticalHeader()->setVisible( false ); + mGroupElevationTable->setModel( mFixedRangePerGroupModel ); + QgsMeshFixedElevationRangeDelegate *tableDelegate = new QgsMeshFixedElevationRangeDelegate( mGroupElevationTable ); + mGroupElevationTable->setItemDelegateForColumn( 1, tableDelegate ); + mGroupElevationTable->setItemDelegateForColumn( 2, tableDelegate ); + + QMenu *calculateFixedRangePerGroupMenu = new QMenu( mCalculateFixedRangePerGroupButton ); + mCalculateFixedRangePerGroupButton->setMenu( calculateFixedRangePerGroupMenu ); + mCalculateFixedRangePerGroupButton->setPopupMode( QToolButton::InstantPopup ); + QAction *calculateLowerAction = new QAction( "Calculate Lower by Expression…", calculateFixedRangePerGroupMenu ); + calculateFixedRangePerGroupMenu->addAction( calculateLowerAction ); + connect( calculateLowerAction, &QAction::triggered, this, [this] + { + calculateRangeByExpression( false ); + } ); + QAction *calculateUpperAction = new QAction( "Calculate Upper by Expression…", calculateFixedRangePerGroupMenu ); + calculateFixedRangePerGroupMenu->addAction( calculateUpperAction ); + connect( calculateUpperAction, &QAction::triggered, this, [this] + { + calculateRangeByExpression( true ); + } ); + syncToLayer( layer ); connect( mModeComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsMeshElevationPropertiesWidget::modeChanged ); @@ -97,6 +125,9 @@ void QgsMeshElevationPropertiesWidget::syncToLayer( QgsMapLayer *layer ) case Qgis::MeshElevationMode::FromVertices: mStackedWidget->setCurrentWidget( mPageFromVertices ); break; + case Qgis::MeshElevationMode::FixedRangePerGroup: + mStackedWidget->setCurrentWidget( mPageFixedRangePerGroup ); + break; } mOffsetZSpinBox->setValue( props->zOffset() ); @@ -116,6 +147,11 @@ void QgsMeshElevationPropertiesWidget::syncToLayer( QgsMapLayer *layer ) mFixedUpperSpinBox->clear(); mLimitsComboBox->setCurrentIndex( mLimitsComboBox->findData( QVariant::fromValue( props->fixedRange().rangeLimits() ) ) ); + mFixedRangePerGroupModel->setLayerData( mLayer, props->fixedRangePerGroup() ); + mGroupElevationTable->horizontalHeader()->setSectionResizeMode( 0, QHeaderView::Stretch ); + mGroupElevationTable->horizontalHeader()->setSectionResizeMode( 1, QHeaderView::Stretch ); + mGroupElevationTable->horizontalHeader()->setSectionResizeMode( 2, QHeaderView::Stretch ); + mLineStyleButton->setSymbol( props->profileLineSymbol()->clone() ); mFillStyleButton->setSymbol( props->profileFillSymbol()->clone() ); @@ -157,6 +193,7 @@ void QgsMeshElevationPropertiesWidget::apply() fixedUpper = mFixedUpperSpinBox->value(); props->setFixedRange( QgsDoubleRange( fixedLower, fixedUpper, mLimitsComboBox->currentData().value< Qgis::RangeLimits >() ) ); + props->setFixedRangePerGroup( mFixedRangePerGroupModel->rangeData() ); props->setProfileLineSymbol( mLineStyleButton->clonedSymbol< QgsLineSymbol >() ); props->setProfileFillSymbol( mFillStyleButton->clonedSymbol< QgsFillSymbol >() ); @@ -176,6 +213,9 @@ void QgsMeshElevationPropertiesWidget::modeChanged() case Qgis::MeshElevationMode::FromVertices: mStackedWidget->setCurrentWidget( mPageFromVertices ); break; + case Qgis::MeshElevationMode::FixedRangePerGroup: + mStackedWidget->setCurrentWidget( mPageFixedRangePerGroup ); + break; } } @@ -188,6 +228,68 @@ void QgsMeshElevationPropertiesWidget::onChanged() emit widgetChanged(); } +void QgsMeshElevationPropertiesWidget::calculateRangeByExpression( bool isUpper ) +{ + QgsExpressionContext expressionContext; + QgsExpressionContextScope *groupScope = new QgsExpressionContextScope(); + groupScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "group" ), 1, true, false, tr( "Group number" ) ) ); + const int groupIndex = mLayer->datasetGroupsIndexes().at( 0 ); + const QgsMeshDatasetGroupMetadata meta = mLayer->datasetGroupMetadata( groupIndex ); + groupScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "group_name" ), meta.name(), true, false, tr( "Group name" ) ) ); + + expressionContext.appendScope( groupScope ); + expressionContext.setHighlightedVariables( { QStringLiteral( "group" ), QStringLiteral( "group_name" )} ); + + QgsExpressionBuilderDialog dlg = QgsExpressionBuilderDialog( nullptr, isUpper ? mFixedRangeUpperExpression : mFixedRangeLowerExpression, this, QStringLiteral( "generic" ), expressionContext ); + + QList > groupChoices; + for ( int group = 0; group < mLayer->datasetGroupCount(); ++group ) + { + const int groupIndex = mLayer->datasetGroupsIndexes().at( group ); + const QgsMeshDatasetGroupMetadata meta = mLayer->datasetGroupMetadata( groupIndex ); + groupChoices << qMakePair( meta.name(), group ); + } + dlg.expressionBuilder()->setCustomPreviewGenerator( tr( "Group" ), groupChoices, [this]( const QVariant & value )-> QgsExpressionContext + { + return createExpressionContextForGroup( value.toInt() ); + } ); + + if ( dlg.exec() ) + { + if ( isUpper ) + mFixedRangeUpperExpression = dlg.expressionText(); + else + mFixedRangeLowerExpression = dlg.expressionText(); + + QgsExpression exp( dlg.expressionText() ); + exp.prepare( &expressionContext ); + for ( int group = 0; group < mLayer->datasetGroupCount(); ++group ) + { + groupScope->setVariable( QStringLiteral( "group" ), group + 1 ); + const int groupIndex = mLayer->datasetGroupsIndexes().at( group ); + const QgsMeshDatasetGroupMetadata meta = mLayer->datasetGroupMetadata( groupIndex ); + groupScope->setVariable( QStringLiteral( "group_name" ), meta.name() ); + + const QVariant res = exp.evaluate( &expressionContext ); + mFixedRangePerGroupModel->setData( mFixedRangePerGroupModel->index( group, isUpper ? 2 : 1 ), res, Qt::EditRole ); + } + } +} + +QgsExpressionContext QgsMeshElevationPropertiesWidget::createExpressionContextForGroup( int group ) const +{ + QgsExpressionContext context; + context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); + QgsExpressionContextScope *groupScope = new QgsExpressionContextScope(); + groupScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "group" ), group + 1, true, false, tr( "Group number" ) ) ); + const int groupIndex = mLayer->datasetGroupsIndexes().at( group ); + const QgsMeshDatasetGroupMetadata meta = mLayer->datasetGroupMetadata( groupIndex ); + groupScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "group_name" ), meta.name(), true, false, tr( "Group name" ) ) ); + context.appendScope( groupScope ); + context.setHighlightedVariables( { QStringLiteral( "group" ), QStringLiteral( "group_name" )} ); + return context; +} + // // QgsMeshElevationPropertiesWidgetFactory @@ -225,3 +327,232 @@ QString QgsMeshElevationPropertiesWidgetFactory::layerPropertiesPagePositionHint return QStringLiteral( "mOptsPage_Metadata" ); } + +// +// QgsMeshGroupFixedElevationRangeModel +// + +QgsMeshGroupFixedElevationRangeModel::QgsMeshGroupFixedElevationRangeModel( QObject *parent ) + : QAbstractItemModel( parent ) +{ + +} + +int QgsMeshGroupFixedElevationRangeModel::columnCount( const QModelIndex & ) const +{ + return 3; +} + +int QgsMeshGroupFixedElevationRangeModel::rowCount( const QModelIndex &parent ) const +{ + if ( parent.isValid() ) + return 0; + return mGroupCount; +} + +QModelIndex QgsMeshGroupFixedElevationRangeModel::index( int row, int column, const QModelIndex &parent ) const +{ + if ( hasIndex( row, column, parent ) ) + { + return createIndex( row, column, row ); + } + + return QModelIndex(); +} + +QModelIndex QgsMeshGroupFixedElevationRangeModel::parent( const QModelIndex &child ) const +{ + Q_UNUSED( child ) + return QModelIndex(); +} + +Qt::ItemFlags QgsMeshGroupFixedElevationRangeModel::flags( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return Qt::ItemFlags(); + + if ( index.row() < 0 || index.row() >= mGroupCount || index.column() < 0 || index.column() >= columnCount() ) + return Qt::ItemFlags(); + + switch ( index.column() ) + { + case 0: + return Qt::ItemFlag::ItemIsEnabled; + case 1: + case 2: + return Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsEditable | Qt::ItemFlag::ItemIsSelectable; + default: + break; + } + + return Qt::ItemFlags(); +} + +QVariant QgsMeshGroupFixedElevationRangeModel::data( const QModelIndex &index, int role ) const +{ + if ( !index.isValid() ) + return QVariant(); + + if ( index.row() < 0 || index.row() >= mGroupCount || index.column() < 0 || index.column() >= columnCount() ) + return QVariant(); + + const int group = index.row(); + const QgsDoubleRange range = mRanges.value( group ); + + switch ( role ) + { + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::ToolTipRole: + { + switch ( index.column() ) + { + case 0: + return mGroupNames.value( group, QString::number( group ) ); + + case 1: + return range.lower() > std::numeric_limits< double >::lowest() ? range.lower() : QVariant(); + + case 2: + return range.upper() < std::numeric_limits< double >::max() ? range.upper() : QVariant(); + + default: + break; + } + break; + } + + case Qt::TextAlignmentRole: + { + switch ( index.column() ) + { + case 0: + return static_cast( Qt::AlignLeft | Qt::AlignVCenter ); + + case 1: + case 2: + return static_cast( Qt::AlignRight | Qt::AlignVCenter ); + default: + break; + } + break; + } + + default: + break; + } + return QVariant(); +} + +QVariant QgsMeshGroupFixedElevationRangeModel::headerData( int section, Qt::Orientation orientation, int role ) const +{ + if ( role == Qt::DisplayRole && orientation == Qt::Horizontal ) + { + switch ( section ) + { + case 0: + return tr( "Group" ); + case 1: + return tr( "Lower" ); + case 2: + return tr( "Upper" ); + default: + break; + } + } + return QAbstractItemModel::headerData( section, orientation, role ); +} + +bool QgsMeshGroupFixedElevationRangeModel::setData( const QModelIndex &index, const QVariant &value, int role ) +{ + if ( !index.isValid() ) + return false; + + if ( index.row() >= mGroupCount || index.row() < 0 ) + return false; + + const int group = index.row(); + const QgsDoubleRange range = mRanges.value( group ); + + switch ( role ) + { + case Qt::EditRole: + { + bool ok = false; + double newValue = value.toDouble( &ok ); + if ( !ok ) + return false; + + switch ( index.column() ) + { + case 1: + { + mRanges[group] = QgsDoubleRange( newValue, range.upper(), range.includeLower(), range.includeUpper() ); + emit dataChanged( index, index, QVector() << role ); + break; + } + + case 2: + mRanges[group] = QgsDoubleRange( range.lower(), newValue, range.includeLower(), range.includeUpper() ); + emit dataChanged( index, index, QVector() << role ); + break; + + default: + break; + } + return true; + } + + default: + break; + } + + return false; +} + +void QgsMeshGroupFixedElevationRangeModel::setLayerData( QgsMeshLayer *layer, const QMap &ranges ) +{ + beginResetModel(); + + mGroupCount = layer->datasetGroupCount(); + mRanges = ranges; + + mGroupNames.clear(); + for ( int group = 0; group < mGroupCount; ++group ) + { + const int groupIndex = layer->datasetGroupsIndexes().at( group ); + const QgsMeshDatasetGroupMetadata meta = layer->datasetGroupMetadata( groupIndex ); + mGroupNames[group] = meta.name(); + } + + endResetModel(); +} + + +// +// QgsFixedElevationRangeDelegate +// + +QgsMeshFixedElevationRangeDelegate::QgsMeshFixedElevationRangeDelegate( QObject *parent ) + : QStyledItemDelegate( parent ) +{ + +} + +QWidget *QgsMeshFixedElevationRangeDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &, const QModelIndex & ) const +{ + QgsDoubleSpinBox *spin = new QgsDoubleSpinBox( parent ); + spin->setDecimals( 4 ); + spin->setMinimum( -9999999998.0 ); + spin->setMaximum( 9999999999.0 ); + spin->setShowClearButton( false ); + return spin; +} + +void QgsMeshFixedElevationRangeDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const +{ + if ( QgsDoubleSpinBox *spin = qobject_cast< QgsDoubleSpinBox * >( editor ) ) + { + model->setData( index, spin->value() ); + } +} diff --git a/src/app/mesh/qgsmeshelevationpropertieswidget.h b/src/app/mesh/qgsmeshelevationpropertieswidget.h index c55e406e7d4e..a5af9b443ad1 100644 --- a/src/app/mesh/qgsmeshelevationpropertieswidget.h +++ b/src/app/mesh/qgsmeshelevationpropertieswidget.h @@ -19,10 +19,53 @@ #include "qgsmaplayerconfigwidget.h" #include "qgsmaplayerconfigwidgetfactory.h" +#include + #include "ui_qgsmeshelevationpropertieswidgetbase.h" class QgsMeshLayer; +class QgsMeshGroupFixedElevationRangeModel : public QAbstractItemModel +{ + Q_OBJECT + + public: + + QgsMeshGroupFixedElevationRangeModel( QObject *parent ); + int columnCount( const QModelIndex &parent = QModelIndex() ) const override; + int rowCount( const QModelIndex &parent = QModelIndex() ) const override; + QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const override; + QModelIndex parent( const QModelIndex &child ) const override; + Qt::ItemFlags flags( const QModelIndex &index ) const override; + QVariant data( const QModelIndex &index, int role ) const override; + QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override; + bool setData( const QModelIndex &index, const QVariant &value, int role ) override; + + void setLayerData( QgsMeshLayer *layer, const QMap &ranges ); + QMap rangeData() const { return mRanges; } + + private: + + int mGroupCount = 0; + QMap mGroupNames; + QMap mRanges; +}; + + +class QgsMeshFixedElevationRangeDelegate : public QStyledItemDelegate +{ + Q_OBJECT + + public: + + QgsMeshFixedElevationRangeDelegate( QObject *parent ); + + protected: + QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem & /*option*/, const QModelIndex &index ) const override; + void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const override; + +}; + class QgsMeshElevationPropertiesWidget : public QgsMapLayerConfigWidget, private Ui::QgsMeshElevationPropertiesWidgetBase { Q_OBJECT @@ -38,11 +81,16 @@ class QgsMeshElevationPropertiesWidget : public QgsMapLayerConfigWidget, private private slots: void modeChanged(); void onChanged(); + void calculateRangeByExpression( bool isUpper ); private: + QgsExpressionContext createExpressionContextForGroup( int group ) const; QgsMeshLayer *mLayer = nullptr; bool mBlockUpdates = false; + QgsMeshGroupFixedElevationRangeModel *mFixedRangePerGroupModel = nullptr; + QString mFixedRangeLowerExpression = QStringLiteral( "@group" ); + QString mFixedRangeUpperExpression = QStringLiteral( "@group" ); }; diff --git a/src/core/mesh/qgsmeshdataset.cpp b/src/core/mesh/qgsmeshdataset.cpp index 56d5dc6830f5..9c6d977f6994 100644 --- a/src/core/mesh/qgsmeshdataset.cpp +++ b/src/core/mesh/qgsmeshdataset.cpp @@ -19,6 +19,8 @@ #include "qgsmeshdataprovider.h" #include "qgsrectangle.h" #include "qgis.h" +#include +#include QgsMeshDatasetIndex::QgsMeshDatasetIndex( int group, int dataset ) : mGroupIndex( group ), mDatasetIndex( dataset ) @@ -142,6 +144,14 @@ QgsMeshDatasetGroupMetadata::QgsMeshDatasetGroupMetadata( const QString &name, , mReferenceTime( referenceTime ) , mIsTemporal( isTemporal ) { + // this relies on the naming convention used by MDAL's NetCDF driver: _: + // If future MDAL releases expose quantities via a standard API then we can safely remove this and port to the new API. + const thread_local QRegularExpression parentQuantityRegex( QStringLiteral( "^(.*):.*?$" ) ); + const QRegularExpressionMatch parentQuantityMatch = parentQuantityRegex.match( mName ); + if ( parentQuantityMatch.hasMatch() ) + { + mParentQuantityName = parentQuantityMatch.captured( 1 ); + } } QMap QgsMeshDatasetGroupMetadata::extraOptions() const @@ -169,7 +179,13 @@ QString QgsMeshDatasetGroupMetadata::name() const return mName; } -QgsMeshDatasetGroupMetadata::DataType QgsMeshDatasetGroupMetadata::dataType() const +QString QgsMeshDatasetGroupMetadata::parentQuantityName() const +{ + return mParentQuantityName; +} + +QgsMeshDatasetGroupMetadata::DataType +QgsMeshDatasetGroupMetadata::dataType() const { return mDataType; } diff --git a/src/core/mesh/qgsmeshdataset.h b/src/core/mesh/qgsmeshdataset.h index ee0d34c9df93..426ac0f40cb9 100644 --- a/src/core/mesh/qgsmeshdataset.h +++ b/src/core/mesh/qgsmeshdataset.h @@ -22,17 +22,21 @@ #include #include #include +#include +#include #include +#include #include "qgis_core.h" #include "qgis_sip.h" -#include "qgspoint.h" -#include "qgsdataprovider.h" class QgsMeshLayer; class QgsMeshDatasetGroup; class QgsRectangle; +class QDomDocument; +class QgsReadWriteContext; + struct QgsMesh; /** @@ -397,6 +401,16 @@ class CORE_EXPORT QgsMeshDatasetGroupMetadata */ QString name() const; + /** + * Returns the name of the dataset's parent quantity, if available. + * + * The quantity can be used to collect dataset groups which represent a single quantity + * but at different values (e.g. groups which represent different elevations). + * + * \since QGIS 3.38 + */ + QString parentQuantityName() const; + /** * Returns the uri of the source * @@ -457,6 +471,7 @@ class CORE_EXPORT QgsMeshDatasetGroupMetadata private: QString mName; + QString mParentQuantityName; QString mUri; bool mIsScalar = false; DataType mDataType = DataType::DataOnFaces; diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index 4982232dd259..9538c3162439 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -797,24 +797,24 @@ void QgsMeshLayer::applyClassificationOnScalarSettings( const QgsMeshDatasetGrou } } -QgsMeshDatasetIndex QgsMeshLayer::activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const +QgsMeshDatasetIndex QgsMeshLayer::activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange, int group ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS if ( mTemporalProperties->isActive() ) - return datasetIndexAtTime( timeRange, mRendererSettings.activeScalarDatasetGroup() ); + return datasetIndexAtTime( timeRange, group >= 0 ? group : mRendererSettings.activeScalarDatasetGroup() ); else - return QgsMeshDatasetIndex( mRendererSettings.activeScalarDatasetGroup(), mStaticScalarDatasetIndex ); + return QgsMeshDatasetIndex( group >= 0 ? group : mRendererSettings.activeScalarDatasetGroup(), mStaticScalarDatasetIndex ); } -QgsMeshDatasetIndex QgsMeshLayer::activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange ) const +QgsMeshDatasetIndex QgsMeshLayer::activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange, int group ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS if ( mTemporalProperties->isActive() ) - return datasetIndexAtTime( timeRange, mRendererSettings.activeVectorDatasetGroup() ); + return datasetIndexAtTime( timeRange, group >= 0 ? group : mRendererSettings.activeVectorDatasetGroup() ); else - return QgsMeshDatasetIndex( mRendererSettings.activeVectorDatasetGroup(), mStaticVectorDatasetIndex ); + return QgsMeshDatasetIndex( group >= 0 ? group : mRendererSettings.activeVectorDatasetGroup(), mStaticVectorDatasetIndex ); } void QgsMeshLayer::fillNativeMesh() @@ -903,11 +903,11 @@ int QgsMeshLayer::closestEdge( const QgsPointXY &point, double searchRadius, Qgs return selectedIndex; } -QgsMeshDatasetIndex QgsMeshLayer::staticVectorDatasetIndex() const +QgsMeshDatasetIndex QgsMeshLayer::staticVectorDatasetIndex( int group ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS - return QgsMeshDatasetIndex( mRendererSettings.activeVectorDatasetGroup(), mStaticVectorDatasetIndex ); + return QgsMeshDatasetIndex( group >= 0 ? group : mRendererSettings.activeVectorDatasetGroup(), mStaticVectorDatasetIndex ); } void QgsMeshLayer::setReferenceTime( const QDateTime &referenceTime ) @@ -1545,11 +1545,11 @@ QList QgsMeshLayer::selectFacesByExpression( QgsExpression expression ) return ret; } -QgsMeshDatasetIndex QgsMeshLayer::staticScalarDatasetIndex() const +QgsMeshDatasetIndex QgsMeshLayer::staticScalarDatasetIndex( int group ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS - return QgsMeshDatasetIndex( mRendererSettings.activeScalarDatasetGroup(), mStaticScalarDatasetIndex ); + return QgsMeshDatasetIndex( group >= 0 ? group : mRendererSettings.activeScalarDatasetGroup(), mStaticScalarDatasetIndex ); } void QgsMeshLayer::setStaticVectorDatasetIndex( const QgsMeshDatasetIndex &staticVectorDatasetIndex ) diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index 75b002b5f1df..2da422e3b431 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -608,27 +608,27 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer, public QgsAbstractProfileSo * Returns dataset index from active scalar group depending on the time range. * If the temporal properties is not active, return the static dataset * - * \param timeRange the time range - * \returns dataset index + * Since QGIS 3.38, the \a group argument can be used to specify a fixed group + * to use. If this is not specified, then the active group from the layer's renderer will be used. * * \note the returned dataset index depends on the matching method, see setTemporalMatchingMethod() * * \since QGIS 3.14 */ - QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) const; /** * Returns dataset index from active vector group depending on the time range * If the temporal properties is not active, return the static dataset * - * \param timeRange the time range - * \returns dataset index + * Since QGIS 3.38, the \a group argument can be used to specify a fixed group + * to use. If this is not specified, then the active group from the layer's renderer will be used. * * \note the returned dataset index depends on the matching method, see setTemporalMatchingMethod() * * \since QGIS 3.14 */ - QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) const; /** * Sets the static scalar dataset index that is rendered if the temporal properties is not active @@ -649,18 +649,24 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer, public QgsAbstractProfileSo void setStaticVectorDatasetIndex( const QgsMeshDatasetIndex &staticVectorDatasetIndex ) SIP_SKIP; /** - * Returns the static scalar dataset index that is rendered if the temporal properties is not active + * Returns the static scalar dataset index that is rendered if the temporal properties is not active. + * + * Since QGIS 3.38, the \a group argument can be used to specify a fixed group + * to use. If this is not specified, then the active group from the layer's renderer will be used. * * \since QGIS 3.14 */ - QgsMeshDatasetIndex staticScalarDatasetIndex() const; + QgsMeshDatasetIndex staticScalarDatasetIndex( int group = -1 ) const; /** - * Returns the static vector dataset index that is rendered if the temporal properties is not active + * Returns the static vector dataset index that is rendered if the temporal properties is not active. + * + * Since QGIS 3.38, the \a group argument can be used to specify a fixed group + * to use. If this is not specified, then the active group from the layer's renderer will be used. * * \since QGIS 3.14 */ - QgsMeshDatasetIndex staticVectorDatasetIndex() const; + QgsMeshDatasetIndex staticVectorDatasetIndex( int group = -1 ) const; /** * Sets the reference time of the layer diff --git a/src/core/mesh/qgsmeshlayerelevationproperties.cpp b/src/core/mesh/qgsmeshlayerelevationproperties.cpp index 2ee3838f55ec..b988c74342ac 100644 --- a/src/core/mesh/qgsmeshlayerelevationproperties.cpp +++ b/src/core/mesh/qgsmeshlayerelevationproperties.cpp @@ -59,6 +59,23 @@ QDomElement QgsMeshLayerElevationProperties::writeXml( QDomElement &parentElemen element.setAttribute( QStringLiteral( "includeUpper" ), mFixedRange.includeUpper() ? "1" : "0" ); break; + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + QDomElement ranges = document.createElement( QStringLiteral( "ranges" ) ); + for ( auto it = mRangePerGroup.constBegin(); it != mRangePerGroup.constEnd(); ++it ) + { + QDomElement range = document.createElement( QStringLiteral( "range" ) ); + range.setAttribute( QStringLiteral( "group" ), it.key() ); + range.setAttribute( QStringLiteral( "lower" ), qgsDoubleToString( it.value().lower() ) ); + range.setAttribute( QStringLiteral( "upper" ), qgsDoubleToString( it.value().upper() ) ); + range.setAttribute( QStringLiteral( "includeLower" ), it.value().includeLower() ? "1" : "0" ); + range.setAttribute( QStringLiteral( "includeUpper" ), it.value().includeUpper() ? "1" : "0" ); + ranges.appendChild( range ); + } + element.appendChild( ranges ); + break; + } + case Qgis::MeshElevationMode::FromVertices: break; } @@ -98,6 +115,25 @@ bool QgsMeshLayerElevationProperties::readXml( const QDomElement &element, const mFixedRange = QgsDoubleRange( lower, upper, includeLower, includeUpper ); break; } + + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + mRangePerGroup.clear(); + + const QDomNodeList ranges = elevationElement.firstChildElement( QStringLiteral( "ranges" ) ).childNodes(); + for ( int i = 0; i < ranges.size(); ++i ) + { + const QDomElement rangeElement = ranges.at( i ).toElement(); + const int group = rangeElement.attribute( QStringLiteral( "group" ) ).toInt(); + const double lower = rangeElement.attribute( QStringLiteral( "lower" ) ).toDouble(); + const double upper = rangeElement.attribute( QStringLiteral( "upper" ) ).toDouble(); + const bool includeLower = rangeElement.attribute( QStringLiteral( "includeLower" ) ).toInt(); + const bool includeUpper = rangeElement.attribute( QStringLiteral( "includeUpper" ) ).toInt(); + mRangePerGroup.insert( group, QgsDoubleRange( lower, upper, includeLower, includeUpper ) ); + } + break; + } + case Qgis::MeshElevationMode::FromVertices: break; } @@ -126,6 +162,15 @@ QString QgsMeshLayerElevationProperties::htmlSummary() const properties << tr( "Elevation range: %1 to %2" ).arg( mFixedRange.lower() ).arg( mFixedRange.upper() ); break; + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + for ( auto it = mRangePerGroup.constBegin(); it != mRangePerGroup.constEnd(); ++it ) + { + properties << tr( "Elevation for group %1: %2 to %3" ).arg( it.key() ).arg( it.value().lower() ).arg( it.value().upper() ); + } + break; + } + case Qgis::MeshElevationMode::FromVertices: properties << tr( "Scale: %1" ).arg( mZScale ); properties << tr( "Offset: %1" ).arg( mZOffset ); @@ -143,6 +188,7 @@ QgsMeshLayerElevationProperties *QgsMeshLayerElevationProperties::clone() const res->setProfileSymbology( mSymbology ); res->setElevationLimit( mElevationLimit ); res->setFixedRange( mFixedRange ); + res->setFixedRangePerGroup( mRangePerGroup ); res->copyCommonProperties( this ); return res.release(); } @@ -154,6 +200,16 @@ bool QgsMeshLayerElevationProperties::isVisibleInZRange( const QgsDoubleRange &r case Qgis::MeshElevationMode::FixedElevationRange: return mFixedRange.overlaps( range ); + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + for ( auto it = mRangePerGroup.constBegin(); it != mRangePerGroup.constEnd(); ++it ) + { + if ( it.value().overlaps( range ) ) + return true; + } + return false; + } + case Qgis::MeshElevationMode::FromVertices: // TODO -- test actual mesh z range return true; @@ -168,6 +224,36 @@ QgsDoubleRange QgsMeshLayerElevationProperties::calculateZRange( QgsMapLayer * ) case Qgis::MeshElevationMode::FixedElevationRange: return mFixedRange; + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + double lower = std::numeric_limits< double >::max(); + double upper = std::numeric_limits< double >::min(); + bool includeLower = true; + bool includeUpper = true; + for ( auto it = mRangePerGroup.constBegin(); it != mRangePerGroup.constEnd(); ++it ) + { + if ( it.value().lower() < lower ) + { + lower = it.value().lower(); + includeLower = it.value().includeLower(); + } + else if ( !includeLower && it.value().lower() == lower && it.value().includeLower() ) + { + includeLower = true; + } + if ( it.value().upper() > upper ) + { + upper = it.value().upper(); + includeUpper = it.value().includeUpper(); + } + else if ( !includeUpper && it.value().upper() == upper && it.value().includeUpper() ) + { + includeUpper = true; + } + } + return QgsDoubleRange( lower, upper, includeLower, includeUpper ); + } + case Qgis::MeshElevationMode::FromVertices: // TODO -- determine actual z range from mesh statistics return QgsDoubleRange(); @@ -187,6 +273,7 @@ QgsMapLayerElevationProperties::Flags QgsMeshLayerElevationProperties::flags() c case Qgis::MeshElevationMode::FixedElevationRange: return QgsMapLayerElevationProperties::Flag::FlagDontInvalidateCachedRendersWhenRangeChanges; + case Qgis::MeshElevationMode::FixedRangePerGroup: case Qgis::MeshElevationMode::FromVertices: break; } @@ -221,6 +308,20 @@ void QgsMeshLayerElevationProperties::setFixedRange( const QgsDoubleRange &range emit changed(); } +QMap QgsMeshLayerElevationProperties::fixedRangePerGroup() const +{ + return mRangePerGroup; +} + +void QgsMeshLayerElevationProperties::setFixedRangePerGroup( const QMap &ranges ) +{ + if ( ranges == mRangePerGroup ) + return; + + mRangePerGroup = ranges; + emit changed(); +} + QgsLineSymbol *QgsMeshLayerElevationProperties::profileLineSymbol() const { return mProfileLineSymbol.get(); diff --git a/src/core/mesh/qgsmeshlayerelevationproperties.h b/src/core/mesh/qgsmeshlayerelevationproperties.h index 88be2fb7c6eb..319f01ba3cf2 100644 --- a/src/core/mesh/qgsmeshlayerelevationproperties.h +++ b/src/core/mesh/qgsmeshlayerelevationproperties.h @@ -22,6 +22,7 @@ #include "qgis_core.h" #include "qgis_sip.h" #include "qgsmaplayerelevationproperties.h" +#include "qgsmeshdataset.h" #include "qgis.h" class QgsLineSymbol; @@ -97,6 +98,30 @@ class CORE_EXPORT QgsMeshLayerElevationProperties : public QgsMapLayerElevationP */ void setFixedRange( const QgsDoubleRange &range ); + /** + * Returns the fixed elevation range for each group. + * + * \note This is only considered when mode() is Qgis::MeshElevationMode::FixedRangePerGroup. + * + * \note When a fixed range is set any zOffset() and zScale() is ignored. + * + * \see setFixedRangePerGroup() + * \since QGIS 3.38 + */ + QMap fixedRangePerGroup() const; + + /** + * Sets the fixed elevation range for each group. + * + * \note This is only considered when mode() is Qgis::MeshElevationMode::FixedRangePerGroup. + * + * \note When a fixed range is set any zOffset() and zScale() is ignored. + * + * \see fixedRangePerGroup() + * \since QGIS 3.38 + */ + void setFixedRangePerGroup( const QMap &ranges ); + /** * Returns the line symbol used to render the mesh profile in elevation profile plots. * @@ -180,6 +205,8 @@ class CORE_EXPORT QgsMeshLayerElevationProperties : public QgsMapLayerElevationP double mElevationLimit = std::numeric_limits< double >::quiet_NaN(); QgsDoubleRange mFixedRange; + + QMap< int, QgsDoubleRange > mRangePerGroup; }; #endif // QGSMESHLAYERELEVATIONPROPERTIES_H diff --git a/src/core/mesh/qgsmeshlayerrenderer.cpp b/src/core/mesh/qgsmeshlayerrenderer.cpp index 58d327272c77..d6003f34e5b6 100644 --- a/src/core/mesh/qgsmeshlayerrenderer.cpp +++ b/src/core/mesh/qgsmeshlayerrenderer.cpp @@ -74,20 +74,6 @@ QgsMeshLayerRenderer::QgsMeshLayerRenderer( mNativeMesh = *( layer->nativeMesh() ); mLayerExtent = layer->extent(); - // copy triangular mesh - copyTriangularMeshes( layer, context ); - - // copy datasets - copyScalarDatasetValues( layer ); - copyVectorDatasetValues( layer ); - - calculateOutputSize(); - - QSet attrs; - prepareLabeling( layer, attrs ); - - mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( *renderContext(), layer ); - if ( layer->elevationProperties() && layer->elevationProperties()->hasElevation() ) { QgsMeshLayerElevationProperties *elevProp = qobject_cast( layer->elevationProperties() ); @@ -110,10 +96,69 @@ QgsMeshLayerRenderer::QgsMeshLayerRenderer( // TODO -- filtering by mesh z values is not currently implemented break; } + + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + // find the top-most group which matches the map range and parent group + int currentMatchingVectorGroup = -1; + int currentMatchingScalarGroup = -1; + QgsDoubleRange currentMatchingVectorRange; + QgsDoubleRange currentMatchingScalarRange; + + const QMap rangePerGroup = elevProp->fixedRangePerGroup(); + + const int activeVectorDatasetGroup = mRendererSettings.activeVectorDatasetGroup(); + const int activeScalarDatasetGroup = mRendererSettings.activeScalarDatasetGroup(); + + for ( auto it = rangePerGroup.constBegin(); it != rangePerGroup.constEnd(); ++it ) + { + if ( it.value().overlaps( context.zRange() ) ) + { + const bool matchesVectorParentGroup = QgsMeshLayerUtils::haveSameParentQuantity( layer, QgsMeshDatasetIndex( activeVectorDatasetGroup ), QgsMeshDatasetIndex( it.key() ) ); + const bool matchesScalarParentGroup = QgsMeshLayerUtils::haveSameParentQuantity( layer, QgsMeshDatasetIndex( activeScalarDatasetGroup ), QgsMeshDatasetIndex( it.key() ) ); + + if ( matchesVectorParentGroup && ( + currentMatchingVectorRange.isInfinite() + || ( it.value().includeUpper() && it.value().upper() >= currentMatchingVectorRange.upper() ) + || ( !currentMatchingVectorRange.includeUpper() && it.value().upper() >= currentMatchingVectorRange.upper() ) ) ) + { + currentMatchingVectorGroup = it.key(); + currentMatchingVectorRange = it.value(); + } + + if ( matchesScalarParentGroup && ( + currentMatchingScalarRange.isInfinite() + || ( it.value().includeUpper() && it.value().upper() >= currentMatchingScalarRange.upper() ) + || ( !currentMatchingScalarRange.includeUpper() && it.value().upper() >= currentMatchingScalarRange.upper() ) ) ) + { + currentMatchingScalarGroup = it.key(); + currentMatchingScalarRange = it.value(); + } + } + } + if ( currentMatchingVectorGroup >= 0 ) + mRendererSettings.setActiveVectorDatasetGroup( currentMatchingVectorGroup ); + if ( currentMatchingScalarGroup >= 0 ) + mRendererSettings.setActiveScalarDatasetGroup( currentMatchingScalarGroup ); + } } } } + // copy triangular mesh + copyTriangularMeshes( layer, context ); + + // copy datasets + copyScalarDatasetValues( layer ); + copyVectorDatasetValues( layer ); + + calculateOutputSize(); + + QSet attrs; + prepareLabeling( layer, attrs ); + + mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( *renderContext(), layer ); + mPreparationTime = timer.elapsed(); } @@ -154,9 +199,9 @@ void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer ) { QgsMeshDatasetIndex datasetIndex; if ( renderContext()->isTemporal() ) - datasetIndex = layer->activeScalarDatasetAtTime( renderContext()->temporalRange() ); + datasetIndex = layer->activeScalarDatasetAtTime( renderContext()->temporalRange(), mRendererSettings.activeScalarDatasetGroup() ); else - datasetIndex = layer->staticScalarDatasetIndex(); + datasetIndex = layer->staticScalarDatasetIndex( mRendererSettings.activeScalarDatasetGroup() ); // Find out if we can use cache up to date. If yes, use it and return const int datasetGroupCount = layer->datasetGroupCount(); @@ -255,9 +300,9 @@ void QgsMeshLayerRenderer::copyVectorDatasetValues( QgsMeshLayer *layer ) { QgsMeshDatasetIndex datasetIndex; if ( renderContext()->isTemporal() ) - datasetIndex = layer->activeVectorDatasetAtTime( renderContext()->temporalRange() ); + datasetIndex = layer->activeVectorDatasetAtTime( renderContext()->temporalRange(), mRendererSettings.activeVectorDatasetGroup() ); else - datasetIndex = layer->staticVectorDatasetIndex(); + datasetIndex = layer->staticVectorDatasetIndex( mRendererSettings.activeVectorDatasetGroup() ); // Find out if we can use cache up to date. If yes, use it and return const int datasetGroupCount = layer->datasetGroupCount(); diff --git a/src/core/mesh/qgsmeshlayerutils.cpp b/src/core/mesh/qgsmeshlayerutils.cpp index e6c00b840f35..b68f5bcfe9cd 100644 --- a/src/core/mesh/qgsmeshlayerutils.cpp +++ b/src/core/mesh/qgsmeshlayerutils.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "qgsmeshlayerutils.h" #include "qgsmeshtimesettings.h" @@ -702,4 +704,17 @@ QVector QgsMeshLayerUtils::calculateNormals( const QgsTriangularMesh return normals; } +bool QgsMeshLayerUtils::haveSameParentQuantity( const QgsMeshLayer *layer, const QgsMeshDatasetIndex &index1, const QgsMeshDatasetIndex &index2 ) +{ + const QgsMeshDatasetGroupMetadata metadata1 = layer->datasetGroupMetadata( index1 ); + if ( metadata1.parentQuantityName().isEmpty() ) + return false; + + const QgsMeshDatasetGroupMetadata metadata2 = layer->datasetGroupMetadata( index2 ); + if ( metadata2.parentQuantityName().isEmpty() ) + return false; + + return metadata1.parentQuantityName().compare( metadata2.parentQuantityName(), Qt::CaseInsensitive ) == 0; +} + ///@endcond diff --git a/src/core/mesh/qgsmeshlayerutils.h b/src/core/mesh/qgsmeshlayerutils.h index 2d25837692b9..da7c985d75fa 100644 --- a/src/core/mesh/qgsmeshlayerutils.h +++ b/src/core/mesh/qgsmeshlayerutils.h @@ -369,6 +369,14 @@ class CORE_EXPORT QgsMeshLayerUtils const QgsTriangularMesh &triangularMesh, const QVector &verticalMagnitude, bool isRelative ); + + /** + * Returns TRUE if the datasets from \a layer at \a index1 and \a index2 share the same parent quantity. + * + * \since QGIS 3.38 + */ + static bool haveSameParentQuantity( const QgsMeshLayer *layer, const QgsMeshDatasetIndex &index1, const QgsMeshDatasetIndex &index2 ); + }; ///@endcond diff --git a/src/core/qgis.h b/src/core/qgis.h index 50e7c39eb629..e1442f858dc3 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -3335,7 +3335,8 @@ class CORE_EXPORT Qgis enum class MeshElevationMode : int { FixedElevationRange = 0, //!< Layer has a fixed elevation range - FromVertices = 1 //!< Elevation should be taken from mesh vertices + FromVertices = 1, //!< Elevation should be taken from mesh vertices + FixedRangePerGroup = 2, //!< Layer has a fixed (manually specified) elevation range per group }; Q_ENUM( MeshElevationMode ) diff --git a/src/ui/mesh/qgsmeshelevationpropertieswidgetbase.ui b/src/ui/mesh/qgsmeshelevationpropertieswidgetbase.ui index 5dbb914e0da0..cf4175997d39 100644 --- a/src/ui/mesh/qgsmeshelevationpropertieswidgetbase.ui +++ b/src/ui/mesh/qgsmeshelevationpropertieswidgetbase.ui @@ -36,6 +36,143 @@ + + + + Profile Chart Appearance + + + + + + + + + Style + + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Line style + + + + + + + + 0 + 0 + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Limit + + + + + + + Fill style + + + + + + + 6 + + + -99999.000000000000000 + + + 99999.000000000000000 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -48,7 +185,7 @@ QFrame::NoFrame - 1 + 2 @@ -198,145 +335,68 @@ - - - - - - Profile Chart Appearance - - - - - - - - - Style - - - - - - - - 0 - 0 - - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Line style - - - - - - - - 0 - 0 - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Limit - - - - - - - Fill style - - - - - - - 6 - - - -99999.000000000000000 - - - 99999.000000000000000 - - - - - - - - 0 - 0 - - - - - - - - + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Each group in the mesh layer is associated with a fixed elevation range.</span></p><p>This mode can be used when a layer has elevation data exposed through different dataset groups.</p></body></html> + + + true + - - - + + + + + + + + 0 + + + + + Qt::Horizontal + + + + 494 + 20 + + + + + + + + ... + + + + :/images/themes/default/mIconExpression.svg:/images/themes/default/mIconExpression.svg + + + QToolButton::MenuButtonPopup + + + false + + + + + + + - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -357,6 +417,8 @@ 1 - + + + diff --git a/tests/src/core/testqgsmeshlayer.cpp b/tests/src/core/testqgsmeshlayer.cpp index 73c3b4786b25..5c6b80909e54 100644 --- a/tests/src/core/testqgsmeshlayer.cpp +++ b/tests/src/core/testqgsmeshlayer.cpp @@ -28,6 +28,7 @@ #include "qgstriangularmesh.h" #include "qgsexpression.h" #include "qgsmeshlayertemporalproperties.h" +#include "qgsmeshlayerutils.h" #include "qgsmeshdataprovidertemporalcapabilities.h" #include "qgsprovidermetadata.h" @@ -37,12 +38,14 @@ * \ingroup UnitTests * This is a unit test for a mesh layer */ -class TestQgsMeshLayer : public QObject +class TestQgsMeshLayer : public QgsTest { Q_OBJECT public: - TestQgsMeshLayer() = default; + TestQgsMeshLayer() + : QgsTest( QStringLiteral( "Mesh layer tests" ) ) + {} private: QString mDataDir; @@ -108,6 +111,8 @@ class TestQgsMeshLayer : public QObject void keepDatasetIndexConsistency(); void symbologyConsistencyWithName(); void updateTimePropertiesWhenReloading(); + + void testHaveSameParentQuantity(); }; QString TestQgsMeshLayer::readFile( const QString &fname ) const @@ -2339,5 +2344,27 @@ void TestQgsMeshLayer::updateTimePropertiesWhenReloading() QCOMPARE( timeExtent1, static_cast( layer->temporalProperties() )->timeExtent() ); } +void TestQgsMeshLayer::testHaveSameParentQuantity() +{ + QgsMeshLayer layer1( + testDataPath( "mesh/netcdf_parent_quantity.nc" ), + QStringLiteral( "mesh" ), + QStringLiteral( "mdal" ) ); + QVERIFY( layer1.isValid() ); + + QVERIFY( QgsMeshLayerUtils::haveSameParentQuantity( &layer1, QgsMeshDatasetIndex( 0 ), QgsMeshDatasetIndex( 1 ) ) ); + QVERIFY( QgsMeshLayerUtils::haveSameParentQuantity( &layer1, QgsMeshDatasetIndex( 0 ), QgsMeshDatasetIndex( 2 ) ) ); + QVERIFY( QgsMeshLayerUtils::haveSameParentQuantity( &layer1, QgsMeshDatasetIndex( 2 ), QgsMeshDatasetIndex( 3 ) ) ); + QVERIFY( !QgsMeshLayerUtils::haveSameParentQuantity( &layer1, QgsMeshDatasetIndex( 0 ), QgsMeshDatasetIndex( 4 ) ) ); + + QgsMeshLayer layer2( + testDataPath( "mesh/mesh_z_ws_d.nc" ), + QStringLiteral( "mesh" ), + QStringLiteral( "mdal" ) ); + QVERIFY( layer2.isValid() ); + + QVERIFY( !QgsMeshLayerUtils::haveSameParentQuantity( &layer2, QgsMeshDatasetIndex( 0 ), QgsMeshDatasetIndex( 1 ) ) ); +} + QGSTEST_MAIN( TestQgsMeshLayer ) #include "testqgsmeshlayer.moc" diff --git a/tests/src/core/testqgsmeshlayerrenderer.cpp b/tests/src/core/testqgsmeshlayerrenderer.cpp index 47e229cc770e..8c3cb3692d40 100644 --- a/tests/src/core/testqgsmeshlayerrenderer.cpp +++ b/tests/src/core/testqgsmeshlayerrenderer.cpp @@ -35,9 +35,6 @@ #include "qgsmesh3daveraging.h" #include "qgsmaplayertemporalproperties.h" -//qgis test includes -#include "qgsrenderchecker.h" - /** * \ingroup UnitTests * This is a unit test for the different renderers for mesh layers. @@ -47,7 +44,7 @@ class TestQgsMeshRenderer : public QgsTest Q_OBJECT public: - TestQgsMeshRenderer() : QgsTest( QStringLiteral( "Mesh Layer Rendering Tests" ) ) {} + TestQgsMeshRenderer() : QgsTest( QStringLiteral( "Mesh Layer Rendering Tests" ), QStringLiteral( "mesh" ) ) {} private: QString mDataDir; @@ -61,10 +58,8 @@ class TestQgsMeshRenderer : public QgsTest void initTestCase();// will be called before the first testfunction is executed. void cleanupTestCase();// will be called after the last testfunction was executed. void init(); // will be called before each testfunction is executed. - bool imageCheck( const QString &testType, QgsMeshLayer *layer, double rotation = 0.0 ); QString readFile( const QString &fname ) const; - void test_native_mesh_rendering(); void test_native_mesh_renderingWithClipping(); void test_triangular_mesh_rendering(); @@ -215,23 +210,6 @@ QString TestQgsMeshRenderer::readFile( const QString &fname ) const return uri; } -bool TestQgsMeshRenderer::imageCheck( const QString &testType, QgsMeshLayer *layer, double rotation ) -{ - mMapSettings->setDestinationCrs( layer->crs() ); - mMapSettings->setExtent( layer->extent() ); - mMapSettings->setRotation( rotation ); - mMapSettings->setOutputDpi( 96 ); - - QgsRenderChecker myChecker; - myChecker.setControlPathPrefix( QStringLiteral( "mesh" ) ); - myChecker.setControlName( "expected_" + testType ); - myChecker.setMapSettings( *mMapSettings ); - myChecker.setColorTolerance( 15 ); - const bool myResultFlag = myChecker.runTest( testType, 0 ); - mReport += myChecker.report(); - return myResultFlag; -} - void TestQgsMeshRenderer::test_native_mesh_rendering() { QgsMeshRendererSettings rendererSettings = mMemoryLayer->rendererSettings(); @@ -240,8 +218,16 @@ void TestQgsMeshRenderer::test_native_mesh_rendering() settings.setLineWidth( 1. ); rendererSettings.setNativeMeshSettings( settings ); mMemoryLayer->setRendererSettings( rendererSettings ); - QVERIFY( imageCheck( "quad_and_triangle_native_mesh", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_native_mesh_rotated_45", mMemoryLayer, 45.0 ) ); + + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_native_mesh", "quad_and_triangle_native_mesh", *mMapSettings, 0, 15 ); + + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_native_mesh_rotated_45", "quad_and_triangle_native_mesh_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_native_mesh_renderingWithClipping() @@ -260,10 +246,12 @@ void TestQgsMeshRenderer::test_native_mesh_renderingWithClipping() mMapSettings->addClippingRegion( region ); mMapSettings->addClippingRegion( region2 ); - const bool res = imageCheck( "painterclip_region", mMemoryLayer ); - + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "painterclip_region", "painterclip_region", *mMapSettings, 0, 15 ); mMapSettings->setClippingRegions( QList< QgsMapClippingRegion >() ); - QVERIFY( res ); } void TestQgsMeshRenderer::test_triangular_mesh_rendering() @@ -275,8 +263,16 @@ void TestQgsMeshRenderer::test_triangular_mesh_rendering() settings.setLineWidth( 0.26 ); rendererSettings.setTriangularMeshSettings( settings ); mMemoryLayer->setRendererSettings( rendererSettings ); - QVERIFY( imageCheck( "quad_and_triangle_triangular_mesh", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_triangular_mesh_rotated_45", mMemoryLayer, 45.0 ) ); + + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setRotation( 0 ); + mMapSettings->setOutputDpi( 96 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_triangular_mesh", "quad_and_triangle_triangular_mesh", *mMapSettings, 0, 15 ); + + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_triangular_mesh_rotated_45", "quad_and_triangle_triangular_mesh_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_edge_mesh_rendering() @@ -288,7 +284,12 @@ void TestQgsMeshRenderer::test_edge_mesh_rendering() settings.setLineWidth( 0.26 ); rendererSettings.setEdgeMeshSettings( settings ); mMemory1DLayer->setRendererSettings( rendererSettings ); - QVERIFY( imageCheck( "lines_edge_mesh", mMemory1DLayer ) ); + + mMapSettings->setDestinationCrs( mMemory1DLayer->crs() ); + mMapSettings->setExtent( mMemory1DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_mesh", "lines_edge_mesh", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_1d_vertex_scalar_dataset_rendering() @@ -308,8 +309,14 @@ void TestQgsMeshRenderer::test_1d_vertex_scalar_dataset_rendering() mMemory1DLayer->setRendererSettings( rendererSettings ); mMemory1DLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "lines_vertex_scalar_dataset", mMemory1DLayer ) ); - QVERIFY( imageCheck( "lines_vertex_scalar_dataset_rotated_45", mMemory1DLayer, 45 ) ); + mMapSettings->setDestinationCrs( mMemory1DLayer->crs() ); + mMapSettings->setExtent( mMemory1DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_vertex_scalar_dataset", "lines_vertex_scalar_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_vertex_scalar_dataset_rotated_45", "lines_vertex_scalar_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_1d_vertex_vector_dataset_rendering() @@ -327,8 +334,14 @@ void TestQgsMeshRenderer::test_1d_vertex_vector_dataset_rendering() mMemory1DLayer->setRendererSettings( rendererSettings ); mMemory1DLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "lines_vertex_vector_dataset", mMemory1DLayer ) ); - QVERIFY( imageCheck( "lines_vertex_vector_dataset_rotated_45", mMemory1DLayer, 45 ) ); + mMapSettings->setDestinationCrs( mMemory1DLayer->crs() ); + mMapSettings->setExtent( mMemory1DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_vertex_vector_dataset", "lines_vertex_vector_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_vertex_vector_dataset_rotated_45", "lines_vertex_vector_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_1d_edge_scalar_dataset_rendering() @@ -348,8 +361,14 @@ void TestQgsMeshRenderer::test_1d_edge_scalar_dataset_rendering() mMemory1DLayer->setRendererSettings( rendererSettings ); mMemory1DLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "lines_edge_scalar_dataset", mMemory1DLayer ) ); - QVERIFY( imageCheck( "lines_edge_scalar_dataset_rotated_45", mMemory1DLayer, 45 ) ); + mMapSettings->setDestinationCrs( mMemory1DLayer->crs() ); + mMapSettings->setExtent( mMemory1DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_scalar_dataset", "lines_edge_scalar_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_scalar_dataset_rotated_45", "lines_edge_scalar_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_1d_edge_vector_dataset_rendering() @@ -362,8 +381,14 @@ void TestQgsMeshRenderer::test_1d_edge_vector_dataset_rendering() mMemory1DLayer->setRendererSettings( rendererSettings ); mMemory1DLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "lines_edge_vector_dataset", mMemory1DLayer ) ); - QVERIFY( imageCheck( "lines_edge_vector_dataset_rotated_45", mMemory1DLayer, 45 ) ); + mMapSettings->setDestinationCrs( mMemory1DLayer->crs() ); + mMapSettings->setExtent( mMemory1DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_vector_dataset", "lines_edge_vector_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_vector_dataset_rotated_45", "lines_edge_vector_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_scalar_dataset_rendering() @@ -376,8 +401,14 @@ void TestQgsMeshRenderer::test_vertex_scalar_dataset_rendering() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_scalar_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_scalar_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_scalar_dataset", "quad_and_triangle_vertex_scalar_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_scalar_dataset_rotated_45", "quad_and_triangle_vertex_scalar_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_dataset_rendering() @@ -395,8 +426,14 @@ void TestQgsMeshRenderer::test_vertex_vector_dataset_rendering() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_dataset", "quad_and_triangle_vertex_vector_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_dataset_rotated_45", "quad_and_triangle_vertex_vector_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_dataset_colorRamp_rendering() @@ -415,7 +452,11 @@ void TestQgsMeshRenderer::test_vertex_vector_dataset_colorRamp_rendering() rendererSettings.setVectorSettings( ds.group(), settings ); mMemoryLayer->setRendererSettings( rendererSettings ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_dataset_colorRamp", mMemoryLayer ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_dataset_colorRamp", "quad_and_triangle_vertex_vector_dataset_colorRamp", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_face_scalar_dataset_rendering() @@ -428,8 +469,14 @@ void TestQgsMeshRenderer::test_face_scalar_dataset_rendering() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_face_scalar_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_face_scalar_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_scalar_dataset", "quad_and_triangle_face_scalar_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_scalar_dataset_rotated_45", "quad_and_triangle_face_scalar_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_face_scalar_dataset_interpolated_neighbour_average_rendering() @@ -445,10 +492,13 @@ void TestQgsMeshRenderer::test_face_scalar_dataset_interpolated_neighbour_averag mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_face_scalar_interpolated_neighbour_average_dataset", mMemoryLayer ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_scalar_interpolated_neighbour_average_dataset", "quad_and_triangle_face_scalar_interpolated_neighbour_average_dataset", *mMapSettings, 0, 15 ); } - void TestQgsMeshRenderer::test_face_vector_dataset_rendering() { const QgsMeshDatasetIndex ds( 3, 0 ); @@ -459,8 +509,14 @@ void TestQgsMeshRenderer::test_face_vector_dataset_rendering() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_dataset", "quad_and_triangle_face_vector_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_dataset_rotated_45", "quad_and_triangle_face_vector_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_scalar_dataset_with_inactive_face_rendering() @@ -473,7 +529,11 @@ void TestQgsMeshRenderer::test_vertex_scalar_dataset_with_inactive_face_renderin mMdalLayer->setRendererSettings( rendererSettings ); mMdalLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_scalar_dataset_with_inactive_face", mMdalLayer ) ); + mMapSettings->setDestinationCrs( mMdalLayer->crs() ); + mMapSettings->setExtent( mMdalLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_scalar_dataset_with_inactive_face", "quad_and_triangle_vertex_scalar_dataset_with_inactive_face", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_wind_barbs() @@ -500,8 +560,14 @@ void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_wind_barbs() mMdalLayer->setRendererSettings( rendererSettings ); mMdalLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMdalLayer->crs() ); + mMapSettings->setExtent( mMdalLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs", "quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45", "quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_face_vector_on_user_grid() @@ -521,8 +587,14 @@ void TestQgsMeshRenderer::test_face_vector_on_user_grid() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_user_grid_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_user_grid_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_user_grid_dataset", "quad_and_triangle_face_vector_user_grid_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_user_grid_dataset_rotated_45", "quad_and_triangle_face_vector_user_grid_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_face_vector_on_user_grid_streamlines() @@ -542,8 +614,14 @@ void TestQgsMeshRenderer::test_face_vector_on_user_grid_streamlines() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_user_grid_dataset_streamlines", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_user_grid_dataset_streamlines_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_user_grid_dataset_streamlines", "quad_and_triangle_face_vector_user_grid_dataset_streamlines", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_user_grid_dataset_streamlines_rotated_45", "quad_and_triangle_face_vector_user_grid_dataset_streamlines_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_on_user_grid() @@ -564,8 +642,14 @@ void TestQgsMeshRenderer::test_vertex_vector_on_user_grid() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset", "quad_and_triangle_vertex_vector_user_grid_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_rotated_45", "quad_and_triangle_vertex_vector_user_grid_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_streamlines() @@ -586,8 +670,14 @@ void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_streamlines() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines", "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_rotated_45", "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_streamlines_colorRamp() @@ -608,7 +698,11 @@ void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_streamlines_colorRamp( mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_colorRamp", mMemoryLayer ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_colorRamp", "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_colorRamp", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_vertex_vector_traces() @@ -635,8 +729,14 @@ void TestQgsMeshRenderer::test_vertex_vector_traces() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_traces", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_traces_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_quad_and_triangle_vertex_vector_traces", "quad_and_triangle_vertex_vector_traces", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_traces_rotated_45", "quad_and_triangle_vertex_vector_traces_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_traces_colorRamp() @@ -663,7 +763,11 @@ void TestQgsMeshRenderer::test_vertex_vector_traces_colorRamp() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_traces_colorRamp", mMemoryLayer ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_traces_colorRamp", "quad_and_triangle_vertex_vector_traces_colorRamp", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_signals() @@ -716,7 +820,11 @@ void TestQgsMeshRenderer::test_stacked_3d_mesh_single_level_averaging() mMdal3DLayer->setRendererSettings( rendererSettings ); mMdal3DLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "stacked_3d_mesh_single_level_averaging", mMdal3DLayer ) ); + mMapSettings->setDestinationCrs( mMdal3DLayer->crs() ); + mMapSettings->setExtent( mMdal3DLayer->extent() ); + mMapSettings->setRotation( 0 ); + mMapSettings->setOutputDpi( 96 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "stacked_3d_mesh_single_level_averaging", "stacked_3d_mesh_single_level_averaging", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_simplified_triangular_mesh_rendering() @@ -733,7 +841,12 @@ void TestQgsMeshRenderer::test_simplified_triangular_mesh_rendering() mMdal3DLayer->setRendererSettings( rendererSettings ); mMdal3DLayer->setMeshSimplificationSettings( simplificatationSettings ); - QVERIFY( imageCheck( "simplified_triangular_mesh", mMdal3DLayer ) ); + + mMapSettings->setDestinationCrs( mMdal3DLayer->crs() ); + mMapSettings->setExtent( mMdal3DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "simplified_triangular_mesh", "simplified_triangular_mesh", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_classified_values() @@ -747,7 +860,11 @@ void TestQgsMeshRenderer::test_classified_values() classifiedMesh.temporalProperties()->setIsActive( false ); classifiedMesh.setStaticScalarDatasetIndex( QgsMeshDatasetIndex( 3, 4 ) ); - QVERIFY( imageCheck( "classified_values", &classifiedMesh ) ); + mMapSettings->setDestinationCrs( classifiedMesh.crs() ); + mMapSettings->setExtent( classifiedMesh.extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "classified_values", "classified_values", *mMapSettings, 0, 15 ); } QGSTEST_MAIN( TestQgsMeshRenderer ) diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 51f359d93116..ad77a8f5c02f 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -177,6 +177,7 @@ ADD_PYTHON_TEST(PyQgsMargins test_qgsmargins.py) ADD_PYTHON_TEST(PyQgsMarkerLineSymbolLayer test_qgsmarkerlinesymbollayer.py) ADD_PYTHON_TEST(PyQgsMatrix4x4 test_qgsmatrix4x4.py) ADD_PYTHON_TEST(PyQgsMergedFeatureRenderer test_qgsmergedfeaturerenderer.py) +ADD_PYTHON_TEST(PyQgsMeshLayer test_qgsmeshlayer.py) ADD_PYTHON_TEST(PyQgsMeshLayerElevationProperties test_qgsmeshlayerelevationproperties.py) ADD_PYTHON_TEST(PyQgsMeshLayerRenderer test_qgsmeshlayerrenderer.py) ADD_PYTHON_TEST(PyQgsMessageLog test_qgsmessagelog.py) diff --git a/tests/src/python/test_qgsmeshlayer.py b/tests/src/python/test_qgsmeshlayer.py new file mode 100644 index 000000000000..ac023360fddf --- /dev/null +++ b/tests/src/python/test_qgsmeshlayer.py @@ -0,0 +1,68 @@ +"""QGIS Unit tests for QgsMeshLayer + +.. note:: 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. +""" + +from qgis.core import ( + QgsMeshLayer, + QgsMeshDatasetIndex +) +import unittest +from qgis.testing import start_app, QgisTestCase + +start_app() + + +class TestQgsMeshLayer(QgisTestCase): + + def test_dataset_group_metadata(self): + """ + Test datasetGroupMetadata + """ + layer = QgsMeshLayer( + self.get_test_data_path('mesh/netcdf_parent_quantity.nc').as_posix(), + 'mesh', + 'mdal' + ) + self.assertTrue(layer.isValid()) + + self.assertEqual( + layer.datasetGroupMetadata(QgsMeshDatasetIndex(0)).name(), + 'air_temperature_height:10') + self.assertEqual( + layer.datasetGroupMetadata( + QgsMeshDatasetIndex(0)).parentQuantityName(), + 'air_temperature_height') + self.assertEqual( + layer.datasetGroupMetadata(QgsMeshDatasetIndex(1)).name(), + 'air_temperature_height:20') + self.assertEqual( + layer.datasetGroupMetadata( + QgsMeshDatasetIndex(1)).parentQuantityName(), + 'air_temperature_height') + self.assertEqual( + layer.datasetGroupMetadata(QgsMeshDatasetIndex(2)).name(), + 'air_temperature_height:30') + self.assertEqual( + layer.datasetGroupMetadata( + QgsMeshDatasetIndex(2)).parentQuantityName(), + 'air_temperature_height') + self.assertEqual( + layer.datasetGroupMetadata(QgsMeshDatasetIndex(3)).name(), + 'air_temperature_height:5') + self.assertEqual( + layer.datasetGroupMetadata( + QgsMeshDatasetIndex(3)).parentQuantityName(), + 'air_temperature_height') + self.assertFalse( + layer.datasetGroupMetadata(QgsMeshDatasetIndex(4)).name()) + self.assertFalse( + layer.datasetGroupMetadata( + QgsMeshDatasetIndex(4)).parentQuantityName()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgsmeshlayerrenderer.py b/tests/src/python/test_qgsmeshlayerrenderer.py index b8e44a14afe1..2668064018f7 100644 --- a/tests/src/python/test_qgsmeshlayerrenderer.py +++ b/tests/src/python/test_qgsmeshlayerrenderer.py @@ -81,6 +81,72 @@ def test_render_fixed_elevation_range_with_z_range_filter(self): map_settings) ) + def test_render_fixed_range_per_group_with_z_range_filter(self): + """ + Test rendering a mesh with a fixed range per group when + map settings has a z range filter + """ + layer = QgsMeshLayer( + self.get_test_data_path( + 'mesh/netcdf_parent_quantity.nc').as_posix(), + 'mesh', + 'mdal' + ) + self.assertTrue(layer.isValid()) + + # set layer as elevation enabled + layer.elevationProperties().setMode( + Qgis.MeshElevationMode.FixedRangePerGroup + ) + layer.elevationProperties().setFixedRangePerGroup( + {1: QgsDoubleRange(33, 38), + 2: QgsDoubleRange(35, 40), + 3: QgsDoubleRange(40, 48)} + ) + + map_settings = QgsMapSettings() + map_settings.setOutputSize(QSize(400, 400)) + map_settings.setOutputDpi(96) + map_settings.setDestinationCrs(layer.crs()) + map_settings.setExtent(layer.extent()) + map_settings.setLayers([layer]) + + # no filter on map settings + map_settings.setZRange(QgsDoubleRange()) + self.assertTrue( + self.render_map_settings_check( + 'No Z range filter on map settings, elevation range per group', + 'elevation_range_per_group_no_filter', + map_settings) + ) + + # map settings range matches group 3 only + map_settings.setZRange(QgsDoubleRange(40.5, 49.5)) + self.assertTrue( + self.render_map_settings_check( + 'Z range filter on map settings matches group 3 only', + 'elevation_range_per_group_match3', + map_settings) + ) + + # map settings range matches group 1 and 2 + map_settings.setZRange(QgsDoubleRange(33, 39.5)) + self.assertTrue( + self.render_map_settings_check( + 'Z range filter on map settings matches group 1 and 2', + 'elevation_range_per_group_match1and2', + map_settings) + ) + + # map settings range excludes layer's range + map_settings.setZRange(QgsDoubleRange(130, 135)) + self.assertTrue( + self.render_map_settings_check( + 'Z range filter on map settings outside of layer group ranges', + 'fixed_elevation_range_excluded', + map_settings) + ) + if __name__ == '__main__': unittest.main() diff --git a/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match1and2/expected_elevation_range_per_group_match1and2.png b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match1and2/expected_elevation_range_per_group_match1and2.png new file mode 100644 index 000000000000..a318096cbc56 Binary files /dev/null and b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match1and2/expected_elevation_range_per_group_match1and2.png differ diff --git a/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match3/expected_elevation_range_per_group_match3.png b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match3/expected_elevation_range_per_group_match3.png new file mode 100644 index 000000000000..4a218535db07 Binary files /dev/null and b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match3/expected_elevation_range_per_group_match3.png differ diff --git a/tests/testdata/control_images/mesh/expected_elevation_range_per_group_no_filter/expected_elevation_range_per_group_no_filter.png b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_no_filter/expected_elevation_range_per_group_no_filter.png new file mode 100644 index 000000000000..3e98a42c40f8 Binary files /dev/null and b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_no_filter/expected_elevation_range_per_group_no_filter.png differ diff --git a/tests/testdata/mesh/netcdf_parent_quantity.nc b/tests/testdata/mesh/netcdf_parent_quantity.nc new file mode 100644 index 000000000000..1d3a88195ea1 Binary files /dev/null and b/tests/testdata/mesh/netcdf_parent_quantity.nc differ