Skip to content

Commit

Permalink
[renderer] Live hillshade renderer for raster layers
Browse files Browse the repository at this point in the history
Thanks to Asger Skovbo Petersen (@AsgerPetersen) for the idea and fixes
Thanks to Nyall for reviews and bug fixes
  • Loading branch information
NathanW2 committed May 28, 2016
1 parent d8ccec0 commit 17b4856
Show file tree
Hide file tree
Showing 15 changed files with 947 additions and 0 deletions.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -284,6 +284,7 @@
%Include raster/qgssinglebandcolordatarenderer.sip
%Include raster/qgssinglebandgrayrenderer.sip
%Include raster/qgssinglebandpseudocolorrenderer.sip
%Include raster/qgshillshaderenderer.sip

%Include symbology-ng/qgscolorbrewerpalette.sip
%Include symbology-ng/qgscptcityarchive.sip
Expand Down
67 changes: 67 additions & 0 deletions python/core/raster/qgshillshaderenderer.sip
@@ -0,0 +1,67 @@
class QgsHillshadeRenderer : QgsRasterRenderer
{
%TypeHeaderCode
#include "qgshillshaderenderer.h"
%End
public:
/** Renderer owns color array*/
QgsHillshadeRenderer( QgsRasterInterface* input, int band , double lightAzimuth, double lightAngle );

~QgsHillshadeRenderer();

virtual QgsHillshadeRenderer * clone() const /Factory/;

static QgsRasterRenderer* create( const QDomElement& elem, QgsRasterInterface* input ) /Factory/;

QgsRasterBlock *block( int bandNo, const QgsRectangle & extent, int width, int height ) /Factory/;

void writeXML( QDomDocument& doc, QDomElement& parentElem ) const;

QList<int> usesBands() const;

/** Returns the band used by the renderer
*/
int band() const;

/** Sets the band used by the renderer.
* @see band
*/
void setBand( int bandNo );

/**
* @brief The direction of the light over the raster between 0-360
* @return The direction of the light over the raster
*/
double azimuth() const;

/**
* @brief The angle of the light source over the raster
* @return The angle of the light source over the raster
*/
double altitude() const;

/**
* @brief Z Factor
* @return Z Factor
*/
double zFactor() const;


/**
* @brief Set the azimith of the light source.
* @param azimuth The azimuth of the light source.
*/
void setAzimuth( double azimuth );

/**
* @brief Set the altitude of the light source
* @param altitude The altitude
*/
void setAltitude( double angle );

/**
* @brief Set the Z factor of the result image.
* @param zfactor The z factor.
*/
void setZFactor( double zfactor );
};
1 change: 1 addition & 0 deletions python/gui/gui.sip
Expand Up @@ -203,6 +203,7 @@
%Include raster/qgssinglebandpseudocolorrendererwidget.sip
%Include raster/qgsrendererrasterpropertieswidget.sip
%Include raster/qgsrastertransparencywidget.sip
%Include raster/qgshillshaderendererwidget.sip

%Include symbology-ng/characterwidget.sip
%Include symbology-ng/qgs25drendererwidget.sip
Expand Down
72 changes: 72 additions & 0 deletions python/gui/raster/qgshillshaderendererwidget.sip
@@ -0,0 +1,72 @@
/**
* @brief Renderer widget for the hill shade renderer.
*/
class QgsHillshadeRendererWidget: QgsRasterRendererWidget
{
%TypeHeaderCode
#include <qgshillshaderendererwidget.h>
%End
public:

/**
* @brief Renderer widget for the hill shade renderer.
* @param layer The layer attached for this widget.
* @param extent The current extent.
*/
QgsHillshadeRendererWidget( QgsRasterLayer* layer, const QgsRectangle &extent = QgsRectangle() );
~QgsHillshadeRendererWidget();

/**
* Factory method to create the renderer for this type.
*/
static QgsRasterRendererWidget* create( QgsRasterLayer* layer, const QgsRectangle &theExtent ) /Factory/;

/**
* @brief The renderer for the widget.
* @return A new renderer for the the config in the widget
*/
QgsRasterRenderer* renderer();

/**
* @brief Set the widget state from the given renderer.
* @param r The renderer to take the state from.
*/
void setFromRenderer( const QgsRasterRenderer* r );

/**
* @brief The direction of the light over the raster between 0-360
* @return The direction of the light over the raster
*/
double azimuth() const;

/**
* @brief The angle of the light source over the raster
* @return The angle of the light source over the raster
*/
double altitude() const;

/**
* @brief Z Factor
* @return Z Factor
*/
double zFactor() const;

public slots:
/**
* @brief Set the altitude of the light source
* @param altitude The altitude
*/
void setAltitude( double altitude );

/**
* @brief Set the azimith of the light source.
* @param azimuth The azimuth of the light source.
*/
void setAzimuth( double azimuth );

/**
* @brief Set the Z factor of the result image.
* @param zfactor The z factor.
*/
void setZFactor( double zfactor );
};
2 changes: 2 additions & 0 deletions src/app/qgsrasterlayerproperties.cpp
Expand Up @@ -50,6 +50,7 @@
#include "qgssinglebandgrayrendererwidget.h"
#include "qgssinglebandpseudocolorrendererwidget.h"
#include "qgshuesaturationfilter.h"
#include "qgshillshaderendererwidget.h"

