Skip to content

Commit 17b4856

Browse files
committedMay 28, 2016
[renderer] Live hillshade renderer for raster layers
Thanks to Asger Skovbo Petersen (@AsgerPetersen) for the idea and fixes Thanks to Nyall for reviews and bug fixes
1 parent d8ccec0 commit 17b4856

15 files changed

+947
-0
lines changed
 

‎python/core/core.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@
284284
%Include raster/qgssinglebandcolordatarenderer.sip
285285
%Include raster/qgssinglebandgrayrenderer.sip
286286
%Include raster/qgssinglebandpseudocolorrenderer.sip
287+
%Include raster/qgshillshaderenderer.sip
287288

288289
%Include symbology-ng/qgscolorbrewerpalette.sip
289290
%Include symbology-ng/qgscptcityarchive.sip
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
class QgsHillshadeRenderer : QgsRasterRenderer
2+
{
3+
%TypeHeaderCode
4+
#include "qgshillshaderenderer.h"
5+
%End
6+
public:
7+
/** Renderer owns color array*/
8+
QgsHillshadeRenderer( QgsRasterInterface* input, int band , double lightAzimuth, double lightAngle );
9+
10+
~QgsHillshadeRenderer();
11+
12+
virtual QgsHillshadeRenderer * clone() const /Factory/;
13+
14+
static QgsRasterRenderer* create( const QDomElement& elem, QgsRasterInterface* input ) /Factory/;
15+
16+
QgsRasterBlock *block( int bandNo, const QgsRectangle & extent, int width, int height ) /Factory/;
17+
18+
void writeXML( QDomDocument& doc, QDomElement& parentElem ) const;
19+
20+
QList<int> usesBands() const;
21+
22+
/** Returns the band used by the renderer
23+
*/
24+
int band() const;
25+
26+
/** Sets the band used by the renderer.
27+
* @see band
28+
*/
29+
void setBand( int bandNo );
30+
31+
/**
32+
* @brief The direction of the light over the raster between 0-360
33+
* @return The direction of the light over the raster
34+
*/
35+
double azimuth() const;
36+
37+
/**
38+
* @brief The angle of the light source over the raster
39+
* @return The angle of the light source over the raster
40+
*/
41+
double altitude() const;
42+
43+
/**
44+
* @brief Z Factor
45+
* @return Z Factor
46+
*/
47+
double zFactor() const;
48+
49+
50+
/**
51+
* @brief Set the azimith of the light source.
52+
* @param azimuth The azimuth of the light source.
53+
*/
54+
void setAzimuth( double azimuth );
55+
56+
/**
57+
* @brief Set the altitude of the light source
58+
* @param altitude The altitude
59+
*/
60+
void setAltitude( double angle );
61+
62+
/**
63+
* @brief Set the Z factor of the result image.
64+
* @param zfactor The z factor.
65+
*/
66+
void setZFactor( double zfactor );
67+
};

‎python/gui/gui.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@
203203
%Include raster/qgssinglebandpseudocolorrendererwidget.sip
204204
%Include raster/qgsrendererrasterpropertieswidget.sip
205205
%Include raster/qgsrastertransparencywidget.sip
206+
%Include raster/qgshillshaderendererwidget.sip
206207

207208
%Include symbology-ng/characterwidget.sip
208209
%Include symbology-ng/qgs25drendererwidget.sip
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* @brief Renderer widget for the hill shade renderer.
3+
*/
4+
class QgsHillshadeRendererWidget: QgsRasterRendererWidget
5+
{
6+
%TypeHeaderCode
7+
#include <qgshillshaderendererwidget.h>
8+
%End
9+
public:
10+
11+
/**
12+
* @brief Renderer widget for the hill shade renderer.
13+
* @param layer The layer attached for this widget.
14+
* @param extent The current extent.
15+
*/
16+
QgsHillshadeRendererWidget( QgsRasterLayer* layer, const QgsRectangle &extent = QgsRectangle() );
17+
~QgsHillshadeRendererWidget();
18+
19+
/**
20+
* Factory method to create the renderer for this type.
21+
*/
22+
static QgsRasterRendererWidget* create( QgsRasterLayer* layer, const QgsRectangle &theExtent ) /Factory/;
23+
24+
/**
25+
* @brief The renderer for the widget.
26+
* @return A new renderer for the the config in the widget
27+
*/
28+
QgsRasterRenderer* renderer();
29+
30+
/**
31+
* @brief Set the widget state from the given renderer.
32+
* @param r The renderer to take the state from.
33+
*/
34+
void setFromRenderer( const QgsRasterRenderer* r );
35+
36+
/**
37+
* @brief The direction of the light over the raster between 0-360
38+
* @return The direction of the light over the raster
39+
*/
40+
double azimuth() const;
41+
42+
/**
43+
* @brief The angle of the light source over the raster
44+
* @return The angle of the light source over the raster
45+
*/
46+
double altitude() const;
47+
48+
/**
49+
* @brief Z Factor
50+
* @return Z Factor
51+
*/
52+
double zFactor() const;
53+
54+
public slots:
55+
/**
56+
* @brief Set the altitude of the light source
57+
* @param altitude The altitude
58+
*/
59+
void setAltitude( double altitude );
60+
61+
/**
62+
* @brief Set the azimith of the light source.
63+
* @param azimuth The azimuth of the light source.
64+
*/
65+
void setAzimuth( double azimuth );
66+
67+
/**
68+
* @brief Set the Z factor of the result image.
69+
* @param zfactor The z factor.
70+
*/
71+
void setZFactor( double zfactor );
72+
};

