From 2379de4a957cf894022778e23e7734f6bf49dcdc Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 23 Feb 2022 13:32:59 +1000 Subject: [PATCH] Add QgsMapLayerElevationProperties subclass for raster layers Allows elevation properties to be set for raster layers, including: - whether raster values represent heights - scale - offset These properties can be set through the new "Elevation" tab in the raster layer properties dialog --- .../qgsmaplayerelevationproperties.sip.in | 5 + .../raster/qgsrasterlayer.sip.in | 2 + .../qgsrasterlayerelevationproperties.sip.in | 112 +++++++++++++++ python/core/core_auto.sip | 1 + src/app/CMakeLists.txt | 2 + src/app/qgisapp.cpp | 3 + .../qgsrasterelevationpropertieswidget.cpp | 107 ++++++++++++++ .../qgsrasterelevationpropertieswidget.h | 65 +++++++++ src/core/CMakeLists.txt | 2 + src/core/qgsmaplayerelevationproperties.h | 5 + src/core/raster/qgsrasterlayer.cpp | 9 +- src/core/raster/qgsrasterlayer.h | 4 + .../qgsrasterlayerelevationproperties.cpp | 60 ++++++++ .../qgsrasterlayerelevationproperties.h | 112 +++++++++++++++ .../qgsrasterelevationpropertieswidgetbase.ui | 136 ++++++++++++++++++ tests/src/python/CMakeLists.txt | 1 + .../test_qgsrasterlayerelevationproperties.py | 56 ++++++++ 17 files changed, 681 insertions(+), 1 deletion(-) create mode 100644 python/core/auto_generated/raster/qgsrasterlayerelevationproperties.sip.in create mode 100644 src/app/raster/qgsrasterelevationpropertieswidget.cpp create mode 100644 src/app/raster/qgsrasterelevationpropertieswidget.h create mode 100644 src/core/raster/qgsrasterlayerelevationproperties.cpp create mode 100644 src/core/raster/qgsrasterlayerelevationproperties.h create mode 100644 src/ui/raster/qgsrasterelevationpropertieswidgetbase.ui create mode 100644 tests/src/python/test_qgsrasterlayerelevationproperties.py diff --git a/python/core/auto_generated/qgsmaplayerelevationproperties.sip.in b/python/core/auto_generated/qgsmaplayerelevationproperties.sip.in index 57d0b627693..9dc566237a2 100644 --- a/python/core/auto_generated/qgsmaplayerelevationproperties.sip.in +++ b/python/core/auto_generated/qgsmaplayerelevationproperties.sip.in @@ -25,12 +25,17 @@ how an individual :py:class:`QgsMapLayer` behaves with relation to z values or e %TypeHeaderCode #include "qgsmaplayerelevationproperties.h" #include "qgspointcloudlayerelevationproperties.h" +#include "qgsrasterlayerelevationproperties.h" %End %ConvertToSubClassCode if ( qobject_cast( sipCpp ) ) { sipType = sipType_QgsPointCloudLayerElevationProperties; } + else if ( qobject_cast( sipCpp ) ) + { + sipType = sipType_QgsRasterLayerElevationProperties; + } else { sipType = 0; diff --git a/python/core/auto_generated/raster/qgsrasterlayer.sip.in b/python/core/auto_generated/raster/qgsrasterlayer.sip.in index 9652f9f3522..3bf9b65c26f 100644 --- a/python/core/auto_generated/raster/qgsrasterlayer.sip.in +++ b/python/core/auto_generated/raster/qgsrasterlayer.sip.in @@ -421,6 +421,8 @@ to be drawn outside the data extent. virtual QgsMapLayerTemporalProperties *temporalProperties(); + virtual QgsMapLayerElevationProperties *elevationProperties(); + public slots: void showStatusMessage( const QString &message ); diff --git a/python/core/auto_generated/raster/qgsrasterlayerelevationproperties.sip.in b/python/core/auto_generated/raster/qgsrasterlayerelevationproperties.sip.in new file mode 100644 index 00000000000..456d7c55641 --- /dev/null +++ b/python/core/auto_generated/raster/qgsrasterlayerelevationproperties.sip.in @@ -0,0 +1,112 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/raster/qgsrasterlayerelevationproperties.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsRasterLayerElevationProperties : QgsMapLayerElevationProperties +{ +%Docstring(signature="appended") +Raster layer specific subclass of :py:class:`QgsMapLayerElevationProperties`. + +.. versionadded:: 3.26 +%End + +%TypeHeaderCode +#include "qgsrasterlayerelevationproperties.h" +%End + public: + + QgsRasterLayerElevationProperties( QObject *parent /TransferThis/ ); +%Docstring +Constructor for QgsRasterLayerElevationProperties, with the specified ``parent`` object. +%End + + virtual bool hasElevation() const; + + virtual QDomElement writeXml( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context ); + + virtual bool readXml( const QDomElement &element, const QgsReadWriteContext &context ); + + virtual bool isVisibleInZRange( const QgsDoubleRange &range ) const; + + virtual QgsDoubleRange calculateZRange( QgsMapLayer *layer ) const; + + + bool isEnabled() const; +%Docstring +Returns ``True`` if the elevation properties are enabled, i.e. the raster layer values represent an elevation surface. + +.. seealso:: :py:func:`setEnabled` +%End + + void setEnabled( bool enabled ); +%Docstring +Sets whether the elevation properties are enabled, i.e. the raster layer values represent an elevation surface. + +.. seealso:: :py:func:`isEnabled` +%End + + double zOffset() const; +%Docstring +Returns the z offset, which is a fixed offset amount which should be added to z values from +the layer. + +.. note:: + + Any scaling specified via :py:func:`~QgsRasterLayerElevationProperties.zScale` is applied before any offset value specified via :py:func:`~QgsRasterLayerElevationProperties.zOffset` + +.. seealso:: :py:func:`setZOffset` +%End + + void setZOffset( double offset ); +%Docstring +Sets the z ``offset``, which is a fixed offset amount which will be added to z values from +the layer. + +.. note:: + + Any scaling specified via :py:func:`~QgsRasterLayerElevationProperties.zScale` is applied before any offset value specified via :py:func:`~QgsRasterLayerElevationProperties.zOffset` + +.. seealso:: :py:func:`zOffset` +%End + + double zScale() const; +%Docstring +Returns the z scale, which is a scaling factor which should be applied to z values from +the layer. + +.. note:: + + Any scaling specified via :py:func:`~QgsRasterLayerElevationProperties.zScale` is applied before any offset value specified via :py:func:`~QgsRasterLayerElevationProperties.zOffset` + +.. seealso:: :py:func:`setZScale` +%End + + void setZScale( double scale ); +%Docstring +Sets the z ``scale``, which is a scaling factor which will be applied to z values from +the layer. + +.. note:: + + Any scaling specified via :py:func:`~QgsRasterLayerElevationProperties.zScale` is applied before any offset value specified via :py:func:`~QgsRasterLayerElevationProperties.zOffset` + +.. seealso:: :py:func:`zScale` +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/raster/qgsrasterlayerelevationproperties.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index c8888f8a0d8..62f7bc4d992 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -577,6 +577,7 @@ %Include auto_generated/raster/qgsrasterinterface.sip %Include auto_generated/raster/qgsrasteriterator.sip %Include auto_generated/raster/qgsrasterlayer.sip +%Include auto_generated/raster/qgsrasterlayerelevationproperties.sip %Include auto_generated/raster/qgsrasterlayertemporalproperties.sip %Include auto_generated/raster/qgsrasterminmaxorigin.sip %Include auto_generated/raster/qgsrasternuller.sip diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 6cd67ca035c..31332f19bec 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -130,6 +130,8 @@ set(QGIS_APP_SRCS pointcloud/qgspointcloudlayerproperties.cpp pointcloud/qgspointcloudlayerstylewidget.cpp + raster/qgsrasterelevationpropertieswidget.cpp + vectortile/qgsvectortilelayerproperties.cpp vertextool/qgslockedfeature.cpp diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 5db093e6b56..30e2bb9ea1f 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -117,6 +117,8 @@ #include "options/qgsgpsdeviceoptions.h" #include "options/qgscustomprojectionoptions.h" +#include "raster/qgsrasterelevationpropertieswidget.h" + #ifdef HAVE_3D #include "qgs3d.h" #include "qgs3danimationsettings.h" @@ -1478,6 +1480,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers registerMapLayerPropertiesFactory( new QgsPointCloudLayer3DRendererWidgetFactory( this ) ); #endif registerMapLayerPropertiesFactory( new QgsPointCloudElevationPropertiesWidgetFactory( this ) ); + registerMapLayerPropertiesFactory( new QgsRasterElevationPropertiesWidgetFactory( this ) ); registerMapLayerPropertiesFactory( new QgsAnnotationItemPropertiesWidgetFactory( this ) ); registerMapLayerPropertiesFactory( new QgsLayerTreeGroupPropertiesWidgetFactory( this ) ); diff --git a/src/app/raster/qgsrasterelevationpropertieswidget.cpp b/src/app/raster/qgsrasterelevationpropertieswidget.cpp new file mode 100644 index 00000000000..1be90ca3d44 --- /dev/null +++ b/src/app/raster/qgsrasterelevationpropertieswidget.cpp @@ -0,0 +1,107 @@ +/*************************************************************************** + qgsrasterelevationpropertieswidget.cpp + --------------------- + begin : February 2022 + copyright : (C) 2022 by Nyall Dawson + email : nyall dot dawson 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 "qgsrasterelevationpropertieswidget.h" +#include "qgsstyle.h" +#include "qgsapplication.h" +#include "qgsmaplayer.h" +#include "qgsrasterlayer.h" +#include "qgsrasterlayerelevationproperties.h" + +QgsRasterElevationPropertiesWidget::QgsRasterElevationPropertiesWidget( QgsRasterLayer *layer, QgsMapCanvas *canvas, QWidget *parent ) + : QgsMapLayerConfigWidget( layer, canvas, parent ) +{ + setupUi( this ); + + mOffsetZSpinBox->setClearValue( 0 ); + mScaleZSpinBox->setClearValue( 1 ); + mElevationGroupBox->setChecked( false ); + + syncToLayer( layer ); + + connect( mOffsetZSpinBox, qOverload( &QDoubleSpinBox::valueChanged ), this, &QgsRasterElevationPropertiesWidget::onChanged ); + connect( mScaleZSpinBox, qOverload( &QDoubleSpinBox::valueChanged ), this, &QgsRasterElevationPropertiesWidget::onChanged ); + connect( mElevationGroupBox, &QGroupBox::toggled, this, &QgsRasterElevationPropertiesWidget::onChanged ); +} + +void QgsRasterElevationPropertiesWidget::syncToLayer( QgsMapLayer *layer ) +{ + mLayer = qobject_cast< QgsRasterLayer * >( layer ); + if ( !mLayer ) + return; + + mBlockUpdates = true; + const QgsRasterLayerElevationProperties *props = qgis::down_cast< const QgsRasterLayerElevationProperties * >( mLayer->elevationProperties() ); + mElevationGroupBox->setChecked( props->isEnabled() ); + mOffsetZSpinBox->setValue( props->zOffset() ); + mScaleZSpinBox->setValue( props->zScale() ); + mBlockUpdates = false; +} + +void QgsRasterElevationPropertiesWidget::apply() +{ + if ( !mLayer ) + return; + + QgsRasterLayerElevationProperties *props = qgis::down_cast< QgsRasterLayerElevationProperties * >( mLayer->elevationProperties() ); + props->setEnabled( mElevationGroupBox->isChecked() ); + props->setZOffset( mOffsetZSpinBox->value() ); + props->setZScale( mScaleZSpinBox->value() ); + mLayer->trigger3DUpdate(); +} + +void QgsRasterElevationPropertiesWidget::onChanged() +{ + if ( !mBlockUpdates ) + emit widgetChanged(); +} + + +// +// QgsRasterElevationPropertiesWidgetFactory +// + +QgsRasterElevationPropertiesWidgetFactory::QgsRasterElevationPropertiesWidgetFactory( QObject *parent ) + : QObject( parent ) +{ + setIcon( QgsApplication::getThemeIcon( QStringLiteral( "propertyicons/elevationscale.svg" ) ) ); + setTitle( tr( "Elevation" ) ); +} + +QgsMapLayerConfigWidget *QgsRasterElevationPropertiesWidgetFactory::createWidget( QgsMapLayer *layer, QgsMapCanvas *canvas, bool, QWidget *parent ) const +{ + return new QgsRasterElevationPropertiesWidget( qobject_cast< QgsRasterLayer * >( layer ), canvas, parent ); +} + +bool QgsRasterElevationPropertiesWidgetFactory::supportLayerPropertiesDialog() const +{ + return true; +} + +bool QgsRasterElevationPropertiesWidgetFactory::supportsStyleDock() const +{ + return true; +} + +bool QgsRasterElevationPropertiesWidgetFactory::supportsLayer( QgsMapLayer *layer ) const +{ + return layer->type() == QgsMapLayerType::RasterLayer; +} + +QString QgsRasterElevationPropertiesWidgetFactory::layerPropertiesPagePositionHint() const +{ + return QStringLiteral( "mOptsPage_Metadata" ); +} + diff --git a/src/app/raster/qgsrasterelevationpropertieswidget.h b/src/app/raster/qgsrasterelevationpropertieswidget.h new file mode 100644 index 00000000000..a67b23f27d2 --- /dev/null +++ b/src/app/raster/qgsrasterelevationpropertieswidget.h @@ -0,0 +1,65 @@ +/*************************************************************************** + qgsrasterelevationpropertieswidget.h + --------------------- + begin : February 2022 + copyright : (C) 2022 by Nyall Dawson + email : nyall dot dawson 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 QGSRASTERELEVATIONPROPERTIESWIDGET_H +#define QGSRASTERELEVATIONPROPERTIESWIDGET_H + +#include "qgsmaplayerconfigwidget.h" +#include "qgsmaplayerconfigwidgetfactory.h" + +#include "ui_qgsrasterelevationpropertieswidgetbase.h" + +class QgsRasterLayer; + +class QgsRasterElevationPropertiesWidget : public QgsMapLayerConfigWidget, private Ui::QgsRasterElevationPropertiesWidgetBase +{ + Q_OBJECT + public: + + QgsRasterElevationPropertiesWidget( QgsRasterLayer *layer, QgsMapCanvas *canvas, QWidget *parent ); + + void syncToLayer( QgsMapLayer *layer ) override; + + public slots: + void apply() override; + + private slots: + + void onChanged(); + + private: + + QgsRasterLayer *mLayer = nullptr; + bool mBlockUpdates = false; + +}; + + +class QgsRasterElevationPropertiesWidgetFactory : public QObject, public QgsMapLayerConfigWidgetFactory +{ + Q_OBJECT + public: + explicit QgsRasterElevationPropertiesWidgetFactory( QObject *parent = nullptr ); + + QgsMapLayerConfigWidget *createWidget( QgsMapLayer *layer, QgsMapCanvas *canvas, bool dockWidget, QWidget *parent ) const override; + bool supportLayerPropertiesDialog() const override; + bool supportsStyleDock() const override; + bool supportsLayer( QgsMapLayer *layer ) const override; + QString layerPropertiesPagePositionHint() const override; +}; + + + +#endif // QGSRASTERELEVATIONPROPERTIESWIDGET_H diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index dad3c5a39d9..009e9578ae9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -657,6 +657,7 @@ set(QGIS_CORE_SRCS raster/qgsrasterinterface.cpp raster/qgsrasteriterator.cpp raster/qgsrasterlayer.cpp + raster/qgsrasterlayerelevationproperties.cpp raster/qgsrasterlayerrenderer.cpp raster/qgsrasterlayertemporalproperties.cpp raster/qgsrasterminmaxorigin.cpp @@ -1648,6 +1649,7 @@ set(QGIS_CORE_HDRS raster/qgsrasterinterface.h raster/qgsrasteriterator.h raster/qgsrasterlayer.h + raster/qgsrasterlayerelevationproperties.h raster/qgsrasterlayerrenderer.h raster/qgsrasterlayertemporalproperties.h raster/qgsrasterminmaxorigin.h diff --git a/src/core/qgsmaplayerelevationproperties.h b/src/core/qgsmaplayerelevationproperties.h index 3dbc5abcfaf..b2cfcacf681 100644 --- a/src/core/qgsmaplayerelevationproperties.h +++ b/src/core/qgsmaplayerelevationproperties.h @@ -41,6 +41,7 @@ class CORE_EXPORT QgsMapLayerElevationProperties : public QObject { #ifdef SIP_RUN #include "qgspointcloudlayerelevationproperties.h" +#include "qgsrasterlayerelevationproperties.h" #endif Q_OBJECT @@ -51,6 +52,10 @@ class CORE_EXPORT QgsMapLayerElevationProperties : public QObject { sipType = sipType_QgsPointCloudLayerElevationProperties; } + else if ( qobject_cast( sipCpp ) ) + { + sipType = sipType_QgsRasterLayerElevationProperties; + } else { sipType = 0; diff --git a/src/core/raster/qgsrasterlayer.cpp b/src/core/raster/qgsrasterlayer.cpp index c8a43aefc5e..095ed028138 100644 --- a/src/core/raster/qgsrasterlayer.cpp +++ b/src/core/raster/qgsrasterlayer.cpp @@ -59,6 +59,7 @@ email : tim at linfiniti.com #include "qgsruntimeprofiler.h" #include "qgsmaplayerfactory.h" #include "qgsrasterpipe.h" +#include "qgsrasterlayerelevationproperties.h" #include #include @@ -107,8 +108,8 @@ QgsRasterLayer::QgsRasterLayer() , QSTRING_NOT_SET( QStringLiteral( "Not Set" ) ) , TRSTRING_NOT_SET( tr( "Not Set" ) ) , mTemporalProperties( new QgsRasterLayerTemporalProperties( this ) ) + , mElevationProperties( new QgsRasterLayerElevationProperties( this ) ) , mPipe( std::make_unique< QgsRasterPipe >() ) - { init(); setValid( false ); @@ -123,6 +124,7 @@ QgsRasterLayer::QgsRasterLayer( const QString &uri, , QSTRING_NOT_SET( QStringLiteral( "Not Set" ) ) , TRSTRING_NOT_SET( tr( "Not Set" ) ) , mTemporalProperties( new QgsRasterLayerTemporalProperties( this ) ) + , mElevationProperties( new QgsRasterLayerElevationProperties( this ) ) , mPipe( std::make_unique< QgsRasterPipe >() ) { mShouldValidateCrs = !options.skipCrsValidation; @@ -1070,6 +1072,11 @@ QgsMapLayerTemporalProperties *QgsRasterLayer::temporalProperties() return mTemporalProperties; } +QgsMapLayerElevationProperties *QgsRasterLayer::elevationProperties() +{ + return mElevationProperties; +} + void QgsRasterLayer::setContrastEnhancement( QgsContrastEnhancement::ContrastEnhancementAlgorithm algorithm, QgsRasterMinMaxOrigin::Limits limits, const QgsRectangle &extent, int sampleSize, bool generateLookupTableFlag ) { setContrastEnhancement( algorithm, diff --git a/src/core/raster/qgsrasterlayer.h b/src/core/raster/qgsrasterlayer.h index c797a35e1de..9480f17e96d 100644 --- a/src/core/raster/qgsrasterlayer.h +++ b/src/core/raster/qgsrasterlayer.h @@ -47,6 +47,7 @@ class QgsRasterPipe; class QgsRasterResampleFilter; class QgsBrightnessContrastFilter; class QgsHueSaturationFilter; +class QgsRasterLayerElevationProperties; class QImage; class QPixmap; @@ -467,6 +468,7 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer bool ignoreExtents() const; QgsMapLayerTemporalProperties *temporalProperties() override; + QgsMapLayerElevationProperties *elevationProperties() override; public slots: void showStatusMessage( const QString &message ); @@ -552,6 +554,8 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer //! Pointer to temporal properties QgsRasterLayerTemporalProperties *mTemporalProperties = nullptr; + QgsRasterLayerElevationProperties *mElevationProperties = nullptr; + //! [ data provider interface ] Timestamp, the last modified time of the data source when the layer was created QDateTime mLastModified; diff --git a/src/core/raster/qgsrasterlayerelevationproperties.cpp b/src/core/raster/qgsrasterlayerelevationproperties.cpp new file mode 100644 index 00000000000..3070d4681c0 --- /dev/null +++ b/src/core/raster/qgsrasterlayerelevationproperties.cpp @@ -0,0 +1,60 @@ +/*************************************************************************** + qgsrasterlayerelevationproperties.cpp + --------------- + begin : February 2022 + copyright : (C) 2022 by Nyall Dawson + email : nyall dot dawson 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 "qgsrasterlayerelevationproperties.h" +#include "qgsrasterlayer.h" + +QgsRasterLayerElevationProperties::QgsRasterLayerElevationProperties( QObject *parent ) + : QgsMapLayerElevationProperties( parent ) +{ +} + +bool QgsRasterLayerElevationProperties::hasElevation() const +{ + return mEnabled; +} + +QDomElement QgsRasterLayerElevationProperties::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) +{ + QDomElement element = document.createElement( QStringLiteral( "elevation" ) ); + element.setAttribute( QStringLiteral( "enabled" ), mEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); + element.setAttribute( QStringLiteral( "zoffset" ), qgsDoubleToString( mZOffset ) ); + element.setAttribute( QStringLiteral( "zscale" ), qgsDoubleToString( mZScale ) ); + parentElement.appendChild( element ); + return element; +} + +bool QgsRasterLayerElevationProperties::readXml( const QDomElement &element, const QgsReadWriteContext & ) +{ + const QDomElement elevationElement = element.firstChildElement( QStringLiteral( "elevation" ) ).toElement(); + mEnabled = elevationElement.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt(); + mZOffset = elevationElement.attribute( QStringLiteral( "zoffset" ), QStringLiteral( "0" ) ).toDouble(); + mZScale = elevationElement.attribute( QStringLiteral( "zscale" ), QStringLiteral( "1" ) ).toDouble(); + return true; +} + +bool QgsRasterLayerElevationProperties::isVisibleInZRange( const QgsDoubleRange & ) const +{ + // TODO -- test actual raster z range + return true; +} + +QgsDoubleRange QgsRasterLayerElevationProperties::calculateZRange( QgsMapLayer * ) const +{ + // TODO -- determine actual z range from raster statistics + return QgsDoubleRange(); +} diff --git a/src/core/raster/qgsrasterlayerelevationproperties.h b/src/core/raster/qgsrasterlayerelevationproperties.h new file mode 100644 index 00000000000..0f2d416d427 --- /dev/null +++ b/src/core/raster/qgsrasterlayerelevationproperties.h @@ -0,0 +1,112 @@ +/*************************************************************************** + qgsrasterlayerelevationproperties.h + --------------- + begin : February 2022 + copyright : (C) 2022 by Nyall Dawson + email : nyall dot dawson 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 QGSRASTERLAYERELEVATIONPROPERTIES_H +#define QGSRASTERLAYERELEVATIONPROPERTIES_H + +#include "qgis_core.h" +#include "qgis_sip.h" +#include "qgsmaplayerelevationproperties.h" + +/** + * \class QgsRasterLayerElevationProperties + * \ingroup core + * \brief Raster layer specific subclass of QgsMapLayerElevationProperties. + * + * \since QGIS 3.26 + */ +class CORE_EXPORT QgsRasterLayerElevationProperties : public QgsMapLayerElevationProperties +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsRasterLayerElevationProperties, with the specified \a parent object. + */ + QgsRasterLayerElevationProperties( QObject *parent SIP_TRANSFERTHIS ); + + bool hasElevation() const override; + QDomElement writeXml( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context ) override; + bool readXml( const QDomElement &element, const QgsReadWriteContext &context ) override; + bool isVisibleInZRange( const QgsDoubleRange &range ) const override; + QgsDoubleRange calculateZRange( QgsMapLayer *layer ) const override; + + /** + * Returns TRUE if the elevation properties are enabled, i.e. the raster layer values represent an elevation surface. + * + * \see setEnabled() + */ + bool isEnabled() const { return mEnabled; } + + /** + * Sets whether the elevation properties are enabled, i.e. the raster layer values represent an elevation surface. + * + * \see isEnabled() + */ + void setEnabled( bool enabled ) { mEnabled = enabled; } + + /** + * Returns the z offset, which is a fixed offset amount which should be added to z values from + * the layer. + * + * \note Any scaling specified via zScale() is applied before any offset value specified via zOffset() + * + * \see setZOffset() + */ + double zOffset() const { return mZOffset; } + + /** + * Sets the z \a offset, which is a fixed offset amount which will be added to z values from + * the layer. + * + * \note Any scaling specified via zScale() is applied before any offset value specified via zOffset() + * + * \see zOffset() + */ + void setZOffset( double offset ) { mZOffset = offset; } + + /** + * Returns the z scale, which is a scaling factor which should be applied to z values from + * the layer. + * + * \note Any scaling specified via zScale() is applied before any offset value specified via zOffset() + * + * \see setZScale() + */ + double zScale() const { return mZScale; } + + /** + * Sets the z \a scale, which is a scaling factor which will be applied to z values from + * the layer. + * + * \note Any scaling specified via zScale() is applied before any offset value specified via zOffset() + * + * \see zScale() + */ + void setZScale( double scale ) { mZScale = scale; } + + private: + + bool mEnabled = false; + double mZScale = 1.0; + double mZOffset = 0.0; +}; + +#endif // QGSRASTERLAYERELEVATIONPROPERTIES_H diff --git a/src/ui/raster/qgsrasterelevationpropertieswidgetbase.ui b/src/ui/raster/qgsrasterelevationpropertieswidgetbase.ui new file mode 100644 index 00000000000..ac2c5e74cc0 --- /dev/null +++ b/src/ui/raster/qgsrasterelevationpropertieswidgetbase.ui @@ -0,0 +1,136 @@ + + + QgsRasterElevationPropertiesWidgetBase + + + + 0 + 0 + 400 + 242 + + + + Raster Elevation Properties + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::StrongFocus + + + Represents Elevation Surface + + + true + + + vectorgeneral + + + + + + Qt::Vertical + + + + + + + Offset + + + + + + + Scale + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Elevation scaling and offset can be used to manually correct elevation values from the layer.</span></p><p>The scale is applied to the raster values before adding the offset.</p></body></html> + + + true + + + + + + + 6 + + + 0.000000000000000 + + + 99999999999.000000000000000 + + + 1.000000000000000 + + + + + + + 6 + + + -99999999999.000000000000000 + + + 99999999999.000000000000000 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
+
+ + mElevationGroupBox + mScaleZSpinBox + mOffsetZSpinBox + + + +
diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index bf84460f7d1..76de442c083 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -272,6 +272,7 @@ ADD_PYTHON_TEST(PyQgsRasterBandComboBox test_qgsrasterbandcombobox.py) ADD_PYTHON_TEST(PyQgsRasterFileWriter test_qgsrasterfilewriter.py) ADD_PYTHON_TEST(PyQgsRasterFileWriterTask test_qgsrasterfilewritertask.py) ADD_PYTHON_TEST(PyQgsRasterLayer test_qgsrasterlayer.py) +ADD_PYTHON_TEST(PyQgsRasterLayerElevationProperties test_qgsrasterlayerelevationproperties.py) ADD_PYTHON_TEST(PyQgsRasterLayerProperties test_qgsrasterlayerproperties.py) ADD_PYTHON_TEST(PyQgsRasterLayerRenderer test_qgsrasterlayerrenderer.py) ADD_PYTHON_TEST(PyQgsRasterColorRampShader test_qgsrastercolorrampshader.py) diff --git a/tests/src/python/test_qgsrasterlayerelevationproperties.py b/tests/src/python/test_qgsrasterlayerelevationproperties.py new file mode 100644 index 00000000000..a7f3f3e189a --- /dev/null +++ b/tests/src/python/test_qgsrasterlayerelevationproperties.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsRasterLayerElevationProperties + +.. 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. +""" +__author__ = 'Nyall Dawson' +__date__ = '09/11/2020' +__copyright__ = 'Copyright 2020, The QGIS Project' + +import qgis # NOQA + +from qgis.core import ( + QgsRasterLayerElevationProperties, + QgsReadWriteContext, +) + +from qgis.PyQt.QtXml import QDomDocument + +from qgis.testing import start_app, unittest + +start_app() + + +class TestQgsRasterLayerElevationProperties(unittest.TestCase): + + def testBasic(self): + props = QgsRasterLayerElevationProperties(None) + self.assertEqual(props.zScale(), 1) + self.assertEqual(props.zOffset(), 0) + self.assertFalse(props.isEnabled()) + self.assertFalse(props.hasElevation()) + + props.setZOffset(0.5) + props.setZScale(2) + props.setEnabled(True) + self.assertEqual(props.zScale(), 2) + self.assertEqual(props.zOffset(), 0.5) + self.assertTrue(props.isEnabled()) + self.assertTrue(props.hasElevation()) + + doc = QDomDocument("testdoc") + elem = doc.createElement('test') + props.writeXml(elem, doc, QgsReadWriteContext()) + + props2 = QgsRasterLayerElevationProperties(None) + props2.readXml(elem, QgsReadWriteContext()) + self.assertEqual(props2.zScale(), 2) + self.assertEqual(props2.zOffset(), 0.5) + self.assertTrue(props.isEnabled()) + + +if __name__ == '__main__': + unittest.main()