#include <QTableWidgetItem>
#include <QHeaderView>
Expand Down Expand Up @@ -366,6 +367,7 @@ QgsRasterLayerProperties::QgsRasterLayerProperties( QgsMapLayer* lyr, QgsMapCanv
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "multibandcolor", QgsMultiBandColorRendererWidget::create );
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "singlebandpseudocolor", QgsSingleBandPseudoColorRendererWidget::create );
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "singlebandgray", QgsSingleBandGrayRendererWidget::create );
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "hillshade", QgsHillshadeRendererWidget::create );

//fill available renderers into combo box
QgsRasterRendererRegistryEntry entry;
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -329,6 +329,7 @@ SET(QGIS_CORE_SRCS
raster/qgssinglebandcolordatarenderer.cpp
raster/qgssinglebandgrayrenderer.cpp
raster/qgssinglebandpseudocolorrenderer.cpp
raster/qgshillshaderenderer.cpp

geometry/qgsabstractgeometryv2.cpp
geometry/qgscircularstringv2.cpp
Expand Down Expand Up @@ -801,6 +802,7 @@ SET(QGIS_CORE_HDRS
raster/qgssinglebandcolordatarenderer.h
raster/qgssinglebandgrayrenderer.h
raster/qgssinglebandpseudocolorrenderer.h
raster/qgshillshaderenderer.h

symbology-ng/qgs25drenderer.h
symbology-ng/qgscategorizedsymbolrendererv2.h
Expand Down

5 comments on commit 17b4856

@haubourg
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi guys,
when testing new live hillshader renderer, I got strange visual artifacts, that I don't get with gdal precalculated hillshade. I see random points and ghost grids. Grids seems to be cleaned by bilinear resampling, when points remain. See :
image

@AsgerPetersen
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@haubourg That looks strange indeed. Do you have a small sample dataset which can be used for reproducing?

@haubourg
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here is one extract:
dem.zip

when using nearest neighbor interpolation, it is even stranger:
image

@AsgerPetersen
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@haubourg The nearest neighbor artefact is QGIS behaving "as designed" as far as I can tell.

When QGIS wants to upsample (for instance render 100x100 DEM pixels to 500x500 screen pixels), you can choose between the three resampling options.

When choosing nearest neighbor, QGIS will resample the DEM to 500x500 pixels using nearest neighbor interpolation. This upsampled raster is then handed to the hillshade renderer. This resampling method results in "staircase" artefacts. And we will get steep gradients for every 5 pixels, and no gradient for the next 5 pixels like your screendump above.

If you choose "Bilinear" or "Cubic" QGIS deploys a completely different strategy. In these cases the 100x100 pixel DEM is handed to the renderer which will render a nice (but small) hillshade. This rendered image is then upsampled (or stretched) to 500x500 pixels by QGIS. This results in a hillshade which is more "blurry" than it had to be.

What would be optimal in the context of the hillshade renderer would be, if QGIS could upsample the DEM data using for instance bilinear resampling and then hand the properly upsampled DEM to the renderer.

@AsgerPetersen
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@haubourg @NathanW2 I fixed this in #3152

Please sign in to comment.