‎src/app/qgsrasterlayerproperties.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include "qgssinglebandgrayrendererwidget.h"
5151
#include "qgssinglebandpseudocolorrendererwidget.h"
5252
#include "qgshuesaturationfilter.h"
53+
#include "qgshillshaderendererwidget.h"
5354

5455
#include <QTableWidgetItem>
5556
#include <QHeaderView>
@@ -366,6 +367,7 @@ QgsRasterLayerProperties::QgsRasterLayerProperties( QgsMapLayer* lyr, QgsMapCanv
366367
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "multibandcolor", QgsMultiBandColorRendererWidget::create );
367368
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "singlebandpseudocolor", QgsSingleBandPseudoColorRendererWidget::create );
368369
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "singlebandgray", QgsSingleBandGrayRendererWidget::create );
370+
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "hillshade", QgsHillshadeRendererWidget::create );
369371

370372
//fill available renderers into combo box
371373
QgsRasterRendererRegistryEntry entry;

‎src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ SET(QGIS_CORE_SRCS
329329
raster/qgssinglebandcolordatarenderer.cpp
330330
raster/qgssinglebandgrayrenderer.cpp
331331
raster/qgssinglebandpseudocolorrenderer.cpp
332+
raster/qgshillshaderenderer.cpp
332333

333334
geometry/qgsabstractgeometryv2.cpp
334335
geometry/qgscircularstringv2.cpp
@@ -801,6 +802,7 @@ SET(QGIS_CORE_HDRS
801802
raster/qgssinglebandcolordatarenderer.h
802803
raster/qgssinglebandgrayrenderer.h
803804
raster/qgssinglebandpseudocolorrenderer.h
805+
raster/qgshillshaderenderer.h
804806

805807
symbology-ng/qgs25drenderer.h
806808
symbology-ng/qgscategorizedsymbolrendererv2.h
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
/***************************************************************************
2+
qgshillshaderenderer.cpp
3+
---------------------------------
4+
begin : May 2016
5+
copyright : (C) 2016 by Nathan Woodrow
6+
email : woodrow dot nathan at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include <QColor>
19+
20+
#include "qgshillshaderenderer.h"
21+
22+
#include "qgsrasterinterface.h"
23+
#include "qgsrasterblock.h"
24+
#include "qgsrectangle.h"
25+
26+
27+
QgsHillshadeRenderer::QgsHillshadeRenderer( QgsRasterInterface *input, int band, double lightAzimuth, double lightAngle ):
28+
QgsRasterRenderer( input, "hillshade" )
29+
, mBand( band )
30+
, mZFactor( 1 )
31+
, mLightAngle( lightAngle )
32+
, mLightAzimuth( lightAzimuth )
33+
{
34+
35+
}
36+
37+
QgsHillshadeRenderer *QgsHillshadeRenderer::clone() const
38+
{
39+
QgsHillshadeRenderer* r = new QgsHillshadeRenderer( nullptr, mBand, mLightAzimuth, mLightAngle );
40+
r->setZFactor( mZFactor );
41+
return r;
42+
}
43+
44+
QgsRasterRenderer *QgsHillshadeRenderer::create( const QDomElement &elem, QgsRasterInterface *input )
45+
{
46+
if ( elem.isNull() )
47+
{
48+
return nullptr;
49+
}
50+
51+
int band = elem.attribute( "band", "0" ).toInt();
52+
double azimuth = elem.attribute( "azimuth", "315" ).toDouble();
53+
double angle = elem.attribute( "angle", "45" ).toDouble();
54+
double zFactor = elem.attribute( "zfactor", "1" ).toDouble();
55+
QgsHillshadeRenderer* r = new QgsHillshadeRenderer( input, band, azimuth , angle );
56+
r->setZFactor( zFactor );
57+
return r;
58+
}
59+
60+
void QgsHillshadeRenderer::writeXML( QDomDocument &doc, QDomElement &parentElem ) const
61+
{
62+
if ( parentElem.isNull() )
63+
{
64+
return;
65+
}
66+
67+
QDomElement rasterRendererElem = doc.createElement( "rasterrenderer" );
68+
_writeXML( doc, rasterRendererElem );
69+
70+
rasterRendererElem.setAttribute( "band", mBand );
71+
rasterRendererElem.setAttribute( "azimuth", QString::number( mLightAzimuth ) );
72+
rasterRendererElem.setAttribute( "angle", QString::number( mLightAngle ) );
73+
rasterRendererElem.setAttribute( "zfactor", QString::number( mZFactor ) );
74+
parentElem.appendChild( rasterRendererElem );
75+
}
76+
77+
QgsRasterBlock *QgsHillshadeRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height )
78+
{
79+
Q_UNUSED( bandNo );
80+
QgsRasterBlock *outputBlock = new QgsRasterBlock();
81+
if ( !mInput )
82+
{
83+
QgsDebugMsg( "No input raster!" );
84+
return outputBlock;
85+
}
86+
87+
QgsRasterBlock *inputBlock = mInput->block( mBand, extent, width, height );
88+
89+
if ( !inputBlock || inputBlock->isEmpty() )
90+
{
91+
QgsDebugMsg( "No raster data!" );
92+
delete inputBlock;
93+
return outputBlock;
94+
}
95+
96+
if ( !outputBlock->reset( QGis::ARGB32_Premultiplied, width, height ) )
97+
{
98+
delete inputBlock;
99+
return outputBlock;
100+
}
101+
102+
double cellXSize = extent.width() / double( width );
103+
double cellYSize = extent.height() / double( height );
104+
double zenithRad = qMax( 0.0, 90 - mLightAngle ) * M_PI / 180.0;
105+
double azimuthRad = -1 * mLightAzimuth * M_PI / 180.0;
106+
double aspectRad = 0;
107+
double cosZenithRad = cos( zenithRad );
108+
double sinZenithRad = sin( zenithRad );
109+
110+
QRgb myDefaultColor = NODATA_COLOR;
111+
112+
for ( qgssize i = 0; i < ( qgssize )height; i++ )
113+
{
114+
115+
for ( qgssize j = 0; j < ( qgssize )width; j++ )
116+
{
117+
118+
if ( inputBlock->isNoData( i, j ) )
119+
{
120+
outputBlock->setColor( i, j, myDefaultColor );
121+
continue;
122+
}
123+
124+
if ( inputBlock->isNoData( i, j ) )
125+
{
126+
outputBlock->setColor( i, j, myDefaultColor );
127+
continue;
128+
}
129+
130+
qgssize iUp, iDown, jLeft, jRight;
131+
if ( i == 0 )
132+
{
133+
iUp = i;
134+
iDown = i + 1;
135+
}
136+
else if ( i < ( qgssize )height - 1 )
137+
{
138+
iUp = i - 1;
139+
iDown = i + 1;
140+
}
141+
else
142+
{
143+
iUp = i - 1;
144+
iDown = i;
145+
}
146+
147+
if ( j == 0 )
148+
{
149+
jLeft = j;
150+
jRight = j + 1;
151+
}
152+
else if ( j < ( qgssize )width - 1 )
153+
{
154+
jLeft = j - 1;
155+
jRight = j + 1;
156+
}
157+
else
158+
{
159+
jLeft = j - 1;
160+
jRight = j;
161+
}
162+
163+
double x11;
164+
double x21;
165+
double x31;
166+
double x12;
167+
double x22; // Working cell
168+
double x32;
169+
double x13;
170+
double x23;
171+
double x33;
172+
173+
// This is center cell. It is not nodata. Use this in place of nodata neighbors
174+
x22 = inputBlock->value( i, j );
175+
176+
x11 = inputBlock->isNoData( iUp, jLeft ) ? x22 : inputBlock->value( iUp, jLeft );
177+
x21 = inputBlock->isNoData( i, jLeft ) ? x22 : inputBlock->value( i, jLeft );
178+
x31 = inputBlock->isNoData( iDown, jLeft ) ? x22 : inputBlock->value( iDown, jLeft );
179+
180+
x12 = inputBlock->isNoData( iUp, j ) ? x22 : inputBlock->value( iUp, j );
181+
// x22
182+
x32 = inputBlock->isNoData( iDown, j ) ? x22 : inputBlock->value( iDown, j );
183+
184+
x13 = inputBlock->isNoData( iUp, jRight ) ? x22 : inputBlock->value( iUp, jRight );
185+
x23 = inputBlock->isNoData( i, jRight ) ? x22 : inputBlock->value( i, jRight );
186+
x33 = inputBlock->isNoData( iDown, jRight ) ? x22 : inputBlock->value( iDown, jRight );
187+
188+
double derX = calcFirstDerX( x11, x21, x31, x12, x22, x32, x13, x23, x33, cellXSize );
189+
double derY = calcFirstDerY( x11, x21, x31, x12, x22, x32, x13, x23, x33, cellYSize );
190+
191+
double slope_rad = atan( mZFactor * sqrt( derX * derX + derY * derY ) );
192+
193+
if ( derX != 0 )
194+
{
195+
aspectRad = atan2( derX, -derY );
196+
if ( aspectRad < 0 )
197+
{
198+
aspectRad = 2 * M_PI + aspectRad;
199+
}
200+
}
201+
else if ( derX == 0 )
202+
{
203+
if ( derY > 0 )
204+
{
205+
aspectRad = M_PI_2;
206+
}
207+
else if ( derY < 0 )
208+
{
209+
aspectRad = 2 * M_PI - M_PI_2;
210+
}
211+
else
212+
{
213+
aspectRad = aspectRad;
214+
}
215+
}
216+
217+
double colorvalue = qBound( 0.0, 255.0 * (( cosZenithRad * cos( slope_rad ) ) +
218+
( sinZenithRad * sin( slope_rad ) *
219+
cos( azimuthRad - aspectRad ) ) ), 255.0 );
220+
221+
outputBlock->setColor( i, j, qRgb( colorvalue, colorvalue, colorvalue ) );
222+
}
223+
}
224+
return outputBlock;
225+
}
226+
227+
QList<int> QgsHillshadeRenderer::usesBands() const
228+
{
229+
QList<int> bandList;
230+
if ( mBand != -1 )
231+
{
232+
bandList << mBand;
233+
}
234+
return bandList;
235+
236+
}
237+
238+
void QgsHillshadeRenderer::setBand( int bandNo )
239+
{
240+
if ( bandNo > mInput->bandCount() || bandNo <= 0 )
241+
{
242+
return;
243+
}
244+
mBand = bandNo;
245+
}
246+
247+
double QgsHillshadeRenderer::calcFirstDerX( double x11, double x21, double x31, double x12, double x22, double x32, double x13, double x23, double x33, double cellsize )
248+
{
249+
Q_UNUSED( x12 );
250+
Q_UNUSED( x22 );
251+
Q_UNUSED( x32 );
252+
return (( x13 + x23 + x23 + x33 ) - ( x11 + x21 + x21 + x31 ) ) / ( 8 * cellsize );
253+
}
254+
255+
double QgsHillshadeRenderer::calcFirstDerY( double x11, double x21, double x31, double x12, double x22, double x32, double x13, double x23, double x33, double cellsize )
256+
{
257+
Q_UNUSED( x22 );
258+
Q_UNUSED( x32 );
259+
Q_UNUSED( x21 );
260+
Q_UNUSED( x23 );
261+
return (( x31 + x32 + x32 + x33 ) - ( x11 + x12 + x12 + x13 ) ) / ( 8 * -cellsize );
262+
}
263+
264+
265+
266+
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/***************************************************************************
2+
qgshillshaderenderer.cpp
3+
---------------------------------
4+
begin : May 2016
5+
copyright : (C) 2016 by Nathan Woodrow
6+
email : woodrow dot nathan at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#ifndef QGSHILLSHADERENDERER_H
19+
#define QGSHILLSHADERENDERER_H
20+
21+
22+
#include "qgsrasterrenderer.h"
23+
24+
class QgsRasterBlock;
25+
class QgsRectangle;
26+
class QgsRasterInterface;
27+
28+
29+
/**
30+
* @brief A renderer for generating live hillshade models.
31+
*/
32+
class CORE_EXPORT QgsHillshadeRenderer : public QgsRasterRenderer
33+
{
34+
public:
35+
/**
36+
* @brief A renderer for generating live hillshade models.
37+
* @param input The input raster interface
38+
* @param band The band in the raster to use
39+
* @param lightAzimuth The azimuth of the light source
40+
* @param lightAltitude The altitude of the light source
41+
*/
42+
QgsHillshadeRenderer( QgsRasterInterface* input, int band , double lightAzimuth, double lightAltitude );
43+
44+
QgsHillshadeRenderer * clone() const override;
45+
46+
/**
47+
* @brief Factory method to create a new renderer
48+
* @param elem A DOM element to create the renderer from.
49+
* @param input The raster input interface.
50+
* @return A new QgsHillshadeRenderer.
51+
*/
52+
static QgsRasterRenderer* create( const QDomElement& elem, QgsRasterInterface* input );
53+
54+
void writeXML( QDomDocument& doc, QDomElement& parentElem ) const override;
55+
56+
QgsRasterBlock *block( int bandNo, QgsRectangle const & extent, int width, int height ) override;
57+
58+
QList<int> usesBands() const override;
59+
60+
/** Returns the band used by the renderer
61+
*/
62+
int band() const { return mBand; }
63+
64+
/** Sets the band used by the renderer.
65+
* @see band
66+
*/
67+
void setBand( int bandNo );
68+
69+
/**
70+
* @brief The direction of the light over the raster between 0-360
71+
* @return The direction of the light over the raster
72+
*/
73+
double azimuth() const { return mLightAzimuth; }
74+
75+
/**
76+
* @brief The angle of the light source over the raster
77+
* @return The angle of the light source over the raster
78+
*/
79+
double altitude() const { return mLightAngle; }
80+
81+
/**
82+
* @brief Z Factor
83+
* @return Z Factor
84+
*/
85+
double zFactor() const { return mZFactor; }
86+
87+
/**
88+
* @brief Set the azimith of the light source.
89+
* @param azimuth The azimuth of the light source.
90+
*/
91+
void setAzimuth( double azimuth ) { mLightAzimuth = azimuth; }
92+
93+
/**
94+
* @brief Set the altitude of the light source
95+
* @param altitude The altitude
96+
*/
97+
void setAltitude( double altitude ) { mLightAngle = altitude; }
98+
99+
/**
100+
* @brief Set the Z factor of the result image.
101+
* @param zfactor The z factor.
102+
*/
103+
void setZFactor( double zfactor ) { mZFactor = zfactor; }
104+
105+
private:
106+
int mBand;
107+
double mZFactor;
108+
double mLightAngle;
109+
double mLightAzimuth;
110+
111+
/** Calculates the first order derivative in x-direction according to Horn (1981)*/
112+
double calcFirstDerX( double x11, double x21, double x31, double x12, double x22, double x32, double x13, double x23, double x33 , double cellsize );
113+
114+
/** Calculates the first order derivative in y-direction according to Horn (1981)*/
115+
double calcFirstDerY( double x11, double x21, double x31, double x12, double x22, double x32, double x13, double x23, double x33 , double cellsize );
116+
};
117+
118+
#endif // QGSHILLSHADERENDERER_H

‎src/core/raster/qgsrasterrendererregistry.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "qgssinglebandcolordatarenderer.h"
2222
#include "qgssinglebandgrayrenderer.h"
2323
#include "qgssinglebandpseudocolorrenderer.h"
24+
#include "qgshillshaderenderer.h"
2425
#include "qgsapplication.h"
2526

2627
#include <QSettings>
@@ -61,6 +62,8 @@ QgsRasterRendererRegistry::QgsRasterRendererRegistry()
6162
QgsSingleBandPseudoColorRenderer::create, nullptr ) );
6263
insert( QgsRasterRendererRegistryEntry( "singlebandcolordata", QObject::tr( "Singleband color data" ),
6364
QgsSingleBandColorDataRenderer::create, nullptr ) );
65+
insert( QgsRasterRendererRegistryEntry( "hillshade", QObject::tr( "Hillshade renderer" ),
66+
QgsHillshadeRenderer::create, nullptr ) );
6467
}
6568

