mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-04 00:06:46 -05:00
[api] Attach QgsPropertyCollection to QgsRasterPipe to allow
for data-defined raster pipeline properties
This commit is contained in:
parent
3c3059c938
commit
c013c78285
@ -11,6 +11,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsRasterPipe
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
@ -22,6 +23,11 @@ Contains a pipeline of raster interfaces for sequential raster processing.
|
||||
%End
|
||||
public:
|
||||
|
||||
enum Property
|
||||
{
|
||||
RendererOpacity,
|
||||
};
|
||||
|
||||
QgsRasterPipe();
|
||||
%Docstring
|
||||
Constructor for an empty QgsRasterPipe.
|
||||
@ -182,6 +188,44 @@ Returns which stage of the pipe should apply resampling
|
||||
.. seealso:: :py:func:`setResamplingStage`
|
||||
|
||||
.. versionadded:: 3.16
|
||||
%End
|
||||
|
||||
QgsPropertyCollection &dataDefinedProperties();
|
||||
%Docstring
|
||||
Returns a reference to the pipe's property collection, used for data defined overrides.
|
||||
|
||||
.. seealso:: :py:func:`setDataDefinedProperties`
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
|
||||
void setDataDefinedProperties( const QgsPropertyCollection &collection );
|
||||
%Docstring
|
||||
Sets the pipe's property ``collection``, used for data defined overrides.
|
||||
|
||||
Any existing properties will be discarded.
|
||||
|
||||
.. seealso:: :py:func:`dataDefinedProperties`
|
||||
|
||||
.. seealso:: Property
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
void evaluateDataDefinedProperties( QgsExpressionContext &context );
|
||||
%Docstring
|
||||
Evalutes any data defined properties set on the pipe, applying their results
|
||||
to the corresponding interfaces in place.
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
static QgsPropertiesDefinition propertyDefinitions();
|
||||
%Docstring
|
||||
Returns the definitions for data defined properties available for use in raster pipes.
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
private:
|
||||
|
||||
@ -168,6 +168,7 @@ QgsRasterLayer *QgsRasterLayer::clone() const
|
||||
if ( mPipe->at( i ) )
|
||||
layer->pipe()->set( mPipe->at( i )->clone() );
|
||||
}
|
||||
layer->pipe()->setDataDefinedProperties( mPipe->dataDefinedProperties() );
|
||||
|
||||
return layer;
|
||||
}
|
||||
@ -1979,6 +1980,10 @@ bool QgsRasterLayer::readSymbology( const QDomNode &layer_node, QString &errorMe
|
||||
setBlendMode( QgsPainting::getCompositionMode( static_cast< QgsPainting::BlendMode >( e.text().toInt() ) ) );
|
||||
}
|
||||
|
||||
QDomElement elemDataDefinedProperties = layer_node.firstChildElement( QStringLiteral( "pipe-data-defined-properties" ) );
|
||||
if ( !elemDataDefinedProperties.isNull() )
|
||||
mPipe->dataDefinedProperties().readXml( elemDataDefinedProperties, QgsRasterPipe::propertyDefinitions() );
|
||||
|
||||
readCustomProperties( layer_node );
|
||||
|
||||
return true;
|
||||
@ -2181,6 +2186,10 @@ bool QgsRasterLayer::writeSymbology( QDomNode &layer_node, QDomDocument &documen
|
||||
interface->writeXml( document, pipeElement );
|
||||
}
|
||||
|
||||
QDomElement elemDataDefinedProperties = document.createElement( QStringLiteral( "pipe-data-defined-properties" ) );
|
||||
mPipe->dataDefinedProperties().writeXml( elemDataDefinedProperties, QgsRasterPipe::propertyDefinitions() );
|
||||
layer_node.appendChild( elemDataDefinedProperties );
|
||||
|
||||
QDomElement resamplingStageElement = document.createElement( QStringLiteral( "resamplingStage" ) );
|
||||
QDomText resamplingStageText = document.createTextNode( resamplingStage() == Qgis::RasterResamplingStage::Provider ? QStringLiteral( "provider" ) : QStringLiteral( "resamplingFilter" ) );
|
||||
resamplingStageElement.appendChild( resamplingStageText );
|
||||
|
||||
@ -254,6 +254,8 @@ QgsRasterLayerRenderer::QgsRasterLayerRenderer( QgsRasterLayer *layer, QgsRender
|
||||
layer->refreshRendererIfNeeded( rasterRenderer, rendererContext.extent() );
|
||||
}
|
||||
|
||||
mPipe->evaluateDataDefinedProperties( rendererContext.expressionContext() );
|
||||
|
||||
const QgsRasterLayerTemporalProperties *temporalProperties = qobject_cast< const QgsRasterLayerTemporalProperties * >( layer->temporalProperties() );
|
||||
if ( temporalProperties->isActive() && renderContext()->isTemporal() )
|
||||
{
|
||||
|
||||
@ -29,6 +29,8 @@
|
||||
#include "qgsrasterprojector.h"
|
||||
#include "qgsrasternuller.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
QgsRasterPipe::QgsRasterPipe( const QgsRasterPipe &pipe )
|
||||
{
|
||||
for ( int i = 0; i < pipe.size(); i++ )
|
||||
@ -49,6 +51,7 @@ QgsRasterPipe::QgsRasterPipe( const QgsRasterPipe &pipe )
|
||||
}
|
||||
}
|
||||
setResamplingStage( pipe.resamplingStage() );
|
||||
mDataDefinedProperties = pipe.mDataDefinedProperties;
|
||||
}
|
||||
|
||||
QgsRasterPipe::~QgsRasterPipe()
|
||||
@ -381,3 +384,46 @@ void QgsRasterPipe::setResamplingStage( Qgis::RasterResamplingStage stage )
|
||||
l_provider->enableProviderResampling( stage == Qgis::RasterResamplingStage::Provider );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsRasterPipe::evaluateDataDefinedProperties( QgsExpressionContext &context )
|
||||
{
|
||||
if ( !mDataDefinedProperties.hasActiveProperties() )
|
||||
return;
|
||||
|
||||
if ( mDataDefinedProperties.isActive( RendererOpacity ) )
|
||||
{
|
||||
if ( QgsRasterRenderer *r = renderer() )
|
||||
{
|
||||
const double prevOpacity = r->opacity();
|
||||
context.setOriginalValueVariable( prevOpacity * 100 );
|
||||
bool ok = false;
|
||||
const double opacity = mDataDefinedProperties.valueAsDouble( RendererOpacity, context, prevOpacity, &ok ) / 100;
|
||||
if ( ok )
|
||||
{
|
||||
r->setOpacity( opacity );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QgsPropertiesDefinition QgsRasterPipe::sPropertyDefinitions;
|
||||
|
||||
void QgsRasterPipe::initPropertyDefinitions()
|
||||
{
|
||||
const QString origin = QStringLiteral( "raster" );
|
||||
|
||||
sPropertyDefinitions = QgsPropertiesDefinition
|
||||
{
|
||||
{ QgsRasterPipe::RendererOpacity, QgsPropertyDefinition( "RendererOpacity", QObject::tr( "Renderer opacity" ), QgsPropertyDefinition::Opacity, origin ) },
|
||||
};
|
||||
}
|
||||
|
||||
QgsPropertiesDefinition QgsRasterPipe::propertyDefinitions()
|
||||
{
|
||||
static std::once_flag initialized;
|
||||
std::call_once( initialized, [ = ]( )
|
||||
{
|
||||
initPropertyDefinitions();
|
||||
} );
|
||||
return sPropertyDefinitions;
|
||||
}
|
||||
|
||||
@ -21,6 +21,8 @@
|
||||
#include "qgis_core.h"
|
||||
#include "qgis_sip.h"
|
||||
#include "qgis.h"
|
||||
#include "qgspropertycollection.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
@ -48,6 +50,15 @@ class CORE_EXPORT QgsRasterPipe
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Data definable properties.
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
enum Property
|
||||
{
|
||||
RendererOpacity, //!< Raster renderer global opacity
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for an empty QgsRasterPipe.
|
||||
*/
|
||||
@ -213,6 +224,48 @@ class CORE_EXPORT QgsRasterPipe
|
||||
*/
|
||||
Qgis::RasterResamplingStage resamplingStage() const { return mResamplingStage; }
|
||||
|
||||
/**
|
||||
* Returns a reference to the pipe's property collection, used for data defined overrides.
|
||||
* \see setDataDefinedProperties()
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
QgsPropertyCollection &dataDefinedProperties() { return mDataDefinedProperties; }
|
||||
|
||||
/**
|
||||
* Returns a reference to the pipe's property collection, used for data defined overrides.
|
||||
* \see setDataDefinedProperties()
|
||||
* \see Property
|
||||
* \note not available in Python bindings
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
const QgsPropertyCollection &dataDefinedProperties() const SIP_SKIP { return mDataDefinedProperties; }
|
||||
|
||||
/**
|
||||
* Sets the pipe's property \a collection, used for data defined overrides.
|
||||
*
|
||||
* Any existing properties will be discarded.
|
||||
*
|
||||
* \see dataDefinedProperties()
|
||||
* \see Property
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
void setDataDefinedProperties( const QgsPropertyCollection &collection ) { mDataDefinedProperties = collection; }
|
||||
|
||||
/**
|
||||
* Evalutes any data defined properties set on the pipe, applying their results
|
||||
* to the corresponding interfaces in place.
|
||||
*
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
void evaluateDataDefinedProperties( QgsExpressionContext &context );
|
||||
|
||||
/**
|
||||
* Returns the definitions for data defined properties available for use in raster pipes.
|
||||
*
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
static QgsPropertiesDefinition propertyDefinitions();
|
||||
|
||||
private:
|
||||
#ifdef SIP_RUN
|
||||
QgsRasterPipe( const QgsRasterPipe &pipe );
|
||||
@ -245,6 +298,14 @@ class CORE_EXPORT QgsRasterPipe
|
||||
bool connect( QVector<QgsRasterInterface *> interfaces );
|
||||
|
||||
Qgis::RasterResamplingStage mResamplingStage = Qgis::RasterResamplingStage::ResampleFilter;
|
||||
|
||||
//! Property collection for data defined raster pipe settings
|
||||
QgsPropertyCollection mDataDefinedProperties;
|
||||
|
||||
//! Property definitions
|
||||
static QgsPropertiesDefinition sPropertyDefinitions;
|
||||
|
||||
static void initPropertyDefinitions();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -256,6 +256,7 @@ ADD_PYTHON_TEST(PyQgsRasterFileWriterTask test_qgsrasterfilewritertask.py)
|
||||
ADD_PYTHON_TEST(PyQgsRasterLayer test_qgsrasterlayer.py)
|
||||
ADD_PYTHON_TEST(PyQgsRasterLayerRenderer test_qgsrasterlayerrenderer.py)
|
||||
ADD_PYTHON_TEST(PyQgsRasterColorRampShader test_qgsrastercolorrampshader.py)
|
||||
ADD_PYTHON_TEST(PyQgsRasterPipe test_qgsrasterpipe.py)
|
||||
ADD_PYTHON_TEST(PyQgsRasterRange test_qgsrasterrange.py)
|
||||
ADD_PYTHON_TEST(PyQgsRasterRendererUtils test_qgsrasterrendererutils.py)
|
||||
ADD_PYTHON_TEST(PyQgsRasterResampler test_qgsrasterresampler.py)
|
||||
|
||||
@ -55,7 +55,11 @@ from qgis.core import (Qgis,
|
||||
QgsRasterHistogram,
|
||||
QgsCubicRasterResampler,
|
||||
QgsBilinearRasterResampler,
|
||||
QgsLayerDefinition
|
||||
QgsLayerDefinition,
|
||||
QgsRasterPipe,
|
||||
QgsProperty,
|
||||
QgsExpressionContext,
|
||||
QgsExpressionContextScope
|
||||
)
|
||||
from utilities import unitTestDataPath
|
||||
from qgis.testing import start_app, unittest
|
||||
@ -1434,6 +1438,59 @@ class TestQgsRasterLayerTransformContext(unittest.TestCase):
|
||||
self.assertTrue(
|
||||
rl.transformContext().hasTransform(QgsCoordinateReferenceSystem('EPSG:4326'), QgsCoordinateReferenceSystem('EPSG:3857')))
|
||||
|
||||
def test_save_restore_pipe_data_defined_settings(self):
|
||||
"""
|
||||
Test that raster pipe data defined settings are correctly saved/restored along with the layer
|
||||
"""
|
||||
rl = QgsRasterLayer(self.rpath, 'raster')
|
||||
rl.pipe().dataDefinedProperties().setProperty(QgsRasterPipe.RendererOpacity, QgsProperty.fromExpression('100/2'))
|
||||
|
||||
doc = QDomDocument()
|
||||
layer_elem = doc.createElement("maplayer")
|
||||
self.assertTrue(rl.writeLayerXml(layer_elem, doc, QgsReadWriteContext()))
|
||||
|
||||
rl2 = QgsRasterLayer(self.rpath, 'raster')
|
||||
self.assertEqual(rl2.pipe().dataDefinedProperties().property(QgsRasterPipe.RendererOpacity),
|
||||
QgsProperty())
|
||||
|
||||
self.assertTrue(rl2.readXml(layer_elem, QgsReadWriteContext()))
|
||||
self.assertEqual(rl2.pipe().dataDefinedProperties().property(QgsRasterPipe.RendererOpacity),
|
||||
QgsProperty.fromExpression('100/2'))
|
||||
|
||||
def test_render_data_defined_opacity(self):
|
||||
path = os.path.join(unitTestDataPath('raster'),
|
||||
'band1_float32_noct_epsg4326.tif')
|
||||
raster_layer = QgsRasterLayer(path, 'test')
|
||||
self.assertTrue(raster_layer.isValid())
|
||||
|
||||
renderer = QgsSingleBandGrayRenderer(raster_layer.dataProvider(), 1)
|
||||
raster_layer.setRenderer(renderer)
|
||||
raster_layer.setContrastEnhancement(
|
||||
QgsContrastEnhancement.StretchToMinimumMaximum,
|
||||
QgsRasterMinMaxOrigin.MinMax)
|
||||
|
||||
raster_layer.pipe().dataDefinedProperties().setProperty(QgsRasterPipe.RendererOpacity, QgsProperty.fromExpression('@layer_opacity'))
|
||||
|
||||
ce = raster_layer.renderer().contrastEnhancement()
|
||||
ce.setMinimumValue(-3.3319999287625854e+38)
|
||||
ce.setMaximumValue(3.3999999521443642e+38)
|
||||
|
||||
map_settings = QgsMapSettings()
|
||||
map_settings.setLayers([raster_layer])
|
||||
map_settings.setExtent(raster_layer.extent())
|
||||
|
||||
context = QgsExpressionContext()
|
||||
scope = QgsExpressionContextScope()
|
||||
scope.setVariable('layer_opacity', 50)
|
||||
context.appendScope(scope)
|
||||
map_settings.setExpressionContext(context)
|
||||
|
||||
checker = QgsRenderChecker()
|
||||
checker.setControlName("expected_raster_data_defined_opacity")
|
||||
checker.setMapSettings(map_settings)
|
||||
|
||||
self.assertTrue(checker.runTest("raster_data_defined_opacity"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
62
tests/src/python/test_qgsrasterpipe.py
Normal file
62
tests/src/python/test_qgsrasterpipe.py
Normal file
@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
***************************************************************************
|
||||
test_qgsrasterpipe.py
|
||||
---------------------
|
||||
Date : June 2021
|
||||
Copyright : (C) 2021 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. *
|
||||
* *
|
||||
***************************************************************************
|
||||
|
||||
From build dir, run: ctest -R PyQgsRasterPipe\$ -V
|
||||
|
||||
"""
|
||||
|
||||
__author__ = 'Nyall Dawson'
|
||||
__date__ = 'June 2021'
|
||||
__copyright__ = '(C) 2021, Nyall Dawson'
|
||||
|
||||
import qgis # NOQA
|
||||
from qgis.core import (QgsRasterPipe,
|
||||
QgsProperty,
|
||||
QgsExpressionContext,
|
||||
QgsSingleBandPseudoColorRenderer
|
||||
)
|
||||
from qgis.testing import start_app, unittest
|
||||
|
||||
from utilities import unitTestDataPath
|
||||
|
||||
# Convenience instances in case you may need them
|
||||
# not used in this test
|
||||
start_app()
|
||||
TEST_DATA_DIR = unitTestDataPath()
|
||||
|
||||
|
||||
class TestQgsRasterPipe(unittest.TestCase):
|
||||
|
||||
def test_data_defined_properties(self):
|
||||
pipe = QgsRasterPipe()
|
||||
|
||||
pipe.dataDefinedProperties().setProperty(QgsRasterPipe.RendererOpacity, QgsProperty.fromExpression('100/2'))
|
||||
self.assertEqual(pipe.dataDefinedProperties().property(QgsRasterPipe.RendererOpacity),
|
||||
QgsProperty.fromExpression('100/2'))
|
||||
|
||||
pipe.set(QgsSingleBandPseudoColorRenderer(None))
|
||||
self.assertTrue(pipe.renderer())
|
||||
self.assertEqual(pipe.renderer().opacity(), 1.0)
|
||||
|
||||
# apply properties to pipe
|
||||
pipe.evaluateDataDefinedProperties(QgsExpressionContext())
|
||||
self.assertEqual(pipe.renderer().opacity(), 0.5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Loading…
x
Reference in New Issue
Block a user