mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-15 00:02:52 -04:00
Ensure raster elevation filtering works nicely with contour renderer
With the contour renderer we must treat out of range pixels as no data values, so that the gdal contouring algorithm correctly ignores them
This commit is contained in:
parent
b5a8722446
commit
365d26ece4
@ -1272,7 +1272,8 @@ Qgis.RasterResamplingStage.__doc__ = "Stage at which raster resampling occurs.\n
|
||||
Qgis.RasterResamplingStage.baseClass = Qgis
|
||||
# monkey patching scoped based enum
|
||||
Qgis.RasterRendererFlag.InternalLayerOpacityHandling.__doc__ = "The renderer internally handles the raster layer's opacity, so the default layer level opacity handling should not be applied."
|
||||
Qgis.RasterRendererFlag.__doc__ = "Flags which control behavior of raster renderers.\n\n.. versionadded:: 3.28\n\n" + '* ``InternalLayerOpacityHandling``: ' + Qgis.RasterRendererFlag.InternalLayerOpacityHandling.__doc__
|
||||
Qgis.RasterRendererFlag.UseNoDataForOutOfRangePixels.__doc__ = "Out of range pixels (eg those values outside of the rendered map's z range filter) should be set using additional nodata values instead of additional transparency values (since QGIS 3.38)"
|
||||
Qgis.RasterRendererFlag.__doc__ = "Flags which control behavior of raster renderers.\n\n.. versionadded:: 3.28\n\n" + '* ``InternalLayerOpacityHandling``: ' + Qgis.RasterRendererFlag.InternalLayerOpacityHandling.__doc__ + '\n' + '* ``UseNoDataForOutOfRangePixels``: ' + Qgis.RasterRendererFlag.UseNoDataForOutOfRangePixels.__doc__
|
||||
# --
|
||||
Qgis.RasterRendererFlags = lambda flags=0: Qgis.RasterRendererFlag(flags)
|
||||
Qgis.RasterRendererFlag.baseClass = Qgis
|
||||
|
@ -685,6 +685,7 @@ The development version
|
||||
enum class RasterRendererFlag /BaseType=IntFlag/
|
||||
{
|
||||
InternalLayerOpacityHandling,
|
||||
UseNoDataForOutOfRangePixels,
|
||||
};
|
||||
|
||||
typedef QFlags<Qgis::RasterRendererFlag> RasterRendererFlags;
|
||||
|
@ -34,6 +34,8 @@ Creates a contour renderer
|
||||
%Docstring
|
||||
QgsRasterContourRenderer cannot be copied. Use :py:func:`~QgsRasterContourRenderer.clone` instead.
|
||||
%End
|
||||
virtual Qgis::RasterRendererFlags flags() const;
|
||||
|
||||
|
||||
static QgsRasterRenderer *create( const QDomElement &elem, QgsRasterInterface *input ) /Factory/;
|
||||
%Docstring
|
||||
|
@ -1247,7 +1247,8 @@ Qgis.RasterResamplingStage.__doc__ = "Stage at which raster resampling occurs.\n
|
||||
Qgis.RasterResamplingStage.baseClass = Qgis
|
||||
# monkey patching scoped based enum
|
||||
Qgis.RasterRendererFlag.InternalLayerOpacityHandling.__doc__ = "The renderer internally handles the raster layer's opacity, so the default layer level opacity handling should not be applied."
|
||||
Qgis.RasterRendererFlag.__doc__ = "Flags which control behavior of raster renderers.\n\n.. versionadded:: 3.28\n\n" + '* ``InternalLayerOpacityHandling``: ' + Qgis.RasterRendererFlag.InternalLayerOpacityHandling.__doc__
|
||||
Qgis.RasterRendererFlag.UseNoDataForOutOfRangePixels.__doc__ = "Out of range pixels (eg those values outside of the rendered map's z range filter) should be set using additional nodata values instead of additional transparency values (since QGIS 3.38)"
|
||||
Qgis.RasterRendererFlag.__doc__ = "Flags which control behavior of raster renderers.\n\n.. versionadded:: 3.28\n\n" + '* ``InternalLayerOpacityHandling``: ' + Qgis.RasterRendererFlag.InternalLayerOpacityHandling.__doc__ + '\n' + '* ``UseNoDataForOutOfRangePixels``: ' + Qgis.RasterRendererFlag.UseNoDataForOutOfRangePixels.__doc__
|
||||
# --
|
||||
Qgis.RasterRendererFlag.baseClass = Qgis
|
||||
Qgis.RasterRendererFlags.baseClass = Qgis
|
||||
|
@ -685,6 +685,7 @@ The development version
|
||||
enum class RasterRendererFlag
|
||||
{
|
||||
InternalLayerOpacityHandling,
|
||||
UseNoDataForOutOfRangePixels,
|
||||
};
|
||||
|
||||
typedef QFlags<Qgis::RasterRendererFlag> RasterRendererFlags;
|
||||
|
@ -34,6 +34,8 @@ Creates a contour renderer
|
||||
%Docstring
|
||||
QgsRasterContourRenderer cannot be copied. Use :py:func:`~QgsRasterContourRenderer.clone` instead.
|
||||
%End
|
||||
virtual Qgis::RasterRendererFlags flags() const;
|
||||
|
||||
|
||||
static QgsRasterRenderer *create( const QDomElement &elem, QgsRasterInterface *input ) /Factory/;
|
||||
%Docstring
|
||||
|
@ -1151,6 +1151,7 @@ class CORE_EXPORT Qgis
|
||||
enum class RasterRendererFlag : int SIP_ENUM_BASETYPE( IntFlag )
|
||||
{
|
||||
InternalLayerOpacityHandling = 1 << 0, //!< The renderer internally handles the raster layer's opacity, so the default layer level opacity handling should not be applied.
|
||||
UseNoDataForOutOfRangePixels = 1 << 1, //!< Out of range pixels (eg those values outside of the rendered map's z range filter) should be set using additional nodata values instead of additional transparency values (since QGIS 3.38)
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -43,6 +43,11 @@ QgsRasterContourRenderer *QgsRasterContourRenderer::clone() const
|
||||
return renderer;
|
||||
}
|
||||
|
||||
Qgis::RasterRendererFlags QgsRasterContourRenderer::flags() const
|
||||
{
|
||||
return Qgis::RasterRendererFlag::UseNoDataForOutOfRangePixels;
|
||||
}
|
||||
|
||||
QgsRasterRenderer *QgsRasterContourRenderer::create( const QDomElement &elem, QgsRasterInterface *input )
|
||||
{
|
||||
if ( elem.isNull() )
|
||||
|
@ -40,6 +40,7 @@ class CORE_EXPORT QgsRasterContourRenderer : public QgsRasterRenderer
|
||||
const QgsRasterContourRenderer &operator=( const QgsRasterContourRenderer & ) = delete;
|
||||
|
||||
QgsRasterContourRenderer *clone() const override SIP_FACTORY;
|
||||
Qgis::RasterRendererFlags flags() const override;
|
||||
|
||||
//! Creates an instance of the renderer based on definition from XML (used by renderer registry)
|
||||
static QgsRasterRenderer *create( const QDomElement &elem, QgsRasterInterface *input ) SIP_FACTORY;
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "qgsrasterlayerutils.h"
|
||||
#include "qgsinterval.h"
|
||||
#include "qgsunittypes.h"
|
||||
#include "qgsrasternuller.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QPointer>
|
||||
@ -354,6 +355,7 @@ QgsRasterLayerRenderer::QgsRasterLayerRenderer( QgsRasterLayer *layer, QgsRender
|
||||
|
||||
if ( !rendererContext.zRange().isInfinite() )
|
||||
{
|
||||
// NOLINTBEGIN(bugprone-branch-clone)
|
||||
switch ( elevationProperties->mode() )
|
||||
{
|
||||
case Qgis::RasterElevationMode::FixedElevationRange:
|
||||
@ -371,28 +373,54 @@ QgsRasterLayerRenderer::QgsRasterLayerRenderer( QgsRasterLayer *layer, QgsRender
|
||||
if ( mPipe->renderer()->usesBands().contains( mElevationBand ) )
|
||||
{
|
||||
// if layer has elevation settings and we are only rendering a slice of z values => we need to filter pixels by elevation
|
||||
if ( mPipe->renderer()->flags() & Qgis::RasterRendererFlag::UseNoDataForOutOfRangePixels )
|
||||
{
|
||||
std::unique_ptr< QgsRasterNuller> nuller;
|
||||
if ( const QgsRasterNuller *existingNuller = mPipe->nuller() )
|
||||
nuller.reset( existingNuller->clone() );
|
||||
else
|
||||
nuller = std::make_unique< QgsRasterNuller >();
|
||||
|
||||
std::unique_ptr< QgsRasterTransparency > transparency;
|
||||
if ( const QgsRasterTransparency *rendererTransparency = mPipe->renderer()->rasterTransparency() )
|
||||
transparency = std::make_unique< QgsRasterTransparency >( *rendererTransparency );
|
||||
// account for z offset/zscale by reversing these calculations, so that we get the z range in
|
||||
// raw pixel values
|
||||
QgsRasterRangeList nullRanges;
|
||||
const double adjustedLower = ( rendererContext.zRange().lower() - mElevationOffset ) / mElevationScale;
|
||||
const double adjustedUpper = ( rendererContext.zRange().upper() - mElevationOffset ) / mElevationScale;
|
||||
nullRanges.append( QgsRasterRange( std::numeric_limits<double>::lowest(), adjustedLower, rendererContext.zRange().includeLower() ? QgsRasterRange::BoundsType::IncludeMin : QgsRasterRange::BoundsType::IncludeMinAndMax ) );
|
||||
nullRanges.append( QgsRasterRange( adjustedUpper, std::numeric_limits<double>::max(), rendererContext.zRange().includeUpper() ? QgsRasterRange::BoundsType::IncludeMax : QgsRasterRange::BoundsType::IncludeMinAndMax ) );
|
||||
nuller->setOutputNoDataValue( mElevationBand, static_cast< int >( adjustedLower - 1 ) );
|
||||
nuller->setNoData( mElevationBand, nullRanges );
|
||||
|
||||
if ( !mPipe->insert( 1, nuller.release() ) )
|
||||
{
|
||||
QgsDebugError( QStringLiteral( "Cannot set pipe nuller" ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
transparency = std::make_unique< QgsRasterTransparency >();
|
||||
{
|
||||
std::unique_ptr< QgsRasterTransparency > transparency;
|
||||
if ( const QgsRasterTransparency *rendererTransparency = mPipe->renderer()->rasterTransparency() )
|
||||
transparency = std::make_unique< QgsRasterTransparency >( *rendererTransparency );
|
||||
else
|
||||
transparency = std::make_unique< QgsRasterTransparency >();
|
||||
|
||||
QVector<QgsRasterTransparency::TransparentSingleValuePixel> transparentPixels = transparency->transparentSingleValuePixelList();
|
||||
QVector<QgsRasterTransparency::TransparentSingleValuePixel> transparentPixels = transparency->transparentSingleValuePixelList();
|
||||
|
||||
// account for z offset/zscale by reversing these calculations, so that we get the z range in
|
||||
// raw pixel values
|
||||
const double adjustedLower = ( rendererContext.zRange().lower() - mElevationOffset ) / mElevationScale;
|
||||
const double adjustedUpper = ( rendererContext.zRange().upper() - mElevationOffset ) / mElevationScale;
|
||||
transparentPixels.append( QgsRasterTransparency::TransparentSingleValuePixel( std::numeric_limits<double>::lowest(), adjustedLower, 0, true, !rendererContext.zRange().includeLower() ) );
|
||||
transparentPixels.append( QgsRasterTransparency::TransparentSingleValuePixel( adjustedUpper, std::numeric_limits<double>::max(), 0, !rendererContext.zRange().includeUpper(), true ) );
|
||||
// account for z offset/zscale by reversing these calculations, so that we get the z range in
|
||||
// raw pixel values
|
||||
const double adjustedLower = ( rendererContext.zRange().lower() - mElevationOffset ) / mElevationScale;
|
||||
const double adjustedUpper = ( rendererContext.zRange().upper() - mElevationOffset ) / mElevationScale;
|
||||
transparentPixels.append( QgsRasterTransparency::TransparentSingleValuePixel( std::numeric_limits<double>::lowest(), adjustedLower, 0, true, !rendererContext.zRange().includeLower() ) );
|
||||
transparentPixels.append( QgsRasterTransparency::TransparentSingleValuePixel( adjustedUpper, std::numeric_limits<double>::max(), 0, !rendererContext.zRange().includeUpper(), true ) );
|
||||
|
||||
transparency->setTransparentSingleValuePixelList( transparentPixels );
|
||||
mPipe->renderer()->setRasterTransparency( transparency.release() );
|
||||
transparency->setTransparentSingleValuePixelList( transparentPixels );
|
||||
mPipe->renderer()->setRasterTransparency( transparency.release() );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// NOLINTEND(bugprone-branch-clone)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,10 +28,12 @@ from qgis.core import (
|
||||
QgsRectangle,
|
||||
QgsDoubleRange,
|
||||
QgsSingleBandGrayRenderer,
|
||||
QgsRasterContourRenderer,
|
||||
QgsContrastEnhancement,
|
||||
QgsRasterLayerElevationProperties,
|
||||
QgsProperty,
|
||||
QgsDateTimeRange
|
||||
QgsDateTimeRange,
|
||||
QgsLineSymbol
|
||||
)
|
||||
import unittest
|
||||
from qgis.testing import start_app, QgisTestCase
|
||||
@ -127,6 +129,46 @@ class TestQgsRasterLayerRenderer(QgisTestCase):
|
||||
map_settings)
|
||||
)
|
||||
|
||||
def test_contour_render_dem_with_z_range_filter(self):
|
||||
raster_layer = QgsRasterLayer(os.path.join(TEST_DATA_DIR, '3d', 'dtm.tif'))
|
||||
renderer = QgsRasterContourRenderer(raster_layer.dataProvider())
|
||||
renderer.setContourInterval(10)
|
||||
renderer.setContourSymbol(
|
||||
QgsLineSymbol.createSimple({'width': 1})
|
||||
)
|
||||
renderer.setContourIndexInterval(0)
|
||||
|
||||
raster_layer.setRenderer(renderer)
|
||||
|
||||
self.assertTrue(raster_layer.isValid())
|
||||
|
||||
map_settings = QgsMapSettings()
|
||||
map_settings.setOutputSize(QSize(400, 400))
|
||||
map_settings.setOutputDpi(96)
|
||||
map_settings.setDestinationCrs(raster_layer.crs())
|
||||
map_settings.setExtent(raster_layer.extent())
|
||||
map_settings.setLayers([raster_layer])
|
||||
map_settings.setZRange(QgsDoubleRange(96, 132))
|
||||
|
||||
# set layer as elevation enabled
|
||||
raster_layer.elevationProperties().setEnabled(True)
|
||||
self.assertTrue(
|
||||
self.render_map_settings_check(
|
||||
'Z range filter on map settings, contour renderer',
|
||||
'dem_filter_contour',
|
||||
map_settings)
|
||||
)
|
||||
|
||||
# with offset and scaling
|
||||
raster_layer.elevationProperties().setZOffset(50)
|
||||
raster_layer.elevationProperties().setZScale(0.75)
|
||||
self.assertTrue(
|
||||
self.render_map_settings_check(
|
||||
'Z range filter on map settings, contour renderer, elevation enabled layer with offset and scale',
|
||||
'dem_filter_contour_offset_and_scale',
|
||||
map_settings)
|
||||
)
|
||||
|
||||
def test_render_fixed_elevation_range_with_z_range_filter(self):
|
||||
"""
|
||||
Test rendering a raster with a fixed elevation range when
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 460 KiB |
Binary file not shown.
After Width: | Height: | Size: 460 KiB |
Loading…
x
Reference in New Issue
Block a user