6669
void QgsRasterRendererRegistry::insert( const QgsRasterRendererRegistryEntry& entry )

‎src/gui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ SET(QGIS_GUI_SRCS
88
raster/qgssinglebandpseudocolorrendererwidget.cpp
99
raster/qgsrendererrasterpropertieswidget.cpp
1010
raster/qgsrastertransparencywidget.cpp
11+
raster/qgshillshaderendererwidget.cpp
1112
raster/qwt5_histogram_item.cpp
1213

1314
symbology-ng/qgs25drendererwidget.cpp
@@ -440,6 +441,7 @@ SET(QGIS_GUI_MOC_HDRS
440441
raster/qgssinglebandpseudocolorrendererwidget.h
441442
raster/qgsrendererrasterpropertieswidget.h
442443
raster/qgsrastertransparencywidget.h
444+
raster/qgshillshaderendererwidget.h
443445

444446
symbology-ng/qgs25drendererwidget.h
445447
symbology-ng/characterwidget.h
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/***************************************************************************
2+
qgshillshaderendererwidget.cpp
3+
---------------------------------
4+
begin : May 2016
5+
copyright : (C) 2016 by Nathan Woodrow
6+
email : woodrow dot nathan at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
19+
#include "qgshillshaderendererwidget.h"
20+
#include "qgsrasterlayer.h"
21+
#include "qgsbilinearrasterresampler.h"
22+
#include "qgshillshaderenderer.h"
23+
24+
25+
QgsHillshadeRendererWidget::QgsHillshadeRendererWidget( QgsRasterLayer *layer, const QgsRectangle &extent )
26+
: QgsRasterRendererWidget( layer, extent )
27+
{
28+
setupUi( this );
29+
30+
mLightAngle->setMaximum( 90 );
31+
mLightAzimuth->setMaximum( 360.00 );
32+
33+
mLightAngle->setValue( 45.00 );
34+
mLightAngle->setClearValue( 45.0 );
35+
mLightAzimuth->setValue( 315.00 );
36+
mLightAzimuth->setClearValue( 315.00 );
37+
38+
// Update the dial correctly
39+
on_mLightAzimuth_updated( 315.00 );
40+
mZFactor->setValue( 1 );
41+
mZFactor->setClearValue( 1 );
42+
43+
if ( mRasterLayer )
44+
{
45+
QgsRasterDataProvider* provider = mRasterLayer->dataProvider();
46+
if ( !provider )
47+
{
48+
return;
49+
}
50+
51+
//fill available bands into combo box
52+
int nBands = provider->bandCount();
53+
for ( int i = 1; i <= nBands; ++i ) //band numbering seem to start at 1
54+
{
55+
mBandsCombo->addItem( displayBandName( i ), i );
56+
}
57+
58+
}
59+
60+
setFromRenderer( layer->renderer() );
61+
62+
connect( mLightAngle, SIGNAL( valueChanged( double ) ), this, SIGNAL( widgetChanged() ) );
63+
connect( mLightAzimuth, SIGNAL( valueChanged( double ) ), this, SLOT( on_mLightAzimuth_updated( double ) ) );
64+
connect( mLightAzimuthDial, SIGNAL( valueChanged( int ) ), this, SLOT( on_mLightAzimuthDail_updated( int ) ) );
65+
connect( mZFactor, SIGNAL( valueChanged( double ) ), this, SIGNAL( widgetChanged() ) );
66+
67+
QgsBilinearRasterResampler* zoomedInResampler = new QgsBilinearRasterResampler();
68+
layer->resampleFilter()->setZoomedInResampler( zoomedInResampler );
69+
70+
}
71+
72+
QgsHillshadeRendererWidget::~QgsHillshadeRendererWidget()
73+
{
74+
75+
}
76+
77+
QgsRasterRenderer *QgsHillshadeRendererWidget::renderer()
78+
{
79+
if ( !mRasterLayer )
80+
{
81+
return nullptr;
82+
}
83+
84+
QgsRasterDataProvider* provider = mRasterLayer->dataProvider();
85+
if ( !provider )
86+
{
87+
return nullptr;
88+
}
89+
90+
int band = mBandsCombo->itemData( mBandsCombo->currentIndex() ).toInt();
91+
QgsHillshadeRenderer* renderer = new QgsHillshadeRenderer( provider, band, mLightAzimuth->value(), mLightAngle->value() );
92+
double value = mZFactor->value();
93+
renderer->setZFactor( value );
94+
return renderer;
95+
}
96+
97+
void QgsHillshadeRendererWidget::setFromRenderer( const QgsRasterRenderer *renderer )
98+
{
99+
const QgsHillshadeRenderer* r = dynamic_cast<const QgsHillshadeRenderer*>( renderer );
100+
if ( r )
101+
{
102+
mBandsCombo->setCurrentIndex( mBandsCombo->findData( r->band() ) );
103+
mLightAngle->setValue( r->altitude() );
104+
mLightAzimuth->setValue( r->azimuth() );
105+
mZFactor->setValue( r->zFactor() );
106+
}
107+
}
108+
109+
void QgsHillshadeRendererWidget::setAltitude( double altitude )
110+
{
111+
mLightAngle->setValue( altitude );
112+
}
113+
114+
void QgsHillshadeRendererWidget::setAzimuth( double azimuth )
115+
{
116+
mLightAzimuth->setValue( azimuth );
117+
}
118+
119+
void QgsHillshadeRendererWidget::setZFactor( double zfactor )
120+
{
121+
mZFactor->setValue( zfactor );
122+
}
123+
124+
void QgsHillshadeRendererWidget::on_mLightAzimuth_updated( double value )
125+
{
126+
int newvalue = ( int )value - 180;
127+
if ( newvalue < 0 )
128+
newvalue += 360;
129+
whileBlocking( mLightAzimuthDial )->setValue( newvalue );
130+
emit widgetChanged();
131+
}
132+
133+
void QgsHillshadeRendererWidget::on_mLightAzimuthDail_updated( int value )
134+
{
135+
int newvalue = ( int )value + 180;
136+
if ( newvalue > 360 )
137+
newvalue -= 360 ;
138+
whileBlocking( mLightAzimuth )->setValue( newvalue );
139+
emit widgetChanged();
140+
}
141+
142+
double QgsHillshadeRendererWidget::azimuth() const
143+
{
144+
return mLightAzimuth->value();
145+
}
146+
147+
double QgsHillshadeRendererWidget::altitude() const
148+
{
149+
return mLightAngle->value();
150+
}
151+
152+
double QgsHillshadeRendererWidget::zFactor() const
153+
{
154+
return mZFactor->value();
155+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/***************************************************************************
2+
qgshillshaderendererwidget.h
3+
---------------------------------
4+
begin : May 2016
5+
copyright : (C) 2016 by Nathan Woodrow
6+
email : woodrow dot nathan at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#ifndef QGSHILLSHADERENDERERWIDGET_H
19+
#define QGSHILLSHADERENDERERWIDGET_H
20+
21+
#include "ui_qghillshaderendererwidget.h"
22+
23+
#include <QDoubleSpinBox>
24+
25+
#include "qgsrasterminmaxwidget.h"
26+
#include "qgsrasterrendererwidget.h"
27+
28+
/**
29+
* @brief Renderer widget for the hill shade renderer.
30+
*/
31+
class GUI_EXPORT QgsHillshadeRendererWidget: public QgsRasterRendererWidget, private Ui::QgsHillShadeWidget
32+
{
33+
Q_OBJECT
34+
public:
35+
36+
/**
37+
* @brief Renderer widget for the hill shade renderer.
38+
* @param layer The layer attached for this widget.
39+
* @param extent The current extent.
40+
*/
41+
QgsHillshadeRendererWidget( QgsRasterLayer* layer, const QgsRectangle &extent = QgsRectangle() );
42+
43+
~QgsHillshadeRendererWidget();
44+
45+
/**
46+
* Factory method to create the renderer for this type.
47+
*/
48+
static QgsRasterRendererWidget* create( QgsRasterLayer* layer, const QgsRectangle &theExtent ) { return new QgsHillshadeRendererWidget( layer, theExtent ); }
49+
50+
/**
51+
* @brief The renderer for the widget.
52+
* @return A new renderer for the the config in the widget
53+
*/
54+
QgsRasterRenderer* renderer() override;
55+
56+
/**
57+
* @brief Set the widget state from the given renderer.
58+
* @param renderer The renderer to take the state from.
59+
*/
60+
void setFromRenderer( const QgsRasterRenderer* renderer );
61+
62+
/**
63+
* @brief The direction of the light over the raster between 0-360
64+
* @return The direction of the light over the raster
65+
*/
66+
double azimuth() const;
67+
68+
/**
69+
* @brief The angle of the light source over the raster
70+
* @return The angle of the light source over the raster
71+
*/
72+
double altitude() const;
73+
74+
/**
75+
* @brief Z Factor
76+
* @return Z Factor
77+
*/
78+
double zFactor() const;
79+
80+
public slots:
81+
/**
82+
* @brief Set the altitude of the light source
83+
* @param altitude The altitude
84+
*/
85+
void setAltitude( double altitude );
86+
87+
/**
88+
* @brief Set the azimith of the light source.
89+
* @param azimuth The azimuth of the light source.
90+
*/
91+
void setAzimuth( double azimuth );
92+
93+
/**
94+
* @brief Set the Z factor of the result image.
95+
* @param zfactor The z factor.
96+
*/
97+
void setZFactor( double zfactor );
98+
99+
private slots:
100+
void on_mLightAzimuth_updated( double value );
101+
void on_mLightAzimuthDail_updated( int value );
102+
};
103+
104+
#endif // QGSSINGLEBANDGRAYRENDERERWIDGET_H
105+
106+

‎src/gui/raster/qgsrendererrasterpropertieswidget.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "qgssinglebandpseudocolorrendererwidget.h"
1010
#include "qgsmultibandcolorrendererwidget.h"
1111
#include "qgspalettedrendererwidget.h"
12+
#include "qgshillshaderendererwidget.h"
1213

1314

1415
static void _initRendererWidgetFunctions()
@@ -21,6 +22,7 @@ static void _initRendererWidgetFunctions()
2122
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "multibandcolor", QgsMultiBandColorRendererWidget::create );
2223
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "singlebandpseudocolor", QgsSingleBandPseudoColorRendererWidget::create );
2324
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "singlebandgray", QgsSingleBandGrayRendererWidget::create );
25+
QgsRasterRendererRegistry::instance()->insertWidgetFunction( "hillshade", QgsHillshadeRendererWidget::create );
2426

2527
initialized = true;
2628
}

‎src/gui/raster/qgssinglebandgrayrendererwidget.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "qgsrasterrendererwidget.h"
2323
#include "ui_qgssinglebandgrayrendererwidgetbase.h"
2424

25+
2526
class GUI_EXPORT QgsSingleBandGrayRendererWidget: public QgsRasterRendererWidget, private Ui::QgsSingleBandGrayRendererWidgetBase
2627
{
2728
Q_OBJECT
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>QgsHillShadeWidget</class>
4+
<widget class="QWidget" name="QgsHillShadeWidget">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>302</width>
10+
<height>541</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Form</string>
15+
</property>
16+
<layout class="QGridLayout" name="gridLayout">
17+
<item row="2" column="1" colspan="2">
18+
<layout class="QVBoxLayout" name="verticalLayout">
19+
<item>
20+
<widget class="QDial" name="mLightAzimuthDial">
21+
<property name="sizePolicy">
22+
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
23+
<horstretch>0</horstretch>
24+
<verstretch>0</verstretch>
25+
</sizepolicy>
26+
</property>
27+
<property name="maximum">
28+
<number>360</number>
29+
</property>
30+
<property name="value">
31+
<number>0</number>
32+
</property>
33+
<property name="sliderPosition">
34+
<number>0</number>
35+
</property>
36+
<property name="invertedAppearance">
37+
<bool>false</bool>
38+
</property>
39+
<property name="invertedControls">
40+
<bool>false</bool>
41+
</property>
42+
<property name="wrapping">
43+
<bool>true</bool>
44+
</property>
45+
<property name="notchTarget">
46+
<double>10.000000000000000</double>
47+
</property>
48+
<property name="notchesVisible">
49+
<bool>true</bool>
50+
</property>
51+
</widget>
52+
</item>
53+
<item>
54+
<widget class="QgsDoubleSpinBox" name="mLightAzimuth">
55+
<property name="sizePolicy">
56+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
57+
<horstretch>0</horstretch>
58+
<verstretch>0</verstretch>
59+
</sizepolicy>
60+
</property>
61+
</widget>
62+
</item>
63+
</layout>
64+
</item>
65+
<item row="1" column="0" colspan="2">
66+
<widget class="QLabel" name="label_2">
67+
<property name="text">
68+
<string>Altitude (degrees)</string>
69+
</property>
70+
</widget>
71+
</item>
72+
<item row="2" column="0">
73+
<widget class="QLabel" name="label">
74+
<property name="text">
75+
<string>Azimuth (degrees)</string>
76+
</property>
77+
</widget>
78+
</item>
79+
<item row="3" column="0" colspan="2">
80+
<widget class="QLabel" name="label_3">
81+
<property name="text">
82+
<string>Z Factor</string>
83+
</property>
84+
</widget>
85+
</item>
86+
<item row="1" column="2">
87+
<widget class="QgsDoubleSpinBox" name="mLightAngle">
88+
<property name="sizePolicy">
89+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
90+
<horstretch>0</horstretch>
91+
<verstretch>0</verstretch>
92+
</sizepolicy>
93+
</property>
94+
<property name="singleStep">
95+
<double>5.000000000000000</double>
96+
</property>
97+
<property name="value">
98+
<double>45.000000000000000</double>
99+
</property>
100+
</widget>
101+
</item>
102+
<item row="4" column="0" colspan="3">
103+
<spacer name="verticalSpacer">
104+
<property name="orientation">
105+
<enum>Qt::Vertical</enum>
106+
</property>
107+
<property name="sizeHint" stdset="0">
108+
<size>
109+
<width>20</width>
110+
<height>40</height>
111+
</size>
112+
</property>
113+
</spacer>
114+
</item>
115+
<item row="0" column="0">
116+
<widget class="QLabel" name="label_4">
117+
<property name="text">
118+
<string>Band</string>
119+
</property>
120+
</widget>
121+
</item>
122+
<item row="0" column="2">
123+
<widget class="QComboBox" name="mBandsCombo"/>
124+
</item>
125+
<item row="3" column="2">
126+
<widget class="QgsDoubleSpinBox" name="mZFactor">
127+
<property name="sizePolicy">
128+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
129+
<horstretch>0</horstretch>
130+
<verstretch>0</verstretch>
131+
</sizepolicy>
132+
</property>
133+
<property name="value">
134+
<double>1.000000000000000</double>
135+
</property>
136+
</widget>
137+
</item>
138+
</layout>
139+
</widget>
140+
<customwidgets>
141+
<customwidget>
142+
<class>QgsDoubleSpinBox</class>
143+
<extends>QDoubleSpinBox</extends>
144+
<header>qgsdoublespinbox.h</header>
145+
</customwidget>
146+
</customwidgets>
147+
<resources/>
148+
<connections/>
149+
</ui>

5 commit comments

Comments
 (5)

haubourg commented on May 30, 2016

@haubourg
Member

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 commented on May 30, 2016

@AsgerPetersen
Contributor

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

haubourg commented on May 30, 2016

@haubourg
Member

here is one extract:
dem.zip

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

AsgerPetersen commented on May 30, 2016

@AsgerPetersen
Contributor

@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 commented on May 30, 2016

@AsgerPetersen
Contributor

@haubourg @NathanW2 I fixed this in #3152

Please sign in to comment.